Claude Media
Claude Codeでquorum queueへ移行する手順 — RabbitMQの宣言とprefetch修正

Claude Codeでquorum queueへ移行する手順 — RabbitMQの宣言とprefetch修正

RabbitMQのクラシックキューをquorum queueへ移す作業を、非対応機能の洗い出し、新キューの並走、prefetchとack/nackの修正の順でClaude Codeに任せる手順です。

クラシックキューからquorum queueへの移行は、キュー定義のx-queue-typeを書き換えるだけでは終わりません。既存キューの種類は後から変えられないため、新しいキューを宣言し直し、コードの前提(prefetchの掛け方、nackで再投入する癖)も合わせて直す作業になります。この記事では、その作業をClaude Codeに分担させる手順を、洗い出し、並走、コード修正、切り替えの順に書きます。

宣言の書き換えだけで済まない理由

quorum queueを作るには、宣言時にx-queue-type引数へquorumを渡します(既定はclassic)。この引数はクライアントが宣言時に渡す必要があり、ポリシーでは設定も変更もできません。ポリシーは動的に変えられるが、キューの種類は変えられないため、という説明です。

すでに存在するキューに別の種類で宣言し直すと、プロパティが一致しないので406 PRECONDITION_FAILEDのチャネルエラーになります。移行は「同名キューを書き換える」作業ではなく、「新しいキューを用意して流れを移す」作業になります。

キューの種類の不一致だけは、緩める手段があります。アプリ側のデプロイが間に合わない環境向けに、次の設定でx-queue-typeの同値性チェックを無視できます。

# rabbitmq.conf
quorum_queue.property_equivalence.relaxed_checks_on_redeclaration = true

この設定は、x-queue-typeを歴史的な理由でclassicと明示しているだけのアプリを楽にするためのものです。exclusiveのような他のプロパティが衝突している場合までは救えません。

なお、ブローカーが3.13系でミラーリング付きクラシックキューを使っている場合は話が別です。クラシックキューのミラーリングはRabbitMQ 4.0で削除されており、3.13.xからの移行にはBlue-Green Deploymentを使う手順が用意されています。rabbitmqadmin v2にそのためのコマンドがあります。本記事は、ミラーリングを使っていないクラシックキューからの移行を扱います。

先にClaude Codeに洗い出させるもの

quorum queueで使えない機能と、挙動が変わる機能を、コードベースから先に拾います。移行の失敗は、ここを飛ばして宣言だけ差し替えたときに起きるためです。機能比較の表から、コードとの突き合わせで効く項目を抜き出します。

確認する対象クラシックキューquorum queueコード側で探すもの
非durableキュークラシックキュー使えるquorum queue使えない(常にdurable)コード側で探すものdurable=False
exclusiveキュークラシックキュー使えるquorum queue使えないコード側で探すものexclusive=True
サーバー採番名のキュークラシックキュー使えるquorum queue使えないコード側で探すもの名前を空文字で宣言する箇所
グローバルQoSクラシックキュー使えるquorum queueチャネルエラーコード側で探すものprefetchのglobal指定
再配信の上限クラシックキュー上限の設定なし(requeueは何度でも)quorum queue4.0から既定20回(3.13系は上限なし)コード側で探すものrequeue=Trueのnack

RPCの返信キューや一時キューのような、短命でチャーンの多いキューはquorum queueに向きません。公式の使いどころも、長く存在して高可用性が要る重要なキュー(受注、投票のような、失うと影響が大きいもの)を想定しています。全キューを一律に移さず、移すものを選ぶ判断が最初に要ります。

Claude Codeには、この洗い出しを計画モードで頼みます。計画モードはファイルを読んで計画を出すだけで、承認するまで編集しません。

claude --permission-mode plan

セッション中にShift+Tabを押して切り替える方法もあります。調査でコンテキストを食いたくないなら、探索をサブエージェントに委ねる指示が使えます。サブエージェントは自分のコンテキストでファイルを読み、要約だけを返します。

サブエージェントで、キュー宣言とconsumeの箇所を全て洗い出して。
次の4点を表にして: キュー名 / durable / exclusive / prefetchの設定。
global指定のprefetchと、nack(requeue=True)の箇所は別に列挙して。

返ってきた表のうち、exclusiveや非durable、サーバー採番名のキューは移行対象から外す候補です。ここで決めた対象キューの一覧は、次の手順書の元になります。

CLAUDE.mdに移行ルールを置く

移行は複数のセッションに分かれるので、判断基準を毎回口頭で伝えず、リポジトリのCLAUDE.mdに固定しておきます。次は一例です。

## RabbitMQ移行ルール(classic -> quorum)
- 既存キューは変更しない。`<name>.v2`のように新名で宣言する
- 宣言は`x-queue-type: quorum`を引数に明示する(ポリシーでは設定できない)
- prefetchはconsumerごとに設定し、global指定は使わない
- 失敗メッセージは`nack(requeue=False)`にして、DLXへ流す
- メッセージ手動ackとpublisher confirmsを必ず使う
- 移行対象はキュー一覧ファイル`docs/mq-targets.md`にあるものだけ

最後の行が効きます。対象を明文化しておくと、Claude Codeが「ついでに」RPC返信キューまで書き換える事故を防げます。

手順: 新キューを並走させて切り替える

流れは「新キューを作る、公開側を向ける、旧キューを空にする、旧キューを消す」の4段です。バインディングは手順3で切り替えるため、手順1〜2の間は新キューに何も届きません。

手順

クラシックキューからの切り替え手順

  1. 1

    新キューを宣言する

    旧キューと同じExchangeに対応させる、名前だけ変えたquorum queueを宣言します。この時点ではバインドしません。宣言コードにはDLXなどの引数も旧キューから写します。

  2. 2

    コンシューマを新キューに向ける

    prefetchとack/nackを後述の方針で直し、新キューの購読を始めます。バインド前なので新キューは空のまま待機し、旧キューの購読は空になるまで残します。

  3. 3

    パブリッシャを新キューへ向ける

    新キューをバインドし、同時に旧キューをアンバインドして、新しいメッセージが新キューにだけ入る状態にします。publisher confirmsを有効にしているか、この時点で確認します。

  4. 4

    旧キューを空にして削除する

    旧キューに残ったメッセージを処理し切るか、Shovelで新キューへ移します。深さが0のまま一定時間続いたのを確認して削除します。

宣言のコード例は次の形になります。queue_bindは手順3で実行する行です(Pythonのpikaで書いた例示で、他のクライアントでもargumentsに相当する引数があれば同様です)。

channel.queue_declare(
    queue="orders.v2",
    durable=True,
    arguments={
        "x-queue-type": "quorum",
        "x-dead-letter-exchange": "orders.dlx",
    },
)
channel.queue_bind(queue="orders.v2", exchange="orders", routing_key="created")

Claude Codeには、この手順を1ステップずつ頼みます。一度に「全部移して」と頼むと、パブリッシャとコンシューマの切り替え順が崩れやすいためです。ステップ1と2を1つのコミット、3を別のコミット、というように、戻せる単位で区切るよう指示すると、問題が出たときに切り戻せます。

旧キューのメッセージを移す

旧キューに溜まったメッセージを新キューに運ぶには、RabbitMQのShovelプラグインが使えます。Shovelは、ソース(通常はキュー)からメッセージを取り出し、宛先(Exchangeなど)へ一方向に移す仕組みで、動的Shovelが現行の推奨です。旧キューを購読するコンシューマの処理能力が足りるなら、旧コンシューマに任せて空にする選択もあります。

prefetchとack/nackの修正

コード側の修正で重要なのは、次の3点です。

グローバルQoSを外す

quorum queueはグローバルQoS、つまりチャネル単位で1つのprefetch上限を全コンシューマに掛ける指定をサポートしません。グローバルQoSを有効にしたチャネルからquorum queueを購読すると、チャネルエラーが返ります。RabbitMQは、プロトコル仕様と違ってprefetchを「チャネル上の各コンシューマに別々に適用」するのが既定なので、コンシューマ単位の指定に寄せます。

channel.basic_qos(prefetch_count=50)  # global指定なし = consumerごと
channel.basic_consume(queue="orders.v2", on_message_callback=handle)

Claude Codeには、globalを渡している箇所をgrepで拾わせ、全て外す指示を出します。値の見直しも一緒に頼むとよいでしょう。0(無制限)を指定しても、quorum queueはコンシューマのprefetchを最大2,000に制限します。無制限のつもりで書かれたコードは、実際には2,000で頭打ちになります。

nackの再投入ループを断つ

クラシックキューに慣れたコードは、失敗時にnack(requeue=True)でメッセージを戻す書き方をよくします。quorum queueでは、これがポイズンメッセージの扱いに直結します。

quorum queueは再配信の失敗回数を数え、x-delivery-countヘッダに載せます。RabbitMQ 4.0からは、上限(delivery limit)の既定が20です。上限を超えたメッセージは、DLXがなければ破棄されます。3.13系の「上限なし」を戻すにはx-delivery-limit=-1を宣言時に付けますが、推奨されていません。

つまり、requeueで永遠に再試行していたコードは、移行後に「20回で消える」コードに変わります。全てのquorum queueに何らかのデッドレター設定を持たせることが勧められています。

加えて、prefetchが1より大きいコンシューマで未ackの配信がまとめて再投入されると、まとめて上限に達して破棄される可能性があります。Claude Codeには次のように頼みます。

nack(requeue=True)と、例外時にackしないまま接続が切れる経路を全て列挙して。
失敗時はrequeue=Falseとし、orders.dlxに流す形へ直す案を、
差分とテストの両方で出して。まず計画だけで編集はしないで。

RabbitMQ 4.3からは、nackによる明示的な返却が配信上限のカウントに入らなくなりました。再配信の失敗はx-delivery-count、コンシューマに割り当てられた回数はx-acquired-countで別々に追えます。同じnackでも、4.3より前と後のブローカーでは挙動が違います。手順書にはブローカーのバージョンを書いておくと、切り戻し時に迷いません。

手動ackとpublisher confirmsを揃える

quorum queueは、パブリッシャが確認応答(publisher confirms)を使うことを前提にしています。確認が返るのは、メッセージがquorum、つまり過半数のメンバーに複製されたあとです。自動ackでconsumeしているコードがあれば、手動ackに直す対象です。

切り替え後に見るもの

次の3点を、移行の完了条件として手順書に書いておきます。

  • 旧キューの深さが0のまま保たれている
  • 新キューのコンシューマ数と未ack数が想定どおり
  • DLXに流れたメッセージ数が、移行前の失敗率と整合している

新しく宣言するキューの種類を揃えたいだけなら、仮想ホストの既定のキュー種類(Default Queue Type)をquorumにする手段もあります。クライアントがx-queue-typeを指定せずに宣言すると、この既定が使われます。アプリ側のコードを触らずに新規キューだけをquorumにできますが、既存のキューは変わらず、クライアントがx-queue-typeを明示していればそちらが優先されます。

よくあるつまずき

  • 406 PRECONDITION_FAILEDで落ちる: 旧キューと同名で、種類だけ変えて宣言していないか確認します。新名で宣言し直すか、同値性チェックの緩和を検討します
  • チャネルエラーで購読できない: グローバルQoSの指定が残っています。consumerごとの指定に直します
  • メッセージが「消えた」: delivery limitを超えた再配信で破棄された可能性があります。DLXの有無とx-delivery-countを見ます
  • スループットが下がった: quorum queueは、データ安全性のための合意アルゴリズムを使うため、遅延が本質的に大きくなります。メンバー数が多いほどスループットは下がる傾向があり、初期グループサイズは3が実用上の最小値とされています

キューの状態確認や、DLQの中身の調査をClaudeに任せたい場合は、RabbitMQのMCPサーバーでClaudeからキューとExchangeを操作するの手順が使えます。MCPサーバーの信頼性の見分け方はKafka・RabbitMQ・NATSのMCPは本家不在にあります。

同じように「旧と新を並走させ、検証してから切る」型の移行は、主キーのbigint移行やJenkinsfileの並走検証でも扱っています。

まとめ

宣言の書き換えで済む移行ではなく、新キューを用意して流れを移す移行です。移行対象のキューを先に選び、グローバルQoSとrequeueの癖をgrepで潰してから、1ステップずつコミットを区切って進めます。Claude Codeには、洗い出しを計画モードで、コード修正を差分とテスト込みで任せる分担が合います。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. rabbitmq.com/docs/quorum-queues
  2. rabbitmq.com/docs/consumer-prefetch
  3. rabbitmq.com/docs/confirms
  4. rabbitmq.com/docs/vhosts
  5. rabbitmq.com/docs/shovel
  6. code.claude.com/docs/en/common-workflows
この記事を共有:XはてブLinkedIn