Ruby on Rails での並列テスト:リスクと軽減戦略
テストが速すぎるという苦情を聞いたことがありますか?私もそうではありません。
迅速なテストは迅速なフィードバックを意味します。ローカルで実行するか継続的統合パイプラインで実行するかに関係なく、テストが早く終了するほど、障害に早く対応してコードを改善できます。生産性の向上に加えて、テストが遅いと開発者が不機嫌になることはよく知られています。開発者が不機嫌になるのが好きな人はいません。
そうは言っても、超高速のテスト スイートを作成するのは、思ったほど簡単ではありません。幸いなことに、Rails 6 では並列テストと呼ばれるエキサイティングな機能が導入されました。 。簡単に始めることができ、テストを大幅に高速化できます。ただし、注意すべき落とし穴がいくつかあります。
並列テストとは何ですか?
並列テストとは一体何を意味するのでしょうか?
テスト スイートを実行するとき、テスト ランナーは通常、単一のプロセスを生成してテストを次々に実行します ( または逐次)。 。 1 つのテスト プロセスで 1 つの CPU コアが使用されます。おそらくご想像のとおり、このアプローチでは、多くの場合数十の CPU コアを搭載する最新のハードウェアを最大限に活用できません。
10 個の CPU コアを搭載した豪華な MacBook をお持ちかもしれませんが、残念ながら、それでテストが速くなることはありません。
これは、個々のテストを複数のワーカー プロセスに分散することで変更できます。テストは次々に実行されるのではなく、隣り合って並列に実行されます。 。 2 つのワーカーでテスト スイートを実行すると、1 つのワーカーで同じテスト スイートを実行する場合の 2 倍の速度になります。
マシンのコアが多いほど、より多くのワーカー プロセスが実行可能になり、テスト スイートの完了が速くなります。テストの実行には通常 8 分かかるとします。 4 つのワーカー プロセスを使用して同じテストを実行すると、実行時間は約 2 分に短縮されます。
同じことを、16 個のワーカーを生成できる、優れた 16 コア マシンで実行することを想像してください。実に素晴らしいですね!
Rails での並列テストの構成
では、どうやってそこに到達するのでしょうか?最近まで、サードパーティの gem を使用してテスト スイートを並列化できましたが、Rails 6 からは並列テストが標準装備されています。 parallelize を追加するのと同じくらい簡単です テストに:
この構成を使用すると、Rails はマシン内のプロセッサの数に基づいてワーカー プロセスを自動的に生成します。 Rails は名前空間データベースも作成します (例:database-test-0) 、database-test-1 、など) に対してテストを実行します。
始めるのに必要なのはこれだけです。もちろん、必要に応じて追加の構成オプションがいくつかあります。
場合によっては、並列テストのために特定のセットアップまたはクリーンアップを実行する必要がある場合があります。 Rails には、使用できるフックが 2 つあります — parallelize_setup と parallelize_teardown 。これらは、新しいワーカー プロセスが生成される前後に呼び出されます。
ワーカーの数を手動で設定することもできます。
あるいは、PARALLEL_WORKERS を使用します。 既存の設定をオーバーライドする環境変数:
ワーカーの代わりにスレッドを使用してテスト スイートを並列化するオプションもあります。
with: :threads JRuby または TruffleRuby を使用する場合のデフォルトのオプションです。理論上、スレッドを使用するとパフォーマンスがわずかに向上します。結局のところ、スレッドはプロセスよりも必要なオーバーヘッドが少なくなります。ただし、実際には、スレッドオールの使用があまりにも便利であると感じたことはありません。ほとんどの場合、プロセスベースの並列化に固執するだけで問題ありません。
落とし穴に注意
したがって、必要なのは parallelize を追加することだけです 既存のテストに加えて、驚くべきスピードアップを体験してみませんか?とても簡単です!
運が良ければ、それは本当に真実です。既存のテスト スイートに初めて並列化を追加する場合、予期せぬ障害に遭遇する可能性が高くなります。これは確かに私に当てはまりました!
一つ問題を解決しましょう。 Minitest ではなく RSpec を使用する場合は、運が悪いです。 RSpec は Rails 6 の組み込み並列テストをサポートしていません。これを変更するための議論が進行中ですが、しばらくは大きな進展はありません。 RSpec を使用した並列テストが必要な場合、最善の策は、grosser/Parallel_tests などのサードパーティの gem を使用することです。
直面する可能性のあるもう 1 つの予期せぬ問題は、少数のテストを並行して実行すると速度が遅くなることです。 次にそれらをシリアルに実行します。並列テストの設定には、複数のデータベースの作成など、大きなオーバーヘッドが伴い、並列化によって得られるメリットがすべて失われる可能性があります。
少数のテストについては、並列テストを無効にした方がよい場合があります。 PARALLEL_WORKERS を使用してこれを行うことができます。 環境変数:
Rails 7 では、多数のテストを実行する場合にのみ並列実行を有効にすることで、この問題に対処しました。したがって、すでにアップグレードしている場合は、この問題は発生しません。デフォルトでは、並列化のしきい値は 50 に設定されていますが、オーバーライドできます。
私が強調したい最後の問題は、あなたが直面する可能性が最も高い問題であり、最も潜伏性があり、対処するのが最も難しい問題でもあります。テスト スイートの並列テストを有効にすると、ランダムなエラーが発生する可能性があります。並列化がどのようにしてこの問題を引き起こすかを理解するために、簡単なテスト ケースを見てみましょう。
少し役に立たないことを除けば、このテストはまったく問題ありません。シリアルに実行する限り、100% の確率で合格します。各テストは分離されており、これらのテストをランダムな順序で実行しても失敗することはありません。複数のプロセスまたはスレッドを混合物に追加すると、状況は変わります。
テストを並列実行する場合、CPU スケジューリングにより、テスト内の個々のステートメントの実行順序が変更される可能性があります。この例を見ると、次のような実行命令が表示される場合があります。
ワーカー 1 のアサーションが実行される前にワーカー 2 がファイルを削除したため、最初のテストは失敗することがあります。さらに悪いことに、実行順序が異なると、2 番目のテストが不合格になり、最初のテストが合格することがありました。
この簡単な例は、ファイルだけでなく、テストが非スレッドセーフな方法でアクセスするシングルトン リソースにも及ぶ問題を示しています。 Redis データベースまたは Elasticsearch インデックスに書き込むとします。その場合、同様の合併症が発生する可能性があります。さらに悪いことに、ランダムなエラーを引き起こすすべてのテストを発見するには時間がかかり、すべてのテストを修正するにはさらに時間がかかる可能性があります。
不安定な並列テストに対処する特効薬はありません。一般に、複数のテスト プロセスがリソースを共有しないようにする必要があります。ファイルの場合は、Tempfiles を使用します。parallelize_setup を使用します。 名前空間リソース (Redis データベースなど) を作成します。などなど。
既存の Rails テストへの並列テストの追加
並列化によるランダムなテストの失敗に悩まされており、それを修正する時間がないとしましょう。ただし、それでも並行テストのメリットを享受したいと考えています。テストのサブセットに対してのみ有効にすることもできます。
parallelize を呼び出すテストのみ 結局のところ、並列化されるので、コンサーンまたは親クラスを使用することで、一度に 1 つのテスト クラスをテスト スイートに並列テストを追加できます。次のようなモジュールを作成できます:
このモジュールを含むテスト クラスはすべて並列実行されるようになります。
あるいは、ParallelTest のような新しいテスト クラスを作成することもできます。 :
次に、並行して実行する必要があるテストをそのテストから継承し、問題があることが判明したテストを除外します。
絆創膏としての並行テスト
並行テストにより、わずかな労力で速度が大幅に向上します。ただし、だまされないでください。これは、テスト スイートの速度を向上させるための他のアプローチの代替となるものではなく、むしろ追加です。
テスト スイートが遅いことがわかり、労力を費やすことができる場合は、時間をかけてテスト スイートのプロファイリングを行い、遅さの根本原因に対処してください。遅いテスト スイートに並列テストを追加すると高速になりますが、すでに高速なテスト スイートを並列で実行するほど速くはなりません。
まとめ
この投稿では、並列テストとは何か、そのセットアップ方法と構成方法について説明しました。テストを高速化する方法が必要な場合、並列テストがまさにそれを提供します。
テスト スイートに並列テストを追加する場合、いくつかの障害に直面する可能性があります。何年も安定して動作していたテストが突然失敗し始めても驚かないでください。テスト スイートのサブセットのみを並列化することで、この問題を回避できます。
どのアプローチを選択する場合でも、並列テストはテストを高速化するための素晴らしいツールです。
コーディングを楽しんでください!
追記Ruby Magic の投稿を報道後すぐに読みたい場合は、Ruby Magic ニュースレターを購読して、投稿を 1 つも見逃さないようにしてください。
ハンス・イェルク・シュネドリッツ
ゲスト著者の Hans は、オーストリアのウィーン出身の Rails エンジニアです。彼はほとんどの時間をコーディングするかコーディングに関する読書に費やしており、時にはそれについてブログに書くこともあります。彼が画面の前に座っていないときは、おそらく彼が外で山に登っているのを見つけるでしょう。
Hans-Jörg Schnedlitz によるすべての記事
-
Ruby開発者向けのデータ構造の概要
データ構造とは何ですか? データ構造は、データを整理してアクセスするための特定の方法です。 。 例: 配列 二分木 ハッシュ さまざまなデータ構造がさまざまなタスクに優れています。 たとえば、辞書(単語と定義)や電話帳(人の名前と番号)のようなデータを保存する場合は、ハッシュが最適です。 利用可能なデータ構造を知る 、およびそれぞれの特徴 、より優れたRuby開発者になります。 それがこの記事で学ぶことです! 配列について 配列は、プログラミングについて読み始めたときに最初に学習するデータ構造です。 配列は、オブジェクトがギャップなしで次々に格納される連続したメモリのチャン
-
RailsアプリでのDynamoDBの使用
DynamoDBは、その中核となるNoSQLデータベースであり、キー値とドキュメントのデータ構造を提供します。これを開梱しましょう。これを読んでいる開発者のほとんどは、テーブル、行、列に正規化された明確に定義されたスキーマとデータを含む従来のリレーショナルデータベースシステムに精通していると思います。これらのテーブルの間には、外部キーを利用する「関係」があります。対照的に、DynamoDBはスキーマレスです。すべてのテーブルに主キーが必要ですが、他の非キー属性に他の制約はありません。これはいつ有益でしょうか?さて、AmazonがDynamoDBを作成した理由を学びましょう。 Amazonは、