-
PHPからpecl_memcachedでkumofsにcasができない
Posted on 7月 29th, 2010 はおりん No commentsTwitterで140文字でチマチマ報告してないで、記事に起こせばいいんじゃん。と気付いてしまったので記事にするぜっ!
まず、説明
知らない人でもうちの記事は(きっと)読めます。それが僕のジャスティス。
kumofsというのは、1つのデータを3台のサーバにコピーして保存してくれる簡易データベースで、3つにコピーされるので2つまで壊れても大丈夫。
表側はmemcacheっていう、一時的なデータ置き場にするサーバ群用のプロトコルを使っていて、これはPHPやらJAVAやらに互換ライブラリがあるので、kumofsを使う側のプログラムの開発が簡単!相当はしょって説明してるぞ、うん(ぉぃぉぃ
1.libmemcachedにバグがある
で、kumofsは複数から同時アクセス出来るので、
・Aが読む
・Bが読む
・Bが値を更新する
・Aが値を更新する
とやると、Aが速いかBが速いかで更新の結果が変わる。
これはいかんので、memcacheプロトコルでは「他から更新されてなければ、上書きする」という「cas」という命令がある。
いわゆる排他制御にはこの命令は必須なんだが・・・pecl_memcachedが裏側で使ってるlibmemcachedの実装が間違っていてcas命令が使えない!(長
memcacheプロトコルはテキストで実装されており、
「cas」の後に半角スペースを1つ空けることになっているのだが、なぜかlibmemcached 0.42では2つ空いてる。
いや、ソースコードを最適化しようとした形跡は見えるんだけどその結果これではどうしようも(ryというわけで、まずここを直す。
Index: storage.c =================================================================== --- storage.c (リビジョン 3365) +++ storage.c (リビジョン 3366) @@ -105,7 +105,7 @@ if (cas) { write_length= (size_t) snprintf(buffer, MEMCACHED_DEFAULT_COMMAND_SIZE, - "%s %.*s%.*s %u %llu %zu %llu%s\r\n", + "%s%.*s%.*s %u %llu %zu %llu%s\r\n", storage_op_string(verb), (int)ptr->prefix_key_length, ptr->prefix_key,
と思ったら!kumofs最新版でkumofs側に対処されたwww
僕の苦労は?wwwまだだ、まだ動かんぞ!!
こっから先はうちだけの問題なのか、それとも僕の勘違いなのか・・・上記の対処でもcasが動かんかった。今度の原因はpecl_memcached 1.0.2内。
すごーく簡単に言うと・・・
64ビット整数を浮動小数点にキャストするとオーバーフローが発生することがあります。
なにを言ってるかわからないと思いますが、説明がすこぶる面倒なんで割愛します(ぇぇぇPHPは64ビット整数は扱えません。
PHPで64ビットの幅がある数値はdouble型、浮動小数点数です。
つまり、pecl_memcachedは、64ビット整数から64ビット浮動小数点数に変換すれば、同じ数値になるだろうと考え・・・
ダウト。浮動小数点数は、「仮数部」と「指数部」ってのがあって、数値は「仮数部を指数部で累乗したもの」で表現されます。つまり、数字がそのまま入っているわけではない。
CPUさんは親切なので、ビット列をそのままコピーせずに、ちゃんと、浮動小数点数で近い値になるように計算して調整してくれます。
このとき、「なにの、なん乗が、同じ数値になるのか」を計算しますが、どうしても同じにならないことがあります。
数値に一切の変動が無ければ、浮動小数点数から64ビット整数に戻したときに同じビット列が再現されるはずですが、残念ながら浮動小数点数では1という数字は0.999999999にズレたりします。つまり、整数に戻したときに同じにならないかもしれない。
でもビット幅は同じ。じゃ、どうすればいいか。僕は考えた。「cas_tokenがpecl_memcachedとPHPの間で行き来してる間に整合性を保てれば良い。PHPで64ビット整数に近い数値を読む必要は無いんじゃ?」
というわけで、見た目の数値よりもビット列を維持することにしました。つまり、64ビット整数で取得したcas_tokenは、PHP側ではデタラメな浮動小数点数に見えます。しかし、ビット列は維持されているので、全く同一の64ビット整数に戻すことが出来ます。
パッチはコレ。
Index: php_memcached.c =================================================================== --- php_memcached.c (リビジョン 3366) +++ php_memcached.c (リビジョン 3367) @@ -428,7 +428,7 @@ } zval_dtor(cas_token); - ZVAL_DOUBLE(cas_token, (double)cas); + ZVAL_DOUBLE(cas_token, *((double *)(&cas))); memcached_result_free(&result); @@ -1231,7 +1231,7 @@ RETURN_FALSE; } - DVAL_TO_LVAL(cas_d, cas); + cas = *((uint64_t *)(&cas_d)); if (i_obj->compression) { flags |= MEMC_VAL_COMPRESSED;
ポインタを経由することで無理やりCPUに「ほぉら浮動小数点数のビット列だよぉ」と誤認させる。かなり無理やり。
double型のビット幅が64ビットじゃ無かったりする環境ではきっとSIG_SEGVを吐くこと間違いなし!でも、うちの環境ではコレで動いてる。
PHPでちゃんとしたcas_tokenが欲しい場合は別の対処があるかもしれないけど、うちの環境では、doubleにキャストした時点で数字が変わってたのでアウト。
文字列で渡せばいいんですけどねー。速度重視したんですよぉー。
未分類One Response to “PHPからpecl_memcachedでkumofsにcasができない”
-
[…] PHPのmemcached APIだとCASがうまく使えないってバグ(?)もありました。 […]
コメントを書く
-