106
WordPressのキャッシュ機構

WordPress のキャッシュ機構

Embed Size (px)

Citation preview

WordPressのキャッシュ機構

• キャッシュの概要

• WordPressに備わっているキャッシュの仕組みの紹介

• WordPress Object Cache https://codex.wordpress.org/Class_Reference/WP_Object_Cache

• Transients API https://wpdocs.osdn.jp/Transients_API

• ※ WordPress コアのコードを例示している箇所はすべてバージョン 4.4.2 からの引用

キャッシュとは

cache = 隠し場所、隠す (単語のもともとの意味)

一度使ったデータを取り出しやすいところに置いておき 次からより素早く使えるようにする仕組み

データを要求する側 応答する側

データを要求する側 応答する側キャッシュ

データを要求する側 応答する側キャッシュ

• キャッシュ機構はデータを要求する側と応答する側の間に立ち、応答結果のコピーを保存

• 要求に応じて、該当するコピーがあればコピーを返す(キャッシュヒット)

• コピーが無ければ応答側にデータを要求してその応答結果を返す(キャッシュミス)

• 本来、キャッシュの存在は意識されないもの (キャッシュの有無にかかわらず、システムは同じように動作することが求められる。キャッシュの仕組みを使う側からはその存在は意識されないが、実はそこにあり、こっそりデータが保存されるイメージ。だから「隠し場所」)

Webサイトにおけるキャッシュ

ユーザー エージェント

DBサーバCDN

Web/アプリケーションサーバ

リバースプロキシ (キャッシュサーバ)

その他 データストア等

ユーザー エージェント

DBサーバCDN

Web/アプリケーションサーバ

リバースプロキシ (キャッシュサーバ)

その他 データストア等

キャッシュの効用

ユーザー エージェント

DBサーバCDN

Web/アプリケーションサーバ

リバースプロキシ (キャッシュサーバ)

その他 データストア等

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

キャッシュなし

※上記のイメージは、以下のページの「動的なサイトがリクエストを受け取って処理する仕組み」の画像の一部抜粋を元に作成 http://takahashifumiki.com/web/programing/2426/

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

キャッシュなし

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

出力キャッシュ (ページキャッシュ)

※上記のイメージは、以下のページの「動的なサイトがリクエストを受け取って処理する仕組み」の画像の一部抜粋を元に作成 http://takahashifumiki.com/web/programing/2426/

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

キャッシュなし

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

出力キャッシュ (ページキャッシュ)

キャッシュヒットすれば この部分の処理が省略される

※上記のイメージは、以下のページの「動的なサイトがリクエストを受け取って処理する仕組み」の画像の一部抜粋を元に作成 http://takahashifumiki.com/web/programing/2426/

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

キャッシュなし

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

出力キャッシュ (ページキャッシュ)

HTTPリクエスト 解析

PHP MySQL PHPHTTPレスポンス

出力

データキャッシュ (MySQLの応答結果をキャッシュするケース)

キャッシュヒットすれば この部分の処理が省略される

※上記のイメージは、以下のページの「動的なサイトがリクエストを受け取って処理する仕組み」の画像の一部抜粋を元に作成 http://takahashifumiki.com/web/programing/2426/

多様な選択肢

• 何を保存するか

• どこに保存するか

• いつまで保存するか

• どうやって更新するか

• どうやって削除するか

• クライアントサイド

• HTTPキャッシュ/プロキシキャッシュ

• サーバーサイド

• アプリケーション

• 出力キャッシュ/データキャッシュ

• ミドルウェア

• リバースプロキシキャッシュ/バイトコードキャッシュ/DBキャッシュ

• OS

• ディスクキャッシュ

画像出典:WordPressキャッシュ系プラグインの比較とサイトに適した選び方 http://tokkono.cute.coocan.jp/blog/slow/index.php/wordpress/comparison-and-selection-of-wordpress-cache-plugin/

キャッシュの難しさ

• どこがボトルネックなのか

• キャッシュすべきでないものは?

• 更新・削除・置き換えの方法は?

• 使い方を誤ると・・・

ほんとうは怖いWP Super Cacheの話 http://takahashifumiki.com/web/programing/2426/

※WP Super Cache がよくないということではありません

WordPressに備わっているキャッシュの仕組み

ユーザー エージェント

DBサーバCDN

Web/アプリケーションサーバ

リバースプロキシ (キャッシュサーバ)

その他 データストア等

• クライアントサイド

• HTTPキャッシュ/プロキシキャッシュ

• サーバーサイド

• アプリケーション

• 出力キャッシュ/データキャッシュ

• ミドルウェア

• リバースプロキシキャッシュ/バイトコードキャッシュ/DBキャッシュ

• OS

• ディスクキャッシュ

WordPress Object Cache

• 非永続キャッシュ

• 同一プロセス内でのみ再利用可能

• ドロップインで拡張することで永続化可能

PHP実行プロセス DB

プログラム Object Cache

投稿データを要求キャッシュミス

DBからデータを取得

PHP実行プロセス DB

プログラム Object Cache

投稿データを要求キャッシュミス

DBからデータを取得

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

キャッシュデータ

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

キャッシュデータ

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

キャッシュデータ

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

投稿データを要求 キャッシュデータ

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュミスDBからデータを取得

投稿データを要求 キャッシュデータ

投稿データを更新

キャッシュデータを削除

キャッシュされたデータの寿命

PHP実行プロセス DB

プログラム

投稿データを要求

Object Cache

キャッシュデータ

PHP実行プロセス

プロセス終了により 無くなる

投稿データを要求キャッシュミス

DBからデータを取得

永続化

APCu などPHP実行プロセス DB

投稿データを要求

Object Cacheプログラム

PHP実行プロセス

投稿データを要求

キャッシュミスDBからデータを取得

投稿データを要求 キャッシュデータ

キャッシュデータ

• WP_Object_Cache クラスで実装

• インスタンス変数にデータを保持

• プログラムから使用するときは WP_Object_Cache に直接アクセスせず wp_cache_* 関数を使う

• 関数の一覧と基本的な使い方を Codex で確認してみましょう

例: 投稿データ

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post( 1 );

get_post 関数より抜粋

function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) { if ( empty( $post ) && isset( $GLOBALS['post'] ) ) $post = $GLOBALS['post'];

if ( $post instanceof WP_Post ) { $_post = $post; } elseif ( is_object( $post ) ) { if ( empty( $post->filter ) ) { $_post = sanitize_post( $post, 'raw' );

$_post = new WP_Post( $_post ); } elseif ( 'raw' == $post->filter ) { $_post = new WP_Post( $post ); } else { $_post = WP_Post::get_instance( $post->ID );

} } else { $_post = WP_Post::get_instance( $post ); }

get_post 関数より抜粋

function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) { if ( empty( $post ) && isset( $GLOBALS['post'] ) ) $post = $GLOBALS['post'];

if ( $post instanceof WP_Post ) { $_post = $post; } elseif ( is_object( $post ) ) { if ( empty( $post->filter ) ) { $_post = sanitize_post( $post, 'raw' );

$_post = new WP_Post( $_post ); } elseif ( 'raw' == $post->filter ) { $_post = new WP_Post( $post ); } else { $_post = WP_Post::get_instance( $post->ID );

} } else { $_post = WP_Post::get_instance( $post ); }

public static function get_instance( $post_id ) { global $wpdb;

$post_id = (int) $post_id; if ( ! $post_id ) return false;

$_post = wp_cache_get( $post_id, 'posts' );

if ( ! $_post ) { $_post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d LIMIT 1", $post_id ) );

if ( ! $_post ) return false;

$_post = sanitize_post( $_post, 'raw' ); wp_cache_add( $_post->ID, $_post, 'posts' ); } elseif ( empty( $_post->filter ) ) { $_post = sanitize_post( $_post, 'raw' ); }

return new WP_Post( $_post ); }

WP_Post クラスより抜粋

public static function get_instance( $post_id ) { global $wpdb;

$post_id = (int) $post_id; if ( ! $post_id ) return false;

$_post = wp_cache_get( $post_id, 'posts' );

if ( ! $_post ) { $_post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d LIMIT 1", $post_id ) );

if ( ! $_post ) return false;

$_post = sanitize_post( $_post, 'raw' ); wp_cache_add( $_post->ID, $_post, 'posts' ); } elseif ( empty( $_post->filter ) ) { $_post = sanitize_post( $_post, 'raw' ); }

return new WP_Post( $_post ); }

WP_Post クラスより抜粋

wp_insert_post 関数より整形して抜粋

if ( empty( $data['post_name'] ) && !in_array( $data[‘post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {

$data['post_name'] = wp_unique_post_slug(

sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data[‘post_status'], $post_type, $post_parent

);

$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ),

$where );

clean_post_cache( $post_ID ); }

if ( empty( $data['post_name'] ) && !in_array( $data[‘post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {

$data['post_name'] = wp_unique_post_slug(

sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data[‘post_status'], $post_type, $post_parent

);

$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ),

$where );

clean_post_cache( $post_ID ); }

wp_insert_post 関数より整形して抜粋

wp_cache_delete( $post->ID, 'posts' );

wp_cache_delete( $post->ID, 'post_meta' );

clean_object_term_cache( $post->ID, $post->post_type );

wp_cache_delete( 'wp_get_archives', 'general' );

do_action( 'clean_post_cache', $post->ID, $post );

if ( 'page' == $post->post_type ) { wp_cache_delete( 'all_page_ids', 'posts' );

do_action( 'clean_page_cache', $post->ID ); }

wp_cache_set( 'last_changed', microtime(), 'posts' );

clean_post_cache 関数より抜粋

• データベースの更新時にいったんキャッシュを破棄

• 投稿そのものの他に、タームとの関係や投稿メタデータなど付随する情報もキャッシュされていることが分かる

• ちなみに、get_posts / WP_Query は・・・

• 取得した投稿リストの各投稿オブジェクトがキャッシュされる

• cache_results パラメータで投稿オブジェクトをキャッシュするかどうかを指定可能

• 投稿リストそのものはキャッシュされない

function get_books( $args = array() ) { $defaults = array( 'post_type' => 'book' ); $args = wp_parse_args( $args, $defaults );

$cache_key = md5( 'books_' . serialize( $args ) );

$books = wp_cache_get( $cache_key, 'books' ); if ( $books === false ) { $books = get_posts( $args ); wp_cache_set( $cache_key, $books, 'books', 60 * MINUTE_IN_SECONDS ); }

return $books; }

カスタム投稿のリストをキャッシュするユーザー定義関数のサンプル

function get_books( $args = array() ) { $defaults = array( 'post_type' => 'book' ); $args = wp_parse_args( $args, $defaults );

$cache_key = md5( 'books_' . serialize( $args ) );

$books = wp_cache_get( $cache_key, 'books' ); if ( $books === false ) { $books = get_posts( $args ); wp_cache_set( $cache_key, $books, 'books', 60 * MINUTE_IN_SECONDS ); }

return $books; }

カスタム投稿のリストをキャッシュするユーザー定義関数のサンプル

例: サイトオプション

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

get_option( 'hoge' );

• オートロード対象のオプションはコアの初期化処理で一括してロードされ、オブジェクトキャッシュに保存される

• wp-settings.php

• wp_not_installed

• is_blog_installed

• wp_load_alloptions

• オートロード対象ではないオプションも、一度�get_option で取得するとキャッシュされる

• オプション関係のキャッシュ ( [キャッシュグループ].[キャッシュID] )

• options.alloptions�… オートロードされたオプションのリスト

• options.notoptions�… 存在しないオプションのリスト

• options.[オプション名] �… オートロード対象外の個別のオプション

ドロップインによる拡張

• memcached

• Redis

• APC/APCu

• etc.

• ※それぞれ拡張用のプラグインがあります

wp_start_object_cache 関数より抜粋 wp-settings.php でこの関数が呼ばれオブジェクトキャッシュが初期化される

$first_init = false; if ( ! function_exists( 'wp_cache_init' ) ) { if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {

require_once ( WP_CONTENT_DIR . '/object-cache.php' ); if ( function_exists( 'wp_cache_init' ) ) wp_using_ext_object_cache( true ); } $first_init = true;

} elseif ( ! wp_using_ext_object_cache() && file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) { wp_using_ext_object_cache( true ); }

if ( ! wp_using_ext_object_cache() ) require_once ( ABSPATH . WPINC . '/cache.php' );

wp_start_object_cache 関数より抜粋 wp-settings.php でこの関数が呼ばれオブジェクトキャッシュが初期化される

$first_init = false; if ( ! function_exists( 'wp_cache_init' ) ) { if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {

require_once ( WP_CONTENT_DIR . '/object-cache.php' ); if ( function_exists( 'wp_cache_init' ) ) wp_using_ext_object_cache( true ); } $first_init = true;

} elseif ( ! wp_using_ext_object_cache() && file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) { wp_using_ext_object_cache( true ); }

if ( ! wp_using_ext_object_cache() ) require_once ( ABSPATH . WPINC . '/cache.php' );

• wp-content の下に object-cache.php という名前でファイルを配置

• object-cache.php 内で wp_cache_* 関数を定義する

• wp_cache_* 関数の引数でキャッシュの有効期限を指定可能 ( 有効期限が使用されるかどうかはドロップインの実装による )

デモ

Transients API

transient = 一時の、はかない

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

データを保存

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

データを保存

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

set_transientデータを保存

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API Options API

set_transientデータを保存 add_option

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求

データ

有効期限

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求 get_transient

データ

有効期限

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求 get_transient get_option

データ

有効期限

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求 get_transient get_option

データ

有効期限

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求 get_transient get_option

データ

有効期限

PHP実行プロセス DB

プログラム

外部サイトから データを取得

Transients API

有効期限

Options API

set_transient

データ

データを保存 add_option

データを要求 get_transient get_option

データ

有効期限

キャッシュされたデータが 削除されるタイミング

PHP実行プロセス DB

プログラム Transients API Options API

get_transient

データ

データを要求 get_option

delete_option 有効期限

有効期限 (期限切れ)

データ

• 「永続的かつ時限的なキャッシュ」 ( Codex より)

• リクエスト( ≒ PHP実行プロセス ) をまたいでデータの再利用が可能

• データに有効期限を設定可能

• データはデータベース ( wp_options テーブル ) に保存

• Codex を見てみましょう

• set_transient

• add_option でデータと有効期限を保存

• get_transient

• 有効期限が過ぎていたら delete_option でデータを削除

• 有効期限がまだ来ていなければ、get_option でデータを取得

• ※有効期限が過ぎていても、次の get_transient が呼ばれるまでデータは残り続けるということ

典型的なユースケース

• フラグメントキャッシュ

• ページ断片の再利用。フッターやサイドバーの HTML をキャッシュして複数のリクエストに対して使い回す

• 重い処理、外部のWebサービス / サイトとの連携を伴う処理

• WordPress コア / プラグイン / テーマ の更新チェック

• SNS の API 利用制限を回避するなど

• フラッシュメッセージ

• 「設定を更新しました」など、1回限りのメッセージをリクエストをまたいで表示

例: コアの更新チェック

$url = $http_url = 'http://api.wordpress.org/core/version-check/1.7/?' . http_build_query( $query, null, '&' );

if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) $url = set_url_scheme( $url, 'https' );

(中略)

$response = wp_remote_post( $url, $options );

(中略)

$updates = new stdClass(); $updates->updates = $offers; $updates->last_checked = time(); $updates->version_checked = $wp_version;

if ( isset( $body['translations'] ) ) $updates->translations = $body['translations'];

set_site_transient( 'update_core', $updates );

wp_version_check 関数より抜粋

$url = $http_url = 'http://api.wordpress.org/core/version-check/1.7/?' . http_build_query( $query, null, '&' );

if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) $url = set_url_scheme( $url, 'https' );

(中略)

$response = wp_remote_post( $url, $options );

(中略)

$updates = new stdClass(); $updates->updates = $offers; $updates->last_checked = time(); $updates->version_checked = $wp_version;

if ( isset( $body['translations'] ) ) $updates->translations = $body['translations'];

set_site_transient( 'update_core', $updates );

wp_version_check 関数より抜粋

$current = get_site_transient( 'update_core' );

(中略)

// Wait 60 seconds between multiple version check requests $timeout = 60; $time_not_changed =

isset( $current->last_checked ) && $timeout > ( time() - $current->last_checked );

if ( ! $force_check && $time_not_changed ) { return; }

wp_version_check 関数より抜粋

$current = get_site_transient( 'update_core' );

(中略)

// Wait 60 seconds between multiple version check requests $timeout = 60; $time_not_changed =

isset( $current->last_checked ) && $timeout > ( time() - $current->last_checked );

if ( ! $force_check && $time_not_changed ) { return; }

wp_version_check 関数より抜粋

Object Cache との関係

• Object Cache がドロップインで拡張されている場合、トランジェントデータの保存先として Object Cache が使われる

• トランジェントデータの読み書きは wp_cache_* 関数に委譲

• プラグイン開発等で Transients API を使う際には、トランジェントデータがデータベース以外に保存されるケースも考慮しておく

デモ

まとめ

• ひとくちにキャッシュといっても、その実現方法はいろいろある

• それぞれの特性を理解して、効果が見込まれる部分に対して導入する

• 「なんとなく・・・」「とりあえず・・・」で使わない

• WordPress に備わっているキャッシュの仕組みとして Object Cache と Transients API がある

• Object Cache は単に PHP の変数でデータを管理。手軽で高速だが、リクエストをまたいでデータを再利用することはできない�( ドロップインで拡張すれば可能になる )

• Transients はサイトオプションの仕組みをうまく使うことで有効期限つきの永続キャッシュを実現。リクエストをまたいでデータを再利用できる

おわり

Special Thanks to• 高橋文樹.com

http://takahashifumiki.com

• ゆっくりと…http://tokkono.cute.coocan.jp

ご清聴ありがとうございましたWordBench新潟 2016.2.20

@katanyan