テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(dm-lc report) Batched Migrationの検討

dm-lcは非同期なデーモン, Flush Daemin, Migrate Daemonを持つ. その関係についてはまず, what-is-dm-lcを見ればよい.

dm-lcは, アップストリームを狙う. ストレージカーネル野郎として認められたい. 私のキャリアはすでに, ストレージカーネルの上にない. 現状を打開するには, dm-lcが一番てっとり早い.

Flash Daemonはwrite barrierに関するackを遅延する. これをdeferred ackと呼ぶ. ここで, あるsegmentのフラッシュには, そこに, バリアがあったかどうかの情報を含むことが出来る. これをバリアフラグと呼ぶ. バリアフラグについてMigration Daemonが守らなければいけないことは, バリアフラグが立っているsegmentをmigrateする時は, その完了の前にbacking storeへの書き込みを永続化することである. 仮想デバイスが一旦, 永続化完了を上位にackしたのに, 後のmigrationではこれを反故にするのは, 契約違反である. バリアフラグについて, dm-lcは, segment粒度でしかバリアフラグを保持しない. これは, segmentの末尾にackの遅延されたREQ_FLUSH-ed bioが入っていることに相当する. これにより, migrationの処理を効率化(and 簡略化)する戦略である.

さて, 現在のdm-lcのmigration実装について話すと,

  1. segmentがバリアフラグを保持していない. そして, migration時には, すべてのsegmentにバリアフラグが立っているものとして処理する. これは, dm-lcの性能測定をする上ではmigrationの実装は二の次であり, migrationを非同期にするということが達成されるうちでもっともシンプルで安全な実装をとったためである.
  2. migrationはsegment単位である. もっとも初期の実装は, segment内のブロックをすべてREQ_FUAで書き出すというものだったが, さすがにお馬鹿さんすぎるので, segment内のブロックをasyncで一気に吐いて, すべてのI/Oのackがとれたらblkdev_issue_flush()によってバリアを発行するという実装にしている.

1について. これは実装するだけなら簡単だが, 含みとして残してある. 状態は少ない方が良い. 2が今回の焦点である. 詳細を以下に述べる.

dm-lcのMigrate Daemonは, last_migrated_id < last_flush_idである場合に実際の書き出しを開始する. これはつまり, 「最後にマイグレートしたsegmentのidよりも新しいidのsegmentがすでにflushされていたら, それはmigrateすべきだ. そういうものがないならmigrateはすべきでない」という意味である. これとユーザランドから設定可能なallow_migrate変数のandをとってmigrateの実行を決めるというのが大体のロジックである.

現在の実装は, ここで, migrateすべきもののうちもっとも古いsegmentのみをflushする. これを, nr_batched_migration個一気にmigrateする実装にする. この変数は, sysfsで動的に設定可能である.

予想される効果について述べる.

dm-lcは, segmentのサイズを最大1MBから最小8KBまで設定可能である. このサイズは, 大きいほどスループットが上がるが, 小さいほど, barrierライトへの応答が良くなる. なぜならば, barrier_ios_deadline(defaultは3ms)でやっとこさ処理される割合が減り, segmentがフルになった時に実行される通常のflush動作で処理されやすくなるからである. 32KBや64KBといったsegment sizeが良いだろうと私は考える. もちろん, ファイルシステムや, その上のデーモンのI/Oパターンに依存して最適化すべきところであるが基本的に, deadlineはあくまでもdeadlineであるため, ここでやっとこさflushされるのは遅すぎる. barrierのackがとれるまで, 上位のコードはblockする. segment sizeが小さい場合, segmentごとのmigrationでは, 一気に発行出来るwrite数が少ない. これはまず, スケジューラを有効活用出来ないことを意味する. また, 現在の実装では, 一つ一つのmigrationについて必ず永続化を行うため, 性能が超悪い. batched migrationと名付けるこの手法では, これらの問題が解決出来る.

より積極的な効果は, I/Oスケジューリングの候補を増やせることである. dm-lcという永続的なバッファでたくわえてから遅延してI/Oスケジューリング出来るため, 通常ならマージされ得ないような時間的距離のあるwriteがマージされる可能性がある. dm-lcによって, Linuxのストレージ系が進化する可能性がある. dm-lcを前提としたスケジューラがあり得る(他にも, dm-lcを前提としたwrite throughキャッシュソフトウェアがあり得る. ポイントはI/Oの並行性である).

RAIDシステムは多くの場合, DRAMキャッシュを持つ. もしこれがBBUを持っている場合, 本手法における上記積極的な効果はそれほど期待出来ない. DRAMキャッシュは通常Gオーダの大きさであり, それだけのwriteを保持してからスケジューリングされては分が悪い. しかし, write throughモードで使う場合には, 多大な効果が得られる. dm-lcの狙いが, 20-30台ディスクがなくBBUもないような中規模なRAIDにおいて, writeを安価に速くすることであるから, BBUなしを仮定することは妥当である. RAIDキャッシュについてはここが良くまとまってると思った. 参照されたい.

segment sizeが十分に大きい(1MB)場合, この粒度でmigrate出来れば十分であろうか. 私はそうとは思わない. backing storeのランダムライト性能は, 10MB/sec級である可能性が大いにある. あるsegment領域がFlush対象になるまでの猶予となる時間間隔が1秒程度であることが現実的なので, この時間で処理出来るwriteについては一気にasyncで投げてしまい, これらすべてをatomicに扱ったのちにbarrierをとるという処理がより効率的である. ディスクをいくつも積んだシステムにおいては, 小さなwriteでbarrierをとってしまうと, いくつか限られたディスクしか稼働しないのでレイテンシに対して並列効果(見かけ上レイテンシが短くなる)が期待出来ない. ディスクが多ければ多いほど, 全部のディスクwriteがいくくらい大量のランダムwriteを一気に発行しなければ, ディスクをmax働かせることが出来ない. あまりにwriteが多いと, backing storeのiowaitが高くなりすぎる可能性があるので, キャパシティ限界までたくさんのwriteを非同期発行してあげるべきである.

Batched Migrationを実装することによってコードは穢れる. 従って, 出来ることならば実装したくない. しかし, 穢れを小さく保つ方法がありそうだし, 何より, 「一気にマイグレーションする最大segment数」(nr_batched_migrationとする予定)の数をsysfsで動的に変更出来ることが最大の決定要因である. この数を遅延出来ることは, 使う側としては心的障壁が低くなる. 動的に変更出来るのは, I/Oスケジューラのキュー長が動的に変更出来ることから明らかである.

以上の検討により我々は, Batched Migrationを実装する. 実装は土日に行う.