Data::Model::Driver::Memcachedで超効率データ保存B!

はじめに

YAPC::Asia 2009で、Data::Modelの話を聞いて以来、Data::Modelがとても気になっているdannです。

今回は、Data::Model::Driver::Memcachedについて紹介します。これは、Memcachedプロトコルを話すサーバーにデータを格納するためのDriverです。これの素晴らしいところは、空間効率を高めることを考えて設計されていて、かつアプリケーション側のコードは綺麗にかける工夫がなされているという点です。

では、どこにその工夫があるかをみていきましょう。

Data::Model::Driver::Memcachedの空間効率を含めるための工夫

Data::Model::Driver:::Memcachedでは、以下のような流れでデータを保存します。

  • データ全体のtable名をrenameし圧縮
  • データのvalueからkeyを削除
  • データのkey名をrenameし圧縮
  • データをMessagePack(defaultでは)で圧縮
  • 最後にMemcachedなどのKVSに保存

このように、データを圧縮することで空間効率を高めています。

valueからのkey名の削除

Data::Modelでは、テーブルに格納するデータをシリアライズして圧縮して、KVSに保存しますが、そのときにはvalueにはkey名は不要なのでkey名をvalueから削除しています。

strip_keysというオプションをdriverに設定することで利用できます。

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';

...

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
        strip_keys => 1,
    )
);

...

テーブル名の圧縮による省スペース化

Data::Modelでは、キー名にテーブル名を含めるのですが、テーブル名をuという短い文字列に圧縮することができます。

schema_options model_name_realname => 'u'; で設定をしています。

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';

...

install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
};

...

キー名の圧縮による省スペース化

テーブル名だけでなく、キー名も圧縮することができます。

以下のようにキー名を数値にマッピングしておくことで、実行時にキー名を数値にリネームしてキーのサイズも圧縮することが出来ます。

schema_options column_name_renameでその設定をしています。

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
...

install_model user => schema {
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };  
};
...

シリアライザにMessagePackを使うことによる省スペース化

シリアライザは選択できるようになっていて、DefaultはMessagePackという空間効率的なシリアライザを使われます。Data::Modelがこのようにシリアライズを切り替えられるようになっていることで、この効率化が実現できるところがポイントです。

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
        serializer => 'Default',
    )   
);

どのように圧縮されるのかは、MessagePackの仕様に書かれているので、それをみるのがいいです。MessagePackがとても空間効率を意識して設計されていることがわかると思います。

http://msgpack.sourceforge.jp/spec

Data::Modelでは、テーブル名・キー名が短くなったデータに対して、さらにMessagePackで圧縮をしているため、かなり空間効率がいいと言えます。

アプリケーションのコードのシンプルさ

以下のようにモデルを定義しておけば、アプリケーションのコードは、どのように圧縮されるのかを意識することなく、効率的にキー名及びデータの格納が行えます。

上記で述べたモデルの設定を実際にまとめてみましょう。

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),
        serializer => 'Default',
        strip_keys => 1,
    )
);
install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };
};

1;

Memcachedなどに格納するときには、以下のようにすることで格納できます。

特に利用する際には、データの圧縮などについて意識することなく利用することが出来ます。

セットする場合

$model->set(
    user => '1',
    {   name     => 'Warai',
        nickname => 'Meshi',
    }   
);

getする場合

my ($get) = $model->get( user => '1' );

空間効率について

Data::Modelでのオプション設定による空間効率について、以下のような簡単なテストコードで確認してみます。

use strict;
use warnings;
use Test::More;
use MyApp::Schema;
use Data::Dumper;

my $model = MyApp::Schema->new;
$model->set(
    user => '1',
    {   name     => 'Warai',
        nickname => 'Meshi',
    }   
);

my ($get) = $model->get( user => '1' );
is( $get->id,       '1',     'id' );
is( $get->name,     'Warai', 'name' );
is( $get->nickname, 'Meshi', 'nickname' );

done_testing;

実行して、telnet localhost 11211でmemcachedに接続して確認してみましょう。

Data::Modelのデフォルト状態で圧縮した状態

デフォルトの設定

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
    )   
);
install_model user => schema {
    key 'id';
    index 'name';
    columns qw/id name nickname/;  
};

1;

telnetでmemcachedにつないでサイズの確認をしてみましょう。

STAT bytes 106

defaultでは、106バイトになりました。

Data::Modelのオプションで圧縮した場合

モデルは、以下のものとします。

package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),
        serializer => 'Default',
        strip_keys => 1,
    )
);
install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };
};

1;

telnetでmemcachedにつないでサイズの確認をしてみましょう。

STAT bytes 69

圧縮した場合には69bytesになりました。

サイズの比較

Data::Modelのオプションを使って圧縮した場合には69bytes、デフォルト状態(Data::ObjectDriverのDriverなど)の場合では106bytesになりました。

したがって、今回のケースでは、圧縮した場合にデフォルト状態と比較して0.65倍となり、空間効率がかなりいいことが確認できました。

まとめ

Data::Model::Driver::Memcachedは、KVSをデータストアとして使うために、空間効率を意識した設計がなされています。

具体的には、キーの値からの削除、テーブル名及びキー名の短縮、MessagePackによる圧縮、により空間効率を高める工夫をしています。

Data::Modelユーザーだけではなく、他のMemcachedプロトコルに対応したNoSQLサーバーを使われる方も、Data::ModelのDriverの実装を参考にされるのがいいのではないかと思います。

それでは、次回は再びyappoさんです。お楽しみに!