RabbitMQパフォーマンスのトラブルシューティング: 遅延と高CPU使用率

キュー、コンシューマー、接続の変動、ディスクI/O、フロー制御、クライアントの動作を確認して、RabbitMQの遅延と高CPU使用率を診断します。

RabbitMQパフォーマンスのトラブルシューティング: 遅延と高CPU使用率

RabbitMQは堅牢で広く採用されているメッセージブローカーですが、他の分散システムと同様に、パフォーマンスの低下が発生することがあり、多くの場合、全体的な遅延や過剰なCPU使用率として現れます。ネットワーク構成、ディスクI/O、アプリケーションロジックのいずれに原因があるにせよ、根本原因を特定することは、システムの健全性と低レイテンシを維持するために重要です。

このガイドは、RabbitMQデプロイメントにおける一般的なパフォーマンスボトルネックを診断および解決するための実践的なトラブルシューティングマニュアルとして機能します。重要な監視ポイントを検証し、スループットを最適化してCPU負荷を安定させるための実行可能な手順を提供し、メッセージブローカーがプレッシャー下でも確実に動作することを保証します。

初期トリアージ: ボトルネックの特定

詳細な設定変更に入る前に、ボトルネックがどこで発生しているかを特定することが不可欠です。高CPUまたは遅延は通常、ネットワークの飽和、集中的なディスクI/O、またはブローカーとの非効率的なアプリケーションの相互作用の3つの領域のいずれかを示しています。

1. RabbitMQの健全性の監視

最初のステップは、RabbitMQの組み込み監視ツール、主に管理プラグインを利用することです。

注目すべき主要メトリクス:

  • メッセージレート: システムの持続容量を超えるパブリッシュまたは配信レートの突然のスパイクを探します。
  • キュー長: 急速に増加するキューは、コンシューマーがプロデューサーに追いついていないことを示し、多くの場合、メモリ/ディスクプレッシャーの増加につながります。
  • チャネル/接続アクティビティ: 高いチャーン(接続/チャネルの頻繁なオープンとクローズ)は、かなりのCPUリソースを消費します。
  • ディスクアラーム: ディスク使用率が設定されたしきい値に近づくと、RabbitMQはデータ損失を防ぐために意図的にメッセージ配信を遅くします(フロー制御)。

2. オペレーティングシステムの検査

RabbitMQはErlang VM上で動作し、OSレベルのリソース競合に敏感です。標準ツールを使用してシステムの健全性を確認します:

  • CPU使用率: topまたはhtopを使用します。rabbitmq-serverプロセスがCPUの大部分を消費していますか?その場合は、Erlangプロセスの内訳を調査します(以下のセクションを参照)。
  • I/O待機: iostatまたはiotopを使用します。特に永続性が多用されている場合、高いI/O待機時間は多くの場合、低速ディスクを指しています。
  • ネットワークレイテンシ: プロデューサー、コンシューマー、ブローカーノード間でpingを使用して、一般的なネットワークの不安定性を排除します。

詳細分析: 高CPU使用率の分析

RabbitMQの高CPU使用率は、多くの場合、Erlang VMまたは特定のプロトコルアクティビティによって処理される集中的な操作に起因します。

Erlangプロセス負荷の理解

Erlangランタイムはプロセスを効率的に管理しますが、特定のタスクはCPUバウンドです。RabbitMQサーバーのCPU使用率がすべてのコアで100%に張り付いている場合は、どのErlangプロセスグループが原因かを調べます。

プロトコルハンドラー(AMQP/MQTT/STOMP)

多くのクライアントが常に接続を確立および切断したり、大量の小さなメッセージをパブリッシュしたりしている場合、認証、チャネルセットアップ、パケット処理のCPUコストが大幅に増加します。頻繁な接続チャーンは、主要なCPUキラーです。

ベストプラクティス: 永続的で長期間の接続を優先します。クライアント側でコネクションプーリングを使用して、繰り返されるハンドシェイクとセットアップフェーズのオーバーヘッドを最小限に抑えます。

キューインデックス作成と永続メッセージ

キューが高負荷で使用されている場合、特にメッセージが永続的(ディスクに書き込まれる)である場合、CPU負荷が急上昇する可能性があります。理由は次のとおりです:

  1. ディスクI/O管理: ディスク書き込みとバッファフラッシュの調整。
  2. メッセージインデックス作成: 特に高耐久性、高スループットのキューにおいて、キュー構造内のメッセージの場所を追跡します。

スロットリングとフロー制御

RabbitMQは、リソースが制約されている場合に自身を保護するためにフロー制御を実装しています。ノードがメモリまたはディスク容量の高水位標に達すると、内部スロットリングが適用され、プロデューサーにとっては遅延として現れる可能性があります。

フロー制御により多数のメッセージがブロックされている場合、即座の解決策はリソースを解放することです(例:コンシューマーがアクティブであることを確認する、またはディスク容量を増やす)。長期的な修正は、クラスターをスケーリングするか、コンシューマーのスループットを最適化することです。

遅いコンシューマーとキュー蓄積のトラブルシューティング

遅延は、コンシューマーが入力レートに追いつけない場合に、アプリケーション層で認識されることがよくあります。これは通常、コンシューマー側の問題、またはコンシューマーとブローカー間のネットワーク問題です。

コンシューマー確認応答戦略

コンシューマーがメッセージを確認応答する方法は、スループットとブローカーのCPU使用率に大きな影響を与えます。

  • 手動確認応答(manual ack): 信頼性を提供しますが、コンシューマーが受信を確認する必要があります。コンシューマーがハングすると、RabbitMQはメッセージを保持し、メモリを圧迫し、そのキュー内の他のメッセージに遅延を引き起こす可能性があります。
  • 自動確認応答(auto ack): 初期スループットを最大化しますが、コンシューマーがメッセージを受信した後、処理する前にクラッシュすると、メッセージは永久に失われます。

手動確認応答を使用していて速度低下が見られる場合は、管理プラグインで未確認メッセージ数を確認します。この数が多い場合、コンシューマーが遅いか、確認応答に失敗しています。

プリフェッチカウントの最適化

qos(サービス品質)設定、特にプリフェッチカウントは、コンシューマーが未確認で保持できるメッセージ数を決定します。

プリフェッチカウントが高すぎる(例:1000)場合、1つの遅いコンシューマーがキューから大量のバックログを引き出し、同じキュー上の他の潜在的に高速なコンシューマーを飢えさせる可能性があります。

例: コンシューマーが1秒あたり10メッセージしか処理していない場合、prefetch_countを100に設定するのは無駄であり、不必要に負荷を集中させます。

# 適切なプリフェッチカウント(例:50)を設定する例
# クライアントライブラリ相当(概念的な表現)
channel.basic_qos(prefetch_count=50)

コンシューマーとブローカー間のネットワークレイテンシ

コンシューマーが高速であるにもかかわらず、ワイヤ経由で受信したメッセージの確認応答に時間がかかる場合、問題はコンシューマーと接続先のRabbitMQノード間のレイテンシまたはネットワーク飽和である可能性があります。

  • テスト: コンシューマーを同じマシン(localhost)上のブローカーに一時的に接続して、ネットワーク変数を排除します。パフォーマンスが大幅に向上する場合は、ネットワークの最適化(専用NIC、中間ファイアウォールの確認など)に焦点を当てます。

ディスクI/Oと永続性の影響

ディスクパフォーマンスは、特に高耐久性を利用するキューの場合、パフォーマンスの上限となることがよくあります。

永続メッセージと耐久性

  • 耐久性のあるエクスチェンジとキュー: ブローカーの再起動時の損失を防ぐために不可欠ですが、メタデータのオーバーヘッドが発生します。
  • 永続メッセージ: 永続としてフラグが立てられたメッセージは、ブローカーがプロデューサーに確認応答を送信する前にディスクに書き込む必要があります。低速ディスクは、直接プロデューサーのスループット低下につながります。

負荷が主に一時的(非永続)メッセージで構成されている場合、キュー自体が耐久性がないことを確認するか、より実用的には、その特定のペイロードに対してデータ損失が許容される場合はメッセージを一時的としてマークします。一時的メッセージはRAMに留まるため(メモリプレッシャーの影響を受けます)、はるかに高速です。

ミラーリングのオーバーヘッド

高可用性(HA)クラスターでは、キューミラーリングがノード間でデータを複製します。フォールトトレランスには不可欠ですが、ミラーリングはクラスターにかなりの書き込み負荷を追加します。ディスクレイテンシが高い場合、この負荷がI/O容量を飽和させ、すべての操作を遅くする可能性があります。

最適化のヒント: 高い書き込みスループットが必要だが、フェイルオーバー中に軽微なデータ損失を許容できるキュー(例:ログストリーム)の場合は、高可用性ノードセットで非ミラーリングキューを使用するか、キュー長が非常に大きくなることが予想される場合はレイジーキューの使用を検討します(レイジーキューは、RAMを節約するために未消費メッセージをより早くディスクに移動します)。

ブローカーの問題とアプリケーションの問題を区別する

RabbitMQは、他の場所で発生するレイテンシの原因として非難されることがよくあります。Webリクエストがタイムアウトする、ジョブの完了が遅れる、ダウンストリームのデータベースが遅い、そしてキューはその深さが見えるため、最も気付きやすいものです。ブローカーをチューニングする前に、RabbitMQが遅いのか、それともRabbitMQがコンシューマーが遅いことを示しているのかを判断します。

影響を受けるキューについて、3つの数値から始めます:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers \
  message_stats.publish_details.rate message_stats.deliver_get_details.rate \
  message_stats.ack_details.rate

コンシューマーが存在し確認応答が遅い間にmessages_readyが増加する場合、コンシューマーが追いついていません。ブローカーは正常である可能性があります。messages_unacknowledgedが増加する場合、コンシューマーはメッセージを受信しているが、完了または確認応答していません。ディスクまたはメモリアラームがアクティブな間にパブリッシュ確認が遅くなる場合、ブローカーはバックプレッシャーを適用しています。CPUが高く接続数が増加している場合、クライアントの動作が原因である可能性があります。

この区別は重要です。RabbitMQにRAMを追加しても、すべてのメッセージに対して低速なAPIを呼び出すのに2秒かかるコンシューマーは修正されません。コンシューマーレプリカを増やしても、ディスク書き込みによってスロットルされているブローカーは修正されません。プリフェッチを変更しても、パブリッシュごとに新しいTCP接続を開くプロデューサーは修正されません。

接続とチャネルのチャーン

RabbitMQによる高CPUは、多くの場合、退屈なものです。つまり、あまりにも多くのクライアントが繰り返し接続を開いたり閉じたりしています。AMQP接続のセットアップは無料ではありません。これには、TCPセットアップ、オプションのTLSネゴシエーション、認証、チューニング、チャネルネゴシエーションが含まれます。アプリケーションがすべてのメッセージ、すべてのHTTPリクエスト、またはすべての短いジョブに対して接続を開く場合、RabbitMQはメッセージを移動する代わりにセットアップ作業にCPUを費やします。

接続の経過時間と数を確認します:

rabbitmqctl list_connections name user peer_host state channels connected_at
rabbitmqctl list_channels connection number user vhost

同じサービスからの短命な接続の絶え間ないストリームが見られる場合は、クライアントを修正します。接続を長期間維持します。チャネルを適切に使用します。ほとんどのサービスは、起動時に接続を作成し、シャットダウンまで再利用し、障害時の再接続ロジックを備える必要があります。Webアプリケーションでは、フレームワークに非常に意図的なコネクションプールがない限り、リクエストハンドラー内でブローカー接続を作成しないでください。

TLSはチャーンをより高価にします。TLSは本番環境には適していますが、繰り返されるハンドシェイクは負荷がかかると顕著になります。接続の再利用が依然として修正策です。

作業に一致するプリフェッチ

プリフェッチは魔法のスループットノブではありません。これは、コンシューマーが保持できる未確認メッセージの数を制御します。適切な値は、処理時間、メッセージサイズ、コンシューマー間の公平性によって異なります。

プリフェッチが1の場合、シンプルで公平ですが、各ジョブにネットワークまたはディスクの小さな待機がある場合、コンシューマーを十分に活用できない可能性があります。プリフェッチが500の場合、ベンチマークでは高速に見えるかもしれませんが、低速なコンシューマーが作業をため込み、クラッシュしたときに再配信の痛みが増す可能性があります。

実用的な開始点は、コンシューマーがメッセージごとに費やす時間を測定することです。作業がCPU主体で、各プロセスが一度に1つのメッセージを処理する場合は、プリフェッチを低く保ちます。作業がリモートサービスを待機し、コンシューマーが内部的に並行性を処理する場合、中程度のプリフェッチでビジー状態を維持できます。段階的に増やして、以下を監視します:

  • 確認応答レート;
  • messages_unacknowledged;
  • コンシューマーメモリ;
  • エンドツーエンドレイテンシ;
  • コンシューマー再起動後の再配信数。

テストには障害を含める必要があります。未確認メッセージを保持している間に1つのコンシューマーを強制終了します。再配信により重複作業の巨大なバーストまたは長時間の停止が発生する場合、そのキューに対してプリフェッチが高すぎる可能性があります。

永続メッセージとディスクの現実

永続メッセージと耐久性のあるキューは、重要な作業には正しい選択ですが、ボトルネックの一部をストレージに移します。パブリッシャーが確認を待つ場合、低速なディスク書き込みは低速なパブリッシングとして現れます。キューが大きくなると、RabbitMQはより多くのインデックス作成とストレージ作業を行う必要があります。クラスター化されたセットアップでは、レプリケーションによりネットワークとディスクの作業も追加されます。

オペレーティングシステムからディスクの症状を確認します:

iostat -xz 1
vmstat 1

高いI/O待機、高いディスク使用率、または長い待機時間は、ブローカーがストレージを待機していることを示しています。これは「永続性をオフにする」という意味ではありません。より高速なストレージ、不要な永続メッセージの削減、より低いパブリッシュレート、より効率的なバッチ処理、または作業をノード間で分散するトポロジーが必要であることを意味します。

正確なセットアップをテストしない限り、RabbitMQデータディレクトリを低速なネットワークディスクに配置しないでください。RabbitMQはスループットと同様にレイテンシを重視します。バルクファイルコピーには許容できるように見えるディスクでも、メッセージワークロードには不十分な場合があります。

キュータイプとレプリケーションの選択

古いRabbitMQガイダンスでは、ミラーリングされたクラシックキューについて言及されることがよくあります。現在のRabbitMQデプロイメントでは、クォーラムキューが複製された耐久性のあるワークロードに一般的に好まれますが、クラシックキューは、複製されていない、または重要度の低いケースに依然として適合します。最適な選択は、RabbitMQのバージョン、運用要件、ワークロードによって異なります。

クォーラムキューは、複製された耐久性のあるキューの障害モデルを改善しますが、無料ではありません。それらはコンセンサスプロトコルを介して複製するため、書き込みには複数のノードが関与します。すべての高ボリュームの一時的なイベントストリームをクォーラムキューに入れると、必要のないパフォーマンス問題が発生する可能性があります。

ビジネス価値に一致する場合に、より強力な耐久性を使用します:

  • 支払い、注文、在庫、監査ワークフローは、多くの場合、耐久性のある複製キューに値します;
  • キャッシュリフレッシュ、メトリクス、再構築可能な通知は、同じ保護を必要としない場合があります;
  • 非常に大きなバックログは、より大きなブローカーだけでなく、設計の見直しが必要になる場合があります。

ポイントは安全性を最小限にすることではありません。再作成可能なデータに最高の信頼性コストを支払うことを避け、同時に失うことのできないメッセージを保護することです。

大きなメッセージはすべてを難しくする

RabbitMQは大きなメッセージを運ぶことができますが、メッセージが小さい場合、キューは通常より健全です。大きな画像、レポート、アーカイブ、または完全なデータベースエクスポートを含むメッセージは、メモリプレッシャー、ディスクプレッシャー、ネットワーク転送時間、再配信コストを増加させます。

大きなペイロードの場合は、ペイロードをオブジェクトストレージまたはデータベースに保存し、参照を含むメッセージを送信します:

{
  "job_id": "report-2026-05-25-001",
  "object_url": "s3://reports-bucket/report-2026-05-25-001.json",
  "sha256": "..."
}

コンシューマーは、処理の準備ができたときにペイロードをフェッチします。この設計は完璧ではありません。ペイロードストアのライフサイクルクリーンアップとアクセス制御が必要になります。しかし、RabbitMQをファイル転送システムにする代わりに、調整に集中させ続けます。

CPUは高いがキューが空の場合

空のキューは、必ずしもRabbitMQがアイドル状態であることを意味しません。クライアントが常に接続、認証、ルーティング不可能なメッセージのパブリッシュ、トポロジーの宣言、または非効率的なパターンでのポーリングを行っているため、CPUが高くなる可能性があります。

管理UIまたはCLIで接続チャーンとチャネル数を確認します。再接続ループのアプリケーションログを確認します。すべてのパブリッシュの前にエクスチェンジとキューを宣言するクライアントを探します。トポロジー宣言は通常べき等ですが、非常に高い頻度で行うと、それでもブローカーの作業が増加します。

また、プラグインを確認します。管理、フェデレーション、シャベル、MQTT、STOMP、トレーシング、カスタムプラグインはすべて、有効にして使用すると作業が追加されます。インシデント中に盲目的にプラグインを無効にしないでください。ただし、負荷がプラグインアクティビティと一致するかどうかを確認します。

より安全なチューニングルーチン

一度に1つのことを変更し、変更前後の数値を記録します。プリフェッチ、コンシューマー数、キュータイプ、永続性、ハードウェアがすべて同じデプロイで変更されると、RabbitMQのパフォーマンス作業は混乱します。

有用なルーチン:

  1. キューレート、キュー深度、未確認メッセージ、接続数、CPU、メモリ、ディスクI/O、パブリッシュ確認レイテンシをキャプチャします。
  2. 可能性の高いボトルネックを選択します。
  3. 1つの変更を行います。
  4. 同じワークロードを実行します。
  5. ブローカーのスループットだけでなく、エンドツーエンドのレイテンシを比較します。

インシデントがアクティブな場合は、最初に可逆的な変更を選択します: コンシューマーを追加する、接続チャーンを停止する、プロデューサーレートを下げる、バックログを排出する、またはオプションのワークロードを移動する。システムがすでにダウンしていて、より安全なパスがない場合を除き、キュータイプの移行とストレージの再設計は計画された作業のために保存します。

実行可能な手順のまとめ

高CPUまたは全般的な遅延に直面した場合は、次のチェックリストに従います:

  1. アラームを確認: ディスクまたはメモリのフロー制御アラームがアクティブでないことを確認します。
  2. クライアントの動作を検査: 高い接続/チャネルチャーン、または不適切にauto-ackを使用しているクライアントを探します。
  3. コンシューマーを最適化: prefetch_countをコンシューマーの実際の処理速度に合わせて調整します。
  4. ディスク速度を確認: ストレージバックエンドが永続性とレプリケーションの要件に十分な速度であることを確認します。
  5. Erlangのプロファイリング(上級者向け): Erlangツール(例:observer)を使用して、CPUがプロトコル処理と内部キュー管理のどちらに費やされているかを確認します。

OS、ブローカー、アプリケーション層でのリソース使用率を体系的に分析することで、RabbitMQパフォーマンス問題の根本原因を効果的に特定し、排除できます。