kestrelのfanout-queuesについて

前回のエントリ
kestrelの作者さんによるkestrelの紹介 - na

の続きです。kestrelの作者さんによるfanout-queuesの解説です。Twitterのいわゆるファンアウト処理の根幹となる機能の説明になります。

Fanout queues in kestrel 1.2

前回紹介した記事の11ヵ月後の記事で、kestrel 1.2の話になります。kestrelのバージョンは現在1.2.2なので、そんなに古い内容ではありません。new featureとして紹介しているので、以前はない機能だったはずです。

1.2の新機能として以下のものを紹介しています。

  • 起動時、ポートをlistenする前にキューファイルをロードするようにしたよ。*1
  • "open"オプションによるトランザクションを、"abort"オプションによって明示的にロールバックできるようにしたよ。
  • "peek"オプションでキューの先頭から1件取得だけするようにしたよ。
  • max_item_sizeとsync_journalというオプションを追加したよ。*2
  • DELETEコマンドでキューが削除できるようにした。ジャーナルからも完全に消すから戻せないよ。*3
  • バグ直したよ。
  • いい感じにドキュメントを書いたよ。http://github.com/robey/kestrel/blob/master/docs/guide.md

以下、fanout-queuesについて和訳します。

ファンアウトキュー

ファンアウトキューは階層関係を持った「親」のキューになります。「親」のキューに項目が追加されると、同じ項目が自動的にすべての「子」のキューにも追加されます。子キューはそれぞれのジャーナルファイルを持った、独立したキューです。ある1つの子キューからキュー項目が削除されても、それはほかの子キュー(兄弟たち)からは削除されません。各子キューに別々の項目を追加することだって可能です。ただファンアウトキューのポイントは、親に追加した項目がすべての子に自動的に追加されることです。

子キューは、キューの名前に"+"が含まれていると自動的に作成されます。たとえば"orders+audit"という名前のキューを作れば、それは"orders"という名前のキューの子になります。子キューが作られると、すぐに新しいキュー項目を受け取るようになります。ただし、既に親キューが持っているキュー項目がコピーされることはありません。このファンアウト処理は子キューがDELETEされるまで続きます。

各子キューは親キューと同じ設定を持ちます。子キュー独自の設定はありません。
子キューが増えると、ファンアウト処理によってキュー項目がたくさんコピーされるため、余分にメモリとディスクを消費します。

シンプルな実装になっているので、変なトリックを使っているよりもきっと分かりやすいと思います。


以上です。
せっかくだから実際にやってみましょう。まずは以下のコマンドでkestrelを起動しておきます。ビルドとか起動の方法については以前のエントリ id:hito_asa:20101014 を参照してみてください。

java -jar kestrel-1.2.2.jar

別のターミナルを開いて、telnetでkestrelに接続します。

telnet localhost 22133

まずは以下のように"orders"というキューにa,bという2つの値を入れてみます。
その後にdump_statsコマンドで"orders"キューの状態を確認してみます。

set orders 0 0 1  <= 入力
a  <= 入力
STORED
set orders 0 0 1  <= 入力
b  <= 入力
STORED
dump_stats  <= 入力
queue 'orders' {
  items=2
  bytes=2
  total_items=2
  logsize=44
  expired_items=0
  mem_items=2
  mem_bytes=2
  age=0
  discarded=0
  waiters=0
  open_transactions=0
}
END

続けて以下のように"orders+audit"というキューにpeekコマンドを送ります。peekでもgetでもなんでもいいんですが、一度参照されるとキューが作成されます。
再びdump_statsコマンドで"orders+audit"というキューが作成されたことを確認してみます。

get orders+audit/peek  <= 入力
END
dump_stats  <= 入力
queue 'orders+audit' {
  items=0
  bytes=0
  total_items=0
  logsize=0
  expired_items=0
  mem_items=0
  mem_bytes=0
  age=0
  discarded=0
  waiters=0
  open_transactions=0
}
queue 'orders' {
  items=2
  bytes=2
  total_items=2
  logsize=44
  expired_items=0
  mem_items=2
  mem_bytes=2
  age=0
  discarded=0
  waiters=0
  open_transactions=0
  children=orders+audit
}
END

orders+auditという名前の空のキューが出来たのがわかると思います。
また、ordersのほうの一番下に"children=orders+audit"という値が増えています。これでキューの親子関係が作成されていることも分かります。

では、親の"orders"キューに項目を追加してみます。既に"a"と"b"が入っているので、"c"と"d"を入れてみます。

set orders 0 0 1  <= 入力
c  <= 入力
STORED
set orders 0 0 1  <= 入力
d  <= 入力
STORED

ここで、dump_statsコマンドを実行して確認すると。

dump_stats  <= 入力
queue 'orders+audit' {
  items=2
  bytes=2
  total_items=2
  logsize=44
  expired_items=0
  mem_items=2
  mem_bytes=2
  age=0
  discarded=0
  waiters=0
  open_transactions=0
}
queue 'orders' {
  items=4
  bytes=4
  total_items=4
  logsize=88
  expired_items=0
  mem_items=4
  mem_bytes=4
  age=0
  discarded=0
  waiters=0
  open_transactions=0
  children=orders+audit
}
END

"orders"のitemsは4になり、子キューである"orders+audit"のitemsも2になっています。親キューに入れたものが子キューにも入りました。あとは、適当にGETして内容を確認してみましょう。

get orders  <= 入力
VALUE orders 0 1
a
END
get orders  <= 入力
VALUE orders 0 1
b
END
get orders  <= 入力
VALUE orders 0 1
c
END
get orders  <= 入力
VALUE orders 0 1
d
END
get orders+audit
VALUE orders+audit 0 1
c
END
get orders+audit
VALUE orders+audit 0 1
d
END

以上です。

*1:ジャーナルファイルをロードしてキューを再現する処理を、サービスが起動する前にした、と言う意味だと思います。

*2:設定ファイルの話。

*3:今まで出来なかったのか・・・。