memcached の二つのプロトコルと API デザインの難題について

yrmcdsという memcached のクローンを開発・メンテナンスしています。yrmcds は memcached を機能拡張しているので、クライアントライブラリも独自に開発する必要があり、libyrmcds という C/C++ 用クライアントライブラリとそれを基にした php-yrmcds という PHP の拡張モジュールも開発・メンテナンスしています。

この時点でもう関心ない人が大半だと思いますが、ここから書くのは世界中でたぶん私くらいしか苦労してないし、することもない話です。苦労したので書き残しておくか、くらいな駄文です。 You've been warned!

memcached には当初から実装されているテキストプロトコルと、後から追加されたバイナリプロトコルがあります。後から追加されたバイナリプロトコルは後発なだけあって、テキストプロトコルにはない、以下の特徴があります。

  • opaque フィールドに ID を持たせることで、非同期処理が可能

    c.f. memcachedバイナリプロトコルは同期プロトコルを禁止するべき - Blog by Sadayuki Furuhashi

  • CAS (compare-and-swap)用フィールドがリクエスト・レスポンスに必ずある

    各種操作(例えば delete)での CAS が可能。
    set/add/replace のレスポンスで新たな CAS 値がわかる。

  • キーに空白やコントロールキャラクターを含めることができる

    インジェクション攻撃に強いともいえる。

難しいのはここから、です。バイナリプロトコルの良い点を既存のテキストプロトコルを前提に設計されたクライアントライブラリが活用することは困難です。まずもって同期処理*1API を非同期にすることが無理筋です。

一方バイナリプロトコルをテキストプロトコルと同じ範囲で使うことは容易なので、世の中にある memcached クライアントライブラリでテキスト・バイナリに両対応しているライブラリは、一様にテキストプロトコルの表現範囲内で API を提供しています。同期的な API を非同期的に使わせるためにイベントループを自前で持つ(利用しにくい)ライブラリもあります。

libyrmcds のようにバイナリプロトコルを前提に設計されたライブラリは、テキストプロトコルをサポートすることが困難です。表現力の豊かなプロトコルに合わせて API 設計しているため、表現力に劣るプロトコルでは API をきちんと実装することができないのです。

制限多くなるのがわかっているテキストプロトコルに libyrmcds を対応させようとはつゆほども思わなかったわけですが、今をときめく h2o が libyrmcds を(たぶん非同期 API の使い勝手の良さを買って)採用してくださっており、テキストプロトコル対応ができると redis も利用できるといった声が寄せられました。

というわけで、libyrmcds にテキストプロトコルモードを実装しました。制限付きではあるのですが、利用者視点ではバイナリプロトコルAPI を大きな差がなく使えるようにできたので、我ながら頑張ったかなと。詳細はこちらをご覧ください。

まとめに変えて、memcachedプロトコルの事例から学べる点を書いておきます。

  • 表現力の違うプロトコルは、クライアントでの対応が困難
  • バイナリプロトコル・テキストプロトコルで表現力を変えたのは悪手
  • やるならテキストプロトコルも強化して、バイナリと同等の表現力を持たせるべきだった
  • サーバーはどんなプロトコルも適当に実装できる。API の表現力といった問題はないため。

もっともその場合、バイナリプロトコルには大した利点はないと思いますけどね。(速度に違いはほとんどないです。実装して計測もしましたし。)お目汚し失礼しました。

*1:memcached はレスポンスパケットが省略されたりされなかったりするので、ひとつひとつのコマンドについて同期的に処理する API 設計にしないとコマンドの対応がとれない