私が好きな面接の質問の 1 つは、「 asyncやawaitなどの単語は、何を伝えているのですか?」です。これは、面接対象者と興味深い議論をする機会が生まれるからです... あるいは、面接対象者がこのトピックについてあまり話さないので、興味深い議論が生まれないこともあります。私の意見では、なぜこのテクニックを使用するのかを理解することは非常に重要です。
多くの開発者は、「これがベストプラクティスである」という主張に頼り、盲目的に非同期メソッドを使用することを好むように感じます。
この記事では、非同期メソッドと同期メソッドの実際の違いを示します。
ツール
- .NET Web API アプリケーション (テスト対象)
- 2 つの Azure SQL データベース
- 2 Windows 上の Azure App Service (アプリケーションをホスト)
- Azure App Insights (メトリックを収集)
- イナゴフレームワーク(ユーザー負荷をシミュレートするため)。
構成
ベンチマークは次のように実行します。2 つの独立した Locust インスタンスが 2 台のマシンで実行されています。Locust インスタンスは、次の操作を行うユーザーをシミュレートします。
- ローカスト ホスト 1 のユーザーは、App Service 1の同期エンドポイントにアクセスし、応答を取得して、0.5~1 秒間アイドル状態を維持します (正確な時間遅延はランダムです)。実験が終了するまで繰り返します。
- ローカスト ホスト 2 のユーザーもまったく同じように動作しますが、唯一の違いは、 App Service 2の非同期エンドポイントにアクセスすることです。
内部的には、各 App Service が独自のデータベースに接続し、5 秒かけて数行のデータを返す SELECT クエリを実行します。参考として、以下のコントローラーのコードを参照してください。データベースへの呼び出しには Dapper を使用します。非同期エンドポイントもデータベースを非同期的に呼び出す ( QueryAsync<T> ) 点に注目してください。
両方のアプリ サービスに同じコードをデプロイすることも付け加えておきます。
テスト中、ユーザー数は目標数 (ユーザー数) まで均等に増加します。増加速度はSpawn Rateパラメータ (1 秒あたりに参加するユニーク ユーザー数) によって制御されます。数値が高いほど、ユーザーがより速く追加されます。すべての実験でSpawn Rateは 10 ユーザー/秒に設定されています。
すべての実験は 15 分に制限されています。
マシン構成の詳細については、この記事の「技術詳細」セクションを参照してください。
メトリクス
- 1 分あたりのリクエスト数 - アプリケーションが実際に処理し、ステータス コードを返したリクエストの数を示します。
- スレッド数- アプリ サービスが消費するスレッドの数を表示します。
- 平均応答時間、ミリ秒
赤い線は非同期エンドポイントを示し、青い線は同期エンドポイントを示します。
理論については以上です。それでは始めましょう。
実験#1
- 利用者数:75(サービスあたり)
両方のエンドポイントのパフォーマンスは同様に、1 分あたり約 750 件のリクエストを処理し、平均応答時間は 5200 ミリ秒であることがわかります。
この実験で最も興味深いグラフは、スレッドの傾向です。同期エンドポイント (青いグラフ) の数値が大幅に増加し、100 を超えるスレッド数になっていることがわかります。
ただし、これは想定内のことであり、理論と一致しています。つまり、リクエストが到着し、アプリケーションがデータベースを呼び出すと、ラウンドトリップが完了するまで待機する必要があるため、スレッドはブロックされます。したがって、別のリクエストが到着すると、アプリケーションはそれを処理するために新しいスレッドを生成する必要があります。
赤いグラフ (非同期エンドポイントのスレッド数) は、異なる動作を示しています。リクエストが到着し、アプリケーションがデータベースを呼び出すと、スレッドはブロックされるのではなく、スレッド プールに戻ります。そのため、別のリクエストが到着すると、この空きスレッドが再利用されます。受信リクエストが増加しているにもかかわらず、アプリケーションは新しいスレッドを必要としないため、スレッド数は変わりません。
3 番目の指標である平均応答時間についても言及する価値があります。両方のエンドポイントで同じ結果 (5200 ミリ秒) が示されました。つまり、パフォーマンスの点では違いはありません。
今こそ、賭け金を引き上げる時です。
実験2
- ユーザー数: 150
負荷を 2 倍にしました。非同期エンドポイントはこのタスクを正常に処理し、 1 分あたりのリクエスト数は 1500 前後で推移しています。同期エンドポイントは最終的に 1410 という同等の数値に達しました。しかし、下のグラフを見ると、10 分かかったことがわかります。
その理由は、同期エンドポイントは別のスレッドを作成して新しいユーザーの到着に反応しますが、Web サーバーが適応できるよりも速くユーザーがシステムに追加されるためです (生成率は 10 ユーザー/秒であることを思い出してください)。これが、最初に非常に多くのリクエストがキューに入れられた理由です。
当然のことながら、非同期エンドポイントのスレッド数メトリックは依然として約 34 ですが、同期エンドポイントでは 102 から 155 に増加しました。平均応答時間は、 1 分あたりのリクエストレートと同様に低下しました。同期応答時間は、実験開始時にははるかに高かったのです。テストを 24 時間続けた場合、平均数は均等になります。
実験#3
- ユーザー数: 200
3 番目の実験は、2 番目の実験で明らかになった傾向を証明することを目的としています。同期エンドポイントのさらなる劣化が確認できます。
結論
同期操作の代わりに非同期操作を使用することで、パフォーマンスやユーザー エクスペリエンスが直接改善されるわけではありません。まず、負荷がかかった状態での安定性と予測可能性が向上します。つまり、負荷しきい値が上がるため、システムのパフォーマンスが低下する前に、より多くの処理を実行できるようになります。
付録1. 技術的な詳細
- Azure App Service: B1、100 ACU 、1.75 Gb メモリ、A シリーズのコンピューティングと同等。
- Azure SQL データベース: Standard S4: 200 DTU、500 MB のストレージ。
- SQL 接続設定: 最大プール サイズ = 200。
付録2. 注記
最もクリーンなテスト結果を得るには、対象の App Services が配置されている同じネットワークにある 2 つの VM からテストを実行する必要があります。
ただし、ネットワーク ラグは両方のアプリに多かれ少なかれ同様の影響を与えると想定しました。したがって、非同期メソッドと同期メソッドの動作を比較するという主な目的が損なわれることはありません。
付録3. ボーナス実験
同期エンドポイントをほぼ非同期として実行するように強制し、以下のグラフをプロットするために、何をハックしたのでしょうか (実験条件は 3 番目と同じ、つまり 200 人のユーザーです)。