RabbitMQのメモリ管理と高スループットのベストプラクティス

RabbitMQのメモリ、ディスク制限、キュー、コンシューマを調整し、高スループットがブローカーの負荷にならないようにします。

RabbitMQのメモリ管理と高スループットのベストプラクティス

RabbitMQは大量のメッセージを処理できますが、メモリがオーバーフローの計画になると問題が発生します。ブローカーは、接続、チャネル、キュープロセス、メッセージメタデータ、未確認の配信、プラグイン、メトリクス、そしてErlangランタイム自体のためにメモリを必要とします。パブリッシャーがコンシューマーよりも長い間高速である場合、問題は「RabbitMQはどれだけ速く動作できるか?」から「最初にどこに負荷が現れるか?」に変わります。

適切なメモリ管理は、主にその負荷を可視化し制御することです。オペレーティングシステムがプロセスを強制終了する前に、RabbitMQがバックプレッシャーを適用するようにしたいものです。また、永続メッセージと内部状態を安全に書き込めるように、十分なディスク空き容量も必要です。

メモリアラームから始めるが、チューニングの魔法として扱わない

RabbitMQはvm_memory_high_watermarkを使用して、メモリ使用量が高すぎるかどうかを判断します。しきい値を超えると、RabbitMQはメモリアラームを発生させ、メモリが減少するまでパブリッシャーをブロックします。この動作は意図的です。ブロックされたパブリッシャーは厄介ですが、メモリ不足のブローカーはさらに悪化します。

一般的な出発点は、利用可能なメモリの約40%の相対的なウォーターマークです:

vm_memory_high_watermark.relative = 0.40

その数値は絶対ではありません。他のサービスが動作している小さなVMでは、より低いしきい値が必要になる場合があります。ワークロードを十分に理解している専用ブローカーでは、異なる値に耐えられる場合があります。重要なのは、OSのページキャッシュ、ファイルシステムのアクティビティ、監視エージェント、そしてグラフが追いつく前に発生するバーストのために余裕を残すことです。

絶対値を設定することもできます。これは、コンテナや「利用可能なメモリ」が誤解されやすい環境では、多くの場合簡単です:

vm_memory_high_watermark.absolute = 6GiB

ノードのデプロイ方法に合わせて、いずれかのスタイルを使用します。コンテナでは、RabbitMQがホストの全メモリではなく、期待するコンテナ制限を認識していることを確認します。間違ったメモリ合計に基づくウォーターマークは、静かに本番インシデントを引き起こす方法です。

ディスク空き容量制限はもう一つの安全レール

RabbitMQのディスク保護設定はdisk_free_limitです。これは空き容量に基づいており、disk_high_watermarkのパーセンテージではありません。空きディスク容量が設定された制限を下回ると、RabbitMQはディスクアラームを発生させ、パブリッシャーをブロックします。

多くの本番ノードでは、相対的な制限よりも絶対的な制限の方が明確です:

disk_free_limit.absolute = 20GB

適切な値は、メッセージサイズ、パブリッシュレート、永続性、ログローテーション、そしてチームがスペースを追加したりキューをドレインしたりする速さによって異なります。大きな永続メッセージを受信するノードは、小さな一時的なイベントを処理するノードよりもはるかに大きなバッファが必要です。

アラームを避けるためだけに、この値を小さく設定しないでください。ディスクアラームはブローカーを保護するためにあります。ディスクの空きバイト数がゼロになると、書き込みの失敗、可用性の低下、そしてはるかに厄介な復旧が発生する可能性があります。

実際にメモリを使用しているものを理解する

メモリが上昇した場合、推測は避けてください。RabbitMQは管理UIとCLIを通じてメモリの内訳を公開します:

rabbitmq-diagnostics memory_breakdown
rabbitmqctl status
rabbitmqctl list_queues name type messages_ready messages_unacknowledged memory

最も有用な最初の分割は、準備完了メッセージと未確認メッセージです。準備完了メッセージはまだキューで待機しています。未確認メッセージはコンシューマーに配信され、basic.ackbasic.nack、またはチャネルクローズを待っています。

準備完了メッセージが増加している場合、プロデューサーがコンシューマーを上回っているか、コンシューマーが接続されていません。未確認メッセージが増加している場合、コンシューマーはメッセージを受信しているが完了していません。これらは異なる問題です。メモリ制限を引き上げても、フローの不均衡が続く限り、時間を稼ぐだけです。

大きなメッセージには特別な注意が必要です。メッセージ数が控えめなキューでも、各メッセージが大きなペイロードを運ぶ場合、大量のメモリを消費する可能性があります。メッセージに画像、ドキュメント、または大きなJSONブロブが含まれている場合は、ペイロードを別の場所に保存し、RabbitMQを通じて参照を送信することを検討してください。メッセージブローカーは、通常、ブロブストアとして機能するよりも、作業通知を移動するのに適しています。

プリフェッチを調整して隠れたバックログを防ぐ

プリフェッチは、RabbitMQがコンシューマーに配信できる未確認メッセージの数を制御します。高いプリフェッチ値は、高速なコンシューマーのスループットを向上させることができますが、バックログをキューからコンシューマーメモリに移動させます。

例えば、prefetch_count=500の10のコンシューマーは、準備完了キュー外に最大5,000の未確認メッセージを保持できます。各メッセージが大きいか処理が遅い場合、メモリプレッシャーと不均一なレイテンシーが発生する可能性があります。新しいメッセージは、すでに1つの遅いコンシューマー内にある数百の古いメッセージの後ろで待つ可能性があります。

作業に合わせたプリフェッチ値から始めてください。遅いAPI呼び出しやデータベース書き込みの場合は、5や10などの小さな数値を試し、測定後にのみ増やしてください。非常に高速なローカルCPU作業の場合は、より高い値が役立つ場合があります。厳密な公平性のためには、prefetch_count=1が適切なトレードオフになることがあります。たとえ全体的なスループットが低くてもです。

重要なのは、処理時間とACK遅延を測定することです。RabbitMQはあなたの代わりにメッセージを完了することはできません。配布する未完了の作業量を制限することしかできません。

可能な限りキューを短く保つ

RabbitMQは、メッセージがキューに何時間も滞留するのではなく、システムを流れるときに最適に動作します。通常はゼロに近く、時折スパイクするキューは健全です。一日中成長し、夜間にドレインするキューは容量の警告です。成長するだけのキューは、ゆっくりとした障害です。

長いバックログについては、バックログが予想されるかどうかを判断してください。予想される場合は、適切なキュータイプとストレージ設計を使用します。Quorumキューは、耐久性のあるレプリケーションワークロードに適しています。ストリームはリプレイスタイルのワークロードに適している場合があります。クラシックキューは、より単純な一時的な作業に適しています。バックログが予想されない場合は、ブローカーメモリを調整する前に、コンシューマーまたはダウンストリームサービスを修正してください。

期限切れの作業が本当に無意味な場合にのみ、メッセージTTLを設定してください。TTLは容量の代わりにはなりません。システムを古いメッセージの処理から保護することはできますが、軽率に適用するとデータ損失を隠す可能性があります。

デッドレターキューは、有害なメッセージを通常のフローから分離するのに役立ちます。デッドレター戦略がないと、1つの不良ペイロードが永久に再試行され、リソースを消費し、キューを実際よりも遅く見せることがあります。

永続性はスループット予算を変える

耐久性のあるキューと永続メッセージは、メッセージがブローカーの再起動後も存続する必要がある場合に適切な選択です。また、ディスク書き込みが必要です。パブリッシャー確認は、ブローカーがメッセージの責任を受け入れたことをパブリッシャーが知るための信頼性シグナルを追加します。

遅いパターンは、1つの永続メッセージを公開し、その確認を同期的に待ち、次に次のメッセージを公開することです。シンプルで安全ですが、スループットはラウンドトリップ時間とディスク動作によって制限されます。より良いパターンは、非同期のパブリッシャー確認または小さなバッチを使用し、同時に否定応答とタイムアウトを処理することです。

非常に特定の理由がない限り、高スループットの公開にはAMQPトランザクションを避けてください。パブリッシャー確認は、RabbitMQパブリッシャーにとって通常の信頼性ツールです。

RabbitMQに退屈なインフラストラクチャを提供する

RabbitMQは予測可能なマシンを好みます:十分なメモリ、永続ワークロード用の高速ディスク、安定したネットワークレイテンシー、そしてCPUを盗むノイズの多い隣人がいないこと。ブローカーがデータベース、ログプロセッサ、およびランダムなcronジョブとホストを共有する場合、メモリチューニングは推測になります。

永続的な高スループットキューにはSSDまたはNVMeストレージを使用してください。ディスク使用率だけでなく、ディスクレイテンシーも監視してください。ディスクは中程度のスループットを示しながら、苦痛な書き込みレイテンシーを持つことがあります。クラウド環境では、プロビジョニングされたIOPSとバーストクレジットがディスクラベルよりも重要になる場合があります。

接続のチャーンを制限してください。長期間の接続とチャネルは、公開ごとに新しい接続を開くよりもコストがかかりません。アプリケーションが数千の短命な接続を作成する場合、メッセージレートが通常でも、メモリとファイル記述子の使用量が増加する可能性があります。

コンテナは明示的な思考が必要

RabbitMQはコンテナ内でうまく動作しますが、メモリ制限を明確にする必要があります。ブローカーのメモリウォーターマークは、コンテナが実際に使用できる制限に対して計算された場合にのみ有用です。RabbitMQがホストのメモリを持っていると思っていても、コンテナランタイムがより小さな制限を強制する場合、RabbitMQ自身のアラーム動作が保護する前にコンテナが強制終了される可能性があります。

コンテナメモリ制限を設定し、その制限内に余裕を残す絶対的なRabbitMQウォーターマークを設定します:

vm_memory_high_watermark.absolute = 3GiB

例えば、4 GiBに制限されたコンテナでは、3 GiBのブローカーウォーターマークは専用ポッドには妥当かもしれませんが、サイドカーやプラグインが意味のあるメモリを使用する場合は、より低い値の方が良いかもしれません。その数値を盲目的にコピーしないでください。重要なのは、関係を明示的にすることです。

永続データには永続ストレージも必要です。コンテナの再起動でRabbitMQデータディレクトリが失われると、耐久性のあるキューと永続メッセージは役に立ちません。適切なボリュームを使用し、ストレージクラスを理解し、セットアップを信頼する前にブローカーの再起動をテストしてください。

レイジーキュー、Quorumキュー、およびメモリの期待

古いRabbitMQのアドバイスでは、「大きなバックログにはレイジーキューを使用する」とよく言われます。そのアドバイスにはコンテキストが必要です。クラシックレイジーキューは、より多くのメッセージをディスクに保持し、長いキューのメモリプレッシャーを軽減するように設計されました。大きなバックログが予想されるクラシックキューワークロードには、依然として有用です。

Quorumキューは異なる動作をし、レプリケートされた耐久性のあるワークロードに一般的に使用されます。バックログを処理できますが、データをレプリケートし、独自のメモリとディスクプロファイルを持っています。Quorumキューは、まず信頼性の選択です。無制限のバックログへの近道ではありません。

ビジネスがメッセージを何日も保持し、多くのコンシューマーによって再生されることを期待している場合、通常のワークキューよりもストリームや他のログスタイルのシステムの方が適しているかもしれません。RabbitMQは作業のディスパッチに優れています。大きな履歴ペイロードの唯一の長期ストレージ層になると、あまり快適ではありません。

ブローカーの症状とワークロードの症状を分離する

メモリアラームは、RabbitMQがプレッシャー下にあることを示します。RabbitMQが根本原因であるかどうかは示しません。遅い課金APIがコンシューマーのACKを停止させ、未確認メッセージが増加し、ブローカーメモリが上昇し、パブリッシャーをブロックする可能性があります。ブローカーアラームは現実ですが、最初の修正はブローカーの外部にある可能性があります。

レビュー中は、パブリッシュレート、配信レート、ACKレート、準備完了メッセージ、未確認メッセージ、メモリ、ディスク空き容量、およびコンシューマー処理時間を一緒にグラフ化してください。動きの順序が重要です。ACKレートがメモリ上昇前に低下した場合、コンシューマーを調べてください。ディスクレイテンシーが確認の低下前にスパイクした場合、ストレージを調べてください。製品ローンチ後にパブリッシュレートが倍増した場合、容量とバックプレッシャーを調べてください。

これが、負荷テストにコンシューマーとダウンストリーム依存関係を含めるべき理由でもあります。パブリッシュのみのベンチマークは、実際のワークフローについてほとんど証明しません。ブローカーはしばらくの間メッセージを迅速に受け入れるかもしれませんが、システムが機能するのは、コンシューマーが必要なレートでメッセージを完了した場合のみです。

バックプレッシャーをアプリケーションチームに可視化する

パブリッシャーのブロックは不可視であってはなりません。アプリケーションは、クライアントライブラリが公開する場合、ブロックおよびブロック解除された接続イベントをログに記録し、パブリッシャーはユーザー向けリクエストを供給するパブリッシュパスの周りにタイムアウトを設定する必要があります。

その可視性がなければ、メモリアラームは漠然とした「アプリが遅い」という苦情になります。可視性があれば、チームはRabbitMQが特定の時間にバックプレッシャーを適用したことを確認し、そのタイムスタンプをキューの深さ、コンシューマーエラー、ディスクレイテンシー、およびデプロイイベントと比較できます。

高スループットのレビュー中に確認すること

私は以下の質問から始めます:

  • メモリアラームまたはディスクアラームが発生していますか?
  • メッセージは主に準備完了ですか、それとも未確認ですか?
  • どのキューが最もメモリを使用していますか?
  • コンシューマーはパブリッシュレートに追いついていますか?
  • パブリッシャー確認は非同期ですか、それとも1つずつブロックしていますか?
  • メッセージは必要以上に大きいですか?
  • バースト中にディスクレイテンシーが上昇していますか?
  • 接続は安定していますか、それとも常に再接続していますか?

これらの答えは通常、修正を指し示します。修正は設定変更である場合もあります。より多くの場合、それはフローの変更です:より高速なコンシューマー、より低いプリフェッチ、より小さなメッセージ、より良いバッチ処理、デッドレターパス、またはワークロードに一致するキュータイプ。

高スループットとは、単にベンチマークの数値が大きいことではありません。メモリ、ディスク、およびレイテンシーの制御を失うことなく、ビジー期間を吸収する能力です。RabbitMQは安全レールを提供しますが、トラフィックを動かし続けるのはあなたの責任です。