Kentaro Kuribayashi's blog

Software Engineering, Management, Books, and Daily Journal.

Hash::Compactを使って、構造をもったカラムのデータを効率よく扱う

Hash::Compactという、HashRefのkey/valueを、keyのエイリアスやデフォルトのvalueをサポートすることでコンパクトに表現できるモジュールがあります。ドキュメントにはCache::Memcached::Fastでの利用例が示されているのですが、今回はそれをDBで使う例を紹介します。必ずしもORMが必要なわけではないのですが、説明の簡単のために、Tengを使います。

テーブル

structカラムに、JSONでシリアライズしたHashRefを格納する、以下のようなテーブルがあったとします。

CREATE TABLE `data` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `struct` blob,
  PRIMARY KEY (`id`)
);

Tengでのスキーマ定義

今回はTengを使うので、以下のようなスキーマ定義を行います。やってることは簡単で、こんな感じ。

  • スキーマ定義
  • structカラムに対するinflate/deflateの設定
  • 格納するhashの、alias, default値の設定

要するに、inflate/deflateで、Hash::Compactを通すようにしているところがポイントです。

package My::Model::Schema;
use JSON;
use Hash::Compact;
use Teng::Schema::Declare;

my $options = {
    foo => {
        alias_for => 'f',
    },
    bar => {
        alias_for => 'b',
        default   => 'bar',
    }
};

sub to_hash {
    Hash::Compact->new(decode_json(shift), $options);
}

sub from_hash {
    my $hash = shift;

    if ((ref $hash || '') eq 'Hash::Compact') {
        encode_json($hash->to_hash);
    }
    else {
        encode_json(Hash::Compact->new($hash, $options)->to_hash);
    }
}

table {
    name 'data';
    pk 'id';
    columns qw(
        id
        struct
    );

    inflate 'struct' => \&to_hash;
    deflate 'struct' => \&from_hash;
};

データをinsert/selectしてみる

これを使って、データをinsertしてみます(コード中のMy::Modelは、Tengを継承したクラス)。

my $model = My::Model->new;
my $row   = $model->insert(
    data => { struct => { foo => 'foo', bar => 'bar' } }
);

structカラムは、Hash::Compactでコンパクト、かつ、デフォルト値は無視して記録されるので、上記のようにinsertしても、実際のレコードは、以下のようになります。

mysql> select * from data;
+----+-------------+
| id | struct      |
+----+-------------+
|  1 | {"f":"foo"} |
+----+-------------+
1 row in set (0.00 sec)

つまり、

  • fooは、実際にはfというkeyのaliasとして扱われます
  • barは、'bar'というデフォルト値と同じなので、記録するまでもなく、データから削除されます。

selectしてみると、ちゃんとinsertした時のデータ構造が復元されます。

my $row    = $model->single(data => { id => 1 });
my $struct = $row->struct; #=> Hash::Compact object

print $struct->param('foo') #=> 'foo'
print $struct->param('bar') #=> 'bar'

まとめ

JSONなどの構造を持ったカラムデータのinflate/deflateにHash::Compactを利用することにより、

  1. コード上はfooやbarといったわかりやすいkeyを使いつつも、
  2. 実際のレコード内にはコンパクトな値を格納することで、
  3. 開発効率と空間効率の両方を得ることが可能となります

とても便利ですね。また、JSONではなくData::MessagePackなどを使って圧縮してやったら、さらに空間効率がよくなって、いい感じになるでしょう。