2009.02.28
FriendFeedはどのようにスキーマレスなデータをMySQLに格納しているか
FriendFeedBret Taylorが、スキーマレスなデータ(スキーマ(型)に制約されないデータ)をMySQLに格納する方法を紹介している。実際にFriendFeedで使っている方法で、最新のものらしい。

Bret Taylor's blog - How FriendFeed uses MySQL to store schema-less data
http://bret.appspot.com/entry/how-friendfeed-uses-mysql

MySQLを通常のRDB的な方法でなくストレージ的に使い、JOINを使わないでスケールさせるというもの。CouchDBなどにも近い、最近有力になりつつあるアプローチだ。これを、実績もあり普及しているMySQLを使って実現し、言語はPythonで実装している。

メインのテーブルは次のようなもの。

CREATE TABLE entities (
added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id BINARY(16) NOT NULL,
updated TIMESTAMP NOT NULL,
body MEDIUMBLOB,
UNIQUE KEY (id),
KEY (updated)
) ENGINE=InnoDB;

このなかの「body」に、次のようなPythonのディクショナリ(JSONフォーマットとほぼ同じ)をpickle化(Pythonデータのバイナリ)し、zlibで圧縮して入れる。Pythonのディクショナリには「スキーマ」の制約がないので、これで「スキーマレス」になるわけだ。

{
"id": "71f0c4d2291844cca2df6f486e96e37c",
"user_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
"feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf",
"title": "We just launched a new backend system for FriendFeed!",
"link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
"published": 1235697046,
"updated": 1235697046,
}

このデータを上記のentitiesテーブルに入れるのに、次のようなPythonコードを使う。

user_id_index = friendfeed.datastore.Index(
table="index_user_id", properties=["user_id"], shard_on="user_id")

# データベースに対応するオブジェクト
datastore = friendfeed.datastore.DataStore(
mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"],
indexes=[user_id_index])

# ひとつの発言に対応したデータ(Pythonのディクショナリ形式)
new_entity = {
"id": binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"),
"user_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
"feed_id": binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"),
"title": u"We just launched a new backend system for FriendFeed!",
"link": u"http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c",
"published": 1235697046,
"updated": 1235697046,
}

# データベースにデータを追加
datastore.put(new_entity)

# データをidで取り出す
entity = datastore.get(binascii.a2b_hex("71f0c4d2291844cca2df6f486e96e37c"))

# ユーザidから、特定ユーザの全データを取り出す
entity = user_id_index.get_all(datastore, user_id=binascii.a2b_hex("f48b0440ca0c4f66991c4d5f6a078eaf"))


(なお「#」以下のコメントは、わかりやすくするために私がつけたものです。コード内容から予想しているだけなので、間違っているかもしれません)

この中の「friendfeed」というのが、FriendFeedの自社ライブラリらしい。最初の行と最後の行で出てくる「user_id_index」というのは、以下のようなインデックス用テーブルに対応している。

CREATE TABLE index_user_id (
user_id BINARY(16) NOT NULL,
entity_id BINARY(16) NOT NULL UNIQUE,
PRIMARY KEY (user_id, entity_id)
) ENGINE=InnoDB;

メインのテーブルとこのインデックス用テーブルの「JOIN」も、SQLではなくプログラム上で、ロジック的におこなっている。

このようなアプローチによって、インデックス作成のコストも下がり、サービスを止めなくて済む。しかしデータの一貫性には課題も生じるので、そのあたりをどう処理しているか、などについても書かれている。

この最新システムによるFriendFeedのパフォーマンス向上も紹介されており、応答時間がほぼ半分になったようだ。ちなみに、メインのテーブルに入っているレコード数は2億5千万件とのこと。

コメント欄ではmixiが開発した「Tokyo Cabinet」なども紹介されたり、このハック的なアプローチをけなす人も出てくるなど、かなりホットな感じだ。最近は「「キー・バリュー型データストア」開発者が大集合した夜」という記事も出ていたし、このあたりの技術に注目が集まってきているのは世界的な傾向のようだ。

Update(2009.3.1):
結城浩さんのWikiに全文訳が上がっている。興味のある人は、ぜひ読んでみてください。

FriendFeed では MySQL を使いどのようにスキーマレスのデータを保存しているのか
http://hyuki.com/yukiwiki/wiki.cgi?HowFriendFeedUsesMySqlToStoreSchemaLessData

訳者はMORITA Hajimeさん。<そういえば一部ゲーム屋さんも schema-less なデータを DB につっこむのが好きだという説を聞いたことがあるけれど真偽は不明. その場合につっこむものは pickled dictionary ではなく memcpied struct なのは言うまでもありません>、とのこと。