メッセージスループットの最大化:自動確認モードと手動確認モード

RabbitMQでピークメッセージスループットを達成するには、確認モードの習得が不可欠です。このガイドでは、自動確認(Auto-Ack)と手動確認の戦略を比較し、Auto-Ackがメッセージの安全性を犠牲にして生の速度を優先する仕組みを詳しく解説します。コンシューマープリフェッチ(QoS)設定が、高負荷システムで重要な配信保証を維持しながらスループットを最大化する上で果たす重要な役割を理解し、実践的なパフォーマンスチューニングを学びましょう。

メッセージスループットの最大化:自動確認モードと手動確認モード

RabbitMQの確認モードは、クライアントコード上では小さな設定に見えますが、運用上は大きな影響を及ぼします。これは、ブローカーがメッセージをいつ破棄してよいかを決定します。その選択は、スループット、メモリ負荷、再試行、重複作業、そしてコンシューマーが処理の途中でクラッシュした場合の動作に影響します。

簡単に言うと、自動確認は高速です。RabbitMQはメッセージが配信された時点で処理済みとみなすからです。手動確認はより安全です。コンシューマーが処理の成功をRabbitMQに明示的に伝えるからです。ほとんどの本番システムは、手動確認から始め、Auto-Ackを検討する前にプリフェッチを調整すべきです。

確認の本当の意味

確認はビジネス上の受領証ではありません。これはブローカーレベルのシグナルです。コンシューマーが basic.ack を送信すると、RabbitMQに「この配信はキューから削除してよい」と伝えます。

この区別は重要です。コンシューマーが注文をデータベースに書き込み、メールを送信し、検索インデックスを更新する場合、適切な確認ポイントは通常、処理の永続的な部分が成功した後です。データベースコミットの前に確認し、プロセスがクラッシュした場合、RabbitMQは要求された通りに動作します。つまり、メッセージを削除します。アプリケーションは作業を失います。

自動確認

Auto-Ackでは、クライアントは自動確認を有効にしてサブスクライブします。RabbitMQはメッセージを送信し、すぐに正常に配信されたものとして扱います。コンシューマーは後で basic.ack を送信しません。

多くのクライアントライブラリでは、この設定はコンシューム時のブール値として表示されます。たとえば、Javaでは basicConsumeautoAck を使用します。いくつかのライブラリでは、名前が少し異なる同じ概念が公開されています。

その魅力は明らかです。プロトコル操作が少なく、ブックキーピングも少なくなります。コンシューマーは、RabbitMQとネットワークが配信できるのと同じ速さでメッセージを受け入れることができます。テレメトリ、一時的な進捗更新、または使い捨てのワークロードでは、これで許容できる場合があります。

リスクも、本番環境で一度見れば明らかです。コンシューマーが1万件のメッセージを受信し、そのインメモリバッファを処理する前にクラッシュした場合、それらのメッセージはキューから失われます。RabbitMQはそれらを再配信できません。なぜなら、それらはすでに自動的に確認済みだからです。

Auto-Ackは、メッセージが重要でない場合、再生成可能な場合、または古いデータが役に立たないライブストリームを表す場合に合理的です。例としては、ベストエフォートのメトリクス、UIプレゼンス更新、または別の永続的なパイプラインが記録のソースであるログ形式のイベントなどがあります。支払い、注文、在庫変更、アカウント更新、またはメッセージを見逃すと手動クリーンアップが必要になるジョブには適していません。

手動確認

手動確認では、RabbitMQは配信されたメッセージを、コンシューマーが応答するまで未確認状態に保ちます。コンシューマー接続が確認前に閉じられた場合、RabbitMQはそれらの未確認メッセージを再キューイングし、再度配信できます。

この動作が、手動確認が重要な作業の通常のデフォルトである理由です。これは、厳密に1回の処理を意味するわけではありません。メッセージが処理され、その後確認を送信する前にコンシューマーがクラッシュする可能性があります。RabbitMQはそれを再配信し、アプリケーションは同じ論理的な作業を2回見る可能性があります。手動確認は最低1回の配信を提供するため、ハンドラーは重複する副作用が問題になる場合に冪等性を依然として必要とします。

安全なコンシューマーループは通常、次のような形状になります:

メッセージを受信
ペイロードを検証
永続的な作業を実行
データベーストランザクションまたは外部副作用をコミット
メッセージを確認

障害の場合、メッセージを再試行するか、遅延させるか、デッドレターにするかを決定します。すべての障害をすぐに再キューイングすると、同じ不良メッセージが一日中CPUを消費するホットループが発生する可能性があります。デッドレター交換、再試行キュー、または遅延再試行パターンがより適切な場合が多いです。

プリフェッチが本当のスループットレバー

多くのチームはAuto-Ackと手動確認を比較し、デフォルト設定では手動確認が遅いことを確認し、誤った結論に飛びつきます。欠けているのはプリフェッチです。

RabbitMQのプリフェッチは、basic.qos で設定され、コンシューマーが一度に保持できる未確認メッセージの数を制限します。手動確認と prefetch=1 では、コンシューマーは1つのメッセージを受信し、処理し、確認し、その後初めて次のメッセージを取得します。これは安全ですが、並行処理が可能なワーカーや小さなローカルバッファを許容できるワーカーにとっては、スループットを無駄にします。

より高いプリフェッチにより、RabbitMQはコンシューマーをビジー状態に保つことができます:

prefetch = ワーカーの並行処理数 * 期待される作業バッファ

ワーカーが8つのジョブを並行処理する場合、プリフェッチ16または32が妥当な出発点です。各メッセージが大きい場合や処理がメモリを大量に消費する場合は、より低い値から始めてください。各メッセージが小さく、処理が主にネットワークI/Oである場合は、より高い数値が役立つ場合があります。

ランダムなプリフェッチ250をすべてのサービスにコピーしないでください。高いプリフェッチは不均等な分散を引き起こす可能性があります。1つのコンシューマーが大きなバッチを受信してそれを保持し、他のコンシューマーがアイドル状態になる可能性があります。また、コンシューマーがダウンした場合の再配信バーストも増加します。RabbitMQはその接続からのすべての未確認配信を再キューイングするため、別のワーカーが突然大きなバックログを継承する可能性があります。

スループットと安全性のトレードオフ

以下が実用的な比較です:

モード RabbitMQの動作 強み 主なリスク
Auto-Ack 配信時にメッセージを削除 最高の生の配信レート コンシューマーがクラッシュした場合の作業損失
手動確認、低プリフェッチ 各確認を待ってからさらに送信 シンプルな障害動作 コンシューマーの未活用
手動確認、調整済みプリフェッチ 制御された数のメッセージをフライト中に保持 リカバリを伴う良好なスループット 冪等なハンドラーと再試行設計が必要

重要な詳細は、手動確認が遅くなければならないわけではないということです。調整が不十分な手動確認は遅いです。適切なプリフェッチ、並行ワーカー、短いデータベーストランザクションを備えた手動確認は、リカバリ動作を維持しながら、深刻なボリュームを処理できます。

具体的なチューニングワークフロー

手動確認と控えめなプリフェッチから始めます:

prefetch = ワーカースレッドあたり1〜4

コンシューマーの使用率、キューの深さ、メッセージ処理時間、メモリ、再配信を測定します。コンシューマーがアイドル状態でキューにメッセージがある場合は、プリフェッチを上げます。メモリが上昇したり、1つのコンシューマーが作業を独占したりする場合は、プリフェッチを下げます。再配信が急増した場合は、プリフェッチを再度変更する前に、クラッシュ、タイムアウト、nack動作を調査します。

ブローカーも監視します。高スループットはコンシューマーの数値だけではありません。ディスクI/O、パブリッシャー確認、キュー型、メッセージサイズ、永続性、ミラーリングまたはクォーラムレプリケーション、ネットワーク帯域幅がすべて結果に影響します。確認モードは、より大きなシステムの中の1つのレバーです。

エラーハンドリングはフラグよりも重要

障害計画のない手動確認コンシューマーは、半分しか完成していません。成功時は確認。一時的な障害時は、即時再試行が意味をなす場合のみ、nackして再キューイング。ポイズンメッセージの場合は、再キューイングなしで拒否またはnackし、設定されていればデッドレター交換にルーティングします。

また、コンシューマーのメインキュー外に最大再試行ポリシーを設定します。RabbitMQは、設計がヘッダー、再試行キュー、またはアプリケーション状態を通じて試行回数を追跡しない限り、不正な形式のJSONメッセージが5回失敗したことを魔法のように認識しません。

デフォルトで選択するもの

ビジネスイベントやバックグラウンドジョブには、手動確認を使用します。ワーカーの並行処理とメモリに基づいてプリフェッチを調整します。ハンドラーを冪等にします。不良メッセージが無限の即時再試行の痛みを教える前に、デッドレタリングを追加します。

Auto-Ackは、損失が許容可能で文書化されている場合にのみ使用します。その文は、インシデントレビュー中に簡単に防御できる必要があります。チームが配信されたが未処理のメッセージが消えたことを知って動揺する場合、Auto-Ackは間違った設定です。

メッセージサイズが答えを変える

2 KBのメッセージに美しく機能するプリフェッチ値は、5 MBのメッセージには無謀かもしれません。プリフェッチは数を制御し、総バイト数は制御しません。1つのコンシューマーが100の未確認メッセージを保持でき、各メッセージが大きい場合、ローカルメモリフットプリントは急速に増加する可能性があります。ブローカーも、それらの配信が確認されるまで追跡する必要があります。

メッセージが大きい場合は、低いプリフェッチから始め、コンシューマープロセスの常駐メモリを測定します。可能であれば、メッセージ本文を小さく保ち、大きなペイロードはオブジェクトストレージなど別の場所に保存し、メッセージには参照とチェックサムを付けます。その設計は常に適切とは限りませんが、ブローカーが大規模ファイル転送になるのを防ぎます。

バッチ確認でプロトコルチャタリングを削減

多くのクライアントライブラリでは、multiple フラグを使用して、1回の確認で複数の配信を確認できます。これにより、コンシューマーがメッセージを順番に処理し、配信タグの範囲を安全に確認できる場合、プロトコルオーバーヘッドを削減できます。

欠点は障害処理です。メッセージを並行処理する場合、配信タグの順序が完了順序と一致しない可能性があります。最新のメッセージが成功したために複数のメッセージを確認すると、まだ実行中または失敗した以前のメッセージを誤って確認する可能性があります。並行ワーカーの場合、メッセージごとの確認がよりシンプルで安全なことが多いです。

有用なルール:コンシューマーの処理モデルが、確認がカバーするメッセージを正確に説明できるほど順序付けられている場合にのみ、バッチ確認を行います。

インシデント中の未確認メッセージを監視

RabbitMQは、準備完了および未確認のメッセージ数を公開します。準備完了メッセージが多いキューは、コンシューマーが追いついていないか、接続されていないことを意味します。未確認メッセージが多いキューは、RabbitMQがコンシューマーに作業を配信したが、まだ確認を受信していないことを意味します。

2番目のケースは、コンシューマーの動作を指し示します:処理の遅さ、スタックした外部呼び出し、高すぎるプリフェッチ、ブロックされたスレッド、または例外後に確認を停止したコンシューマー。これは、パブリッシャーがコンシューマーが受信できるよりも速くキューをフラッディングしているのとは異なります。

管理UIまたは rabbitmqctl を使用して、以下を確認します:

rabbitmqctl list_queues name messages_ready messages_unacknowledged consumers

messages_unacknowledged が高く、コンシューマーが生きている場合は、ブローカー設定を変更する前にコンシューマーログとスレッドダンプを確認します。ブローカーは単にアプリケーションが作業を終えるのを待っているだけかもしれません。

再配信は正常だが、繰り返しの再配信は問題の兆候

手動確認は、コンシューマー障害後にメッセージが再配信される可能性があることを意味します。これは予想されます。避けたいのは、同じポイズンメッセージが配信され、失敗し、再キューイングされ、永遠に再配信されることです。

再試行を診断するための十分なメタデータを追加します。一部のチームはヘッダーを使用して試行回数を追跡します。他のチームは障害を再試行交換に移動し、制限後にデッドレターキューに移動します。正確なパターンは異なりますが、運用目標は同じです:一時的な障害は別のチャンスを得て、永続的な障害は可視化され、有用な作業をブロックしなくなります。

ハンドラーが冪等でない場合、再配信は危険になります。ワーカーがカードに請求し、確認前にクラッシュしたとします。RabbitMQはメッセージを再配信します。ハンドラーが再度請求した場合、バグを作成したのはブローカーではなく、欠落した冪等性キーを明らかにしたのです。外部副作用については、永続的な操作IDを保存し、副作用を繰り返しても安全にします。

パブリッシャー確認は別の懸念事項

コンシューマー確認は、コンシューマーが配信を処理したことをRabbitMQに伝えます。パブリッシャー確認は、RabbitMQが公開されたメッセージを受け入れたことをパブリッシャーに伝えます。これらはフローの反対側を解決します。

システムは手動コンシューマー確認を使用しても、パブリッシャーが確認なしでファイアアンドフォーゲットし、接続が不適切なタイミングで切断された場合、公開時にメッセージを失う可能性があります。同様に、パブリッシャー確認は、コンシューマーがメッセージを受信した後の作業を保護しません。信頼性の高いパイプラインの場合は、ビジネスケースで必要な場合に両方を使用します:公開側での確認、消費側での手動確認、適切な場合の永続キュー、アプリケーション層での冪等処理。

キュー型と永続性が同じスループットの議論に影響

確認モードは単独で存在しません。非永続メッセージを持つ一時的なクラシックキューは、永続メッセージを持つ永続的なクォーラムキューとは異なるパフォーマンスと安全性のプロファイルを持ちます。使い捨てキューでAuto-Ackをベンチマークし、その結果を永続的な本番キューに適用しても、比較は有用ではありません。

重要なワークロードでは、永続キューと永続メッセージが一般的ですが、ディスクとレプリケーションの作業が追加されます。クォーラムキューは、古いミラーリングされたクラシックキューパターンと比較してデータの安全性を向上させますが、スループット特性も変更します。実際に実行するキュー型を測定します。

公正なテストでは、これらの変数を一定に保ちます:

同じメッセージサイズ
同じキュー型
同じ永続性設定
同じパブリッシャー確認動作
同じコンシューマー数
同じプリフェッチ
同じダウンストリーム処理

一度に1つのレバーのみを変更します。そうしないと、結果が確認モード、プリフェッチ、キュー型、メッセージサイズ、コンシューマーコードのいずれによるものかがわかりません。

コンシューマーの並行処理は作業に合わせるべき

各メッセージがほとんどの時間をHTTPやデータベースの待機に費やす場合、コンシューマーは並行処理の恩恵を受ける可能性があります。各メッセージがCPU負荷が高い場合、並行処理が多すぎるとすべてのメッセージが遅くなる可能性があります。プリフェッチはその現実に従うべきです。

シングルスレッドのコンシューマーの場合、プリフェッチ100は単に大きなローカル待機室を作成する可能性があります。20のアクティブな処理スロットを持つワーカーの場合、プリフェッチ40はそれらのスロットを供給し続ける可能性があります。4つのコアを持つCPUバウンドのプロセスの場合、並行処理100はコンテキストスイッチングを増加させ、スループットを向上させない可能性があります。

キューの深さだけでなく、コンシューマー内部の処理時間を測定します。受信時間、開始時間、終了時間、確認時間、失敗理由、再配信フラグのログまたはメトリクスを追加します。これらのタイムスタンプにより、作業がRabbitMQで待機しているのか、コンシューマー内部で待機しているのか、ダウンストリームシステムでスタックしているのかをはるかに簡単に判断できます。