スケーラビリティ
スケーラビリティという用語は、多くの場合、誤用されます。 このセクションでは、デュアル定義を提供します。
- スケーラビリティとは、マルチプロセッサ システム (2、4、8、32、または複数のプロセッサ) で使用可能な処理能力を十分に利用できる機能です。
- スケーラビリティは、多数のクライアントにサービスを提供する機能です。
これら 2 つの関連する定義は、一般に スケールアップと呼ばれます。 このトピックの最後では、スケールアウトに関するヒントを提供 します。
この説明では、スケーラブルなサーバーがより一般的な要件であるため、スケーラブルなクライアントではなく、スケーラブルなサーバーの記述のみに焦点を当てています。 このセクションでは、RPC サーバーと RPC サーバーのみのコンテキストでのスケーラビリティについても説明します。 競合の削減、グローバル メモリの場所での頻繁なキャッシュ ミスの回避、誤った共有の回避など、スケーラビリティに関するベスト プラクティスについては、ここでは説明しません。
サーバーが RPC 呼び出しを受信すると、RPC によって提供されるスレッドでサーバー ルーチン (マネージャー ルーチン) が呼び出されます。 RPC では、ワークロードの変動に応じて増減するアダプティブ スレッド プールが使用されます。 Windows 2000 以降、RPC スレッド プールのコアは完了ポートです。 完了ポートとその RPC による使用法は、競合が少ないサーバー ルーチンに対して 0 に調整されます。 つまり、一部がブロックされた場合、RPC スレッド プールはサービス スレッドの数を積極的に増やします。 ブロックはまれであるという前提に基づいて動作し、スレッドがブロックされた場合、これは迅速に解決される一時的な条件です。 この方法により、競合が少ないサーバーの効率が向上します。 たとえば、高速システム エリア ネットワーク (SAN) 経由でアクセスされる 8 プロセッサ 550MHz サーバーで動作する void 呼び出し RPC サーバーは、200 を超えるリモート クライアントから 1 秒あたり 30,000 回を超える void 呼び出しを処理します。 これは、1 時間あたり 1 億 8000 万回を超える通話を表します。
その結果、サーバー上の競合が高い場合に、積極的なスレッド プールが実際に邪魔になります。 説明するために、ファイルにリモートでアクセスするために使用される頑丈なサーバーを想像してみてください。 サーバーが最も簡単なアプローチを採用しているとします。RPC がサーバー ルーチンを呼び出すスレッドでファイルを同期的に読み取り/書き込みするだけです。 また、多くのクライアントにサービスを提供する 4 プロセッサ サーバーがあるとします。
サーバーは 5 つのスレッドで開始されます (これは実際には異なりますが、わかりやすくするために 5 つのスレッドが使用されます)。 RPC は、最初の RPC 呼び出しを取得すると、その呼び出しをサーバー ルーチンにディスパッチし、サーバー ルーチンが I/O を発行します。 まれに、ファイル キャッシュを見逃し、結果の待機をブロックします。 ブロックされるとすぐに、5 番目のスレッドが解放されて要求が取得され、6 番目のスレッドがホット スタンバイとして作成されます。 10 分の 1 の I/O 操作ごとにキャッシュが見落とされ、100 ミリ秒 (任意の時間値) がブロックされ、4 プロセッサ サーバーが 1 秒あたり約 20,000 回の呼び出し (プロセッサあたり 5,000 回の呼び出し) を提供すると仮定すると、単純なモデリングでは、各プロセッサが約 50 個のスレッドを生成すると予測されます。 これは、ブロックする呼び出しが 2 ミリ秒ごとに来ると想定し、100 ミリ秒後に最初のスレッドが再び解放されるため、プールは約 200 スレッド (プロセッサあたり 50) で安定します。
スレッドの数が多いと、サーバーの速度が低下し、新しいスレッドの作成速度が遅くなる余分なコンテキストスイッチが発生する可能性があるため、実際の動作はより複雑になりますが、基本的な考え方は明らかです。 サーバー上のスレッドがブロックを開始し、何らかの待機を開始すると、スレッドの数がすぐに増えます (I/O、またはリソースへのアクセスなど)。
RPC と受信要求をゲートする完了ポートは、サーバー内の使用可能な RPC スレッドの数を、コンピューター上のプロセッサの数と同じになるように維持しようとします。 つまり、4 プロセッサ サーバーでは、1 つのスレッドが RPC に戻ると、4 つ以上の使用可能な RPC スレッドがある場合、5 番目のスレッドは新しい要求を取得することは許可されず、代わりに現在使用可能なスレッドブロックのいずれかが発生した場合にホット スタンバイ状態になります。 5 番目のスレッドが、使用可能な RPC スレッドの数がプロセッサ数を下回らずにホット スタンバイとして十分に長く待機すると、解放されます。つまり、スレッド プールは減少します。
スレッドが多いサーバーを想像してみてください。 前に説明したように、RPC サーバーは多くのスレッドで終わりますが、スレッドが頻繁にブロックされる場合にのみ発生します。 スレッドがブロックされることが多いサーバーでは、RPC に戻るスレッドがホット スタンバイ リストからすぐに取り出されます。これは、現在使用可能なすべてのスレッドがブロックされ、処理する要求が与えられるためです。 スレッドがブロックされると、カーネル内のスレッド ディスパッチャーはコンテキストを別のスレッドに切り替えます。 このコンテキスト切り替え自体は CPU サイクルを消費します。 次のスレッドは、異なるコードを実行し、異なるデータ構造にアクセスし、異なるスタックを持つことになります。つまり、メモリ キャッシュのヒット 率 (L1 キャッシュと L2 キャッシュ) が大幅に低くなり、実行速度が遅くなります。 同時に実行される多数のスレッドにより、ヒープ、サーバー コード内のクリティカル セクションなど、既存のリソースの競合が増加します。 これにより、リソースのコンボイが形成されるにつれて競合がさらに増加します。 メモリが少ない場合、スレッドの数が多く増加することでメモリ不足が発生すると、ページ エラーが発生し、スレッドがブロックされる速度がさらに高まり、さらに多くのスレッドが作成されます。 サーバーは、ブロックする頻度と使用可能な物理メモリの量に応じて、コンテキストの切り替え率が高いパフォーマンスレベルで安定するか、実際の作業を実行せずにハード ディスクとコンテキストの切り替えに繰り返しアクセスするだけになる可能性があります。 もちろん、この状況は軽いワークロードでは表示されませんが、負荷の高いワークロードでは問題がすぐに表面化します。
これを防ぐにはどうすればよいですか? スレッドがブロックされることが予想される場合は、呼び出しを非同期として宣言し、要求がサーバー ルーチンに入ったら、I/O システムや RPC の非同期機能を使用するワーカー スレッドのプールにキューに入れます。 サーバーが RPC 呼び出しを行っている場合は、これらの呼び出しを非同期にし、キューが大きくなりすぎないようにします。 サーバー ルーチンがファイル I/O を実行している場合は、非同期ファイル I/O を使用して I/O システムに対する複数の要求をキューに入れ、少数のスレッドだけがキューに入れ、結果を取得します。 サーバー ルーチンがネットワーク I/O を実行している場合は、システムの非同期機能を使用して要求を発行し、応答を非同期的に取得し、可能な限り少数のスレッドを使用します。 I/O が完了するか、サーバーが行った RPC 呼び出しが完了したら、要求を配信した非同期 RPC 呼び出しを完了します。 これにより、サーバーを可能な限り少数のスレッドで実行できるため、パフォーマンスが向上し、サーバーがサービスを提供できるクライアントの数が増えます。
特定のクライアント アドレスからのすべての要求が同じサーバーに送信されるように NLB が構成されている場合は、ネットワーク負荷分散 (NLB) を操作するように RPC を構成できます。 各 RPC クライアントは接続プールを開くので (詳細については、「 RPC とネットワーク」を参照)、指定されたクライアントのプールからのすべての接続が同じサーバー コンピューター上で終了することが不可欠です。 この条件が満たされている限り、NLB クラスターは、スケーラビリティに優れた 1 つの大規模な RPC サーバーとして機能するように構成できます。