
メッセージキュー入門 - RabbitMQとKafkaの違いと使い分け
分散システムとログの本質を学べる定番書
Kafkaの設計と運用を日本語で体系的に
非同期連携の設計判断に効く一冊
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
「注文が確定したらメールを送り、在庫を引き当て、ポイントを付与し、分析基盤にも記録する」。こうした処理をすべて1つのリクエストの中で同期的に実行すると、どれか1つの障害が全体を巻き込み、応答も遅くなります。この問題への古典的かつ現役の答えがメッセージキュー(メッセージブローカー)です。この記事では、メッセージキューが何を解決するのかという基礎から、配信保証の3レベル、キュー型(RabbitMQ)とログ型(Apache Kafka)というアーキテクチャの違い、代表プロダクトの使い分け、そして冪等コンシューマやDead Letter Queueといった実践パターンまでを、公式ドキュメントを出典に整理します。
NOTE
本記事のバージョン・仕様は2026年8月時点の各公式ドキュメントで確認したものです。RabbitMQは4.3系(最新4.3.5、2026年8月17日リリース)、Apache Kafkaは4.3系(最新4.3.1、2026年6月25日リリース)を前提にしています。
メッセージキューとは - 何を解決するのか
メッセージキューは、サービス間でデータ(メッセージ)をいったん仲介者(ブローカー)に預けてやり取りする仕組みです。送信側はブローカーに書き込んだ時点で自分の仕事を終え、受信側は自分のペースで取り出して処理します。直接HTTPで呼び合う同期連携と比べて、導入の狙いは大きく3つあります。
1. 非同期処理。時間のかかる処理(メール送信、画像変換、外部API呼び出しなど)をリクエストの応答から切り離せます。ユーザーへの応答は「受け付けました」で即座に返し、実処理は裏で進める形です。処理結果を後からクライアントへ届けたい場合は、WebSocketやSSEのようなリアルタイム通知と組み合わせます。
2. 疎結合。送信側は受信側のURLも台数も生死も知る必要がありません。受信側が一時的に停止していてもメッセージはブローカーに残り、復旧後に処理されます。同期呼び出しであればタイムアウトやリトライ、サーキットブレーカーで守るしかない障害の伝播を、そもそも構造的に断ち切れます。
3. ピーク平準化(バッファリング)。セールやテレビ放映で流量が10倍になっても、キューが緩衝材になり、コンシューマは一定のペースで処理を続けられます。瞬間的なスパイクを「あとで処理する残量」に変換できるのがキューの本質的な価値です。
[同期連携]
注文API --(HTTP)--> メールサービス <- 落ちていたら注文自体が失敗
--(HTTP)--> 在庫サービス <- 遅いと応答全体が遅くなる
[メッセージキューを挟む]
注文API --> [ブローカー: 注文キュー] --> メールワーカー
--> 在庫ワーカー
・注文APIはキューに書けた時点で応答を返せる
・ワーカーが落ちていてもメッセージは残る
・スパイク時はキューが伸び、ワーカーは自分のペースで消化するもちろんタダではありません。ブローカーという運用対象が増え、処理は「結果整合」になり、後述する重複配信や順序の問題と向き合う必要があります。それでも一定規模以上のシステムでほぼ必ず登場するのは、この3つの利点が大きいからです。
基本用語 - Producer / Consumer / Broker / Queue / Topic
プロダクトごとに呼び名の揺れはありますが、共通する登場人物は次のとおりです。
| 用語 | 役割 |
|---|---|
| Producer(プロデューサ) | メッセージを作ってブローカーへ送る側。パブリッシャーとも呼ぶ |
| Consumer(コンシューマ) | ブローカーからメッセージを受け取って処理する側。サブスクライバとも呼ぶ |
| Broker(ブローカー) | メッセージを預かり、ルーティングし、配送するサーバー。RabbitMQやKafka本体 |
| Queue(キュー) | メッセージを貯める入れ物。基本はFIFO(先入れ先出し)で、1つのメッセージは原則1つのコンシューマが処理する |
| Topic(トピック) | 配信の宛先となる論理チャネル。複数の購読者が同じメッセージを受け取るpub/subの単位 |
| Ack(確認応答) | コンシューマが「処理し終えた」ことをブローカーへ伝える応答。配信保証の土台 |
メッセージングのパターンとしては、1つのメッセージを1つの処理者だけが受け取るポイントツーポイント(ワークキュー)と、1つのメッセージを複数の購読者全員に届けるパブリッシュ/サブスクライブ(pub/sub)の2つが基本形です。ワークキューで複数のコンシューマがキューを分担する構成は「競合コンシューマ(competing consumers)」と呼ばれ、コンシューマの台数を増やすだけで処理能力をスケールできます。
配信保証の3レベル
メッセージキューを扱ううえで最も重要な概念が配信保証(delivery guarantee)です。Kafkaの設計ドキュメントは、配信のセマンティクスを次の3つに分類しています。
| レベル | 意味 | 失敗時に起きること |
|---|---|---|
| at-most-once(最大1回) | メッセージは失われる可能性があるが、重複配信はされない | 消失 |
| at-least-once(最低1回) | メッセージは失われないが、重複して配信される可能性がある | 重複 |
| exactly-once(正確に1回) | 各メッセージが正確に1回だけ処理される | 理想だが実現条件が厳しい |
なぜ「最低1回」で重複が起きるのかは、確認応答(Ack)の仕組みを考えると分かります。ブローカーはコンシューマからAckが返るまでメッセージを配送済みとみなしません。コンシューマが処理を終えた直後、Ackを返す直前にクラッシュすると、ブローカーは未処理と判断して同じメッセージを再配送します。結果、処理は2回実行されます。逆に「受け取った瞬間にAckを返してから処理する」方式にすると、処理前のクラッシュでメッセージが消えます。これがat-most-onceです。
各プロダクトの公式ドキュメントもこの整理に沿っています。
- RabbitMQ: 信頼性ガイドに「確認応答を使えばat-least-once配信が保証される。使わなければメッセージ消失があり得るためat-most-onceになる」と明記されています。またブローカーからの確認(publisher confirm)がプロデューサに届く前に接続が切れると再送が起きるため、「メッセージ重複の可能性がある」ことも明記されています
- Kafka: 既定はat-least-onceです。プロデューサのリトライを無効化し、コンシューマが処理前にオフセットをコミットすればat-most-onceになります。exactly-onceは、冪等プロデューサ(重複排除用のシーケンス番号)とトランザクション、
read_committed分離レベルの組み合わせで、Kafkaトピック間の読み取り・処理・書き込みに限って実現できます(Kafka Streamsがこの仕組みの代表的な利用者です) - Amazon SQS: 標準キューはat-least-once配信、FIFOキューは重複排除IDを使った「exactly-once processing」を提供すると公式ドキュメントに記載されています
ここで重要なのは、Kafkaのドキュメント自身が注意しているとおり、「exactly-once」を名乗る仕組みには必ず適用範囲の但し書きがあることです。外部のデータベースやAPIへの書き込みまで含めた完全なexactly-onceは、ブローカー単体では実現できません。だからこそ実務の定石は「at-least-onceで運び、コンシューマ側を冪等にする」です。冪等化の具体策は後半の実践パターンで扱います。
キュー型とログ型 - アーキテクチャの根本的な違い
RabbitMQとKafkaはどちらも「メッセージング基盤」ですが、内部のデータ構造がまったく違います。この違いを押さえると、両者の得意・不得意が芋づる式に理解できます。
キュー型(RabbitMQなど): 読んだら消える
伝統的なメッセージブローカーは、メッセージをキューから取り出してAckされたら削除するモデルです。ブローカーが「どのメッセージが誰に配送済みか」を管理し、ルーティング(RabbitMQならexchangeとbindingによる振り分け)もブローカー側が担います。いわば「賢いブローカーと、シンプルなコンシューマ」の構図です。
[キュー型]
Producer --> [Exchange] --ルーティング--> [Queue: A B C D] --> Consumer 1
--> Consumer 2
・メッセージはAckされたらキューから消える
・ブローカーが配送状態を管理する
・コンシューマを増やすと1つのキューを分担して処理する利点は、メッセージ単位の柔軟な制御です。優先度、メッセージごとのTTL、複雑なルーティング、処理失敗時の個別の再配送などが得意です。弱点は、消費が「破壊的」であるため、同じメッセージを後からもう一度読み直す(リプレイする)ことができない点です。
ログ型(Kafkaなど): 読んでも消えない
Kafkaは、メッセージを追記専用ログ(append-only log)として保存します。メッセージはトピックを分割したパーティションの末尾に追記され、読まれても削除されません。保持期間(retention)を過ぎるまで残り続けます。各コンシューマは「自分がどこまで読んだか」を示すオフセットを自分で管理し、コンシューマグループ単位でパーティションを分担します。
[ログ型]
Producer --> [Topic: パーティション0 | 0 1 2 3 4 5 ...追記]
[ パーティション1 | 0 1 2 3 ...]
^ ^
コンシューマグループAのオフセット グループBのオフセット
・メッセージは読まれても消えず、保持期間まで残る
・複数のグループが同じデータを独立に読める
・オフセットを巻き戻せば過去データを再処理できるこのモデルの利点は3つあります。第一にリプレイ。バグ修正後にオフセットを巻き戻して再処理したり、新しい分析システムに過去データを最初から流し込んだりできます。第二に大規模なファンアウト。同じデータを複数のチーム・システムがそれぞれのペースで消費できます。第三にスループット。追記専用のシーケンシャルI/OはOSのページキャッシュと相性がよく、Kafkaの設計ドキュメントはこの点を性能の柱として詳しく説明しています。
一方で、メッセージ単位の削除や優先度制御はできず、並列度はパーティション数に縛られます。「1件ずつ確実に処理して消し込む」ジョブキュー的な使い方は、本来のログ型の得意分野ではありません。
両者は歩み寄っている
興味深いのは、近年両陣営が互いの領域に機能を伸ばしていることです。RabbitMQは3.9でStreamsという追記専用ログのデータ構造を導入し、非破壊読み取りとリプレイをサポートしました。逆にKafkaは、KIP-932「Queues for Kafka」でshare groupという協調消費の仕組みを導入し(4.0でearly access、4.2.0でproduction-ready)、パーティション数を超えるコンシューマでのキュー的な分担消費ができるようになりました。それでも設計の重心(キュー型はメッセージ単位の配送管理、ログ型はストリームの共有と再読)は変わっておらず、選定の軸として依然有効です。
代表プロダクト比較
2026年8月時点の各公式情報に基づいて、代表的な4つを比較します。
| 項目 | RabbitMQ | Apache Kafka | Redis Streams | Amazon SQS |
|---|---|---|---|---|
| 種別 | キュー型ブローカー(Streamsも追加) | 分散ログ(イベントストリーミング基盤) | Redisのデータ型(追記専用ログ) | フルマネージドキュー |
| 最新版 | 4.3系(4.3.5) | 4.3系(4.3.1) | Redisに内蔵 | マネージドのため版なし |
| プロトコル | AMQP 0-9-1、AMQP 1.0(4.0からコア対応)、MQTT/STOMPほか | Kafka独自プロトコル | RESP(Redisコマンド) | HTTPS API |
| 配信保証 | Ack使用でat-least-once | 既定at-least-once。トランザクションでKafka内exactly-once | XACKによるat-least-once相当 | 標準: at-least-once / FIFO: exactly-once processing |
| リプレイ | 通常のキューは不可(Streamsは可) | 可(オフセット巻き戻し) | 可(IDで範囲取得) | 不可 |
| 順序 | 単一キュー内はFIFOが基本 | パーティション内で保証 | ID順 | 標準はベストエフォート / FIFOはグループ内保証 |
| 得意分野 | タスクキュー、複雑なルーティング、多プロトコル | 大量イベント、ストリーム処理、複数システムへの配信 | 軽量な非同期処理、既にRedisがある構成 | 運用レスのジョブキュー、AWS内の連携 |
RabbitMQは、Erlang/OTP製の老舗ブローカーです。プロトコルの多様さが特徴で、AMQP 0-9-1に加えて4.0からはAMQP 1.0(ISO/IEC 19464、OASIS標準)がコアプロトコルになり、プラグインでMQTT 3.1/3.1.1/5.0やSTOMPも話せます。exchangeの種類(direct/topic/fanout/headers)を組み合わせた柔軟なルーティングが強みです。
Apache Kafkaは、LinkedIn発の分散イベントストリーミング基盤です。大きな節目が2025年3月の4.0で、公式アナウンスに「Apache ZooKeeperなしで完全に動作する最初のメジャーリリース」と明記されました。メタデータ管理をKafka自身のRaft実装に一本化したことで、別途ZooKeeperクラスタを運用する負担がなくなっています。
Redis Streamsは、独立したブローカーではなくRedisのデータ型です。公式ドキュメントは「追記専用ログのように振る舞い、その典型的な制限を克服する操作を持つデータ構造」と説明しています。XADDで追記し、XREADGROUPでコンシューマグループとして読み、XACKで確認応答する、というブローカーの基本要素が一通り揃っており、未Ackメッセージの一覧(PEL)をXPENDINGで確認し、落ちたコンシューマの分をXAUTOCLAIMで引き継げます。すでにRedisがあるなら追加コストほぼゼロで使える手軽さが魅力ですが、永続化はRedis本体のRDB/AOF設定に依存する点は理解しておく必要があります。
Amazon SQSは、サーバー管理が一切不要なマネージドキューです。標準キューはほぼ無制限のスループットを持つ代わりに、配信はat-least-once、順序はベストエフォートです。FIFOキューはメッセージグループ内の順序保証と、重複排除IDによるexactly-once processingを提供する代わりに、スループット上限があります(バッチ使用で秒間3,000メッセージ、高スループットモードで引き上げ可能、と公式ドキュメントに記載)。
使い分けの指針
「どれが一番良いか」ではなく「自分のワークロードがどれか」で選びます。
- ジョブキュー・タスクの非同期化が主目的: RabbitMQ、またはAWS上ならSQS。1件ずつ確実に処理して消し込むワークロードはキュー型の得意分野です
- イベントを複数のシステムに配りたい、後から再処理したい: Kafka。ログ型のリプレイとファンアウトがそのまま効きます。マイクロサービス間のイベント駆動連携やデータ基盤への取り込みが典型です
- 秒間数十万件級のイベント流量: Kafka。パーティションによる水平分割とシーケンシャルI/Oは大流量のために設計されています
- 複雑なルーティングや多様なプロトコルが必要: RabbitMQ。topic exchangeによるパターン振り分けや、MQTT/STOMP混在環境に強みがあります
- とにかく運用したくない: SQS(あるいは各クラウドの同等サービス)。ブローカーのクラスタ運用・パッチ適用から解放されます
- 小規模で、すでにRedisがある: Redis Streamsで始めるのは合理的です。要件が育ったら専用ブローカーへの移行を検討します
なお、Webhookの受信処理をいったんキューに積んでから処理する構成は、Webhook設計のベストプラクティスで扱ったリトライ・重複対策とも直結します。外部からの不定期な流入を受け止める場面は、メッセージキューが最も輝く場面の1つです。
実践パターン - 冪等コンシューマ・DLQ・順序保証
冪等コンシューマ
前述のとおり実務の基本線はat-least-once配信です。つまり同じメッセージが2回届く前提でコンシューマを書く必要があります。定石は次の2つです。
- 処理自体を冪等にする。「在庫を1減らす」ではなく「注文ID 123の在庫引当を確定させる」のように、同じ入力なら何度実行しても結果が変わらない操作に設計し直します
- 処理済みIDを記録する。メッセージに一意のIDを持たせ、処理前に「このIDは処理済みか」をデータベースで確認します。処理結果の保存と処理済みIDの記録を同一トランザクションで行うのがポイントです
この考え方は、REST APIの冪等性設計で扱ったIdempotency-Keyとまったく同じ構造です。また「データベース更新とメッセージ送信を確実に両立させたい」場合のOutboxパターンについては、分散トランザクション(2PC/Saga)の記事で解説しています。
コードの雰囲気を掴むために、Node.jsでの最小例を示します。まずRabbitMQ(公式推奨クライアントの1つであるamqplib)です。
const amqp = require('amqplib');
// プロデューサ
const conn = await amqp.connect('amqp://localhost');
const ch = await conn.createChannel();
await ch.assertQueue('task_queue', { durable: true });
ch.sendToQueue(
'task_queue',
Buffer.from(JSON.stringify({ orderId: 'order-123' })),
{ persistent: true } // ブローカー再起動でも失われないよう永続化
);
// コンシューマ
await ch.prefetch(1); // 未Ackのまま先読みする数を制限
await ch.consume('task_queue', async (msg) => {
const task = JSON.parse(msg.content.toString());
await processOrder(task.orderId); // 冪等に実装しておく
ch.ack(msg); // 処理完了後にAck。ここまでにクラッシュすると再配送される
});次にKafka(kafkajs)です。コンシューマグループを指定するだけで、同じgroupIdのプロセス間でパーティションが自動的に分担されます。
const { Kafka } = require('kafkajs');
const kafka = new Kafka({ clientId: 'order-app', brokers: ['localhost:9092'] });
// プロデューサ: 同じkeyのメッセージは同じパーティションに入る
const producer = kafka.producer();
await producer.connect();
await producer.send({
topic: 'orders',
messages: [{ key: 'order-123', value: JSON.stringify({ status: 'created' }) }],
});
// コンシューマ: groupId単位でパーティションを分担
const consumer = kafka.consumer({ groupId: 'order-workers' });
await consumer.connect();
await consumer.subscribe({ topic: 'orders' });
await consumer.run({
eachMessage: async ({ partition, message }) => {
await handleOrderEvent(JSON.parse(message.value.toString()));
},
});Dead Letter Queue(DLQ)
処理に失敗し続けるメッセージ(不正なフォーマット、存在しないIDへの参照など)を無限に再試行すると、キューが詰まり後続がすべて止まります。こうした「毒メッセージ(poison message)」の隔離先がDead Letter Queueです。
- RabbitMQでは、Dead Letter Exchange(DLX)を設定します。公式ドキュメントによれば、コンシューマが
requeue=falseで拒否した、メッセージTTLが切れた、キュー長の上限を超えた、quorum queueの配送回数上限(delivery-limit)を超えた、という4つの契機でメッセージがDLXへ送られます。設定はポリシーでdead-letter-exchangeを指定する方法が推奨されています - SQSでは、redrive policyの
maxReceiveCountで「何回受信されたらDLQへ移すか」を指定します。公式ドキュメントは、十分なリトライを許容できる程度に大きい値を設定するよう推奨しています - Kafkaにはブローカー組み込みのDLQ機構はなく、処理失敗時に別トピック(例:
orders.DLQ)へ書き込む形をアプリケーションやフレームワーク側で実装するのが通例です
DLQは作って終わりではありません。DLQにメッセージが入ったら通知する監視と、原因修正後にメッセージを元のキューへ戻す運用(SQSにはDLQ redrive機能があります)までセットで設計します。
順序保証
「全メッセージの完全な順序」を求めると、処理を1本に直列化するしかなくなり、スケールしません。実務では順序が必要な範囲を絞るのが定石です。
- Kafkaでは、順序はパーティション内でのみ保証されます。「同じ注文IDのイベントは同じパーティションへ」とキーを設計すれば、注文単位の順序は保ちながら全体は並列処理できます
- SQS FIFOでは、メッセージグループIDが同じ役割を果たします。グループ内は順序保証、グループ間は並列です
- RabbitMQでも、単一キュー・単一コンシューマなら順序は保たれますが、競合コンシューマを増やした時点で全体の順序保証はなくなります
WARNING
at-least-once配信では「再配送によって順序が乱れる」ケースもあります。順序に依存するロジックを書く前に、「本当に順序が必要か」「必要なのはどの単位(ユーザー単位、注文単位)か」を疑うのが健全です。イベントにバージョン番号やタイムスタンプを持たせ、古い更新を無視できるようにしておくと、順序への依存自体を減らせます。
まとめ
- メッセージキューの価値は非同期処理・疎結合・ピーク平準化の3つ。同期連携の障害伝播とスパイクを構造的に断ち切れる
- 配信保証はat-most-once / at-least-once / exactly-onceの3レベル。exactly-onceには必ず適用範囲の但し書きがあり、実務の定石は「at-least-once + 冪等コンシューマ」
- キュー型(RabbitMQ)は読んだら消えるモデルで、メッセージ単位の制御と柔軟なルーティングが得意。ログ型(Kafka)は読んでも消えない追記専用ログで、リプレイ・大規模ファンアウト・大流量が得意
- RabbitMQはStreams(3.9で導入)でログ型へ、Kafkaはshare group(KIP-932、4.2.0でproduction-ready)でキュー型へ歩み寄っているが、設計の重心は変わらない
- Kafka 4.0からはZooKeeperが廃止されKRaftモードのみに。RabbitMQ 4.0からはAMQP 1.0がコアプロトコルになった
- 迷ったら「ジョブキューならキュー型かSQS、イベント配信・再処理ならKafka、既にRedisがあって小規模ならRedis Streams」から検討する
- 運用では冪等コンシューマ・DLQと監視・順序が必要な範囲の限定の3点セットを最初から設計に組み込む
メッセージキューは「入れて出すだけ」の単純な道具に見えて、配信保証と障害時の挙動を理解しているかどうかで結果が大きく変わる領域です。まずは小さなワークキューから導入し、重複と失敗を前提にしたコンシューマを書く習慣をつけるところから始めるのがおすすめです。
参考リンク
- RabbitMQ: Which protocols does RabbitMQ support?
- RabbitMQ: Reliability Guide
- RabbitMQ: Streams
- RabbitMQ: Dead Letter Exchanges
- RabbitMQ: Release Information
- Apache Kafka 4.0.0 Release Announcement
- Apache Kafka 4.2.0 Release Announcement
- Apache Kafka: Design(Message Delivery Semanticsを含む)
- Apache Kafka: Downloads
- Redis: Streams
- Amazon SQS: Queue types
- Amazon SQS: Using dead-letter queues
- amqplib(Node.js AMQP 0-9-1クライアント)
- KafkaJS: Getting Started
- WebSocket・SSE・ポーリングの使い分け(当ブログ)
- Webhook設計のベストプラクティス(当ブログ)
- REST APIの冪等性設計(当ブログ)
- 分散トランザクション入門 2PCとSaga(当ブログ)
- タイムアウト・リトライ・サーキットブレーカー(当ブログ)