BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース 堅牢なテスト戦略により自信を持ってDropbox Syncをリライト

堅牢なテスト戦略により自信を持ってDropbox Syncをリライト

原文(投稿日:2020/04/26)へのリンク

過去数年間、Dropboxのエンジニアはクライアント側の同期エンジンをゼロから書き直してきた。DropboxのエンジニアであるIsaac Goldberg氏は、明確なテスト戦略を定義していなければクイックなリリースサイクルで新しいエンジンを構築して出荷するのは、不可能だったと述べている。

新しい同期プロトコルであるNucleusをテスト可能にするための重要な要件は「無効な状態を設計する」という原則であった。これはRustの型システムを使うためである。これは古い同期エンジンからの明らかな前進であった。これは無効な状態を経て最終的な、おそらく正常な状態に到達できるように進化した。このシフトを可能にした重要な設計上の決定は、一貫したファイルシステムの状態に関連付けられた3つのツリーを通じてNucleus状態を表現することであった。3つのツリーは、リモートファイルシステムの状態、ローカルファイルシステムの状態、直近の完全同期状態である。

Synced Treeは、正しい同期結果を明確に導き出すことができる重要な革新です。バージョン管理に精通している場合は、Synced Treeの各ノードがマージベースと考えることができます。マージベースを使用すると、変更の方向を導き出すことができ、「ユーザはファイルをローカルで編集したか、それともdropbox.comで編集したか?」という質問に答えます。

Dropboxのレガシーの同期エンジンとNucleusの間のもう1つの重要な違いは、同時実行モデルにある。従来のエンジンは完全に自由にスレッドを使っていたが、Nucleusはすべての制御タスクを単一のスレッドに結び付け、I/Oやハッシュなどの二次操作のみをバックグラウンドスレッドにオフロードする。テストの目的で、すべてのバックグラウンド操作をメインスレッドにシリアル化できるため、テストの再現性と決定性が保証される。

Dropboxのテスト戦略の要は、ランダム化されたテストである。その理由は、何億ものユーザマシンで実行したときに発生する可能性のあるエッジケースの数が多いためである。テストのランダム化を効果的にするには、完全に決定論的で再現性がなければならない、とGoldberg氏は述べている。これは、疑似乱数ジェネレーターを使用し、テストが失敗した場合に初期化に使用されるランダムシードをログに記録することで実現される。このアプローチにより、さらに調査するために失敗したランダムテストを再実行することが可能になる。

私たちは毎晩、数千万のランダム化されたテストを実行します。一般的に、最新のマスターでは100%緑色となります。リグレッションが潜入すると、CIは、失敗したシードごとに追跡タスクを自動的に作成します。これには、その時点での最新のコミットのハッシュも含まれます。

Goldberg氏は、Nucleusで使用される2つのテストフレームワークがどのように機能するかをさらに詳しく説明している。1つはCanopyCheckで、Dropbox同期エンジンのコアアルゴリズムであるplannerのバグをキャッチし、Nucleusの状態を表す3つのツリーを段階的に収束できる一連の操作を構築する。したがって、CanopyCheckは、リモートファイルシステムとローカルファイルシステム、および直近の同期状態を表す3つのテストツリーを生成し、完全に収束するまでツリーを処理するようにプランナーに繰り返し要求する。そうする上で、生成されたツリーの分析に基づいて、いくつかの不変条件を適用する。これによって、最終的な同期結果が正しいことを保証する。

CanopyCheckはHaskell QuickCheckアプローチを活用する。このアプローチは失敗した各テストケースの失敗を再現する複雑さが最小限の入力を見つけようとするものである。このアプローチは「最小化」と呼ばれ、CanopyCheckで実行される。その処理では、入力ツリーからノードを繰り返し削除し、障害が続くことを確認する。

Goldberg氏が詳細に説明している2番目のテストフレームワークはTrinityである。これは、エンジンの同時実行性、特に競合状態にフォーカスしている。Trinityは、モックを多用して、あらゆる種類の非同期動作を注入することにより、ランダムな方法でNucleusと対話する。非同期動作には、ローカルまたはリモート状態の変更、I/O障害のシミュレーション、タイミングの制御などがある。Trinityの動作に関する微妙な点の1つは、Rust Futuresを使用してNucleusとともにメインスレッドで完全に実行されることである。Trinityは、Nucleusが使用するすべてのFutureを傍受し、どちらが失敗するか成功するかを決定する。Trinityは、モックでない状態でも機能する。つまり、ネイティブファイルシステムとネットワークを使用して、時間効率は犠牲になるが、プラットフォーム固有のエッジケースを再現できる。

全体として、このアプローチのおかげで、Dropboxのエンジニアは、レガシーエンジンのライフサイクルに沿って蓄積された膨大な数のバグ修正を回帰するリスクを負うことなく、同期エンジンを書き直すことができた。Goldberg氏による元の記事には、ここで説明している内容よりもはるかに詳細な情報が含まれているため、興味がある場合は見逃さないようにしてください。

この記事に星をつける

おすすめ度
スタイル

BT