ライブコミュニティ事業本部Pococha事業部の小張です。 この記事では、 Pococha の配信を支える新しいステートフルなアーキテクチャへの移行と、その技術的な工夫の一部をご紹介します。
ステートレスからステートフルへ
Pocochaのライブ配信は、配信を届けるだけではなく、ライバーとリスナーのやり取りそのものが中心にあります。ライバーとリスナーがコメントやアイテムを通じてリアルタイムに繋がり、そのひとつひとつのインタラクションが配信の「状態」を刻々と変化させていく、いわばステートフルなセッションです。
しかし、現在に至るまでPocochaを支え続けているRuby on Railsベースのシステムは、Webの基本であるステートレスな設計思想に基づいています。この設計は、一般的なWebアプリケーションには最適ですが、リアルタイムで複雑な状態を管理し続けるライブストリーミングの要件とは、相性が悪い部分もあります。
このミスマッチは、パフォーマンスのボトルネックや開発の複雑化といった課題を生んでいました。これらの課題を解決するため、私たちはGo言語をベースにしたマイクロサービスを開発し、Kubernetes上で運用する新しいアーキテクチャへ部分的な移行を進めていくことを決定しました。
その狙いは、従来のステートレス設計では難しかったリアルタイム性を補うために、ステートフルな仕組みを導入することです。具体的には、配信の状態をメモリ上で直接管理し、リアルタイムな要求に効率的に応えられるアーキテクチャの実現を目指しました。
なお、本記事はアプリケーション側の観点にフォーカスしています。インフラの観点についても別途記事がありますのでぜひご覧ください。
https://engineering.dena.com/blog/2025/09/pococha-kubernetes-deploy
「応援ポイント」から始める段階的移行
稼働中の大規模サービスを一度に刷新することは現実的ではありません。そのため、本番系統を停止させることなく新システムへ機能を移していく段階的な移行のアプローチを採用する必要があります。 この段階的移行における最初のスコープとして、私たちは「 応援ポイント 」関連機能を選択しました。
応援ポイントとは、配信に対するアイテム使用等の応援行動によって決まるポイントであり、ライバーの報酬に関わってくるPocochaの主要機能の1つです。 選定理由としては、応援ポイントの計算ロジックこそ、リアルタイムに発生する無数のイベントを正確に処理し、配信の状態に反映させるステートフルな処理の塊であり、既存アーキテクチャが抱える課題がもっとも顕著に現れていた領域だったからです。困難でありながら、移行による効果が期待できるこの領域が最初の移行対象として選ばれました。
ステートフルアーキテクチャにおけるイベント伝搬
では、実際にこの新しいステートフルアーキテクチャはどのように動作するのでしょうか。 ライブ配信中に発生する無数のイベント(配信開始、アイテム使用、コメント投稿など)は、いくつかのコンポーネントが連携することで、リアルタイムに処理されます。
中でも中心的な役割を担うのが以下の3つです。
Manager — メッセージフローのオーケストレーター
Kubernetes 上で稼働する Pod。Message Bus(Redis Stream)からイベントを取り込み、配信IDをキーに担当 Engine Pod を Broker に問い合わせ、該当 Pod へ gRPC 双方向ストリームで転送します。
Broker — 配信IDとPodを紐づけるレジストリ
計算リソースである Engine と配信IDの対応を Redis に保持・管理し、Pod の登録/解除/問い合わせ/割り当てを行うランタイムのサービスディスカバリを提供します。特定キー(配信ID)で担当Podを解決・固定し、同一配信のトラフィックを一台に集約します。
Engine ― 配信状態を担うステートフルサーバー
配信に関するビジネスルール(応援ポイント計算ロジック等)をカプセル化した ステートフルなKubernetes Pod。 同じ配信の全トラフィックは同一 Pod に集約され、メモリ上の状態へ逐次適用します。
以下は、既存システムから発行されたイベントが、新システムでどのように処理されるかを示しています。
- 既存システム が 配信開始 / アイテム使用 / 配信終了 などの配信内イベントを Message Bus へ発行します(図①)。
- Manager がイベントを購読します(図②)。
- Manager が配信IDをキーに Broker に照会して、担当 Engine の Pod(以下 Engine Pod)を解決(図③)。ただし、イベント種別により一部処理が分岐します。
- 配信開始:Manager が 配信の割り当て を Broker に要求。Broker は空き/負荷を見て Engine Pod を割り当て、Manager はその Pod と gRPC 双方向ストリームを確立し配信開始をリクエスト。Engine Pod はインメモリ状態を初期化し、レスポンスを返します。
- アイテム使用(配信中の応援行動):Manager は Broker の割り当てにしたがって担当Engine Pod へイベントを転送。Engine Pod は メモリ上の状態を更新し、応援ポイントを再計算します。
- 配信終了:Manager が Broker で担当 Pod を確認し、配信終了 をリクエスト。Engine Pod は最終スコアをMongoDBへ保存し、インメモリ状態を破棄した後、レスポンスを返します。続けて Manager が Broker に通知し、その配信の割り当てを解放します。
ステートフルサーバーのシャットダウンと状態引き継ぎ
ステートフル化はリアルタイム性を引き上げる一方、配信状態が Pod のメモリにあるため、停止や障害で状態を正しく引き継げないと、取りこぼし・二重計上・順序崩れなどの不具合が起きてしまいます。以下では、デプロイ時に、配信の状態を失わずに次のPod へ引き継ぐために、適切に再割り当てを行う手順を示します。
-
Engine Podを停止
Engine Podが停止(SIGTERM)を受けたら、まず Brokerに「利用不可」と登録して新規割り当てを止め(図❶,❷)、続けて Managerへ「自身に割り当てられている配信IDの一覧」を通知します(図❸)。Managerは該当するgRPC接続を順に閉じ(図❹)、旧Engine Podに新しいイベントが入ってこない状態を素早く作ります。その後、旧Engine Podはシャットダウンします(図❺)。
-
配信の再割り当て
Managerは配信IDごとに Brokerへ問い合わせ、次の担当Engine Pod を決めてもらいます(図❻,❼)。決まったら Manager↔新Engine PodでgRPCを張り直し(図❽)、Managerから 「この配信の状態を復元して」 と依頼します(図❾)。新Engine Podは RedisのCacheから直近の状態を読み戻して 準備を整えます(図➓)。
-
再開
復元が済んだ配信は、Engine Podから受け取ったオフセットからイベントの転送を再開します(図⓫,⓬)。リスナーやライバーから見ると、止まらずに続いているように見えます。
Pod入れ替え時の整合性保証
- 二重計上を防ぐ:Engine側でイベントの重複は IDで検出して無視(冪等化)。
- 順番が崩れない:同じ配信IDは 常に1台のEngineだけ が処理。切替点をまたいでも順序を保ちます。
- 取りこぼさない: 配信状態復元のリクエストに対し、Engine は 最後に処理したイベントの位置 を返します。Manager はこの 位置 を再開ポイントとして Message Bus の 読み出し位置 を合わせ、未処理分のみを順に転送するため、切替中のイベント欠落を防ぎます。
まとめ — リアーキテクチャの出発点
本稿では、Pocochaにおける応援ポイントの計算処理を、従来のステートレスなアーキテクチャから、ステートフルなマイクロサービスへと段階的に移行する取り組みについて解説しました。 この移行により、パフォーマンスの向上を実現しました。インメモリでの状態管理を導入することでデータベースアクセスを抑制し、UIへリアルタイムに反映可能な基盤を実現しました。 また、開発・運用の効率化も大きな成果です。ビジネスロジックをEngineにカプセル化することで責務を明確に分離し、gRPC双方向ストリームによる効率的な通信を実現しました。
今回触れたのはPocochaのリアーキテクチャプロジェクトの最初の一歩に過ぎません。今後もプロジェクトの進展に応じて、取り組みや知見を発信していければと思います。同じようにリアルタイム性やステートフル性に向き合う方々にとって、少しでもヒントになれば幸いです。
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。