Redis Pub/Sub入門:リアルタイム通信の基本を解説

Redis Pub/Sub入門:リアルタイム通信の基本と応用を徹底解説

現代のWebアプリケーションやサービスにおいて、ユーザー体験を向上させる上で「リアルタイム性」は欠かせない要素となっています。チャットアプリのメッセージ受信、SNSのフィード更新、株価のリアルタイム表示、システムアラートの即時通知など、様々な場面でサーバーからクライアントへ、あるいは異なるサービス間で非同期かつ即座に情報を伝達する仕組みが求められています。

リアルタイム通信を実現する技術はいくつか存在しますが、シンプルで高速、そして軽量なメッセージングシステムとして注目されているのが、インメモリデータ構造ストアであるRedisが提供するPub/Sub機能です。

本記事では、Redis Pub/Sub(Publish/Subscribe)機能に焦点を当て、その基本的な概念から、コマンドの使い方、内部動作、利点と欠点、代表的なユースケース、さらには他のリアルタイム通信技術との比較や実装上の注意点まで、詳細かつ網羅的に解説します。本記事を読むことで、Redis Pub/Subがどのようなものであり、どのように活用できるのか、そしてどのような場合に適しているのかを深く理解できるでしょう。

1. はじめに:リアルタイム通信の重要性とRedis Pub/Subの概要

1.1. なぜリアルタイム通信が重要なのか?

インターネット黎明期のWebは、クライアント(ブラウザ)からのリクエストに対してサーバーが応答を返すという、いわゆる「プル型」の通信が主流でした。クライアントは新しい情報を得るために、定期的にサーバーに問い合わせを行う(ポーリング)必要があり、情報の鮮度やユーザー体験に限界がありました。

しかし、現代のWebアプリケーションでは、サーバー側で発生したイベントを即座にクライアントに通知したい、あるいは異なるアプリケーションコンポーネント間で非同期に連携したいというニーズが高まっています。これにより、以下のようなメリットが生まれます。

  • ユーザー体験の向上: メッセージの即時受信、通知のプッシュ配信などにより、ユーザーは常に最新の情報にアクセスでき、インタラクティブな体験が得られます。
  • システムの効率化: ポーリングのような無駄なリクエストを削減し、サーバーやネットワークのリソースを効率的に利用できます。
  • 疎結合なシステム設計: 各コンポーネントが直接通信するのではなく、メッセージブローカーを介して非同期に連携することで、システム全体の柔軟性や保守性が向上します。

1.2. Redis Pub/Subとは何か?

Redis Pub/Subは、このようなリアルタイム通信や非同期メッセージングを実現するための一つの手段です。Pub/Subは「Publish/Subscribe」の略であり、「発行/購読」モデルに基づいています。

このモデルでは、メッセージを送信する側を「発行者(Publisher)」、メッセージを受信する側を「購読者(Subscriber)」と呼びます。発行者は特定の「チャンネル(Channel)」に対してメッセージを発行し、そのチャンネルを「購読」しているすべての購読者がそのメッセージを受信します。発行者と購読者は直接相手を知る必要がなく、メッセージブローカーであるRedisを介して間接的に通信します。

Redis Pub/Subは、Redisがインメモリで動作するという特性を活かし、非常に高速なメッセージ配信を可能にします。また、Redisの他の機能と同様に、セットアップが容易で使い方もシンプルです。

1.3. Redis Pub/Subの主なメリット

  • シンプルさ: Pub/Subモデル自体がシンプルであり、Redisのコマンドも直感的です。
  • 高速性: メッセージはメモリ上で処理されるため、非常に低遅延で配信されます。
  • 軽量性: Redis Pub/Sub自体に複雑な機能(メッセージの永続化、確認応答、キューイングなど)はありません。これにより、オーバーヘッドが少なく高速な処理が可能です。
  • ファンアウト: 一つのメッセージを、そのチャンネルを購読している複数の購読者すべてに同時に配信できます(一対多通信)。
  • 疎結合: 発行者と購読者は直接通信しないため、互いの存在を知る必要がありません。

これらのメリットにより、Redis Pub/Subは特定のユースケースにおいて非常に強力なツールとなります。ただし、後述する欠点も理解した上で、適切に利用することが重要です。

2. Pub/Subの基本概念

Redis Pub/Subを理解するために、まずはPublish/Subscribeモデルにおける基本的な概念を整理しましょう。

2.1. Channel (チャネル)

チャンネルは、メッセージが流れる名前付きの「経路」のようなものです。発行者は特定のチャンネルにメッセージを発行し、購読者は特定のチャンネルを購読します。メッセージは、発行されたチャンネルと同じチャンネルを購読している購読者にのみ配信されます。

例えば、「news」「chat:room:1」「system:alert」といった文字列がチャンネル名として使われます。チャンネル名はRedisのキー空間とは独立しており、特定のデータ構造に関連付けられるものではありません。単にメッセージを分類・ルーティングするための識別子として機能します。

2.2. Publisher (発行者)

発行者は、メッセージを作成し、それを特定のチャンネルに送信するエンティティです。発行者はメッセージを送信するだけであり、誰がそのメッセージを受け取るのか(誰がそのチャンネルを購読しているのか)を知る必要はありません。Redisに対して PUBLISH コマンドを実行することでメッセージを発行します。

2.3. Subscriber (購読者)

購読者は、特定のチャンネルからメッセージを受信するエンティティです。購読者は一つまたは複数のチャンネルを「購読」することで、そのチャンネルに発行されたメッセージを受け取れるようになります。Redisに対して SUBSCRIBE コマンドや PSUBSCRIBE コマンドを実行することで購読を開始します。

購読中のクライアントは、メッセージを受信する専用のモードに入ります。この状態では、購読や購読解除以外の一般的なRedisコマンド(GET, SET など)を実行することはできません。

2.4. Message (メッセージ)

メッセージは、発行者から購読者へ伝えたい実際の情報です。Redis Pub/Subでは、メッセージは単純なバイト列として扱われます。通常は文字列ですが、バイナリデータでも構いません。メッセージの内容はRedisによって解釈されることはなく、単に購読者にそのまま転送されます。メッセージのフォーマット(例:JSON、プレーンテキスト)は、発行者と購読者の間で合意しておく必要があります。

2.5. Pub/Subの連携イメージ

これらの要素は以下のように連携します。

  1. 購読者は、特定のチャンネル(例: channel:A)をRedisに SUBSCRIBE します。
  2. 別の購読者は、別のチャンネル(例: channel:B)をRedisに SUBSCRIBE します。
  3. 発行者は、channel:A に対してメッセージを発行します (PUBLISH channel:A "hello")。
  4. Redisは channel:A を購読しているすべての購読者に対して、そのメッセージを配信します。
  5. channel:B を購読している購読者は、このメッセージを受け取りません。
  6. 発行者は、channel:B に対してメッセージを発行することもできます (PUBLISH channel:B "world")。
  7. Redisは channel:B を購読しているすべての購読者に対して、そのメッセージを配信します。
  8. channel:A を購読している購読者は、このメッセージを受け取りません。

このように、チャンネルを介することで、発行者と購読者は完全に分離され、柔軟なメッセージ配信が可能になります。

3. Redis Pub/Subのコマンド

Redis Pub/Sub機能を利用するためには、以下の主要なコマンドを使用します。

3.1. PUBLISH command

PUBLISH コマンドは、特定のチャンネルにメッセージを発行するために使用します。

構文:

PUBLISH channel message

  • channel: メッセージを発行したいチャンネル名(文字列)。
  • message: 発行したいメッセージの内容(文字列またはバイト列)。

戻り値:

メッセージが実際に配信された購読者の数(整数)を返します。これは、コマンドが実行された時点で購入読していたクライアントの数を示します。購読者が一人もいなかった場合は0を返します。

実行例:

Redis CLIを開き、一つのターミナルを 발행자(Publisher)として使用します。

“`bash

ターミナル1 (Publisher)

redis-cli
PUBLISH notifications “システムに重要な更新があります。”
(integer) 1 # 例: notificationsチャンネルを購読しているクライアントが1つあった場合
PUBLISH news “新しい記事が公開されました。”
(integer) 0 # 例: newsチャンネルを購読しているクライアントが誰もいなかった場合
“`

3.2. SUBSCRIBE command

SUBSCRIBE コマンドは、一つまたは複数の特定のチャンネルを購読するために使用します。このコマンドを実行したクライアントは、購読モードに入り、指定したチャンネルに発行されたメッセージを受信できるようになります。

構文:

SUBSCRIBE channel [channel ...]

  • channel: 購読したいチャンネル名(一つ以上指定可能)。

戻り値:

SUBSCRIBE コマンドは、一般的なRedisコマンドとは異なり、即座に単一の値を返すのではなく、継続的にメッセージを「プッシュ」してきます。
コマンド実行の応答として、Redisは以下の形式の応答を連続して送信します。

  • 購読が成功したことを示すメッセージ: (メッセージの種類 "subscribe") (購読したチャンネル名) (現在購読中のチャンネル数)
  • 購読したチャンネルにメッセージが発行された場合のメッセージ: (メッセージの種類 "message") (メッセージが発行されたチャンネル名) (メッセージの内容)

購読モードに入ったクライアントは、UNSUBSCRIBE コマンドを実行するか、接続を切断するまでこの状態を維持します。

実行例:

別のターミナルを開き、購読者(Subscriber)として使用します。

“`bash

ターミナル2 (Subscriber)

redis-cli
SUBSCRIBE notifications news
“`

上記コマンドを実行すると、以下のような応答が返ってきます。

Reading messages... (press Ctrl+C to quit)
1) "subscribe" # メッセージの種類
2) "notifications" # 購読したチャンネル名
3) (integer) 1 # 現在購読中のチャンネル数 (notifications)
1) "subscribe" # メッセージの種類
2) "news" # 購読したチャンネル名
3) (integer) 2 # 現在購読中のチャンネル数 (notifications, news)

この状態で、ターミナル1から PUBLISH notifications "重要な通知" を実行すると、ターミナル2に以下のようなメッセージがプッシュされます。

1) "message" # メッセージの種類
2) "notifications" # メッセージが発行されたチャンネル名
3) "重要な通知" # メッセージの内容

同様に、ターミナル1から PUBLISH news "最新情報" を実行すると、ターミナル2に以下がプッシュされます。

1) "message" # メッセージの種類
2) "news" # メッセージが発行されたチャンネル名
3) "最新情報" # メッセージの内容

3.3. UNSUBSCRIBE command

UNSUBSCRIBE コマンドは、現在購読している特定のチャンネル、またはすべてのチャンネルの購読を解除するために使用します。

構文:

UNSUBSCRIBE [channel [channel ...]]

  • channel: 購読を解除したいチャンネル名。省略した場合、現在購読しているすべてのチャンネルの購読を解除します。

戻り値:

SUBSCRIBE と同様に、購読解除が成功したことを示すメッセージを継続的に送信します。

  • 購読解除が成功したことを示すメッセージ: (メッセージの種類 "unsubscribe") (購読解除したチャンネル名) (現在購読中のチャンネル数)

現在購読中のチャンネル数が0になると、クライアントは購読モードを終了し、再び一般的なRedisコマンドを実行できるようになります。

実行例:

ターミナル2 (Subscriber) で、引き続き購読中の状態から、特定のチャンネルの購読を解除します。

“`bash

ターミナル2 (Subscriber)

UNSUBSCRIBE notifications
“`

応答例:

1) "unsubscribe" # メッセージの種類
2) "notifications" # 購読解除したチャンネル名
3) (integer) 1 # 現在購読中のチャンネル数 (newsのみ残っている)

さらに、残りのチャンネルも購読解除します。

“`bash

ターミナル2 (Subscriber)

UNSUBSCRIBE news
“`

応答例:

1) "unsubscribe" # メッセージの種類
2) "news" # 購読解除したチャンネル名
3) (integer) 0 # 現在購読中のチャンネル数 (すべて解除)

現在購読中のチャンネル数が0になったため、ターミナル2は購読モードを終了し、SET mykey "myvalue" のような他のコマンドを実行できるようになります。

3.4. PSUBSCRIBE command (パターンマッチング購読)

PSUBSCRIBE コマンドは、一つまたは複数のパターンを指定してチャンネルを購読するために使用します。パターンにマッチする名前を持つチャンネルにメッセージが発行されると、そのメッセージが購読者に配信されます。これは、関連する複数のチャンネルをまとめて購読したい場合に非常に便利です。

パターンでは、以下のワイルドカードが使用できます。

  • *: 0個以上の任意の文字にマッチします。
  • ?: 1個の任意の文字にマッチします。

構文:

PSUBSCRIBE pattern [pattern ...]

  • pattern: 購読したいチャンネル名のパターン(一つ以上指定可能)。

戻り値:

SUBSCRIBE と同様に、継続的にメッセージをプッシュします。

  • 購読が成功したことを示すメッセージ: (メッセージの種類 "psubscribe") (購読したパターン) (現在購読中のパターン数)
  • パターン購読にマッチするチャンネルにメッセージが発行された場合のメッセージ: (メッセージの種類 "pmessage") (マッチしたパターン) (メッセージが発行されたチャンネル名) (メッセージの内容)

SUBSCRIBE とは異なり、パターン購読でメッセージを受け取った際のメッセージの種類は "pmessage" となります。また、メッセージが発行された「実際の」チャンネル名も含まれます。

実行例:

別のターミナルを開き、パターン購読者として使用します。

“`bash

ターミナル3 (Pattern Subscriber)

redis-cli
PSUBSCRIBE chat: system:
“`

応答例:

Reading messages... (press Ctrl+C to quit)
1) "psubscribe" # メッセージの種類
2) "chat:*" # 購読したパターン
3) (integer) 1 # 現在購読中のパターン数
1) "psubscribe" # メッセージの種類
2) "system:*" # 購読したパターン
3) (integer) 2 # 現在購読中のパターン数

この状態で、ターミナル1 (Publisher) から以下のコマンドを実行します。

“`bash

ターミナル1 (Publisher)

PUBLISH chat:general “皆さんこんにちは!”
PUBLISH chat:random “何か面白いことありますか?”
PUBLISH system:error “ディスク容量が不足しています。”
PUBLISH updates “新しいバージョンが利用可能です。” # これはどのパターンにもマッチしない
“`

ターミナル3 (Pattern Subscriber) には、以下のようなメッセージがプッシュされます。

“`
1) “pmessage” # メッセージの種類
2) “chat:*” # マッチしたパターン
3) “chat:general” # メッセージが発行されたチャンネル名
4) “皆さんこんにちは!” # メッセージの内容

1) “pmessage” # メッセージの種類
2) “chat:*” # マッチしたパターン
3) “chat:random” # メッセージが発行されたチャンネル名
4) “何か面白いことありますか?” # メッセージの内容

1) “pmessage” # メッセージの種類
2) “system:*” # マッチしたパターン
3) “system:error” # メッセージが発行されたチャンネル名
4) “ディスク容量が不足しています。” # メッセージの内容
“`

updates チャンネルへのPUBLISHは、購読しているパターン (chat:*, system:*) のいずれにもマッチしないため、ターミナル3には配信されません。

また、一つのクライアントが通常の購読 (SUBSCRIBE) とパターン購読 (PSUBSCRIBE) の両方を同時に行うことも可能です。その場合、同じメッセージが両方の購読方法で重複して配信される可能性があります。例えば、chat:generalSUBSCRIBE しつつ、chat:*PSUBSCRIBE しているクライアントは、PUBLISH chat:general "メッセージ" が発行された際に、message タイプと pmessage タイプのメッセージをそれぞれ一つずつ受信します。

3.5. PUNSUBSCRIBE command

PUNSUBSCRIBE コマンドは、現在購読している特定のパターン、またはすべてのパターンの購読を解除するために使用します。

構文:

PUNSUBSCRIBE [pattern [pattern ...]]

  • pattern: 購読を解除したいパターン。省略した場合、現在購読しているすべてのパターンの購読を解除します。

戻り値:

PSUBSCRIBE と同様に、購読解除が成功したことを示すメッセージを継続的に送信します。

  • 購読解除が成功したことを示すメッセージ: (メッセージの種類 "punsubscribe") (購読解除したパターン) (現在購読中のパターン数)

パターン購読と通常の購読のどちらも行っていない状態(購読パターン数と購読チャンネル数の両方が0)になった場合に、クライアントは購読モードを終了します。

3.6. クライアントライブラリでのPub/Subの利用

実際のアプリケーション開発では、Redis CLIではなく、各プログラミング言語のRedisクライアントライブラリを使用します。ほとんどのライブラリは、これらのPub/Subコマンドを簡単に呼び出すためのAPIを提供しています。

例えば、Pythonの redis-py ライブラリでは、以下のようにPub/Sub機能を利用します。

“`python
import redis
import time

Redisに接続

r = redis.Redis(decode_responses=True) # decode_responses=Trueで文字列として扱う

購読者 (別のプロセス/スレッドで実行)

def subscriber():
p = r.pubsub() # Pub/Subオブジェクトを作成
p.subscribe(‘news’, ‘alerts’) # チャンネルを購読
print(“購読を開始しました…”)

# メッセージの受信ループ
for message in p.listen():
    print(f"受信: {message}")
    if message['type'] == 'message':
        print(f"  チャンネル: {message['channel']}, メッセージ: {message['data']}")
    elif message['type'] == 'subscribe':
         print(f"  購読成功: {message['channel']}, 現在購読中: {message['data']} チャンネル")
    # ここで特定のメッセージ(例: 'QUIT')を受け取ったらループを抜ける処理を追加することも可能
    if message['type'] == 'message' and message['data'] == 'QUIT':
         print("購読を終了します。")
         break

発行者

def publisher():
time.sleep(1) # 購読者が準備できるまで少し待つ
print(“メッセージを発行します…”)
r.publish(‘news’, ‘新しい製品が発表されました!’)
time.sleep(0.1)
r.publish(‘alerts’, ‘システム負荷が高いです。’)
time.sleep(0.1)
r.publish(‘news’, ‘セールが始まりました!’)
time.sleep(0.1)
r.publish(‘alerts’, ‘QUIT’) # 購読を終了させるためのメッセージ例

別スレッドで購読者を起動し、メインスレッドで発行者を実行

import threading
sub_thread = threading.Thread(target=subscriber)
sub_thread.start()

publisher()

購読スレッドが終了するのを待つ (QUITメッセージ受信後など)

sub_thread.join()
print(“Pub/Subデモが終了しました。”)

“`

多くのライブラリでは、Pub/Sub用のオブジェクト(上記の p = r.pubsub() のようなもの)を作成し、そのオブジェクトを使って購読やメッセージ受信を行います。メッセージ受信はブロッキング操作になる場合が多い(上記の p.listen())ため、通常は別のスレッドやプロセス、あるいは非同期I/O(asyncioなど)と組み合わせて使用します。

4. Redis Pub/Subの内部動作

RedisがPub/Subメッセージをどのように処理しているのか、その内部的な仕組みを理解することで、Pub/Subの特性(特にメッセージ永続性がない点)がより明確になります。

4.1. メッセージの転送メカニズム

Redisサーバーは、Pub/Sub機能のために特別なデータ構造や永続化機構を持ちません。メッセージは、サーバーのメモリ上で一時的に扱われ、購読しているクライアントに即座に転送されることを目的としています。

  1. PUBLISH channel message コマンドの受信: Redisサーバーがクライアントから PUBLISH コマンドを受け取ります。
  2. 購読者リストの検索: サーバーは、指定された channel を現在購読しているすべてのクライアントのリストを検索します。また、指定されたチャンネル名にマッチするパターン(*, ? を含む)を PSUBSCRIBE しているクライアントのリストも検索します。
  3. メッセージのキューイング: 該当するすべての購読クライアントに対して、受信した message をそれぞれのクライアントの出力バッファにキューイングします。
  4. メッセージの送信: Redisのイベントループは、出力バッファにデータがあるクライアントに対して、ノンブロッキングI/Oを使用してメッセージを送信します。メッセージはTCP接続を介してクライアントにプッシュされます。
  5. メッセージの破棄: メッセージはクライアントの出力バッファにキューイングされた後、またはクライアントへの送信が完了した時点で、サーバーのメモリから削除されます。Redisは発行されたメッセージの履歴を保持しません。

4.2. チャンネルと購読者の管理

Redisサーバーは、購読されているチャンネルやパターン、そしてそれらを購読しているクライアントのリストを内部的に管理しています。おそらく、チャンネル名をキー、そのチャンネルを購読しているクライアントのセットを値とするようなデータ構造(ハッシュマップなど)を使用していると考えられます。パターン購読についても同様に、購読されているパターンと、それにマッチするチャンネルを受信すべきクライアントのリストを管理しています。

クライアントが SUBSCRIBEPSUBSCRIBE コマンドを実行すると、そのクライアントの接続情報は該当するチャンネルまたはパターンのリストに追加されます。UNSUBSCRIBEPUNSUBSCRIBE、あるいはクライアントの接続が切断されると、その情報がリストから削除されます。

4.3. メッセージの永続性について

Redis Pub/Subの最も重要な特性の一つは、メッセージに永続性がないことです。

これは、PUBLISH コマンドが実行されたときに、そのチャンネルを「現在」購読しているクライアントにのみメッセージが配信されることを意味します。

  • メッセージが発行された時点で購読していなかったクライアントは、後からそのチャンネルを購読を開始しても、発行済みの過去のメッセージを受け取ることはできません。
  • メッセージが発行された時点で購読していたクライアントであっても、ネットワークの遅延やクライアント側の処理遅延、あるいはクライアントの出力バッファの溢れなどにより、メッセージを受信できなかったり、見落としたりする可能性があります。Redisサーバーは、一度出力バッファにキューイングしたメッセージを、クライアントが本当に受信したかどうかの確認(ACK)を行いません。
  • クライアントの接続が切断された場合、そのクライアントが再接続して購読を再開するまでの間に発行されたメッセージは失われます。

この設計は、Redis Pub/Subが「最大努力配信(best-effort delivery)」モデルを採用しているためです。これは、メッセージの遅延を最小限に抑え、高速なリアルタイム配信を優先する代わりに、メッセージの確実な配信(At-Least-Once や Exactly-Once 配信)を保証しないモデルです。

この特性は、チャットメッセージのように多少のメッセージ損失が許容されるケースや、システムの状態変化の通知のように最新の状態だけが重要であるケースには適していますが、注文処理や金融取引のように厳密なメッセージ処理順序や確実な配信が求められるケースには不向きです。そのような場合は、後述するRedis Streamsや、Kafka、RabbitMQなどのメッセージキューシステムを検討する必要があります。

4.4. スケーラビリティ

Redis Pub/Subのスケーラビリティは、主に以下の側面に依存します。

  • 発行者側: 発行者はRedisサーバーに対して単に PUBLISH コマンドを実行するだけなので、多数の発行者が同時にメッセージを発行しても、Redisサーバー自体のコマンド処理能力がボトルネックにならない限り、比較的容易にスケールできます。
  • 購読者側: 購読者の数は、Redisサーバーのアウトプット帯域幅とCPUに依存します。一つのチャンネルを購読する購読者の数が多いほど、Redisサーバーは多くのクライアントに対して同じメッセージを送信する必要があるため、サーバーの負荷が増大します。特に、大量のメッセージを多数の購読者に配信する「ファンアウト」型のシナリオでは、サーバーのアウトプット帯域幅やネットワーク性能がボトルネックになる可能性があります。

Redis Cluster環境では、Pub/Subメッセージはすべてのマスターノードにブロードキャストされます。これにより、どのノードに接続している購読者もメッセージを受け取ることができますが、クラスター内のノード数が多いほど、メッセージのブロードキャストによるネットワーク負荷が増大します。

5. Redis Pub/Subの利点と欠点

Redis Pub/Subを適切に活用するためには、その利点と欠点を十分に理解することが重要です。

5.1. 利点

  • シンプルで導入が容易: Pub/Subモデル自体がシンプルであり、Redisのコマンドも直感的です。Redisが既に導入されている環境であれば、追加の設定なくすぐに利用を開始できます。
  • 非常に高速: メッセージはRedisのメモリ上で処理され、ディスクI/Oが発生しないため、非常に低遅延でメッセージを配信できます。リアルタイム性が求められるアプリケーションに適しています。
  • 軽量: メッセージの永続化や複雑な配信保証機能がないため、Redisサーバー側のオーバーヘッドが非常に小さいです。
  • 効率的なファンアウト: 一つのメッセージを、そのチャンネルを購読している多数のクライアントに効率的に配信できます。一対多通信が必要なシナリオ(例: 全員への通知)に強いです。
  • 疎結合: 発行者と購読者が互いの存在を知る必要がないため、システムの各コンポーネントを独立して開発・デプロイしやすくなります。

5.2. 欠点

  • メッセージの永続性がない: 最も大きな欠点です。メッセージは発行時に購読しているクライアントにのみ配信され、サーバー側で保持されません。発行時に購読していなかったり、切断していたりしたクライアントは、そのメッセージを永久に失います。
  • 配信の信頼性が限定的: Redis Pub/Subはメッセージが購読者に届いたかどうかの確認(ACK)を行いません。ネットワークの問題やクライアント側の処理遅延などにより、メッセージが失われる可能性があります(At-Most-Once 配信に近い特性)。厳密な配信保証が必要な業務には不向きです。
  • 購読者の負荷分散が困難: 同一のチャンネルを複数のクライアントインスタンス(例えば、スケールアウトしたアプリケーションサーバーの複数のプロセス)が購読している場合、発行されたメッセージはそれらすべてのインスタンスに配信されます。特定のメッセージを複数のインスタンスで分担して処理するような負荷分散の仕組みは、Pub/Sub単体では実現できません。(これにはRedis StreamsのConsumer Group機能が適しています)。
  • 購読者側の処理遅延がサーバーに影響を与える可能性: 遅い購読者(メッセージの受信や処理に時間がかかるクライアント)がいると、そのクライアントの出力バッファが一杯になる可能性があります。Redisサーバーの client-output-buffer-limit 設定によっては、遅いクライアントの接続を強制的に切断することでサーバー全体の安定性を保とうとしますが、これはメッセージロスの原因となります。
  • 購読クライアントは他のコマンドを実行できない: SUBSCRIBE または PSUBSCRIBE を実行したクライアント接続は、購読モードに入り、他のほとんどのコマンドを実行できなくなります。そのため、Pub/Subと他のRedisコマンド(例: GET, SET, LPUSH)を同一の接続で利用することはできません。通常、Pub/Sub専用の接続と、他の操作用の接続を分けて使用する必要があります。

これらの欠点を理解すると、Redis Pub/Subはすべてのメッセージング要件を満たす万能なツールではないことが分かります。特に、メッセージの確実な配信や処理順序の保証、あるいはメッセージキューのような機能が必要な場合は、別の技術やRedis Streamsのようなより適切な機能を選択する必要があります。

6. Pub/Subの一般的なユースケース

Redis Pub/Subは、そのシンプルさと高速性を活かして、様々なリアルタイム通信シナリオで利用されています。

  • リアルタイム通知システム:
    • チャットアプリケーション: ユーザーがメッセージを送信した際に、そのチャットルームに対応するチャンネルにメッセージを発行し、その部屋に参加している全ユーザー(クライアント)が購読しているチャンネルを通じてメッセージを受け取る。
    • ライブフィード/タイムライン更新: 新しい投稿やアクティビティが発生した際に、関連するチャンネルに通知を発行し、そのフィードを閲覧しているユーザーに即座に更新を通知する。
    • システムアラート/監視: システムの異常や特定のイベント発生時に、アラートチャンネルにメッセージを発行し、監視ツールや管理画面にリアルタイムで表示する。
  • キャッシュ無効化:
    • 分散システムにおいて、あるアプリケーションサーバーがデータを更新した際に、そのデータがキャッシュされている他のサーバーにキャッシュを無効化するよう通知する。例えば、cache:invalidate:user:123 のようなチャンネルにメッセージを発行し、キャッシュを持つサーバーがこれを購読してキャッシュをクリアする。
  • イベントドリブンアーキテクチャ(簡易版):
    • マイクロサービス間で簡単なイベント通知を行うために利用する。例えば、注文サービスが注文完了イベントを order:completed チャンネルに発行し、在庫サービスや配送サービスがこれを購読して後続処理を開始する。ただし、これはイベントの確実な処理が必要ない場合に限られます。
  • リアルタイムデータストリームの入口:
    • センサーデータやログデータなどのリアルタイムストリームデータを、初段としてRedis Pub/Subチャンネルに流し込み、複数の異なるコンシューマー(データ分析、監視、アーカイブなど)がそれぞれ独立して購読・処理する。
  • アプリケーションコンポーネント間の同期/連携:
    • 同一アプリケーション内の異なるプロセスやスレッド間で、特定のイベント発生を通知したり、簡単な同期を取ったりするために使用する。

これらのユースケースでは、メッセージの損失が許容されるか、あるいは最新の情報のみが重要であることが多いです。例えば、チャットメッセージで1つ2つ抜けても会話は成り立つことが多いですし、キャッシュ無効化では最新の状態に追いつければ問題ない場合が多いです。システムアラートも、最新の通知を受け取ることが重要であり、過去の通知を見逃してもログなどで後から確認できることが一般的です。

7. Pub/Subと他のリアルタイム通信技術の比較

Redis Pub/Subはリアルタイム通信を実現する技術の一つですが、他にも様々なアプローチやツールが存在します。それぞれの技術には得意なことと苦手なことがあり、要件に応じて適切なものを選択する必要があります。

7.1. WebSocket

WebSocketは、クライアント(ブラウザなど)とサーバー間の双方向かつ持続的な通信路を確立するためのプロトコルです。HTTPハンドシェイクから開始され、その後はTCP接続上で独立したフレームを送受信します。

  • Redis Pub/Subとの関係: WebSocket自体はメッセージブローカーではありません。サーバー側でPub/Subブローカー(例: Redis)を使い、そこで受信したメッセージをWebSocket接続を介してクライアントにプッシュするという連携が一般的です。クライアントサイドのブラウザにリアルタイムにメッセージを配信する場合、サーバーサイドでRedis Pub/Subの購読者として動作し、受け取ったメッセージをWebSocket接続を開いているクライアントに転送するというアーキテクチャがよく用いられます。
  • 特徴:
    • 双方向通信が可能。
    • 接続が持続的であるため、オーバーヘッドが少ない。
    • ブラウザ対応が進んでいる。
  • 使い分け: クライアント(特にブラウザ)とサーバー間で双方向のリアルタイム通信が必要な場合にWebSocketを使用します。Redis Pub/Subはサーバーサイドのコンポーネント間や、サーバーからWebSocketサーバーへのメッセージ配信に使われることが多いです。

7.2. Server-Sent Events (SSE)

SSEは、サーバーからクライアント(ブラウザ)への一方的なデータプッシュを可能にする技術です。HTTP/1.1上で動作し、テキストベースのイベントストリームを配信します。

  • Redis Pub/Subとの関係: WebSocketと同様に、SSE自体はメッセージブローカーではありません。サーバーサイドでRedis Pub/Subからメッセージを受信し、SSE接続を通じてブラウザにプッシュするという連携が可能です。
  • 特徴:
    • サーバーからクライアントへの一方通行。
    • HTTP上で動作するため、プロキシやファイアウォールとの相性が良い場合がある。
    • テキストベースのメッセージ。
    • WebSocketよりシンプルで、ブラウザでの実装も容易な場合がある。
  • 使い分け: サーバーからブラウザへの一方的なデータプッシュ(例: ニュースフィード、株価更新)が必要な場合に適しています。双方向通信が必要ならWebSocketを選択します。Redis Pub/Subはサーバーサイドのデータソースとして利用できます。

7.3. MQTT

MQTT (Message Queuing Telemetry Transport) は、IoT (Internet of Things) 分野などでよく使われる軽量なPub/Subプロトコルです。ブローカーを介して多数のクライアント(センサーデバイスなど)がメッセージを発行・購読します。

  • Redis Pub/Subとの関係: MQTTはPub/Subの概念に基づいている点で似ていますが、Redis Pub/Subよりも多くの機能を持ち、プロトコル仕様も詳細です。MQTTはQoSレベルによる配信保証、Persistent Session、Last Will and Testamentなどの機能を持ちます。Redis Pub/SubはRedisサーバーの一部機能として提供されるのに対し、MQTTは専用のMQTTブローカーソフトウェア(例: Mosquitto, EMQX)が必要です。
  • 特徴:
    • 軽量で帯域幅の狭いネットワークに適している。
    • 複数のQoSレベル(At-Most-Once, At-Least-Once, Exactly-Once)による配信保証を提供。
    • Persistent Sessionにより、切断中のメッセージを保持できる。
    • 多くのプラットフォームやデバイスでクライアントライブラリが提供されている。
  • 使い分け: 多数の低リソースデバイスからのデータ収集や、配信保証が必要なIoTアプリケーションなどにはMQTTが適しています。Redis Pub/Subは、より高性能でシンプル、かつRedisの他の機能と連携しやすいサーバーサイドのアプリケーション間連携や、ウェブサービスでのリアルタイム機能に適しています。

7.4. Kafka, RabbitMQなどのメッセージキュー/ブローカー

KafkaやRabbitMQのような専用のメッセージキュー/ブローカーシステムは、より高機能で信頼性の高いメッセージングを提供します。

  • Redis Pub/Subとの関係: これらのシステムは、Pub/Subモデルだけでなく、キューモデル(Point-to-Point)もサポートし、メッセージの永続化、配信保証(ACK)、コンシューマーグループによる負荷分散、順序保証、メッセージ履歴の保持など、Redis Pub/Subにはない多くの重要な機能を提供します。これらは通常、Redisよりも複雑なセットアップや運用が必要です。
  • 特徴:
    • メッセージの永続化と信頼性の高い配信。
    • コンシューマーグループによるメッセージ処理の負荷分散とスケーラビリティ。
    • 複雑なルーティングやキューイング戦略。
    • 大規模なデータストリーム処理や、複数のサービス間での信頼性の高い非同期連携に適している。
  • 使い分け: メッセージの損失が許されない、処理順序を保証したい、複数のワーカーでメッセージ処理を分担したい、切断したクライアントが後から未受信メッセージを取得できるようにしたい、といった要件がある場合は、KafkaやRabbitMQ、あるいはRedis Streamsのようなより機能が豊富なシステムを選択すべきです。Redis Pub/Subは、これらの要件が厳しくなく、シンプルさ、高速性、Redisとの統合性が重視される場合に適しています。

8. Redis Pub/Subの実装上の注意点

Redis Pub/Subをアプリケーションに組み込む際には、いくつかの注意点があります。

8.1. 接続管理

  • Pub/Sub専用接続: 前述の通り、SUBSCRIBE または PSUBSCRIBE コマンドを実行したクライアント接続は購読モードに入り、他のコマンドを受け付けなくなります。したがって、Pub/Sub購読用の接続と、その他のRedis操作(データ操作など)を行うための接続は、必ず分ける必要があります。通常、アプリケーションはRedisに対して複数の接続プールを持つことになります。
  • 再接続ロジック: ネットワークの問題、Redisサーバーの再起動、クライアント側のエラーなどにより、購読接続はいつでも切断される可能性があります。切断が発生した場合、Pub/Subの特性上、切断中に発行されたメッセージは永久に失われます。したがって、信頼性を高めるためには、クライアント側で自動再接続のロジックを実装することが不可欠です。再接続後は、再度必要なチャンネルやパターンを購読する必要があります。再接続の間隔を適切に設定し、サーバーに過剰な負荷をかけないように注意が必要です(指数バックオフなど)。

8.2. メッセージ損失の許容度

  • 失われたメッセージ: Pub/Subはメッセージの永続性を持ちません。アプリケーションの要件としてメッセージの損失が許されない場合、Pub/Subは不適切な選択肢である可能性が高いです。そのような場合は、Redis Streamsや、Kafkaのような永続性のあるメッセージングシステムを検討してください。Pub/Subを使用する場合は、メッセージ損失が発生しうることを前提とした設計(例: 補助的な仕組みで重要なイベントを補完するなど)が必要になります。

8.3. 購読者側の処理速度とバックプレッシャー

  • 遅い購読者: 購読者のメッセージ処理速度が、メッセージの発行速度に追いつかない場合、Redisサーバーのそのクライアントに対する出力バッファが溢れる可能性があります。Redisはデフォルトで特定のバッファ制限 (client-output-buffer-limit pubsub) を持っており、これを超過するとクライアント接続を強制的に切断します。これにより、サーバー全体の安定性は保たれますが、遅いクライアントはメッセージを失います。
  • 対策:
    • 購読者側のメッセージ処理を高速化する。
    • メッセージ処理をブロッキングしないように、非同期処理やワーカースレッド/プロセスを利用する。
    • Pub/Subの代わりに、Consumer Group機能を持つRedis Streamsなど、購読者側の処理遅延に対して耐性のある(バックプレッシャーをかけられる)システムを検討する。
    • バッファ制限を適切に設定する(ただし、過剰な増加はサーバーメモリを消費します)。

8.4. チャンネル設計

  • 粒度: チャンネル名の粒度は、アプリケーションの要件に合わせて適切に設計する必要があります。
    • 粒度を細かくしすぎる(例: ユーザーごとにチャンネルを作る)と、管理が煩雑になり、Redisサーバーが管理するチャンネル数が増大する可能性があります。
    • 粒度を粗くしすぎる(例: 全ての通知を一つのチャンネルに集約する)と、購読者が必要のないメッセージまで大量に受信することになり、購読者側の負荷が増加したり、帯域幅を無駄に消費したりする可能性があります。
  • 命名規則: チャンネル名に一貫した命名規則(例: [モジュール]:[イベントタイプ]:[ID] のような形式)を設けることで、管理やパターン購読が容易になります。

8.5. セキュリティ

  • 認証: Redisサーバーへの接続には、パスワード認証(requirepass ディレクティブや AUTH コマンド)を設定することを強く推奨します。これにより、未認証のクライアントが勝手に SUBSCRIBE したり PUBLISH したりすることを防ぎます。
  • ACLs (Access Control Lists): Redis 6.0以降で導入されたACL機能を使用すると、特定のユーザーに対して特定のコマンドやチャンネルへのアクセス権限を細かく制御できます。例えば、あるユーザーには特定のチャンネルへの PUBLISH のみを許可し、別のユーザーには特定のチャンネルへの SUBSCRIBE のみを許可するといった設定が可能です。これにより、よりセキュアなPub/Sub環境を構築できます。

8.6. Pub/Subと他のRedis機能の連携 (Keyspace Notifications)

RedisのPub/Sub機能は、他のRedis機能と連携して強力なリアルタイム機能を実現できます。その代表例が Keyspace Notifications です。

Keyspace Notificationsを有効にすると、Redisのキー空間で発生した様々なイベント(例: キーの作成、更新、削除、有効期限切れなど)が、特別なPub/Subチャンネルを通じて通知されます。

有効化方法:

Redisの設定ファイル (redis.conf) または CONFIG SET コマンドで、notify-keyspace-events パラメータを設定します。例えば、キー空間イベント (K) とキーイベント (E) をすべて通知する場合、以下のように設定します。

CONFIG SET notify-keyspace-events "AKE"
A はすべてのイベントフラグのエイリアスです。詳細は公式ドキュメントを参照してください。)

チャンネル名:

通知されるチャンネル名は以下の形式です。

  • キー空間イベント: __keyspace@<db>__:<key>
  • キーイベント: __keyevent@<db>__:<event>

例: データベース0 (db0) で mykey というキーが削除された場合
* キー空間イベントチャンネル: __keyspace@0__:mykey
* キーイベントチャンネル: __keyevent@0__:del

購読者はこれらのチャンネルを SUBSCRIBE または PSUBSCRIBE することで、キー空間イベントをリアルタイムに取得できます。

例:

“`bash

ターミナル1 (Subscriber)

redis-cli
SUBSCRIBE keyevent@0:del keyspace@0:mykey
“`

“`bash

ターミナル2 (Commands)

redis-cli
SELECT 0 # データベース0を選択
SET mykey “hello”
DEL mykey
“`

ターミナル1には以下のようなメッセージが届きます。

“`
Reading messages… (press Ctrl+C to quit)
1) “subscribe”
2) “keyevent@0:del”
3) (integer) 1
1) “subscribe”
2) “keyspace@0:mykey”
3) (integer) 2
1) “message” # キーイベントチャンネルからの通知
2) “keyevent@0:del”
3) “mykey” # イベントに関連するキー名

1) “message” # キー空間イベントチャンネルからの通知
2) “keyspace@0:mykey”
3) “del” # 発生したイベントの種類 (キーイベント)
“`

Keyspace Notificationsは、キャッシュの自動無効化(特定のキーが更新・削除されたら関連キャッシュをクリア)、特定のキーの監視、データ変更のトリガーといった用途に活用できます。ただし、大量のイベントが発生する場合、通知によるサーバー負荷が増大する可能性があるため、必要なイベントタイプのみを有効にするように注意が必要です。

9. 高度なトピック

9.1. パターンマッチング購読 (PSUBSCRIBE) の詳細

PSUBSCRIBE は非常に便利ですが、その動作にはいくつかの注意点があります。

  • パターンとチャンネル名のマッチング: Redisは指定されたパターンに対して、発行されたチャンネル名が文字列としてマッチするかどうかを単純にチェックします。正規表現のような複雑なマッチングはできません。ワイルドカードは * (0個以上の任意の文字) と ? (1個の任意の文字) のみです。
  • パフォーマンス: パターン購読は、購読しているパターンが少ない場合は問題ありませんが、多数の複雑なパターンを多数のクライアントが購読している場合、新しいメッセージが発行されるたびにすべてのパターンに対してチャンネル名がマッチするかどうかをチェックする必要があるため、サーバー側のCPU負荷が増加する可能性があります。シンプルなチャンネル名と少ないパターンを心がけるのが良いでしょう。
  • 重複購読: 前述の通り、同一チャンネルを通常の購読 (SUBSCRIBE) とパターン購読 (PSUBSCRIBE) の両方で行っている場合、メッセージが重複して配信されます。クライアント側で重複を処理する必要があるか、あるいはどちらか一方の購読方法に絞るかを検討する必要があります。

9.2. メッセージフォーマット

Redis Pub/Subはメッセージの内容を解釈しないため、メッセージフォーマットは発行者と購読者の間で自由に定義できます。一般的には、構造化されたデータを表現するためにJSON形式がよく用いられます。

例:

json
{
"type": "new_message",
"data": {
"room_id": "chat:room:1",
"sender": "user123",
"text": "皆さんこんにちは!"
},
"timestamp": 1678886400
}

メッセージを受信した購読者は、この文字列をJSONとしてパースし、メッセージの種類 (type) に応じて適切な処理を行います。

9.3. Redis ClusterにおけるPub/Subの動作

Redis Cluster環境では、Pub/Subメッセージの配信は通常のデータ操作とは異なる方法で行われます。通常のデータ操作は、キーのハッシュスロットに基づいて特定のノードにルーティングされますが、Pub/Subメッセージはすべてのマスターノードにブロードキャストされます。

  1. 発行者がいずれかのノードに PUBLISH コマンドを送信します。
  2. そのノードは、受信したメッセージをクラスター内のすべてのマスターノードに転送(ブロードキャスト)します。
  3. 各マスターノードは、自分に接続している購読クライアントのうち、そのメッセージのチャンネルまたはパターンを購読しているクライアントに対してメッセージを配信します。

このブロードキャストメカニズムにより、どのノードに購読クライアントが接続していてもメッセージを受け取ることができます。しかし、クラスター内のノード数が増えるほど、メッセージのブロードキャストにかかるネットワークトラフィックが増大し、サーバーの負荷も高まります。大規模なクラスターでPub/Subを多用する場合は、この点に注意が必要です。

9.4. Sentinel環境でのフェイルオーバー時の挙動

SentinelはRedisの高可用性ソリューションです。マスターに障害が発生した場合、Sentinelはレプリカを新しいマスターに昇格させ、クライアントに新しいマスターのアドレスを通知します。

Pub/Sub購読中のクライアントがマスターに接続している場合、フェイルオーバーが発生するとその接続は切断されます。クライアントはSentinelから新しいマスターのアドレスを取得し、再接続して購読を再開する必要があります。

前述の通り、再接続中に発行されたメッセージは失われます。Pub/Subのメッセージ永続性がない特性はSentinel環境でも変わりません。

9.5. クライアントライブラリでの非同期Pub/Sub

多くのRedisクライアントライブラリは、Pub/Sub購読用のブロッキングAPIと、非同期API(コールバックベースやasync/awaitベース)を提供しています。

  • ブロッキングAPI: pubsub().listen() のように、メッセージが届くまで待ち続ける形式です。これをメインスレッドで実行すると他の処理がブロックされるため、通常は専用のワーカースレッドやプロセスで実行します。
  • 非同期API: イベントループに統合される形式です。メッセージが届くと登録しておいたコールバック関数が実行されたり、await可能なオブジェクトからメッセージを取得できたりします。これにより、単一スレッド/プロセスでPub/Sub購読と他の非同期処理を両立させやすくなります。 Node.jsのnode-redis や Pythonのredis-py (asyncio対応) などが非同期Pub/Subをサポートしています。リアルタイム性の高いアプリケーションや、多数のクライアント接続を扱うサーバーサイドのコンポーネントでは、非同期APIの利用が推奨されます。

10. Pub/Subの代替または組み合わせ:Redis Streamsとの比較

Redis 5.0で導入されたStreamsは、Pub/Subと同様にリアルタイムメッセージングに使用できますが、Pub/Subが持たない「永続性」「順序保証」「コンシューマーグループ」といった重要な機能を提供します。

10.1. Redis Streamsの概要

Redis Streamsは、 append-only なデータ構造であり、時系列に並んだメッセージのログのようなものです。各メッセージには一意のIDが付与され、永続的に保存されます(ただし、最大長を設定して古いメッセージを削除することも可能です)。クライアントはメッセージをストリームの末尾に追加(XADD)したり、特定のIDから読み出し(XRANGE, XREAD)たりできます。

10.2. Pub/Sub と Streams の違い

特徴 Redis Pub/Sub Redis Streams
モデル 発行されたメッセージは即座に購読者にプッシュ メッセージはストリームに追加され、コンシューマーがプル
メッセージ永続性 なし(発行時に購読していないと失われる) あり(ストリームに保存される)
配信保証 最大努力配信 (At-Most-Once に近い) 少なくとも一回配信 (At-Least-Once)、手動でExactly-Onceも可能
コンシューマー メッセージは購読者全員に配信される(ファンアウト) コンシューマーグループによる負荷分散が可能(メッセージを分担して処理)
メッセージ履歴 保持しない ストリームに履歴として保持(指定した範囲を読み出し可能)
複雑さ シンプル Pub/Subより高機能な分、コマンドや概念がやや複雑
ユースケース 一時的なリアルタイム通知、キャッシュ無効化など 信頼性の高いメッセージキュー、イベントソーシング、タスクキュー

10.3. 使い分け

  • Pub/Subが適しているケース:
    • メッセージの損失が許容される。
    • 最新の情報のみが重要。
    • 多数のクライアントに同じメッセージを「ブロードキャスト」したい(ファンアウト)。
    • シンプルさと低遅延を最優先したい。
    • Redisの他の機能と連携しつつ、簡単な通知システムを構築したい。
  • Streamsが適しているケース:
    • メッセージの損失を許容できない。
    • メッセージの処理順序を保証したい。
    • 複数のワーカープロセスでメッセージ処理を分担したい(コンシューマーグループ)。
    • 切断したクライアントが再接続時に未受信メッセージを取得できるようにしたい。
    • メッセージ履歴を一定期間保持したい。

Pub/SubとStreamsは排他的なものではなく、組み合わせて使用することも可能です。例えば、Streamsに永続的にイベントを記録しつつ、最新のイベント発生をリアルタイムに通知するためにPub/Subも利用するといったアーキテクチャも考えられます。

11. まとめ

Redis Pub/Subは、Redisのシンプルさと高速性を活かした、強力なリアルタイム通信機能です。発行/購読モデルに基づき、特定のチャンネルを介してメッセージを非同期かつ効率的に配信できます。チャット、通知、キャッシュ無効化など、メッセージの損失が許容されるリアルタイム性の高いシナリオで特に有効です。

しかし、Pub/Subはメッセージの永続性を持たず、配信保証も限定的であるという重要な特性があります。メッセージの確実な配信や処理順序の保証、コンシューマーによる負荷分散といった要件が必要な場合は、Redis Streamsや他のメッセージキューシステムを検討する必要があります。

Redis Pub/Subをアプリケーションに組み込む際は、Pub/Sub専用接続の使用、再接続ロジックの実装、購読者の処理速度への配慮、適切なチャンネル設計、そしてKeyspace Notificationsのような関連機能の活用可能性など、実装上の注意点を理解しておくことが重要です。

本記事を通じて、Redis Pub/Subの基本から応用までを深く理解し、ご自身のプロジェクトにおけるリアルタイム通信の実現に役立てていただければ幸いです。RedisはPub/Sub以外にも様々な機能を備えており、これらを組み合わせることで、さらに高度なシステムを構築することが可能です。ぜひ、他のRedis機能についても学びを深めてみてください。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール