Software Transactional Memo

STM関係のことをメモっていこうと思います。

python-memcacheへの不満

STMの話題では無いのだけれど自分がKVS上でのSTMというものをPythonで作りながら感じた事

pip install python-memcacheで入る、純Python実装のmemcacheクライアントライブラリ

memcachedクライアントライブラリは他にも幾つか有ったのだけれどことごとく要件を満たさなかった

- cmemcached : libmemcachedをcythonで包んだラッパ。失敗したら論理エラーだろうが物理エラーだろうが全部数字の0を返すというダメっぷりに辟易。ついでにcythonなので今のPyPyでは動かない

- pylibmc : libmemcachedを純Cで包んだラッパ。論理的な失敗(add, replace, casのセマンティクス上の失敗)ならきちんとboolで返って来るし、通信ミスなどの物理エラーはきちんと例外飛ばしてくれる。マルチスレッドの時に不気味な高パフォーマンスを叩き出すけれど挙動がまともじゃない、必要なロックを取ってないんじゃないかと思う。PyPyでも動くんだけど何故か低速化する。全体的にパフォーマンスは高いけれど、まだバグがある気がする。プロジェクトがgithub上に置いてあるので気軽にパッチ投げれる、育てるならこれ。

- python-memcache : 純粋にPythonで書かれたクライアントライブラリ。使ってて明らかにおかしい挙動を示したことは無い。でもmemcachedの論理上のエラーはTrule, Falseで返り、物理エラーは0で返るという狂気。なのでisinstanceで切り分ける事になる。多分意地でも例外投げない哲学で書いたんだと思う。

で、このpython-memcacheが今のところ一番信頼出来る実装と判断したので使ってるのだけれど問題が幾つかある

コンストラクタでcache_cas=Trueしないとcasコマンドを全部setコマンドでそのまま代用するという大変Kooolな事をやらかしてくれる。警告か例外投げればいいのに。もしくは全部Falseで返して欲しかった。危険方向に倒すというのがイミフ。

cache_casするとcas_idsというディクショナリにそのID値が蓄積されるのだけれど、ディクショナリなので無尽蔵に保存し続けると消せなくなって実質リークするので、ときどきreset_cas()コマンドを発行して回避する必要がある。でもcasに使うIDはmemcachedに操作を加えるたびに加算される論理クロックなので、casやsetやreplaceで書き換えた場合やdeleteで消した場合に対応するID値はmemcachedの仕様上絶対使われることが無くdel cas_ids[key]してしまって何も問題ない。更にはそんなリークもどきに頭を悩ますぐらいならPythonにはpylruという良いLRU実装があるのでそれを使うべき。その速度差がどうのなんて気にする人は多分初めから純Python実装なんて物を選ばない。

Pickleでシリアライズしている。これ自体はどうでもいいのだけれどJSONやmsgpackなどに随時すげ替えれる構造にしてくれると助かる人は多そう。PickleをPython以外から扱うのは面倒なので、初めから多言語文化のある物で保存できると素敵なのでは。

Launchpadに本体が置いてあるので気軽にパッチを投げれない。バグトラックを見る限り機能追加などの要望がほとんどでこういう基本的なところをきちんとしろという類の要望が無い。仕様は仕様だものね。memcachedの機能なんてSQLの補助やちょっとしたblobストアぐらいにしか思ってない人が主流なのかもしれない。

以下納得できない挙動など

>>> mc=memcache.Client(["127.0.0.1:11211"], cache_cas=True)
>>> print mc.gets("foo")
None
>>> print mc.set("foo", "bar")
>>> print mc.gets("foo")
bar
>>> print mc.set("foo", None)
>>> print mc.gets("foo")
>>> # ここでmemcachedを終了させる
>>> print mc.gets("foo")
None
>>> mc.cas_ids["foo"]
1
>>> print mc.gets("fooo")
None
酷い挙動だと思う。