素晴らしい質問マット (+1)、そしてオリバー氏自身が答え (+1) として答えたのを見ました!
私は、あなたが目にしている毎秒 3,000 コミットのボトルネックを助けるために、私自身が遊んでいる少し異なるアプローチを取り入れたかったのです。
JOliver の EventStore を使用するほとんどの人が従おうとしているように見える CQRS パターンでは、多数の「スケールアウト」サブパターンが可能です。人々が最初に待ち行列に入れるのは、イベント コミット自体であり、ボトルネックが見られます。「待ち行列に入れる」とは、実際のコミットからオフロードし、それらを書き込み最適化されたノンブロッキング I/O プロセスに挿入することを意味し、または「列"。
私の大まかな解釈は次のとおりです。
コマンド ブロードキャスト -> コマンド ハンドラ -> イベント ブロードキャスト -> イベント ハンドラ -> イベント ストア
これらのパターンには、コマンド ハンドラーとイベント ハンドラーの 2 つのスケールアウト ポイントが実際にあります。上記のように、ほとんどの場合、イベント ハンドラーの部分、または場合によってはコミットを EventStore ライブラリにスケール アウトすることから始めます。これは通常、どこか (Microsoft SQL Server データベースなど) に永続化する必要があるため、これが最大のボトルネックになるためです。
私自身、これらのコミットを「キューに入れる」ための最高のパフォーマンスをテストするために、いくつかの異なるプロバイダーを使用しています。CouchDB と .NET の AppFabric キャッシュ (優れた GetAndLock() 機能を備えています)。[OT]AppFabric の耐久性の高いキャッシュ機能が非常に気に入っています。これにより、複数のマシンにまたがってリージョンをバックアップする冗長キャッシュ サーバーを作成できます。したがって、少なくとも 1 つのサーバーが稼働している限り、キャッシュは存続します。[/OT]
したがって、イベント ハンドラーがコミットを EventStore に直接書き込まないことを想像してください。代わりに、Windows Azure Queue、CouchDB、Memcache、AppFabric Cache などの「キュー」システムにハンドラを挿入します。ポイントは、イベントをキューに入れるためのブロックがほとんどまたはまったくないシステムを選択することです。これは冗長性が組み込まれているため耐久性があります (Memcache は冗長性オプションとしてはあまり好きではありません)。サーバーがドロップした場合でもイベントがキューに入れられている場合に備えて、その冗長性が必要です。
この「Queued Event」から最終的にコミットするには、いくつかのオプションがあります。これについては、Windows Azure のキュー パターンが気に入っています。これは、多くの「ワーカー」が常にキュー内で作業を探すことができるためです。ただし、Windows Azure である必要はありません。バックグラウンド スレッドで実行される「キュー」と「ワーカー ロール」を使用して、ローカル コードで Azure のキュー パターンを模倣しました。それは本当にうまくスケーリングします。
ユーザー更新イベントのこの「キュー」を常に調べている 10 人のワーカーがいるとします (私は通常、イベント タイプごとに 1 つのワーカー ロールを作成します。これにより、各タイプの統計を監視できるようになるため、スケールアウトが容易になります)。2 つのイベントがキューに挿入され、最初の 2 つのワーカーがそれぞれ即座にメッセージを取得し、同時に EventStore に直接挿入 (コミット) します。Jonathan が回答で述べたように、マルチスレッドです。そのパターンのボトルネックは、選択したデータベース/イベントストアのバッキングになります。EventStore が MSSQL を使用していて、ボトルネックがまだ 3,000 RPS であるとします。システムは、RPS が 20,000 バースト後に 50 RPS などに低下したときに「追いつく」ように構築されているため、問題ありません。これは、CQRS が許可する自然なパターンです: 「結果整合性」。
CQRS パターンに固有のスケールアウト パターンが他にもあると言いました。もう 1 つは、前述のとおり、コマンド ハンドラー (またはコマンド イベント) です。これは私が行ったことの 1 つです。特に、私のクライアントの 1 つがそうであるように、非常に豊富なドメイン ドメインがある場合 (すべてのコマンドでプロセッサを集中的に使用する検証チェックが数十回行われます)。その場合、コマンド自体を実際にキューに入れ、一部のワーカー ロールによってバックグラウンドで処理されます。これにより、イベントの EvetnStore コミットを含むバックエンド全体をスレッド化できるため、優れたスケールアウト パターンも得られます。
明らかに、これの欠点は、リアルタイムの検証チェックが失われることです。私は通常、ドメインを構築する際に検証を 2 つのカテゴリに分割することで、この問題を解決しています。1 つは、ドメインでの Ajax またはリアルタイムの「軽量」検証 (プレコマンド チェックのようなもの) です。その他は、ドメイン内でのみ実行され、リアルタイムのチェックには使用できないハード フェイル検証チェックです。次に、ドメイン モデルで障害対応のコードを作成する必要があります。つまり、何かが失敗した場合の回避策を常にコーディングします。通常は、何かがうまくいかなかったというユーザーへの通知メールの形式で行われます。このキューに入れられたコマンドによってユーザーがブロックされなくなったため、コマンドが失敗した場合にユーザーに通知する必要があります。
そして、「バックエンド」に行く必要がある検証チェックは、クエリまたは「読み取り専用」データベースに送られますよね?たとえば、一意の電子メール アドレスを確認するために EventStore にアクセスしないでください。フロント エンドのクエリ用に、可用性の高い読み取り専用データストアに対して検証を行うことになります。CQRS のクエリ部分として、システム内のすべてのメール アドレスのリスト専用の 1 つの CouchDB ドキュメントを用意してください。
CQRSは単なる提案です...重い検証方法のリアルタイムチェックが本当に必要な場合は、その周りにクエリ(読み取り専用)ストアを構築し、検証を高速化できます-PreCommandステージで、挿入される前に待ち行列。多くの柔軟性。また、空のユーザー名や空のメールなどの検証はドメインの問題ではなく、UI の責任 (ドメインでリアルタイムの検証を行う必要性をオフロードする) であるとさえ主張します。私は、MVC/MVVM ViewModel で非常に豊富な UI 検証を行ったいくつかのプロジェクトを設計しました。もちろん、処理前に有効であることを確認するために、私のドメインには非常に厳密な検証がありました。しかし、平凡な入力検証チェック、または私が「軽量」検証と呼んでいるものをViewModelレイヤーに移動すると、エンドユーザーにほぼ瞬時のフィードバックが得られます。私のドメインに到達することなく。(ドメインとの同期を維持するためのトリックもあります)。
要約すると、これらのイベントがコミットされる前にキューに入れることを検討してください。Jonathan が回答で述べているように、これは EventStore のマルチスレッド機能にうまく適合します。