こんにちは、WordPressのパフォーマンスを向上させるための方法を日々研究しています。今日はその中でも、永続オブジェクトキャッシュ(APCu)の使用方法について紹介します。
永続オブジェクトキャッシュとは?
永続オブジェクトキャッシュは、PHPの拡張モジュールの一つで、データベースのクエリ結果や計算結果などのデータを高速にキャッシュすることができます。これにより、同じデータへのアクセスが高速化され、サイトのパフォーマンスが向上します。
WordPressでのAPCuの使用方法
APCuのインストール
最初に、サーバーにAPCuをインストールする必要があります。多くのホスティング環境では、すでにインストールされている場合がありますが、自分で管理しているサーバーの場合は手動でインストールする必要があります。
サイトで永続オブジェクトキャッシュを有効化できるかどうかは、ダッシュボードから、サイトヘルスステータスを確認することでわかります。
お使いのホスティングサービスで、オブジェクトキャッシュサービスをサポートしている場合は、
お使いのホスティングサービスでは、次のオブジェクトキャッシュサービスをサポートしているようです: APCu, Redis。
のように、表示されます。
WordPressのオブジェクトキャッシュをAPCuで使用する
WordPressのオブジェクトキャッシュをAPCuで使用するには、'object-cache.php'を
wp-content内に配置し、キャッシュ関数 wp_cache_*() をAPCu版に置き換えることで使用できます。
<?php //(object-cache.php)
/**
* WPのオブジェクトキャッシュをAPCuで使用する
* (wp-content内のobject-cache.phpとして配置する)
*/
//直接のアクセスを防ぐ
if (!defined('ABSPATH')) {
exit;
}
//APCuが利用できない場合は終了
if (!function_exists('apcu_fetch')) {
return;
}
if (function_exists('wp_cache_add')) {
//Regular die, not wp_die(), because it gets sandboxed and shown in a small iframe
die('<strong>ERROR:</strong> This is <em>not</em> a plugin, and it should not be activated as one.<br /><br />Instead, <code>' . str_replace($_SERVER['DOCUMENT_ROOT'], '', __FILE__) . '</code> must be moved to <code>' . str_replace($_SERVER['DOCUMENT_ROOT'], '', trailingslashit(WP_CONTENT_DIR)) . 'object-cache.php</code>');
} else {
//キャッシュ関数 wp_cache_*() をAPCu版に置き換える
//オーバーライド関数をここに
function wp_cache_add($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->add($key, $data, $group, (int) $expire);
}
function wp_cache_close() {
return true;
}
function wp_cache_decr($key, $offset = 1, $group = '') {
global $wp_object_cache;
return $wp_object_cache->decr($key, $offset, $group);
}
function wp_cache_delete($key, $group = '') {
global $wp_object_cache;
return $wp_object_cache->delete($key, $group);
}
function wp_cache_flush() {
global $wp_object_cache;
return $wp_object_cache->flush();
}
function wp_cache_get($key, $group = '', $force = false, &$found = null) {
global $wp_object_cache;
return $wp_object_cache->get($key, $group, $force, $found);
}
function wp_cache_incr($key, $offset = 1, $group = '') {
global $wp_object_cache;
return $wp_object_cache->incr($key, $offset, $group);
}
function wp_cache_init() {
if (function_exists('apcu_fetch')) {
//APCuオブジェクトキャッシュのインスタンスを作成
$GLOBALS['wp_object_cache'] = new APCu_Object_Cache();
}
}
function wp_cache_replace($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->replace($key, $data, $group, (int) $expire);
}
function wp_cache_set($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->set($key, $data, $group, (int) $expire);
}
function wp_cache_switch_to_blog($blog_id) {
global $wp_object_cache;
$wp_object_cache->switch_to_blog($blog_id);
}
function wp_cache_add_global_groups($groups) {
global $wp_object_cache;
$wp_object_cache->add_global_groups($groups);
}
function wp_cache_add_non_persistent_groups($groups) {
global $wp_object_cache;
$wp_object_cache->wp_cache_add_non_persistent_groups($groups);
}
function wp_cache_reset() {
global $wp_object_cache;
$wp_object_cache->reset();
}
class APCu_Object_Cache {
//APCuオブジェクトキャッシュの使用
private $prefix = '';
private $local_cache = array();
private $global_groups = array();
private $non_persistent_groups = array();
private $multisite = false;
private $blog_prefix = '';
public function __construct() {
global $table_prefix, $blog_id;
$this->multisite = is_multisite();
$this->blog_prefix = $this->multisite ? intval($blog_id) : '';
$this->prefix = DB_HOST . '.' . DB_NAME . '.' . $table_prefix;
}
private function get_group($group) {
return empty($group) ? 'default' : $group;
}
private function get_key($group, $key) {
if ($this->multisite && !isset($this->global_groups[$group])) {
return $this->prefix . '.' . $group . '.' . $this->blog_prefix . ':' . $key;
} else {
return $this->prefix . '.' . $group . '.' . $key;
}
}
public function add($key, $data, $group = 'default', $expire = 0) {
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (function_exists('wp_suspend_cache_addition') && wp_suspend_cache_addition()) {
return false;
}
if (isset($this->local_cache[$group][$key])) {
return false;
}
//FIXME: Somehow apcu_add does not return false if key already exists
if (!isset($this->non_persistent_groups[$group]) && apcu_exists($key)) {
return false;
}
if (is_object($data)) {
$this->local_cache[$group][$key] = clone $data;
} else {
$this->local_cache[$group][$key] = $data;
}
if (!isset($this->non_persistent_groups[$group])) {
return apcu_add($key, $data, (int) $expire);
}
return true;
}
//グローバルなキャッシュグループを追加
public function add_global_groups($groups) {
//グローバルグループの追加の使用
if (is_array($groups)) {
foreach ($groups as $group) {
$this->global_groups[$group] = true;
}
} else {
$this->global_groups[$groups] = true;
}
}
public function wp_cache_add_non_persistent_groups($groups) {
if (is_array($groups)) {
foreach ($groups as $group) {
$this->non_persistent_groups[$group] = true;
}
} else {
$this->non_persistent_groups[$groups] = true;
}
}
public function decr($key, $offset = 1, $group = 'default') {
if ($offset < 0) {
return $this->incr($key, abs($offset), $group);
}
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (isset($this->local_cache[$group][$key]) && $this->local_cache[$group][$key] - $offset >= 0) {
$this->local_cache[$group][$key] -= $offset;
} else {
$this->local_cache[$group][$key] = 0;
}
if (isset($this->non_persistent_groups[$group])) {
return $this->local_cache[$group][$key];
} else {
$value = apcu_dec($key, $offset);
if ($value < 0) {
apcu_store($key, 0);
return 0;
}
return $value;
}
}
public function delete($key, $group = 'default', $force = false) {
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
unset($this->local_cache[$group][$key]);
if (!isset($this->non_persistent_groups[$group])) {
return apcu_delete($key);
}
return true;
}
public function flush() {
$this->local_cache = array();
//APCuのキャッシュエントリをクリアする
apcu_clear_cache();
return true;
}
public function get($key, $group = 'default', $force = false, &$found = null) {
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (!$force && isset($this->local_cache[$group][$key])) {
$found = true;
if (is_object($this->local_cache[$group][$key])) {
return clone $this->local_cache[$group][$key];
} else {
return $this->local_cache[$group][$key];
}
} elseif (isset($this->non_persistent_groups[$group])) {
$found = false;
return false;
} else {
$value = apcu_fetch($key, $found);
if ($found) {
if ($force) {
$this->local_cache[$group][$key] = $value;
}
return $value;
} else {
return false;
}
}
}
public function incr($key, $offset = 1, $group = 'default') {
if ($offset < 0) {
return $this->decr($key, abs($offset), $group);
}
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (isset($this->local_cache[$group][$key]) && $this->local_cache[$group][$key] + $offset >= 0) {
$this->local_cache[$group][$key] += $offset;
} else {
$this->local_cache[$group][$key] = 0;
}
if (isset($this->non_persistent_groups[$group])) {
return $this->local_cache[$group][$key];
} else {
$value = apcu_inc($key, $offset);
if ($value < 0) {
apcu_store($key, 0);
return 0;
}
return $value;
}
}
public function replace($key, $data, $group = 'default', $expire = 0) {
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (isset($this->non_persistent_groups[$group])) {
if (!isset($this->local_cache[$group][$key])) {
return false;
}
} else {
if (!isset($this->local_cache[$group][$key]) && !apcu_exists($key)) {
return false;
}
apcu_store($key, $data, (int) $expire);
}
if (is_object($data)) {
$this->local_cache[$group][$key] = clone $data;
} else {
$this->local_cache[$group][$key] = $data;
}
return true;
}
public function reset() {
//This function is deprecated as of WordPress 3.5
//Be safe and flush the cache if this function is still used
$this->flush();
}
public function set($key, $data, $group = 'default', $expire = 0) {
$group = $this->get_group($group);
$key = $this->get_key($group, $key);
if (is_object($data)) {
$this->local_cache[$group][$key] = clone $data;
} else {
$this->local_cache[$group][$key] = $data;
}
if (!isset($this->non_persistent_groups[$group])) {
return apcu_store($key, $data, (int) $expire);
}
return true;
}
public function stats() {
//Only implemented because the default cache class provides this.
//This method is never called.
echo '';
}
public function switch_to_blog($blog_id) {
$this->blog_prefix = $this->multisite ? intval($blog_id) : '';
}
}
}
キャッシュのクリアトリガー・クリア設定
キャッシュは定期的にクリアする必要があります。以下のコードをfunctions.phpに追加することで、キャッシュのクリアを制御することができます。
php
<?php //(functions.php)
/* 永続オブジェクトキャッシュ(APCu)
-------------------------------------- */
//最後のクリアから一定時間経過後にクリアするように(過度のクリア防止)
define('CACHE_CLEAR_INTERVAL', 5); //5秒
function should_clear_cache() {
$last_cleared = get_option('last_cache_cleared_time', 0);
return time() - $last_cleared > CACHE_CLEAR_INTERVAL;
}
//APCuキャッシュクリア関数
function clear_apcu_cache() {
if (function_exists('apcu_clear_cache')) {
apcu_clear_cache();
} elseif (function_exists('apc_clear_cache')) {
apc_clear_cache();
}
update_option('last_cache_cleared_time', time());
}
//記事投稿・更新時のAPCuキャッシュのクリア
function my_save_post($post_id, $post, $update) {
// リビジョンやオートドラフトはスキップ
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
//APCuキャッシュのクリア
if (should_clear_cache()) {
clear_apcu_cache();
}
}
add_action('save_post', 'my_save_post', 10, 3);
//メニュー更新時のAPCuキャッシュのクリア
function clear_cache_on_menu_update($menu_id, $menu_data) {
if (should_clear_cache()) {
clear_apcu_cache();
}
}
add_action('wp_update_nav_menu', 'clear_cache_on_menu_update', 10, 2);
//カスタマイザ更新時のAPCuキャッシュのクリア
function clear_cache_on_customize_save($wp_customize) {
if (should_clear_cache()) {
clear_apcu_cache();
}
}
add_action('customize_save_after', 'clear_cache_on_customize_save');
まとめ
永続オブジェクトキャッシュ(APCu)は、WordPressのパフォーマンス向上に有効な手段の一つです。上記の手順に従って設定することで、サイトの応答速度を向上させることが期待できます。ぜひ、試してみてください。
以上、WordPressでの永続オブジェクトキャッシュ(APCu)の使用方法についてのまとめでした。次回も役立つ情報をお届けしますので、お楽しみに!