Redis Pub/Sub から Streams への移行で帯域 99% 削減 — 同時接続 30 万超チャットの実践記録

Keisuke Nishitani 氏(@Keisuke69)のポストで、LY Corp(旧 LINE)の技術ブログ記事が紹介されていました。同時接続数 30 万超の LINE 公式アカウント(OA)チャットが、メッセージ配信基盤を Redis Cluster の Pub/Sub から Redis Streams へ移行した事例です。ピーク時 1.5 Gbps だったノードあたりの帯域が 11 Mbps まで削減されたという結果は、大規模リアルタイムシステムを運用するエンジニアにとって示唆に富む内容です。

同時接続数30万超のチャットサービスのメッセージ配信基盤をRedis Pub/SubからRedis Streamsにした話 — @Keisuke69

背景 — Redis Cluster Pub/Sub のスケール限界

LINE 公式アカウントのチャット機能(OA チャット)は、ユーザーから送られたメッセージを OA オーナーにリアルタイムで配信する仕組みを持っています。この配信基盤として Redis Cluster の Pub/Sub を使用していました。

問題は Redis Cluster における Pub/Sub の仕様にあります。あるシャードに publish されたメッセージは、クラスター内の全シャードにブロードキャストされます。24 シャード構成であれば、1 メッセージが残り 23 シャードに伝搬するため、アウトバウンドトラフィックはインバウンドの 23 倍になります。

指標
クラスター構成24 シャード / 48 ノード
ノードあたり帯域(平常時)500 Mbps
ノードあたり帯域(ピーク時)1.5 Gbps

シャードを増やせばクラスター性能は上がりますが、同時にブロードキャストのトラフィックも増えるというジレンマがあり、スケールアウトが頭打ちになっていました。

一時対策 — クラスターレベルの水平シャーディング

根本対策に先立ち、24 シャード 1 クラスターを 8 クラスター(各 3 シャード)に分割する水平シャーディングが行われました。これにより、クラスター内のアウトバウンドはインバウンドの 2 倍にまで抑えられ、全体トラフィックは約 1/8 に削減されています。

ただし、この構成にもスケールアウトの課題は残ります。

  • クラスター数を増やす場合: チャンネルの振り分けロジックを自前で実装する必要があり、無停止での追加にリスクが伴う
  • クラスター内シャードを増やす場合: ブロードキャスト問題が再発する

Redis Streams の選定理由

Redis 5 で導入された Streams は、時系列データの追記に特化したデータ型です。Pub/Sub と比較して以下の特性があります。

比較項目Redis Pub/SubRedis Streams
データ保持配信後すぐ消える保存され、後から参照可能
ネットワーク帯域シャード増加でトラフィック増大シャード増加の影響なし
接続数複数チャンネルを 1 接続で subscribe 可能N ストリームに最大 N 接続が必要
再接続時の取得別途保存が必要(Redis Lists 等)Stream 自体から過去分を取得可能

OA チャットでは 1 クライアントあたり 2 ストリームで済むため、接続数は Pub/Sub の高々 2 倍程度。スケールアウトでシャードあたりの接続数を抑えられるため、実質的な問題にはなりませんでした。

Redis 7 の Sharded Pub/Sub という選択肢

Redis 7 では Sharded Pub/Sub が導入され、チャンネルを特定のシャードに割り当てることでブロードキャスト問題を解消できます。SPUBLISH / SSUBSCRIBE コマンドで利用できます。

LY Corp のチームは以下の理由で Streams を選択しました。

  1. 検討時点でインフラ環境が Redis 7 未対応だった
  2. 過去配信分の保存と即時配信を 1 つのデータ型で実現できる Streams の方がアーキテクチャがシンプル

Streams による新アーキテクチャ

従来は 2 つの Redis Cluster(Pub/Sub + Lists)が必要でしたが、Streams への移行で 1 つに統合されました。

従来の構成:

  1. イベント処理サーバーが Redis Lists にメッセージを保存(再接続時用)
  2. 同時に Redis Pub/Sub に publish(即時配信用)
  3. ストリーミングサーバーが subscribe して OA オーナーに配信

新しい構成:

  1. イベント処理サーバーが Redis Streams に XADD(保存 + 配信を兼ねる)
  2. ストリーミングサーバーが XREAD BLOCK で新規メッセージを受信
  3. 再接続時は同じ Stream から過去分を取得

Streams ではデータ取得後にコマンドが完了するため、継続的な受信にはポーリングが必要です。擬似コードで示すと以下のようになります。

var lastId = "0"
while(true) {
    elements = redis.call("XREAD BLOCK 0 STREAMS mystream lastId")
    elements.forEach { send(it.body) }
    lastId = elements.last().id
}

BLOCK 0 は次のメッセージが届くまで待機するオプションで、実質的にリアルタイム受信が可能です。

無停止移行の戦略

30 万超の同時接続を持つサービスで無停止移行を実現するために、4 段階のステップが採られました。

ステップ書き込み先読み込み先
Step 0Pub/Sub + ListsPub/Sub + Lists
Step 1Pub/Sub + Lists + StreamsPub/Sub + Lists
Step 2Pub/Sub + Lists + StreamsStreams
Step 3Streams のみStreams のみ

各ステップ内で適用割合を段階的に増やしていくことで、問題発生時の影響を最小化し、切り戻しを容易にしています。適用割合の動的変更には LINE の OSS である Central Dogma が活用されました。Central Dogma は設定の集中管理と即時通知を提供し、アプリケーションの再起動なしに設定変更を反映できます。

結果

指標Pub/Sub(移行前)Streams(移行後)
ノードあたり帯域(平常時)500 Mbps6 Mbps
ノードあたり帯域(ピーク時)1.5 Gbps11 Mbps
Redis Cluster 数2(Pub/Sub + Lists)1(Streams)

帯域は平常時で約 99%、ピーク時でも 99.3% の削減です。ノード数は従来比 2 倍に増やしていますが、ネットワーク帯域のボトルネックが解消されたことで、今後はシャード追加による素直なスケールアウトが可能になっています。

自社サービスへの適用を考える

この事例から得られる判断基準を整理します。

Redis Streams が適するケース:

  • メッセージの永続化が必要(再接続・リプレイ)
  • Redis Cluster でシャード数が多く、帯域がボトルネック
  • 即時配信と過去分取得を 1 つの仕組みで済ませたい

Redis Pub/Sub が適するケース:

  • 火消し型の通知(配信後の保持が不要)
  • 接続数が極めて多く、1 接続で複数チャンネルを subscribe したい
  • 超低レイテンシが最優先(Streams は 1–2 ms のオーバーヘッドがある)

Redis 7 Sharded Pub/Sub が適するケース:

  • Pub/Sub のシンプルさを維持しつつ帯域問題を解決したい
  • Redis 7 以上が利用可能な環境

まとめ

  • Redis Cluster Pub/Sub の本質的課題: publish メッセージが全シャードにブロードキャストされるため、シャード数に比例してネットワーク帯域が膨張する
  • Redis Streams はシャード局所化: データは書き込んだシャードでのみ読み書きされるため、シャード追加がトラフィック増加を招かない
  • アーキテクチャの簡素化: 即時配信(Pub/Sub)+ 過去分保存(Lists)の 2 系統が、Streams 1 系統に統合された
  • 帯域 99% 削減: ピーク時 1.5 Gbps から 11 Mbps へ劇的に改善
  • 無停止移行の実現: 書き込みの二重化 → 読み込み切替 → 旧系統撤去の 4 段階で、Central Dogma による動的な適用割合制御を活用
  • 接続数のトレードオフ: Streams では最大 N ストリーム分の接続が必要だが、スケールアウトで吸収可能
  • Redis 7 Sharded Pub/Sub という代替案: 帯域問題だけなら Sharded Pub/Sub でも解決できるが、Streams はデータ永続化の利点も併せ持つ

参考