AJAJA: memcachedを使えるようにしてみよう

大よそ、C言語でのAJAJAのモジュールの書き方がわかってきたので、例題としてmemcachedAJAJAで使えるようにしてみました。(何でこのタイミングでmemcachedバインディングを書いたのか?と聞かれると困ります(汗。単に興味があったからやってみただけですw。) memcachedとは、簡単に言うとメモリーキャッシュサーバのことで、LiveJournalmixiはてななどの大規模システムで利用されているツールです。

memcachedC言語のバイディングAPIを提供しているlibmemcacheを使って、memcachedの機能の一部を利用できるようにしました。昨日はじめてmemcachedをインストールして触りだしたので、機能的にはまだまだしょぼいですが、一応動きます。

クライアントでの利用例

現段階で、以下のようなコードが動作します。keyとvalueのペアをmemcachedにキャッシュして、keyをもとにキャッシュしたvalueを単に取り出しているだけです。APIは、CPANモジュールのCache::Memcached::XSを少し参考にしましたが、まだ程遠いのが現状です。現状では、keyとvalueにはStringが利用できます。オブジェクトをmemcachedにキャッシュするのは、どうも難しそうな気配がします。

% vim memcached_test.js
use('System');
use('Memcached');

memd = new Memcached({server:"127.0.0.1", port: "11211"});
memd.set("hello", "world");
memd.set("good", "bye!");

System.puts("hello, " + memd.get("hello") + "\n");
System.puts("good " + memd.get("good") + "\n");

memcachedlocalhostの11211ポートで走らせておいて、ssjsで実行させると、期待通りの結果が得られます。

% memcached -d -p 11211
% ssjs memcached_test.js
hello, world
good bye!

モジュールのソースコード

モジュールのソースコードは以下の通りです。AJAJAに搭載されているSQLiteバインディングのコード(SQLite/SQLite.c)をかなり参考にして書きました。LLな言語になれていると、C言語でのコーディングはやりにくく感じますが、そのぶん細かい制御ができるので、それはそれで楽しいですね。結構C言語にはまりつつあります。。

/*
 * memcached for AJAJA
 *
 */
#include <string.h>

#include "jsapi.h"
#include "jsprf.h"
#include "jscntxt.h"
#include "jsinterp.h"

#include <memcache.h>

#ifdef DEBUG
    #define DBG(msg...)         \
        fprintf(stderr, msg)
#else
    #define DBG(msg...)
#endif

/* prototype */
static JSClass mc_class;

static JSBool
memcached_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str_server, *str_port;
    jsval jsval_server, jsval_port;
    JSObject *obj_argv;
    char *server, *port;
    struct memcache *mc;

    if (!JSVAL_IS_OBJECT(argv[0]))
        goto error;

    obj_argv = JSVAL_TO_OBJECT(argv[0]);

    if (!JS_GetProperty(cx, obj_argv, "server", &jsval_server))
        goto error;

    if (!JS_GetProperty(cx, obj_argv, "port", &jsval_port))
        goto error;

    if ((str_server = JS_ValueToString(cx, jsval_server)) == NULL)
        goto error;

    if ((str_port = JS_ValueToString(cx, jsval_port)) == NULL)
        goto error;

    server = JS_GetStringBytes(str_server);
    port = JS_GetStringBytes(str_port);

    DBG("memcached_constructor(): server = %s, port = %s\n", server, port);

    /* libmemcache */
    mc = mc_new();
    mc_server_add(mc, server, port);

    if (!JS_SetPrivate(cx, obj, mc))
        goto error;

    return JS_TRUE;

error:
    *rval = JSVAL_VOID;
    return JS_FALSE;
}

static void
memcached_finalize(JSContext *cx, JSObject *obj)
{
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    DBG("memcached_finalize()\n");

    mc_free(mc);
}

static JSBool
memcached_set(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str;
    char *key, *value;
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    /* get key */
    if ((str = JS_ValueToString(cx, argv[0])) == NULL)
        return JS_FALSE;
    key = JS_GetStringBytes(str);

    /* get value */
    if ((str = JS_ValueToString(cx, argv[1])) == NULL)
        return JS_FALSE;
    value = JS_GetStringBytes(str);

    DBG("memcached_set(): key = %s, value = %s\n", key, value);

    mc_add(mc, key, strlen(key), value, strlen(value), 10, 0);

    return JS_TRUE;
}

static JSBool
memcached_get(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    JSString *str;
    char *key, *value;
    struct memcache *mc;

    mc = JS_GetInstancePrivate(cx, obj, &mc_class, NULL);

    /* get key */
    if ((str = JS_ValueToString(cx, argv[0])) == NULL)
        return JS_FALSE;
    key = JS_GetStringBytes(str);

    value = (char *)mc_aget(mc, key, strlen(key));
    *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, value));

    DBG("memcached_get(): key = %s, value = %s\n", key, value);

    return JS_TRUE;
}

static JSClass mc_class = {
    "Memcached",
    JSCLASS_HAS_PRIVATE,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_PropertyStub,
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    memcached_finalize
};

static struct JSPropertySpec mc_props[] = {
    /* {name, tinyid, flags, getter, setter}, */
    {0}
};

static struct JSFunctionSpec mc_funcs[] = {
    /* {name, call, nargs, flags, extra}, */
    {"set", memcached_set, 2, 0, 0},
    {"get", memcached_get, 1, 0, 0},
    {0}
};

int
JS_InitModule_Memcached(JSContext *cx, JSObject *obj)
{
    JS_InitClass(cx, obj, NULL,
                 &mc_class,
                 memcached_constructor,
                 1,
                 mc_props,
                 mc_funcs,
                 NULL,
                 NULL);

    return 1;
}

モジュールのビルドとインストール

以下の手順でモジュールのビルドとインストールができます。libmemcacheを予めインストールしておく必要があります。ajaja-build/と同じ階層でMemcached-0.01.tar.gzを展開すれば、うまくmakeできるはずです。libmemcache.aが/usr/local/lib/以下にあると仮定しているので、環境によっては、これが原因でmakeできないかもしれません。その時は、Makefileにある「LDFLAGS」のパスを調整して下さい。

% cd ajaja
% ls
ajaja-build/
% wget http://vaio.redirectme.net/lib/ajaja/Memcached-0.01.tar.gz
% tar zxvf Memcached-0.01.tar.gz
% cd Memcached-0.01
% make
% sudo make install