Redisをメッセージブローカーとして使うべき時とは?

Redisの2つの主要機能であるPub/SubとStreamsを活用して、Redisをメッセージブローカーとして利用する理想的なシナリオを発見しましょう。この包括的なガイドでは、Redisメッセージングのパフォーマンス上の利点、低レイテンシ、インフラストラクチャ上のメリットについて詳しく説明します。エフェメラルなPub/Subと永続的なStreamsの重要な違いを理解し、Kafkaのような専用ブローカーと比較した場合の制限を把握し、シンプルなキャッシュ無効化から堅牢で軽量なタスクキューまで、非同期通信のニーズに適したツールを選択するための実用的なユースケースを学びます。

Redisをメッセージブローカーとして使うべき時とは?

Redisは、ジョブが小さく、高速で、すでにRedisに保存しているデータに近い場合に、優れたメッセージブローカーになり得ます。一方、強力な配信保証、長期保存、ルーティング機能、またはメッセージ履歴がメモリよりもはるかに大きい場合でも快適に動作するブローカーが必要な場合には、適さないツールとなる可能性があります。

「Redisはメッセージングができるか?」と問うのをやめ、「許容できる障害モードに合致するRedisのメッセージングプリミティブはどれか?」と問うようになれば、判断は容易になります。

Redisはいくつかのパターンを提供しますが、この質問に関して最も重要なのは次の2つです。

  • ライブブロードキャスト用のPub/Sub。
  • 永続的でログのようなメッセージ処理とコンシューマグループを備えたStreams。

遠くから見ると似ていますが、運用上は似ていません。

メッセージを見逃しても許容できる場合はPub/Subを使用する

Redis Pub/Subはライブブロードキャストです。パブリッシャーがチャンネルに送信し、接続されているサブスクライバーがメッセージを受信します。Redisは切断されたサブスクライバーのためにそのメッセージを保存せず、組み込みの確認応答もありません。

これは一部のジョブに最適です。

SUBSCRIBE cache:invalidations
PUBLISH cache:invalidations 'product:123'

あるアプリケーションインスタンスが不適切なタイミングで再起動したためにその無効化を見逃したとしても、世界が終わるわけではありません。ローカルキャッシュにはTTL、バージョンチェック、または別の回復方法が必要です。Pub/Subは通知パスであり、信頼できる情報源ではありません。

Pub/Subの優れたユースケース:

  • アプリケーションインスタンス間のキャッシュ無効化。
  • クライアントが現在の状態のみを気にするライブUI更新。
  • 「ユーザーが入力中」や「ワーカーハートビートが変更された」などのプレゼンスシグナル。
  • 軽量なデプロイメントまたは設定変更の通知。
  • メッセージの見逃しが許容されるファンアウトイベント。

Pub/Subの不適切なユースケース:

  • 支払い処理。
  • 最終的に送信しなければならないメールジョブ。
  • スキップしてはならない在庫更新。
  • 監査ログ。
  • 切断されたコンシューマが後で追いつく必要があるもの。

Pub/Subは処理が少ないため高速です。それがトレードオフです。

コンシューマが追いつく必要がある場合はStreamsを使用する

Redis Streamsはストリームデータ構造にエントリを保存します。

XADD orders:events * order_id 42 status paid

コンシューマは位置から読み取ることができます。

XREAD COUNT 10 STREAMS orders:events 0

コンシューマグループを使用すると、複数のワーカーが作業を共有できます。

XGROUP CREATE orders:events order-workers 0 MKSTREAM
XREADGROUP GROUP order-workers worker-1 COUNT 10 STREAMS orders:events >
XACK orders:events order-workers 1740000000000-0

コンシューマグループを使用すると、ワーカーに配信されたメッセージは、XACKで確認応答されるまで保留エントリリストに残ります。ワーカーが読み取り後に確認応答する前にダウンした場合、別のワーカーが保留中の作業を検査して要求できます。これにより、コンシューマを正しく構築した場合、少なくとも1回の処理が保証されます。

少なくとも1回の処理は、重複が発生する可能性があることを意味します。ワーカーは冪等である必要があります。たとえば、メールワーカーは、再度送信を試みる前に、email_job_id=abc123が送信されたことを記録する必要があります。注文ワーカーは、同じストリームエントリを2回見た場合に二重請求を避ける必要があります。

Streamsの優れたユースケース:

  • 軽量なバックグラウンドジョブ。
  • 短時間の停止後にリプレイが必要な内部サービスイベント。
  • 小規模から中規模のイベントログ。
  • 各ジョブがグループ内の1つのワーカーによって処理される必要があるワーカープール。
  • バインドされた保持期間を持つアクティビティフィードまたは状態変更ログ。

Streamsは無料ではありません。エントリは、設計によってトリミングまたは期限切れにされない限り、Redisメモリに常駐します。ビジーなストリームをトリミングしないと、そのストリームは次のメモリインシデントになります。

トリミングを使用する:

XADD orders:events MAXLEN ~ 100000 * order_id 42 status paid
XTRIM orders:events MAXLEN ~ 100000

~を使用した近似トリミングは、通常、正確なトリミングよりも安価です。保持期間は、希望ではなく回復のニーズに基づいて選択します。

Redisリストはシンプルなキューに依然として有用

Streamsが登場する前は、多くのRedisキューがリストを使用していました。

LPUSH jobs:email '{"to":"[email protected]"}'
BRPOP jobs:email 5

リストは、特にブロッキングポップ動作が必要で、コンシューマグループや履歴が不要な場合、非常にシンプルなキューに依然として適しています。制限は回復です。ワーカーがジョブをポップして、完了する前にクラッシュした場合、追加のブックキーピングを行わない限り、そのジョブは失われます。

BRPOPLPUSHまたはBLMOVEを使用してジョブを処理リストに移動し、成功後に削除するパターンがあります。これらのパターンは機能しますが、保留中の追跡、再試行、および複数のコンシューマが必要になると、通常はStreamsの方が明確な出発点となります。

シンプルさがブローカーの機能よりも重要な場合はRedisを選択する

Redisメッセージングは、Redisがすでにスタックの一部であり、ワークロードが適度である場合に魅力的です。別の分散システムを運用する必要がありません。開発者はすでにRedisクライアント、監視、認証情報、およびデプロイパスを理解しています。

これは正当な理由です。運用のシンプルさには実際の価値があります。

Redisは非常に低レイテンシでもあります。アプリケーションとRedisが同じリージョンまたはプライベートネットワーク内にある場合、小さな通知を公開するのは通常、安価で迅速です。キャッシュ無効化やライブステータス更新の場合、より重いブローカーは不要かもしれません。

Redisを使用すると、状態変更とメッセージを注意深く組み合わせることもできます。Luaスクリプトまたはトランザクションは、1つのRedis側操作でキーを更新し、ストリームに追加できます。これは、Redisが中央の状態ホルダーである小規模システムに役立ちます。

問題は、Redisが偶発的な万能ブローカーになるべきではないということです。すべてのサービスが保持計画なしに大量のストリームを追加し始めると、「シンプルな」選択が過負荷のインメモリログストアになります。

障害処理が製品である場合は専用ブローカーを選択する

Kafka、RabbitMQ、Pulsar、NATS JetStream、およびクラウドキューサービスは、メッセージングがすぐに複雑になるために存在します。

次のような機能が必要な場合は、専用ブローカーを使用します。

  • 週、月、または年単位の長期保存。
  • メモリよりもはるかに大きいメッセージ履歴。
  • ブローカーに組み込まれたデッドレターキューと再試行ポリシー。
  • 遅延配信、優先順位、ルーティングキー、交換機、またはトピックパーティショニング。
  • メッセージング用に設計されたクロスリージョンレプリケーションパターン。
  • 同じイベント履歴をリプレイする多数の独立したコンシューマグループ。
  • ラグ、オフセット、リバランス、および監査に関するより強力なツール。

Kafkaは通常、高ボリュームのイベントパイプラインとリプレイ可能なログに適しています。RabbitMQは通常、高度なルーティング、確認応答、およびワークキューに適しています。クラウドキューは、管理された耐久性とシンプルな運用境界が必要な場合に適しています。

Redis Streamsは有用な本番ワークロードを処理できますが、それでもRedisです。そのデータはメモリ中心であり、その永続性設定を理解する必要があり、そのブローカー機能は専用システムよりも意図的に小さくなっています。

具体的な判断方法

Redisを選択する前に、次の質問を自問してください。

  1. コンシューマがデータ損失なしにメッセージを見逃すことはできますか?
  2. 切断されたコンシューマは追いつく必要がありますか?
  3. メッセージはどのくらいの期間保持する必要がありますか?
  4. 保持するメッセージデータはRedisメモリに快適に収まりますか?
  5. ワーカーは重複メッセージを安全に処理しますか?
  6. デッドレターキュー、遅延再試行、優先順位、またはルーティングルールが必要ですか?
  7. このトラフィックはRedisのキャッシュまたはセッションに干渉しますか?

メッセージの見逃しが許容される場合、Pub/Subで十分かもしれません。

コンシューマが追いつく必要があり、保持期間が制限されている場合、Streamsで十分かもしれません。

メッセージデータに長期保存、多くのチームによるリプレイ、複雑なブローカー動作、または強力な運用分離が必要な場合は、専用ブローカーを使用します。

例: キャッシュ無効化

アプリケーションは製品ページをローカルプロセスメモリとRedisに保存します。製品が変更されると、管理サービスが公開します。

PUBLISH cache:invalidate product:123

cache:invalidateにサブスクライブしているすべてのWebインスタンスは、ローカルコピーを削除します。1つのWebインスタンスがメッセージを見逃した場合でも、そのローカルエントリには5分のTTLがあり、次のリクエストで製品バージョンフィールドもチェックします。回復パスがあるため、Pub/Subで問題ありません。

ここでKafkaを使用すると、おそらく価値以上の運用負荷が追加されるでしょう。

例: バックグラウンドメールジョブ

ユーザーがサインアップし、ウェルカムメールを送信する必要があります。ワーカーが1分間ダウンした場合でも、ジョブは後で送信する必要があります。Pub/Subは不適切です。

Redis Streamsは機能します。

XADD email:jobs MAXLEN ~ 100000 * job_id abc123 type welcome user_id 42

ワーカーはコンシューマグループを介して読み取り、メールを送信し、job_idを完了として記録し、XACKを呼び出します。モニターは保留中のジョブをチェックし、古いものを再利用します。これは適度な内部キューとしては妥当です。

メール配信が大規模になり、遅延再試行、デッドレター処理、顧客ごとのレート制限、および豊富な運用ダッシュボードが必要になると、専用キューがより適切に見え始めます。

例: 監査イベント

監査イベントには通常、耐久性、検索、保持、および場合によっては法的またはコンプライアンス処理が必要です。Redis Streamsは短いバッファとして役立つかもしれませんが、Redisが最終的な監査ストアになるべきではありません。保持とレビュー用に設計された永続ログ、データベース、オブジェクトストレージパイプライン、または管理イベントサービスを使用します。

Redisを選択した場合の運用上の注意点

Pub/Subの場合:

  • client-output-buffer-limit pubsubを設定します。
  • 専用のサブスクライバー接続を使用します。
  • 再接続と再サブスクライブ動作を構築します。
  • メッセージをヒントとして扱い、永続的な事実として扱わないでください。

Streamsの場合:

  • MAXLENMINID、または明示的なトリミングで保持ポリシーを設定します。
  • 保留中のエントリを監視します。
  • コンシューマを冪等にします。
  • 作業が成功した後にのみXACKを使用します。
  • 停滞したメッセージがどのように要求され、再試行されるかを計画します。
  • メモリ、永続性、およびレプリケーションラグを監視します。

Redisは、ジョブに合ったRedisの部分を選択した場合に、優れたメッセージブローカーになります。Pub/Subはライブシグナルです。Streamsは制限付きの永続ログです。どちらも、Redisがすでに実行されているからといって選択されるべきではありませんが、その障害モデルがアプリケーションに合致する場合、最もシンプルで正しい答えになり得ます。

居心地の悪い中間領域

多くのチームは中間に位置します。Pub/Subは損失が多すぎ、Kafkaは大きすぎると感じ、RabbitMQはもう1つ運用するシステムのように感じられます。Redis Streamsはそこでの良い答えになり得ますが、それを魔法のリストではなく実際のキューとして扱う場合に限ります。

健全なStreams設計には、次の詳細に関する所有権が必要です。

  • 誰がストリームとコンシューマグループを作成するか?
  • 予想されるコンシューマの数は?
  • メッセージが再利用される前の最大保留期間は?
  • 繰り返し失敗した後はどうなるか?
  • どのくらいのストリーム履歴が保持されるか?
  • 増大するラグを示すダッシュボードまたはアラートは?

これらの回答がなければ、Streamsは静かに失敗する可能性があります。ワーカーがメッセージを読み取り、XACKの前にクラッシュし、エントリを永久に保留状態にする可能性があります。別のワーカーがそれらを要求しない可能性があります。トリミングを設定していないため、ストリーム長が増加し続ける可能性があります。Redisメモリは増加しますが、アプリケーションチームは「キューは永続的だ」と考えているため、インスタンスがプレッシャーを受けるまで気づきません。

シンプルなワーカーは通常、次のループを実行する必要があります。

小さなバッチを読み取る
各メッセージを冪等に処理する
成功したメッセージのみを確認応答する
定期的に保留中のメッセージを検査する
古い保留中のメッセージを要求する
保持ポリシーに従ってトリミングする

これはPub/Subよりも多くの作業であり、それがポイントです。耐久性は常にどこかに複雑さを移動させます。Redis Streamsはブローカー側をかなり小さく保ちますが、アプリケーションは依然として再試行、デッドレター動作、および冪等性を所有します。

チームの誰もこれらの詳細を所有したくない場合、管理キューは初日は重く見えても、長期的には安価になる可能性があります。最良のブローカーは、ベンチマークで最速のものではありません。それは、チームが午前3時に推測せずに運用できる障害動作を持つブローカーです。