« WEB+DB PRESS 連載「大規模Webサービスの裏側 第二回 | メイン

mod_libmemcached_cacheでApacheのcacheをmemcachedに保存する

Apacheのmod_cacheのキャッシュ保存先にmemcachedが使えればいいのにと長年思ってきましたが、mod_libmemcached_cacheがそれを実現してくれました。
しかも、libmemcachedを利用しているので、性能も高く、またConsitent Hashingも使えますし、バイナリプロトコルもばっちりです。

mod_libmemcached_cache.png


図にするとこんな感じ。revserse proxyのcacheがmemcachedになるので、cache効率が上がり、またApplicationサーバからも同じmemcachedが参照できるのでcacheを変更したりできるかもしれません。

導入


mod_libmemcached_cacheはgithubから入手できます
http://github.com/agentzh/mod-libmemcached-cache/tree/master

git clone git://github.com/agentzh/mod-libmemcached-cache.git
cd mod-libmemcached-cache


ビルドには、Apacheのソース(mod_cache.h)とlibmemcachedが必要です。
libmemcachedはrpmもあるのでさくっと導入できると思います。

あと、若干のバグ(レスポンスが1byte多い)とlibmemcachedのオプションを指定したかったので以下のpatchを当てました。

diff --git a/mod_libmemcached_cache.c b/mod_libmemcached_cache.c
index ca39cd8..8042f17 100644
--- a/mod_libmemcached_cache.c
+++ b/mod_libmemcached_cache.c
@@ -295,7 +295,7 @@ static int open_entity(cache_handle_t *h, request_rec *r, const char *key) {
             /* Found the data file */
             found_body = 1;
             lobj->body = ret_val;
-            lobj->body_len = ret_val_len + 1;
+            lobj->body_len = ret_val_len;
         } else {
             /* ret_key[ret_key_len - 1] = '\0'; */
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
@@ -614,7 +614,11 @@ static int libmem_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
             "Found %d memcached servers.",
             memcached_server_count(sconf->memc));
     //memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 1);
-    //memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
+    memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
+    memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_TCP_NODELAY, 1);
+    memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_DISTRIBUTION, MEMCACHED_DISTRIBUTION_CONSISTENT );
+    //memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
+    memcached_behavior_set(sconf->memc, MEMCACHED_BEHAVIOR_IO_KEY_PREFETCH, 1);
 
     return OK;
 }


make時にApacheのapxsと、ソースの場所を指定します

$ make APXS=/usr/sbin/apxs \
   MOD_CACHE_SRC_DIR=/path/to/httpd/httpd-2.2.9/modules/cache  

今回ちょっと面倒だったのでmake installはしていません。
Apacheから直接buildしたディレクトリを参照しちゃいました。

Apacheのhttpd.confには以下を追加しました。
memcachedは4台設定です

LoadModule cache_module modules/mod_cache.so
LoadModule libmemcached_cache_module /path/to/mod-libmemcached-cache/.libs/mod_libmemcached_cache.so
<IfModule mod_libmemcached_cache.c>
  LibmemCacheServers 127.0.0.1:11255,127.0.0.1:11256,127.0.0.1:11257,127.0.0.1:11258
  CacheEnable libmemcached /
</IfModule>


memcachedは今回1.4.0-rc1を使いました。
httpd.confで指定した、4台起動します。

$ memcached -p 11255 -u 0 -d
$ memcached -p 11256 -u 0 -d
$ memcached -p 11257 -u 0 -d
$ memcached -p 11258 -u 0 -d

「-u 0」はUDPをListenしないためのオプションです。

設定ができたので、Apacheを起動します。起動方法は様々でしょう

$ apachectl -k start


確認


curlを使ってcacheがされているか確認します。test.jpgというファイルをDocumentRootに置いて、

$ curl -s -v -O /dev/null 'http://localhost:8888/test.jpg'

なんどかアクセスして、レスポンスのヘッダに

Age: 数字

が記録されていたら成功です。どこかのmemcachedにcacheされています。
本当にmemcachedに保存されているかは、memcachedを起動する時に、デーモンにせずに-vvオプションを付けておけばコンソールにログが流れるのでわかるでしょう。

ベンチマーク


ベンチマークは、abではなく、http_loadを使いました。http_loadだと複数のURLにアクセスができるのでmemcachedの分散を確認するのにもこちらを選択しました。
まずDocumentRootに、1.jpgから40.jpgまでファイルを作成します。今回は3.5KBのjpgを利用しました。ファイル名が異なるだけで中身はすべて同じです。

urls_fileにベンチマークで使うURLを書きます。

http://localhost:8888/1.jpg
http://localhost:8888/1.jpg
..
http://localhost:8888/40.jpg


このファイルを指定してhttp_loadを起動します。

$ http_load -verbose -parallel 8 -fetches 10000 urls_file


ベンチマーク結果


ベンチマーク結果です。
まず、mod_cacheを無効にした状態。直接配信

10000 fetches, 8 max parallel, 3.549e+07 bytes, in 1.49197 seconds
3549 mean bytes/connection
6702.53 fetches/sec, 2.37873e+07 bytes/sec
msecs/connect: 0.184619 mean, 6.372 max, 0.018 min
msecs/first-response: 0.706464 mean, 15.463 max, 0.365 min
HTTP response codes:
  code 200 -- 10000


次に、mod_libmemcached_cacheを有効にした状態

verbose -parallel 8 -fetches 10000 urls
10000 fetches, 8 max parallel, 3.549e+07 bytes, in 1.06743 seconds
3549 mean bytes/connection
9368.33 fetches/sec, 3.32482e+07 bytes/sec
msecs/connect: 0.13438 mean, 1.019 max, 0.017 min
msecs/first-response: 0.488267 mean, 3.157 max, 0.129 min
HTTP response codes:
  code 200 -- 10000


なんと、memcachedの方が1.5倍高速です!
直接配信の方は、diskにnoatimeオプションを付けていないという理由もありますが、これには驚きです。

今回のテストはApacheはpreforkで動かしていますが、workerの場合どうなるのか、
また、memcachedと接続するport数はどうなるのかなど検証がもうちょっと必要です。