2009年07月05日

ケイレキ.jpの中でケイレキ.jpに招待して欲しい人を呼びかけても絶賛スルーされてるYappoです。さて今回は今巷で大人気のKey Value StorageでORマッパーを使う事についてお話するのじゃ。

一般的にORマッパーとはオブジェクトとリレーショナルデータベースをマッピングする為の仕組みの呼び名だと言うのは知られている所です。はい、そうするとKVSってのはハッシュデータベースであるわけなのでおかしいですね。今回の話はData::Model::Driver::Memcachedを使う事を前提としてるので問題が無いのです。なぜなら「data/object mapper」とか書いてあるから。

いわゆるPerlなORマッパーってのは行データをHASHで管理します。それはRDBが一般的に表形式でデータを管理しているからなんだと思います。なんでKVSをオブジェクトにマッピングするなら他の方法でも良いかもしれませんが、Data::Modelは異種ストレージでも同じインターフェィスで使えるようにする。例えば最初はSQLiteで動かすけどあとあとTokyoTyrantに乗せ替えるみたいな事をやる時にアプリケーションをほぼ変更無しで乗せ替えを可能にするといった事を考えて作ってあります。
厳密に言うとwhereとかindexとか使うクエリ使ってしまったら今のところ駄目ですけども。。。

本題に入るけども、先ほどリリースしたData::ModelからDriver::Memcachedを大幅に強化して各種KVSへデータを保存するのに有利な仕組みをいくつか追加しました。Driver::Memcachedはmemcached protocolに対応したサーバもしくは、Cache::Memcachedと互換なインターフェィスをストレージとして利用します。素のmemcached以外の物で使ってもらう事を考えてますが、データの生存期間に合わせてそのへんは選べば良いと思います。僕も特定のデータだったらmemcachedの方に突っ込んでるし。

テーブル名変換

Data::Modelはmemcachedのkeyにテーブルを含めます。
またinstall_modelでkeyとして定義されたkeyの値もふくまれます。
例えば「test_table」 という名前のテーブルで、keyに「1」があるレコードだとmemcachedのkeyとして「test_table:1」というkeyになります。

ただし、これだとtest_tableという名前は人間にはわかり易いけどスペース効率的によろしく無いケースもあるでしょう。
そういった時は

  install_model test_table => schema {
      schema_options model_name_realname => 't';
};
という設定を仕込む事で、$model->set( test_table => { k => 1 } )という書き方のままで、memcachedのkeyには「t:1」という省スペースなkeyが使われます。

カラム名変換

これも、思想としてはテーブル名変換と同じで、memcachedのvalueにはカラム名を含むハッシュを保存するため、カラム名を短縮形式に変換すると省スペースになります。

  install_model simple => schema {
      schema_options model_name_realname => 's';
      key 'id';
      column 'id';
      column 'name';
      column 'nickname';
      schema_options column_name_rename => {
          id       => 1,
          name     => 2,
          nickname => 3,
      };
  };
数値にすると、後述するMessagePackでのシリアライズで有利になるのですし、ここのカラム名の所はとにかく小さくしとかないと大量のレコードを保存した時のコストが馬鹿にならないのです。

key data strip

KVSはたいがい行データのkeyカラムをkey/valueのkeyとして保存しておくので、シリアライズされたvalueのなかにkeyの値が入ってると重複して無駄なので、シリアライズするハッシュ要素からkeyのデータを取り除きます。
lookup/getした時は、検索するのに使ったkeyから行データを復元する感じです。

シリアライズ

普通にmemcached使ってるなら$memcached->add( $key, { k => 'v' })みたいにしてオブジェクト突っ込めば良いんだけど、この場合サーバがあるFLAGを保存するようにしないとgetする時に上手く動かなかったりします。風の噂でTokyoTyrantがそのまんまだと使えないとか聞きました。
こういった事を解消する為に、シリアライザを自由に変更出来るようにしました。Cache::Memcached::Fastでもシリアライザ変更出来るけども、全然別のモジュール使うときを考えてData::Modelのレイヤでシリアライズする感じです。

  my $driver = Data::Model::Driver::Memcached->new(                                                                                                                                             
      memcached  => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),                                                                                         
      serializer => 'Default', # default is L<Data::MessagePack> or messagepack minimum set for Data::Model                                                                                     
  );
という感じでデフォルトでMessagePackのフォーマットを使って直列化しています。
なお Data::MessagePackが入っていなくても組み込みのシリアライザを使います。Data::MessagePackの0.05以上がインストールされた環境では、Data::MessagePackを利用します。これにより3倍程度のシリアライズの高速化が可能です。

MessagePackはサイズ効率よくシリアライズができるようで、上記のカラム名変換機能で数値カラム名に変換するとMessagePackのFixNumという超省スペースなデータが使われるため、とても効率が良くなります。

Cache::Memcached::Fast標準で使われるStorableだとバージョンの差異でデシリアライズとか出来なくなってはまるときもあるのですが、MessagePackだと当面そういった事も無いでしょう。

全部組み合わせると。。。

実装のし易さやユーザの選択が出来るように、これらの要素はバラバラに設定するようになってますが、基本的に全てのオプションを全部使う事でKVSにとっても嬉しいデータを作り出すようになっています。
シリアライズ以外の要素は、その効果がとてもわかり易いですが、シリアライズの部分もStorableを使ったよりもサイズがコンパクトになります。

例えば

install_model bookmark => schema {
      schema_options model_name_realname => 'b';
      key 'id';
      column 'id' => int => { unsigned => auto_increment => 1 };
      column 'user_id';
      column 'url_id';
      column 'bookmark_at';
      schema_options column_name_rename => {
          id          => 1,
          user_id     => 2,
          url_id      => 3,
          bookmark_at => 4,
      };
  };
という定義で
$model->set( bookmark => {
    user_id     => 1,
    url_id      => 1,
    bookmark_id => time(),
}
といった形でsetすると、keyの部分で(idの文字数)+2バイト、valueで(Mapの定義が1バイト+カラム名の部分のサイズ合計3バイト+user_idがFixNumなので1バイト+user_idも1バイト+bookmark_atはuint32なので5バイト)バイトの計11バイトで格納出来ます。
user_idとurl_idの値のサイズは最小な値になってるですが、0x7fまでだと1バイト、0x80から0xffまでだと2バイト、2バイトの数値は3バイト、32bit整数までなら5バイトで表現可能です。

Driver::Memcachedの利点

と、これまでに紹介してない利点としては、KVSに保存するのはシリアライズされたハッシュオブジェクトなので、カラムの追加とかを自由に出来ると言う事ですね。ALTER TABLEなんかやらないでもアプリケーションのコードを変更するだけでカラムが増やせたり減らせるので簡単です!(減らしてもデータは消されないけど。)
特定のカラムの値でしかlookupしないのであれば、KVSをストレージとして選択してしまうというのもありかもしれませんね。
あんまり良くわかってないけどRemedieとかもそういう使い方をSQLiteでやってる?

特定のkey以外でもKVSを使ってデータの取得をするインターフェィスも作りたいとは思ってるので、それなりにRDBMSの置き換えとかできるかもしれないです。

まとめ

KVSで簡単にORM風なインターフェィスを使うのにはData::Modelが最高というお話でした。

sfujiwaraさんがPostgreSQL用のDBDドライバも作ってくださってるようなので、ますます広がるData::Modelの可能性にご期待下さい!

Posted by Yappo at 2009年07月05日 14:33 | TrackBack | Perl
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。