memcached の無期限キャッシュを取得する方法

memcached に expire を 0 で値を set すると、メモリから溢れるまで消えることはない。
この、無期限にキャッシュされた値を取得する方法が必要になったので調べてみた。


memcachedプロトコルには、キーの一覧やキーの情報を直接取得するようなコマンドはない。
そこで、統計情報を返す stats を利用して取得することになる。

stat items コマンドで、値を保持するslab(キャッシュ可能なサイズ毎に分かれた部屋のようなもの)の一覧が得られる

>stats items
STAT items:1:number 4
STAT items:1:age 229308
STAT items:1:evicted 0
STAT items:1:outofmemory 0
STAT items:2:number 1
STAT items:2:age 229308
STAT items:2:evicted 0
STAT items:2:outofmemory 0
(中略)
END


stat items の出力は以下のフォーマットで、非正規化された構造になっている。

STAT items:: \r\n

items: に続く数字が slabを識別する値(slabclass)であり、
number に続く数字が、そのslabで保持しているキャッシュの数(但し、期限切れ含む)である。


また、この slabclass を用いて stats cachedump を呼び出すことで、
そのslabに含まれているキャッシュのキーと情報を得ることが出来る。

stats cachedump

に、大きな値を渡しても問題なく取得できるが、
stats items で取得した number の値が最大となるので、それを渡せばよい。

>stats cachedump 1 4
ITEM ca55e225a3df00a5f8afd5e01ab1 [125 b; 1256870354 s]
ITEM a55e1a25a3dacecf0a678fd50ax1 [143 b; 1256991227 s]
ITEM ma521a525a3d0na565f8afe1absw [214 b; 1256868424 s]
ITEM ce21a525a3d00a5675fcafd5e01a [156 b; 1256814456 s]
END

stats cachedump の出力は以下のフォーマットである。

ITEM [ b;

キャッシュキー
保持されている値のバイト数
有効期限(unix timestamp)


有効期限は、過去であればキャッシュ切れと言うことになる。
が、無期限キャッシュの場合 0 というわけではない。


cachedump で出力される有効期限は、
アイテムに保持されている有効期限*1 + memcached起動時刻*2
なのである。 *3


get の期限チェックを見ても、memcached 内部ではサーバの稼働時間をベースで計算していることが読み取れる。 *4


なので、無期限キャッシュであることを確認するには、
出力された有効期限からmemcached起動時刻を引いた値が0かどうかでチェックする必要がある。

で、memcached起動時刻を取得する方法であるが、これは stats コマンドの
"time"の項 - "uptime"の項が、それに該当する。*5



さて、さんざん出力フォーマットなんかの話をしておきながら、
PHP の場合 pecl の memcache を使えばその辺を気にせずに利用できる。

$m = new Memcache();
$m->addServer('localhost', 11211, false) or die ("cannot connect");

$stats = $m->getStats();
$memcached_started_time = $stats['time'] - $stats['uptime'];

$stats_item = $m->getStats('items');
$items = $stats_item['items'];
foreach ($items as $slabclass => $item) {
    $stats_cachedump = $m->getStats('cachedump', $slabclass, $item['number']);
    foreach ($stats_cachedump as $cache_key => $v) { 
        if (0 == ($v[1] - $memcached_started_time)) {
            echo $cache_key, "\n";
        }
    }
}

動作の確認は repcached 2.1 (memcached 1.2.6) にて。

キーの数が多いと、memcached の起動時刻とキーの expire の値が乖離してくる場合があるので注意。


*1:exptime (struct _stritem 構造体のメンバ)

*2:1.2.8:stats.started / 1.4.2:process_started

*3:SourceCode: 1.2.8:item.c@341(do_item_cachedump関数) / 1.4.2:item.c@369(do_item_cachedump関数

*4: SourceCode: 1.2.8:items.c@487(do_item_get_notedeleted関数) / 1.4.2:item.c@478(do_item_get関数)

*5: SourceCode: 1.2.8:memcached.c@1077(process_stat関数) / 1.4.2:memcached.c@2147(server_stats関数)