ブロックデバイスのクラッシュ模擬ツールcrashblk の紹介

こんにちは、サイボウズ・ラボの星野です。

先日の記事(半年かかったバグ調査の顛末は)では、長期間苦労した不具合調査についてあっさりとまとめて頂いたので、その行間に起きたたくさんのことを思い起こし、ひとり感慨にふけっています。 (私も記事原稿をレビューしましたが、もし私が書いたら思い入れが強すぎて長文になってしまい、きっと読みづらくなってしまったでしょう。。)

さて、今回は、crashblk というソフトウェアをオープンソース化したので、その紹介をしたいと思います。 ソースコードは GitHub レポジトリ に置いてあります。 ライセンスは GPLv2 or 3 です。

crashblk とは

crashblk (くらっしゅぶろく、と読んでいます) は、Linux カーネルのブロックデバイスドライバやファイルシステムなどをテストするためのブロックデバイスドライバです。私は WalB を開発していますが、その品質を高めるための道具のひとつとして crashblk を作りました。

crashblk の機能は以下の 3 つです。

  • クラッシュ模擬
  • IO エラー模擬
  • レスポンスタイム設定

クラッシュ模擬は、突然の電源断により書き込みは完了しているものの、永続化されていないデータが消える挙動を、実際の電源断をせずに再現します。IO エラー模擬は、ストレージの故障における振舞いとして、IO が全てエラーになる挙動や書き込み IO だけエラーになる挙動を模擬します。レスポンスタイム設定は、read, write, flush のリクエスト種類毎に、レスポンスタイムの振れ幅を設定することができ、ストレージの様々な性能特性を再現することが出来ます。

crashblk は、永続化保証されたはずのデータが永続化されているか、IO エラー時に期待した挙動をするか、などをテストするために使います。WalB カーネルドライバのテストにおいては、まさにそれらの用途で使いました。また、ファイルシステムのジャーナルによる一貫性制御が正常に動いているかなどのテストにも使えると思います。

実装は、メモリブロックデバイスドライバとなっています。内部的には、アドレスをキーとする rb-tree 構造を 2 つ使って永続化済みでないデータと永続化済みのデータを分けて保持し、永続化済みではないデータを捨てることでクラッシュ模擬を実現しています。また、レスポンスタイムを調整するために、delayed workqueue を利用しています。rb-tree や delayed workqueue は Linux カーネル内の道具として用意されており、WalB カーネルドライバの開発経験もあったので、crashblk を実装するのは比較的簡単でした。

crashblk と不具合調査を巡るドラマ

実は、最初に crashblk を作ったのは 2014 年で、WalB カーネルドライバをテストし、見つかった不具合を修正して、そこで満足していました。しかし、 先日の記事のひとつ目の不具合は、 WalB カーネルドライバの flush (永続化)に関するものだったにも関わらず、crashblk によるテストで発見することが出来ていませんでした。

その理由は、crashblk の IO 処理方法が実在のストレージにおける IO 挙動に比べると単純すぎたことです。一般にストレージデバイスは、IO をスケジューリングし実行順序を並び換えることが許されており、IO が submit されてから、実際に処理を行い、completion を返すまでの時間幅もあります。複数 IO の実行時間は重複し得るのです。

以下の図を見てください。 f:id:cybozuinsideout:20160108174447p:plain Write A が完了する前に submit された左側の Flush は、A を永続化する責任がありませんが、右側の FlushA を永続化する責任があります。件の不具合は、左側の FlushA が永続化されるものだと勘違いしており、実装もそうしていたことが原因でした。実際に評価に使ったストレージはまさにこの違いが出ることのある性能特性を持っていたのです。

それまでの crashblk は IO を直列に実行してしまうので、複数 IO 実行時間が重複するという挙動を模擬できていませんでした。それに関連して起きる不具合も、当然再現することは出来なかったということになります。それがまさに件の不具合だったのです。

先程紹介した 3 番目の「レスポンスタイム設定」機能は、件の不具合を発見できなかった反省から、昨年末に開催された社内ハッカソン(こちらの記事を参照)で、実装しました。この機能を使うことにより、不具合を容易に再現出来ることを確認しました。

以下の図が、ハッカソン前後の IO 処理フローを示したものになります。 f:id:cybozuinsideout:20160108174502p:plain f:id:cybozuinsideout:20160108174512p:plain 通常のキューではなく、ランダム時間待ってタスクを実行する delayed queue を利用することで、IO 順序の入れ替えを表現し、複数 IO の重複を模擬できるようになりました。

先日の記事では、件の不具合を含め 2 つの不具合が発見された旨を紹介していますが、実は 3 つ目の不具合が、WalB のユーザーランドツールに存在していました。これら 3 つの不具合は全て invalid logpack という同じ現象として観測されるので、苦労して不具合を直しても、残っている不具合起因で現象が再現する、という悪夢が 2 回も起きたことになります。心が折れそうになって有給休暇を使いまくっても仕方ないですね(^^;

他の 2 つに比べて原因特定までの時間が短かったため、先日の記事では省略されていますが、3 つ目の不具合再現頻度を 3 日に 1 回から 30 分に 1 回と、2 ケタ上昇させることが出来、原因特定までの時間を早めてくれたのは、改良版の crashblk でした。ありがとう、crashblk。頑張った、俺。

まとめ

今回オープンソース化した crashblk の紹介をさせていただきました。これを使いたくなる方は少ないかも知れませんが、興味のある方は是非触ってみてください。比較的単純な作りになっていますので、Linux カーネルのブロックデバイスドライバを作ってみたいという方の勉強用としてもオススメです。