RabbitMQのスケーリング:クラスタトポロジ最適化ガイド

クラスタリング、レプリケーション、スループットを混同せずにスケールするRabbitMQクラスタを設計する。

RabbitMQのスケーリング:クラスタトポロジ最適化ガイド

RabbitMQのスケーリングは、ある厄介な事実から始まります。クラスタは魔法の大きなブローカーではありません。メタデータを共有し、キュー・タイプに応じてキュー・データをレプリケートする可能性のあるブローカーのセットです。単一のキューが過負荷になっている場合、その周りにノードを追加すると可用性が向上する可能性がありますが、その1つのキューが自動的に高速に消費されるわけではありません。

この区別は、多くの悪い設計を防ぎます。私は、ビジーなRabbitMQデプロイメントに2つのノードを追加し、何も移動せず、キューのレイアウトも変更せず、それでも毎日午後に同じキューがバックアップする理由を疑問に思うチームを見てきました。キューリーダーは依然として同じノードにありました。同じコンシューマーが同じ作業を続けていました。クラスタにはより多くのマシンがありましたが、ボトルネックは移動していませんでした。

RabbitMQクラスタトポロジは、主にキューがどこに存在するか、重要なメッセージのコピーがいくつ必要か、スループットが低下する前にどの程度の障害に耐えられるかを決定することです。短命なメトリクスパイプラインに適した答えは、支払い、注文処理、監査イベントに適した答えと同じではありません。

クラスタリングが実際に共有するもの

RabbitMQクラスタ内のノードは、定義(仮想ホスト、ユーザー、権限、交換機、キュー、バインディング、ポリシー)と、クラスタが動作するために必要なランタイムメタデータを共有します。あるノードに接続されたプロデューサーは、キューリーダーが別のノードにある交換機にパブリッシュできます。コンシューマーは、キューをホストするノードとは異なるノードに接続できます。

これは、すべてのメッセージがどこにでも存在することを意味するわけではありません。

クラシックキューは1つのノードにリーダーを持ちます。クォーラムキューはリーダーとレプリカを持ちます。ストリームには独自のレプリケーションモデルがあります。キューリーダーがそれを使用するほとんどのクライアントからリモートにある場合、RabbitMQはクラスタ相互接続を介してトラフィックを移動する必要があります。これは適度であれば問題ありません。すべてのパブリッシャーがノードAに接続し、すべてのホットキューがノードBに存在し、すべてのコンシューマーがノードCに接続する場合、コストが高くなります。

シンプルな最初のルールがうまく機能します。アプリケーションを使用するキューに近いノードに接続するか、クラスタの前にロードバランサーを配置し、キューリーダーが適度にバランスされていることを確認します。ラウンドロビンのクライアント接続がラウンドロビンのキュー負荷を生み出すと想定しないでください。

創造的になる前に3ノードを優先する

ほとんどの本番RabbitMQクラスタでは、1つの低レイテンシリージョンまたはアベイラビリティゾーングループ内の3ノードがクリーンな出発点です。これにより、クォーラムキューは1つのノード障害に耐えられる多数決モデルを得られ、インシデント中に推論するのに十分シンプルなクラスタ調整を維持できます。

2ノードクラスタは安価に見えますが、レプリケートされたキューには扱いにくいです。クォーラムベースのシステムでは、過半数が必要です。2つのノードのうち1つが消失すると、過半数がなくなります。一部の分散システムでは、証人スタイルの3番目のノードを追加できますが、RabbitMQでは通常、十分なディスクとネットワーク容量を持つ3つの実際のノードを実行する方がシンプルで信頼性が高くなります。

5ノードは、多くのキューがある場合、より多くの配置オプションが必要な場合、またはより多くのマシンに負荷を分散したい場合に意味があります。また、クラスタ通信と運用面の量も増加します。3から5に移行する前に、ノードの飽和を解決しているのか、キューの設計問題を解決しているのかを確認してください。1つのキューがホットな場合、ノードを増やすだけではそのキューの作業は分割されません。

クォーラムキューは無料の速度ではなく、レプリケートされた信頼性のため

新しい高可用性ワークロードでは、メッセージの耐久性が重要な場合、クォーラムキューが通常適切なデフォルトです。それらはコンセンサスプロトコルを使用してメッセージをレプリケートします。3つのメンバーを持つクォーラムキューは、過半数が正常である限り、1つのメンバーが利用できなくなっても動作を継続できます。

トレードオフは書き込みコストです。パブリッシュされた永続メッセージは、安全に受け入れられたと見なされる前に、十分なメンバーにレプリケートされる必要があります。これは重要な作業にまさに必要なものですが、一時的なクラシックキューと同じパフォーマンスプロファイルではありません。

アプリケーションがトポロジを管理する方法に応じて、引数またはポリシーを使用してクォーラムキューを宣言します。

rabbitmqadmin declare queue name=orders durable=true arguments='{"x-queue-type":"quorum"}'

ポリシーについては、慎重にスコープします。広範なパターンが .* に一致したという理由だけで、仮想ホスト内のすべてのキューを誤ってクォーラムキューに変換しないでください。適切なポリシー名と狭いキュー接頭辞は、最も良い意味で退屈です。

rabbitmqctl set_policy qq-orders '^orders\.' '{"queue-type":"quorum"}' --apply-to queues

クラシックミラードキューから移行する場合は、フラグを切り替えるのではなく、移行として扱います。クラシックミラードキューとクォーラムキューは、順序付け、ポイズンメッセージ、メモリ使用量、フェイルオーバーに関して異なる動作をします。新しいキュータイプを作成し、制御されたトラフィックスライスをルーティングし、確認応答とコンシューマーレイテンシを監視してから、残りを移動します。

クラシックキューにもまだ場所がある

クラシックキューは、レプリケーションが不要なワークロード、メッセージが一時的な場合、またはキューがサービスにローカルで別のソースから再構築できる場合に依然として有用です。また、ノード障害時にいくつかのメッセージを失うことが許容される、大量の低価値イベントにも適しています。

クラシックキューは意図的に使用します。クラシックキューが永続的で永続メッセージを受信する場合、それらのメッセージはそのキューをホストするノードに保存されます。そのノードがダウンしている場合、ノードが戻るまでキューは利用できません。これはバックグラウンドの調整ジョブには問題ないかもしれません。通常、顧客から見える注文状態には適していません。

長いバックログについては、ワークロードをストリームまたは別のストレージシステムにする必要があるかどうかを検討します。RabbitMQはキューを保持できますが、数百万の古いメッセージを持つキューは、多くの場合、コンシューマーが小さすぎる、ダウンストリームシステムが障害を起こしている、またはビジネスプロセスにキューセマンティクスではなくリプレイセマンティクスが必要であるというシグナルです。

クラスタの周りにレイテンシ境界を置く

RabbitMQクラスタリングは、低レイテンシで信頼性の高いネットワークリンクを期待します。1つのクラスタを遠く離れたリージョンにまたがらせることは、通常、悪いトレードオフです。ノード間トラフィックが遅くなり、フェイルオーバーが予測しにくくなり、ネットワークパーティションは回避しようとしていた停止よりも有害になる可能性があります。

実用的な設計は、リージョンごとに1つのRabbitMQクラスタを持ち、リージョン間でデータを移動する必要がある場合にアプリケーションレベルのルーティングまたはフェデレーション/シャベルを使用することです。これにより、ローカルのパブリッシュとコンシュームが高速に保たれます。また、障害ドメインが明確になります。リージョンAが正常でない場合、リージョンBは同じクラスタメンバーシップ問題に引きずり込まれません。

1つのリージョン内のマルチAZは異なります。ゾーン間のレイテンシが低く安定している場合、3つのゾーンにまたがる3ノードはうまく機能します。実際の負荷でテストします。クラウドプロバイダーが何かをアベイラビリティゾーンと呼んでいるという事実は、ビジーな時間帯にメッセージサイズ、確認応答、クォーラムキューがどのように動作するかを教えてくれません。

ノードだけでなく、キューリーダーのバランスを取る

クラスタはCPUグラフでバランスが取れているように見えても、キュー・レベルではひどく偏っている可能性があります。1つのノードが最もビジーなキューのリーダーを所有し、他のノードはほとんど静かなレプリカを保持している場合があります。

キューの配置を確認します。

rabbitmqctl list_queues name type leader members messages_ready messages_unacknowledged

1つのノードがほとんどのホットリーダーを所有している場合は、バージョンとキュータイプでサポートされているRabbitMQのツールを使用して、キューを移動またはリバランスします。クォーラムキューの場合、メンバーの配置とリーダーの場所が重要です。クラシックキューの場合、古い用語ではキューマスターの配置が重要ですが、新しいバージョンではリーダーという用語がより一貫して使用されます。

適切なトポロジは、無関係なホットキューをノード間で分散します。たとえば、email.sendimage.resizebilling.captureは、それぞれに大量のトラフィックがある場合、すべて同じノードがリードするべきではありません。billing.captureが唯一のホットキューである場合、コンシューマーがそれらのシャードを安全に独立して処理できる場合にのみ、マーチャントグループやリージョンなどの実際のビジネスシャードで分割します。

クライアント接続動作を設計する

クライアント接続の配置はトポロジの一部です。すべてのアプリケーションが最初のDNS結果に永久に接続する場合、キューがクラスタ全体に分散していても、1つのノードがほとんどのクライアントトラフィックを運ぶ可能性があります。ロードバランサーは役立ちますが、ノードが実際にAMQPトラフィックに利用可能かどうかを理解するヘルスチェックを使用する必要があります。

接続は長期間維持します。RabbitMQは多くの接続を処理できますが、接続のチャーンはCPU、メモリ、ファイル記述子、TLSオーバーヘッドを消費します。Webリクエストは、新しいAMQP接続を開き、1つのメッセージをパブリッシュして閉じるべきではありません。クライアントライブラリに適した接続またはチャネルプールを使用します。

また、ノード障害時にクライアントが何をするかを決定します。優れたクライアントはバックオフで再接続し、チャネルを再オープンし、必要に応じてプライベートトポロジを再宣言し、確認応答またはコンシュームを慎重に再開します。悪いクライアントはタイトなループで再接続し、ノードの再起動を接続ストームに変えます。

コンシューマーの場合、局所性を考慮しますが、過適合は避けます。コンシューマーをそのキューリーダーと同じノードに接続すると、ノード間トラフィックを削減できますが、キューリーダーは障害後に移動する可能性があります。コンシューマーは手動変更なしでそれを生き残る必要があります。

パーティションは設定だけでなく、運用イベントである

ネットワークパーティションは、クラスタ図がテストされるところです。RabbitMQにはパーティション処理モードがありますが、明確な運用上の決定の必要性をなくす設定はありません。クラスタの2つの側面が通信できない場合、そのワークロードにとって可用性と一貫性のどちらがより重要かを決定する必要があります。

クォーラムキューはレプリカの過半数を必要とします。それがポイントです。マイノリティ側は、安全に合意できない書き込みを受け入れ続けるべきではありません。これは、すべての生存ノードが書き込み可能であり続けると期待していたチームを驚かせる可能性があります。計画してください。関心のある障害を過半数が生き残れる場所にクォーラムキューメンバーを配置します。

3ノードのクォーラムキューを3つの遠く離れたリージョンに分散し、通常のインターネットレイテンシ中にスムーズな動作を期待しないでください。クォーラムは、メンバー間のネットワークと同じくらい快適です。低レイテンシと低パケットロスは、容量要件であり、あれば良いものではありません。

非本番環境でパーティションドリルを実行します。ノード間のトラフィックをブロックし、どのキューが利用可能かを観察し、クライアントが再接続するのを監視し、リカバリ手順を書き留めます。パーティション動作を初めて学ぶのは、実際のネットワークインシデント中であってはなりません。

ブローカーをスケーリングする前にコンシューマーをスケーリングする

キューが増大している症状がある場合、ブローカーが常にボトルネックであるとは限りません。多くの場合、コンシューマーが単にパブリッシャーよりも遅いだけです。RabbitMQノードを追加する前に、コンシューマーの使用率、未確認カウント、処理時間、ダウンストリームレイテンシを確認します。

メッセージが準備完了であるが未確認の場合、RabbitMQはメッセージを待機しており、コンシューマーが十分な速さで取得していません。コンシューマーを追加するか、プリフェッチを修正するか、ダウンストリームの遅延を削除します。メッセージがほとんど未確認の場合、コンシューマーはすでに作業を受け取っており、確認応答に時間がかかりすぎています。ブローカーノードを追加しても、それらのハンドラーが高速になるわけではありません。

ここでプリフェッチが重要です。遅いワーカーでの500のプリフェッチは、コンシューマープロセス内にバックログを隠す可能性があります。高速なローカルワーカーでの1のプリフェッチは、ラウンドトリップに時間を浪費する可能性があります。小さい値から始め、エンドツーエンドのレイテンシとコンシューマーメモリを測定し、調整します。

退屈な制限を監視する

スケーリング計画は、トポロジについて話し、ファイル記述子、ディスクアラーム、メモリアラーム、接続チャーン、チャネル数を忘れることがよくあります。RabbitMQはそれらすべてに敏感です。

各ノードについて、使用メモリ、空きディスク、ファイル記述子、ソケット、キュープロセスメモリ、メッセージレート、確認応答レイテンシ、Erlangスケジューラ使用率を監視します。クライアント側では、再接続ループとチャネル作成レートを監視します。パブリッシュごとに新しい接続を開くサービスは、メッセージボリュームが印象的に見えるずっと前にクラスタに害を及ぼす可能性があります。

クライアントライブラリがサポートする場合は、長期間の接続とチャネルを使用します。接続制限とハートビート設定を設計に含め、停止中のパニック変更ではなく、設計に含めます。

通常機能するトポロジ

典型的なビジネスアプリケーションの場合、私は1つのリージョンに3つのRabbitMQノードから始め、ネットワークが良好であればゾーン間で分散します。重要な永続ワークロードにはクォーラムキューを使用します。障害動作が許容される一時的な作業にはクラシックキューを使用します。パブリッシャーとコンシューマーをクラスタの近くに保ちます。クライアントアクセスにはロードバランサーを使用しますが、ロードバランサーがブローカーの配置を解決したと想定するのではなく、キューリーダーのバランスを確認します。

次に、厄介なケースをテストします。1つのノードを強制終了し、コンシューマーグループを一時停止し、キューを満たし、ディスクを遅くし、パブリッシャーを再起動します。RabbitMQのスケーリングは、最も美しい図面よりも、図面がストレスを受けたときに何が起こるかを知ることに関係しています。