メッセージ処理の遅延トラブルシューティング:RabbitMQのボトルネック特定
プロデューサー、ブローカー、キュー、コンシューマー、ディスク、確認応答のボトルネックを分離して、RabbitMQの速度低下を診断します。
メッセージ処理の遅延トラブルシューティング:RabbitMQのボトルネック特定
RabbitMQのキューが滞留している場合、キューは症状を示しているに過ぎません。ボトルネックは、遅いコンシューマー、ブロックされたパブリッシャー、ディスクアラーム、不適切なプリフェッチ値、巨大なメッセージペイロード、または静かにタイムアウトし始めたダウンストリームデータベースである可能性があります。RabbitMQを再起動すると、数分間はグラフがクリアになるかもしれませんが、メッセージが遅かった理由を修正することはほとんどありません。
最速のトラブルシューティング方法は、フローを「RabbitMQへのパブリッシュ」「キューへのルーティング」「メッセージの保存」「コンシューマーへの配信」「処理の実行」「完了の確認応答」という部分に分割することです。各部分は異なる証拠を残します。
最初の分割:ready または unacknowledged
キューカウンターから始めます:
rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers state
messages_ready は、メッセージがキューにあり、配信を待っていることを意味します。この数値が増加する場合、RabbitMQに利用可能なコンシューマーがいないか、コンシューマーがプリフェッチ制限に達しているか、別の条件によって配信がブロックされています。
messages_unacknowledged は、メッセージがすでにコンシューマーに配信され、RabbitMQがack、nack、reject、またはチャネルクローズを待っていることを意味します。この数値が増加する場合、ボトルネックは通常、コンシューマー内部またはコンシューマーが呼び出す何かにあります。
この区別は重要です。readyメッセージが多く、unackedが少ない場合、ブローカーのメモリを増やしてもコンシューマーが現れるわけではありません。unackedが多い場合、キューを追加分割しても役に立たないかもしれません。なぜなら、作業はすでにキューから出ているからです。
コンシューマーが実際に存在するか確認する
驚くほど多くの「RabbitMQが遅い」というインシデントは、実際には「コンシューマーが実行されていない」インシデントです。デプロイに失敗した、オートスケーリングがゼロになった、認証情報が変更された、間違った仮想ホストが使用された、またはサービスがステージングに接続されている一方でプロデューサーが本番にパブリッシュしている、などです。
以下を使用します:
rabbitmqctl list_consumers queue_name channel_pid consumer_tag ack_required prefetch_count active
コンシューマーがいない場合は、まずそれを修正します。コンシューマーは存在するが非アクティブな場合は、アプリケーションログと接続状態を確認します。すべてのコンシューマーのプリフェッチが1で、各メッセージに数秒かかる場合、低い配信並行性は予想されるかもしれません。すべてのコンシューマーのプリフェッチが500で、unackedが巨大な場合、コンシューマーがすぐに完了できない作業をため込んでいる可能性があります。
コンシューマーの処理時間を測定する
RabbitMQはメッセージがunackedであることを教えてくれますが、コンシューマーが巨大なペイロードを解析しているのか、PostgreSQLを待っているのか、HTTP呼び出しをリトライしているのか、ロックでスタックしているのかは教えてくれません。
実際のハンドラーの周りにタイミングを追加します:
message_received_at
decode_ms
business_logic_ms
database_ms
external_api_ms
ack_ms
message_completed_at
完璧なトレーシングシステムがなくても何かを学ぶことができます。いくつかの構造化ログフィールドだけでも、ハンドラーが通常80ミリ秒かかるところが、ダウンストリームAPIを待つために4秒かかっていることを示せます。
作業が遅いが並列化可能な場合は、コンシューマーインスタンスを追加するか、内部ワーカーの並行性を高めます。ダウンストリームシステムが限界である場合、コンシューマーを追加すると状況が悪化する可能性があります。レート制限、バッチ処理、キャッシュ、または別のリトライキューが必要かもしれません。
ハンドラーを理解した後にプリフェッチを調整する
プリフェッチは、RabbitMQが各コンシューマーに送信できる未確認メッセージの数を制御します。これは、バックログがどこに見えるかを変えるため、処理遅延のインシデントによく関与します。
プリフェッチが低い場合、メッセージはコンシューマーがさらに準備できるまでRabbitMQでready状態を保ちます。これは公平で観察しやすいですが、非常に高速なコンシューマーを十分に活用できない可能性があります。
プリフェッチが高い場合、メッセージはすぐにコンシューマーに移動します。これにより、高速なハンドラーのスループットが向上する可能性がありますが、レイテンシを隠すこともあります。プリフェッチ値が大きい遅いコンシューマーは、他のコンシューマーが作業を使い果たしている間に、何百ものメッセージを抱え込む可能性があります。
実用的なインシデント対応として、遅いまたは不安定なコンシューマーのプリフェッチを下げ、テールレイテンシが改善されるかどうかを観察します。CPU使用率が低く、readyカウントが高い高速なコンシューマーの場合は、慎重にプリフェッチを上げて再度測定します。
パブリッシャー側のボトルネックを探す
時々、キューが滞留しているのはコンシューマーが遅いからではありません。プロデューサーがバーストでパブリッシュし、確認応答を非効率的に待っているからです。
パブリッシャー確認応答は、パブリッシャーがRabbitMQがメッセージを受け入れたことを知る必要がある場合に適切なツールです。遅いパターンは、次のメッセージをパブリッシュする前に各確認応答を待つことです。これにより、すべてのパブリッシュがラウンドトリップになります。
より良いパターンは、非同期確認応答または制限付きバッチを使用します。パブリッシャーは、限られた数のメッセージをフライト中に保ち、nackを処理し、それでもすべてのメッセージでブロックすることを避けられます。制限は重要です。無制限のフライト中パブリッシュは、ボトルネックをパブリッシャーメモリまたはブローカー負荷に移す可能性があります。
パブリッシャーメトリクスを確認します:パブリッシュレート、確認応答レイテンシ、フライト中確認応答数、再接続、返されたメッセージ、チャネル例外。管理UIで、パブリッシュイン率と配信/確認応答率を比較します。アプリケーションがビジーであるにもかかわらずパブリッシュインが低い場合、プロデューサーが確認応答、トランザクション、または接続の変動を待っている可能性があります。
高スループットのパブリッシュには、特別な理由がない限りAMQPトランザクションを避けてください。典型的な信頼性のあるパブリッシュでは、パブリッシャー確認応答よりもはるかに高コストです。
RabbitMQを非難する前にディスクを確認する
永続メッセージ、クォーラムキュー、ストリーム、大規模なバックログはすべてディスクを伴います。ディスクレイテンシが上昇すると、メッセージフローが劇的に遅くなる可能性があります。
RabbitMQノードで、以下を確認します:
rabbitmq-diagnostics status
rabbitmq-diagnostics alarms
rabbitmq-diagnostics memory_breakdown
OSレベルでは、iostat、vmstat、またはクラウド監視グラフなどのツールを使用します。スループットだけでなく、ディスクレイテンシとI/O待機を確認します。バーストクレジットを使い果たしたクラウドディスクは、設定では正常に見えても実際にはひどい場合があります。
ディスクがボトルネックの場合、可能な修正には、より高速なストレージ、永続書き込みの削減、より小さなメッセージ、より良いパブリッシャー確認応答バッチ処理、キュー分割、またはリプレイスタイルのワークロードをストリームや別のログ指向システムに移行することが含まれます。グラフを緑にするためだけに、ビジネスが失ってはならないメッセージの永続性を無効にしないでください。
アラームとブロックされた接続を確認する
RabbitMQはメモリとディスクアラームで自身を保護します。アラームがアクティブな場合、パブリッシャーがブロックされる可能性があります。これはプロデューサー側からはアプリケーションの遅延のように見えるかもしれません。
以下を実行します:
rabbitmq-diagnostics alarms
rabbitmqctl list_connections name user state channels send_pend recv_cnt send_cnt
メモリアラームがアクティブな場合、メモリがキュー、接続、未確認メッセージ、バイナリ、またはプラグインによって保持されているかどうかを調べます。ディスクアラームがアクティブな場合、ブローカーを通じてより多くのメッセージをプッシュしようとする前に、空き容量を増やすか容量を追加します。
ブロックされた接続はそれ自体がバグではありません。ノードが可用性を保護しているため、RabbitMQがパブリッシャーに速度を落とすよう指示しているのです。
メッセージサイズが静かな原因である可能性
1秒間に10,000の小さなメッセージを処理するシステムは、1秒間に500の大きなメッセージで苦労するかもしれません。大きなペイロードは、ネットワーク転送、メモリ負荷、ディスク書き込み、ガベージコレクション作業、コンシューマー処理時間を増加させます。
メッセージに大きなドキュメント、画像、レポート、または大きな配列が含まれている場合、ペイロードをオブジェクトストレージやデータベースに保存し、RabbitMQを通じて参照を送信することを検討してください。ルーティングと冪等性のために十分なメタデータを含めますが、可能な限りブローカーをバルクストレージの役割から遠ざけます。
また、圧縮の選択肢も確認します。巨大なペイロードを圧縮すると、ネットワークとディスクの使用量が減るかもしれませんが、CPUが増加します。それが役立つかどうかは、ボトルネックがどこにあるかによります。
リトライがボトルネックを作り出す可能性
障害が発生しているダウンストリームサービスは、1つのメッセージを多くの試行に変える可能性があります。コンシューマーが失敗をすぐに再キューに入れる場合、同じ悪いメッセージを繰り返し処理し、新しい作業が待機している間に、キュー深度が上昇し、CPUはビジーに見え、有用な作業はほとんど行われません。
高い再配信率と、同じメッセージIDを持つ繰り返しのエラーログを探します。同じペイロードが何度も失敗する場合、それをメインフローから移動します。デッドレターエクスチェンジ、遅延リトライキュー、またはスケジュールされたリトライメカニズムにより、依存関係が回復する時間を与え、ポイズンメッセージが通常の作業をブロックするのを防ぎます。
リトライストームに注意してください。APIが10分間ダウンし、すべてのメッセージが毎秒リトライする場合、APIが戻ったときに回復が難しくなります。バックオフを使用します。試行回数を制限します。最終的な失敗を、調査するのに十分なコンテキストを持つデッドレターキューで可視化します。
冪等性もパフォーマンストラブルシューティングの一部です。コンシューマーが作業を部分的に完了した後にリトライする場合、重複がデータベースの競合、一意キーエラー、または余分なダウンストリーム呼び出しを引き起こす可能性があります。同じメッセージを安全に2回処理できるハンドラーは、スケーリングと回復がはるかに容易です。
管理UIのレートにはコンテキストが必要
RabbitMQ管理UIは便利ですが、レートチャートは1つのラインだけを読むと誤解を招く可能性があります。高い配信率と低い確認応答率は、作業が完了されるよりも速く配布されていることを意味します。高い確認応答率と高いreadyカウントは、コンシューマーが作業しているが追いつくのに十分でないことを意味するかもしれません。インシデント中の低いパブリッシュ率は、プロデューサーがブロックされているか、確認応答を待っていることを意味するかもしれません。
いくつかのレートを一緒に見ます:
publish:エクスチェンジに入るメッセージ。deliver/get:コンシューマーに送信されるメッセージ。ack:コンシューマーによって完了されたメッセージ。redeliver:以前の失敗またはチャネルクロージャの後に再配信されるメッセージ。
健全な定常作業キューでは、パブリッシュと確認応答のレートは時間の経過とともに近くなるはずです。短いバーストは正常です。長いギャップは、バックログが蓄積または排出されていることを意味します。再配信が急増した場合、単にコンシューマーを追加しないでください。メッセージが戻ってくる理由を見つけてください。
サンプリングウィンドウも重要です。1分間のチャートは、ユーザーに影響を与える5秒の停止を隠す可能性があります。1秒間のチャートは、通常のバースト性を混乱のように見せることがあります。チャートウィンドウを、ユーザーまたはダウンストリームシステムが気にするレイテンシに合わせてください。
正常なバックログと壊れたバックログを区別する
すべてのバックログが緊急事態というわけではありません。バッチシステムは、日中に意図的に作業をキューに入れ、夜間に排出する場合があります。ユーザー向けのワークフローでは、メッセージが30秒待つと問題があるかもしれません。同じキュー深度が、あるシステムでは許容され、別のシステムでは深刻である場合があります。
単なるカウントではなく、経過時間に基づくシグナルを定義します。メッセージ数はいくつ待っているかを示しますが、メッセージの経過時間はビジネスが遅れているかどうかを示します。監視が最も古いメッセージの経過時間またはパブリッシュから確認応答までのエンドツーエンド時間を追跡できる場合、キュー深度だけよりも早く速度低下を捉えることができます。
アラートをその期待値に結び付けます。10,000メッセージでアラートを出すと、夜間のエクスポートキューではノイズが多く、パスワードリセットキューでは手遅れになる可能性があります。「最も古いメッセージがサービス目標よりも古い」でアラートを出すと、通常ユーザーが気にする内容に近くなります。
1つのホットキューは依然として1つのホットキュー
クラスタノードを追加しても、1つのキューが自動的にすべてのノードに分割されるわけではありません。単一のホットキューは、そのリーダー、そのコンシューマー、およびそのストレージパスによって制限されたままになる可能性があります。
1つのキューが無関係な作業タイプを運んでいる場合、実際の処理動作に基づいて分割します。例えば、画像リサイズ、メール送信、課金キャプチャは、異なるレイテンシとリトライ要件がある場合、1つの汎用的な jobs キューを共有すべきではありません。別々のキューにより、コンシューマーを独立してスケーリングし、ポイズンメッセージを分離できます。
1つの作業タイプが依然としてホットすぎる場合、順序要件が許す場合にのみシャーディングします。顧客ID、テナント、リージョン、または別の安定したキーによるシャーディングは機能する可能性がありますが、ルーティングと運用に複雑さをもたらします。遅いハンドラーを修正することを避けるためだけにシャーディングしないでください。
冷静なトラブルシューティングの順序
インシデントでは、次の順序を使用します:
- アラームを確認:メモリ、ディスク、ブロックされた接続。
- キューカウンターを確認:ready、unacked、コンシューマー。
- コンシューマーログとハンドラータイミングを確認。
- プリフェッチとコンシューマーごとのunacked分布を確認。
- パブリッシャー確認応答レイテンシと返されたメッセージを確認。
- ディスクレイテンシとノードリソース負荷を確認。
- メッセージサイズと最近のペイロード変更を確認。
- その後にのみトポロジを変更するかブローカーノードを追加する。
この順序は、ボトルネックがワーカーであるときにブローカーをスケーリングしたり、ボトルネックがディスクであるときにワーカーをスケーリングするという一般的な間違いを防ぎます。
RabbitMQは通常、適切なカウンターを読めば非常に明確です。増加するreadyカウントは作業が待っていることを示します。増加するunackedカウントは作業が進行中だが完了していないことを示します。ブロックされたパブリッシャーはブローカーが自身を保護していることを示します。各シグナルを手がかりとして扱えば、修正はずっとドラマチックではなくなります。