WordPress Object Cache のバックエンドに Tokyo Cabinet を利用する

2.5以前の WordPress には Object Cache という機能がありました。
最近の WordPress では、この機能はデフォルトでは使えない状態になっているのですが、wp-content フォルダに object-cache.php というファイルを置き、wp-config.php に "define(’ENABLE_CACHE’,true);" を追加することで、この機能が使用できるようになります。
この object-cache.php は、WordPress のコアには含まれていないので、どこからか調達してくる必要があります。
バックエンドに何を使うかにも寄りますが、ファイルや memcached に Cache 情報を持たせるモジュールが提供されています。

参考URL:

で、この object-cache.php を書いてしまえば、Memcached やファイルだけでなく、軽量データベースライブラリ Tokyo Cabinet なんかもバックエンドのDBとして利用できるのです。
ってなわけで、レンタルサーバに Tokyo Cabinet をインストールして、WordPress から利用してみましょう。

Tokyo Cabinet のインストール

さくらのレンタルサーバにインストールする方法をざっくりと紹介。
参考にしたのは、BLOG::broomie.net さんの「さくらのレンタルサーバにTokyo Promenadeをインストールする方法」というエントリ。

まず、Tokyo Cabinet Files on SourceForge.net から、Tokyo Cabinet のソースをダウンロードしましょう。
2009年11月2日現在では、Ver.1.4.9 が最新バージョンのようです。

次に、ダウンロードしたファイルを展開後、configure -> make してインストールです。
ざっと、こんな感じ。

$ tar zxvf tokyocabinet-1.4.9.tar.gz
$ cd tokyocabinet-1.4.9
$ ./configure -prefix=$HOME/usr/local
$ gmake
$ gmake install

注意点は、configure するときにプリフィックスをつけることくらいかな。

Tokyo Cabinet の PHP バインディングモジュールのインストール

次に Tokyo Cabinet の PHP バインディングモジュールphp_tokyocabinet をインストールしましょう。
最新バージョンは、php_tokyocabinet-0.3.0 のようなので、それをダウンロードします。
ざっと、こんな感じ。

$ tar zxvf php_tokyocabinet-0.3.0.tgz
$ cd php_tokyocabinet-0.3.0
$ phpize
$ setenv  PKG_CONFIG_PATH $HOME/usr/local/lib/pkgconfig
$ configure --enable-tokyocabinet
$ gmake

# configure内でpkg-configを使うので、PKG_CONFIG_PATH をセットしています。

で、ルート権限があれば、このあと gmake install としてしまえば良いですが、共用レンタルサーバでは、それはできません。
なので、modules/tokyocabinet.so を ~/lib/ext とかにコピーしておいてあげましょう。
その状態で php.ini に以下の記述を追加すれば、OKです。

[tokyocabinet]
extension_dir = /home/username/lib/ext
extension = tokyocabinet.so

モジュールが読み込まれているかどうかは phpinfo() で確認できます。
tokyocabinet

object-cache の設置

あとは Object Cache のバックエンドとして、Tokyo Cabinet を使う object-cache.php を作成しましょう。
とりあえず、適当に書いたのが、こんな感じ。

<?php
/*
Name: Tokyo Cabinet
Description: Tokyo Cabinet backend for the WP Object Cache.
Version: 0.1.0
URI: 
Author: wokamoto

Install this file to wp-content/object-cache.php
*/

function wp_cache_add($key, $data, $flag = '', $expire = 0) {
	global $wp_object_cache;
	return $wp_object_cache->add($key, $data, $flag, $expire);
}

function wp_cache_incr($key, $n = 1, $flag = '') {
	global $wp_object_cache;
	return $wp_object_cache->incr($key, $n, $flag);
}

function wp_cache_decr($key, $n = 1, $flag = '') {
	global $wp_object_cache;
	return $wp_object_cache->decr($key, $n, $flag);
}

function wp_cache_close() {
	global $wp_object_cache;

	return $wp_object_cache->close();
}

function wp_cache_delete($id, $flag = '') {
	global $wp_object_cache;
	return $wp_object_cache->delete($id, $flag);
}

function wp_cache_flush() {
	global $wp_object_cache;
	return $wp_object_cache->flush();
}

function wp_cache_get($id, $flag = '') {
	global $wp_object_cache;
	return $wp_object_cache->get($id, $flag);
}

function wp_cache_init() {
	global $wp_object_cache;
	$wp_object_cache = new WP_Object_Cache();
}

function wp_cache_replace($key, $data, $flag = '', $expire = 0) {
	global $wp_object_cache;
	return $wp_object_cache->replace($key, $data, $flag, $expire);
}

function wp_cache_set($key, $data, $flag = '', $expire = 0) {
	global $wp_object_cache;
	if ( defined('WP_INSTALLING') == false )
		return $wp_object_cache->set($key, $data, $flag, $expire);
	else
		return $wp_object_cache->delete($key, $flag);
}

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->add_non_persistent_groups($groups);
}

class WP_Object_Cache {
	var $global_groups = array(
		'users',
		'userlogins',
		'usermeta',
		'site-options',
		'site-lookup',
		'blog-lookup',
		'blog-details',
		'rss'
		);

	var $no_hdb_groups = array(
		'posts',
		'comment',
		'counts'
		);

	var $autoload_groups = array(
		'options'
		);

	var $cache = array();
	var $db = array();
	var $stats = array();
	var $group_ops = array();
	var $pre;

	var $cache_enabled = true;
	var $default_expiration = 0;

	function WP_Object_Cache() {
		__construct();
	}
	function __construct() {
		global $table_prefix;
		$this->pre = $table_prefix;
		foreach ( $this->autoload_groups as $group ) {
			$this->get_hdb($group);
		}
	}

	function add($id, $data, $group = 'default', $expire = 0) {
		$key = $this->key($id, $group);

		if ( in_array($group, $this->no_hdb_groups) ) {
			$this->cache[$key] = $data;
			return true;
		}

		$hdb =& $this->get_hdb($group);
		if ( $hdb !== FALSE ) {
			$result = $hdb->put($key, $this->maybe_serialize($data));
			@ ++$this->stats['add'];
			$this->group_ops[$group][] = "add $id";
		} else {
			$result = FALSE;
		}

		if ( false !== $result )
			$this->cache[$key] = $this->maybe_serialize($data);

		return $result;
	}

	function add_global_groups($groups) {
		if ( ! is_array($groups) )
			$groups = (array) $groups;

		$this->global_groups = array_merge($this->global_groups, $groups);
		$this->global_groups = array_unique($this->global_groups);
	}

	function add_non_persistent_groups($groups) {
		if ( ! is_array($groups) )
			$groups = (array) $groups;

		$this->no_hdb_groups = array_merge($this->no_hdb_groups, $groups);
		$this->no_hdb_groups = array_unique($this->no_hdb_groups);
	}

	function close() {

		foreach ( $this->db as $bucket => $hdb )
			$hdb->close();
	}

	function delete($id, $group = 'default') {
		$key = $this->key($id, $group);

		if ( in_array($group, $this->no_hdb_groups) ) {
			unset($this->cache[$key]);
			return true;
		}

		$hdb =& $this->get_hdb($group);
		$result = ( $hdb !== FALSE ? $hdb->out($key) : FALSE );

		@ ++$this->stats['delete'];
		$this->group_ops[$group][] = "delete $id";

		if ( false !== $result )
			unset($this->cache[$key]);

		return $result; 
	}

	function flush() {
		return true;
	}

	function get($id, $group = 'default') {
		$key = $this->key($id, $group);
		$hdb =& $this->get_hdb($group);

		if ( isset($this->cache[$key]) )
			$value = $this->maybe_unserialize($this->cache[$key]);
		else if ( in_array($group, $this->no_hdb_groups) || $hdb === FALSE )
			$value = false;
		else
			$value = $this->maybe_unserialize($hdb->get($key));

		@ ++$this->stats['get'];
		$this->group_ops[$group][] = "get $id";

		if ( NULL === $value )
			$value = false;

		$this->cache[$key] = $this->maybe_serialize($value);

		if ( 'checkthedatabaseplease' == $value )
			$value = false;

		return $value;
	}

	function key($key, $group) {	
		if ( empty($group) )
			$group = 'default';
		$group = $this->pre.$group;

		return preg_replace('/\s+/', '', "$group:$key");
	}

	function replace($id, $data, $group = 'default', $expire = 0) {
		$result = $this->add($id, $data, $group, $expire);
		return $result;
	}

	function set($id, $data, $group = 'default', $expire = 0) {
		$result = $this->add($id, $data, $group, $expire);
		return $result;
	}

	function &get_hdb($group) {
		if ( empty($group) )
			$group = 'default';

		if ( in_array($group, $this->no_hdb_groups) )
			return FALSE;

		$group = $this->pre . $group;
		if ( !isset($this->db[$group]) ) {
			$this->db[$group] = new TCHDB();
			$this->db[$group]->open(trailingslashit(WP_CONTENT_DIR) . 'cache/' . $group . '.hdb', TCHDB::OWRITER | TCHDB::OCREAT);
		}

		return $this->db[$group];
	}

	function maybe_serialize( $data ) {
		if ( is_array( $data ) || is_object( $data ) )
			return serialize( $data );

		if ( is_serialized( $data ) )
			return serialize( $data );

		return $data;
	}

	function maybe_unserialize( $original ) {
		if ( is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
			return @unserialize( $original );
		return $original;
	}

}
?>

ただし Tokyo Cabinet は TCHDB::OWRITER オプションで、DBをオープンしている際はテーブルロックをかけてしまうので、このままではパフォーマンスが悪いです。
その辺も、考慮してこのモジュールを修正すれば、結構イケるのでは無いでしょうか?
共用レンタルサーバだから memcached は使えないんだよなぁという人はチャレンジしてみてください。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください