WordPressのパフォーマンス改善策のひとつに、永続オブジェクトキャッシュ(APCu)があります。この記事では、APCuを使ったオブジェクトキャッシュの仕組みと、実際の設定手順をコード付きで解説します。
永続オブジェクトキャッシュとは
WordPressは標準でもオブジェクトキャッシュを持っていますが、これはリクエストをまたいで保持されない非永続キャッシュです。毎回のリクエストでデータベースに同じクエリを発行することになります。
永続オブジェクトキャッシュを使うと、データベースのクエリ結果や計算結果をサーバーのメモリに保持し、次回以降のリクエストでは再クエリなしにデータを取得できます。結果として、データベース負荷の軽減とサイトの応答速度向上が期待できます。
APCu(Alternative PHP Cache user)はPHPの拡張モジュールのひとつで、多くのレンタルサーバーで標準搭載されています。
設定前の確認:APCuが使えるか調べる
管理画面の ダッシュボード → サイトヘルス を確認してください。以下のような表示があればAPCuが利用できます。
「お使いのホスティングサービスでは、次のオブジェクトキャッシュサービスをサポートしているようです: APCu, Redis。」
また、「永続オブジェクトキャッシュを使用中です」 と表示されていれば、すでに有効化されています。

設定方法
1. object-cache.php を wp-content に配置する
WordPressのオブジェクトキャッシュは、wp-content/object-cache.php を置くことで上書きできます。以下のコードを object-cache.php として保存し、wp-content/ 直下に配置してください。
<?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')) {
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')) {
$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 {
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;
}
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_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->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() {
echo '';
}
public function switch_to_blog($blog_id) {
$this->blog_prefix = $this->multisite ? intval($blog_id) : '';
}
}
}
2. functions.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());
}
// 記事投稿・更新時のキャッシュクリア
function my_save_post($post_id, $post, $update) {
if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
if (should_clear_cache()) {
clear_apcu_cache();
}
}
add_action('save_post', 'my_save_post', 10, 3);
// メニュー更新時のキャッシュクリア
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);
// カスタマイザ更新時のキャッシュクリア
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');
有効化の確認
設定後、管理画面の ダッシュボード → サイトヘルス を開き、「永続オブジェクトキャッシュを使用中です」と表示されていれば有効化完了です。
Query Monitor プラグインを使うと、Object Cache の欄に「88% hit rate」のようなヒット率が表示され、キャッシュが効いていることを確認できます。
まとめ
永続オブジェクトキャッシュ(APCu)の設定は、object-cache.php の配置と functions.php へのクリア処理の追加だけで完了します。データベースへのアクセスが減り、ページ生成時間の短縮が期待できます。
サイトヘルスで「APCu」または「Redis」がサポートされていると表示されているサーバーであれば、今日からでも試せる改善策です。
