自己紹介とアジェンダ

堀内和也氏:みなさん、こんにちは。本セッションでは「RedisのPubSubを使って設定情報を大規模ユーザーに高速配布した話」と題して、開発3センターの堀内が話します。

突然ですが、大型アップデートをしたり新しい機能をリリースしたりすると、ちゃんと動いているのかとか、不具合でなにか事故ってないかとか、心配になりませんか? そうでなくとも、日々システムが正常に動いているか気になりますよね。

私たちも心配で、頻繁にログを眺めたり、メトリクスを眺めたりしています。ログを眺めている最中に、出力しなくてもよさそうなエラーログやワーニングログを見つけて、ついでにログ改善をしておいたりすることも日常茶飯事です。

あとほかに大変なのは、インフラやネットワークの障害などもあるのではないでしょうか。こういったものは突発的に発生するので大変ですが、ある程度それを見越した作りができていれば、自動復旧できて安心感が増しますよね。今日は、そんな障害を見越して工夫したり作ったりというのも、一緒に紹介していこうと思っています。

それでは前段はこのくらいにして、本日のアジェンダですが、まず「LINE LIVE」は何かというのを軽く説明してから、LINE LIVEのチャットシステムの概要をお伝えします。

そのあとに、従来のものから新しくなったものという順番で、本題である設定の同期や、配信に関するアーキテクチャをお伝えします。また、新しいアーキテクチャは、流用して新機能を開発するような想定もしているので、どのようなユースケースが想定できるかもお伝えします。

そして、新しいアーキテクチャで耐障害性について工夫したことがあるので、それをお伝えしてから、最後に運用してみた結果、どんな具合かをお伝えします。

LINE LIVEのサービスについて

まず「LINE LIVE」についての説明です。LINE LIVEは有名人や著名人、企業、その他一般のユーザーが自由にライブ配信できる、ライブストリーミングサービスです。

ファンは配信者の配信を視聴するだけでなく、応援アイテムや無料ハートを送って配信者を応援できます。また、チャットのコメント機能を使って、配信者と視聴者が会話をしたり、視聴者同士で会話したりもできます。

ライブ配信は“生もの”なので、今この瞬間を楽しめないと意味がありません。配信者も視聴者も、今この瞬間を大切にしてサービスを使っています。なにか障害が発生したり運用ミスが発生すると、その時に予定していたライブ配信がすべて失敗になって、大事故につながったりもします。そうすると、配信者だけでなく、それを楽しみにしていた視聴者、そして私たちサービスの提供側も全員が悲しくてつらい思いをします。

そのため、私たちもよりリアルタイムなコミュニケーションを楽しんでもらえるサービスを安定して提供できるように、日々がんばっています。

今日は、このチャットのコメント機能にフォーカスしつつ、より高いリアルタイム性の実現に向けた、新しい仕組みの話をしていきます。何度も出てきている、“リアルタイム性”とは、LINE LIVEでかなり重要な要素になるので、ぜひ念頭に置き続けて聞いてもらえるとありがたいです。

LINE LIVEのチャットシステムの概要

まずはLINE LIVEのチャットシステム全体を知ってもらうために、コア部分の説明を軽くします。チャットサーバーは、Akkaのアクターモデルを使い、並列処理を実現しています。(スライドを指して)図のAkka Actorsで囲った、ChatSupervisor、ChatRoomActor、UserActorがそれにあたります。

次にクライアントとサーバーの通信ですが、WebSocketを使って相互通信を可能にしています。(スライドを指して)図のClient 1からMessageHandlerにコメントを送ったり、ほかのクライアントから送られてきたメッセージは、サーバーのUserActorからクライアントに送られたりする部分になります。

現在、LINE LIVEのチャットサーバーは120台あるので、サーバー間でユーザーのコメントを共有する必要があります。この共有はRedisのPub/Subを使って実現しています。

Chat Server1と2が、Redisを使ってPublish、Subscribeをしている部分にあたります。そして、パブリッシャーは送られたコメントを共有するのと同時に、Redisに一時的にコメントを保存します。

MySQLに直接保存していないのは、性能を最優先にするためです。ただ、一時的にRedisに保存したコメントは裏のバッチ処理で正規化して、定期的にMySQLに保存します。

最後に、チャットシステムでは、ユーザーやシステムが設定した情報を使ってコメントを制御したりもしています。今回メインで話すのは、この設定情報を120台あるチャットサーバー間で、どのように同期してコメントの制御を反映しているのかという部分です。

LINE LIVEのことや、チャットシステムの概要の説明は以上になります。

設定情報をチャットサーバー全台に配布するための従来の仕組み

ここからは、本日のメインの話に入っていきます。どのようにユーザーやシステムの設定情報をチャットサーバー全台に配布するかという話になりますが、まずは今まで動いていた従来の仕組みを説明してから、新しい仕組みを説明します。

まずは従来の仕組みの話になりますが、イメージしやすいようにユースケースを1つ挙げながら説明します。今回例にするのは、配信者が視聴者をブロックするケースです。例えば配信者が視聴者をブロックすると、ブロックされた視聴者のコメントは、その配信者の配信には表示されないようにする機能があります。

(スライドを指して)この図の場合、LIVE BroadcasterがLIVE Viewer Aをブロックすると、まずLINE LIVE APIにブロック情報を送ります。このブロック情報は、LINE LIVE全体のMySQLに保存されつつ、チャットのInternal APIサーバーにブロック情報を送ります。受け取ったチャットのInternal APIサーバーは、MySQLにブロック情報を保存します。

その後、LIVE Viewer Aがコメントを送信すると、チャットのWebSocketサーバーは、ローカルメモリのキャッシュのブロック情報を確認します。そしてローカルメモリに保存すればそれを使うし、もし存在しなければMySQLから最新のブロック情報を取得して、期限つきでローカルメモリにキャッシュをしつつ、それを使います。

このパターンだとLIVE Viewer Aはブロックされていると判断できるので、ほかの視聴者にはLIVE Viewer Aのコメントは送られないことになります。

このケースでは、誰かがコメントをするたびにブロック情報をMySQLから参照していては、パフォーマンスの低下につながりますし、たとえRedisなどのキャッシュを使ったとしても、多数の視聴者からコメントが集中してスパイクすると、ネットワーク帯域の枯渇につながる危険性があるので、各チャットサーバーのローカルメモリに期限つきで保存をしています。

ブロックを例に挙げましたが、今までの機能で、全チャットサーバーに対してユーザーが設定した情報は、ある程度のスピードで反映できれば問題なかったし、各サーバーで設定が反映されるタイミングもそこまでシビアに合わせる必要はありませんでした。

そのため、シンプルに期限つきのローカルメモリを使って、期限が切れたら新しい情報に置き換える方法で、各サーバーで個別に情報を取得して回していました。

新しい仕組みを取り入れた背景

ではなぜ今までの仕組みを使い続けるのではなく、新しい仕組みを作ったのかという話に入っていきますが、この話をわかりやすくするために、もう1つ機能を紹介します。

最近、LINE LIVEではチャットのデコレーション機能をリリースしました。この機能は自分のコメントに背景色やフレームをつけることが可能で、ほかの視聴者のコメントより目立ってドヤ顔できるような機能になります。

デコレーションの設定はユーザーが任意で自由に変更できますが、変更したらリアルタイムでほかの全視聴者に反映させる必要があります。

なぜそんなにリアルタイムかつ同時にこだわるのかというと、例えば、友達と横並びで配信を見ている時に、1人だけデコレーションされていないコメントが表示されたら悲しいですよね。

(スライドを指して)この画像でいうと、上の段の真ん中の画像の状態がそれにあたります。表示されなかった人はもちろん悲しいし、周りの友達はサービスに変な疑問を抱くし、そしてユーザーをそんな状態にさせてしまった私たちも悲しいしで、なにもいいことがありません。

そんな未来は嫌なので、私たちは新しい同期の仕組みを作ることに決めました。そして、それを実現するための手段として、RedisのPub/Sub機能を使って、全チャットサーバーに同時に情報を発信する方法を選択しました。

新しい仕組みの構造

ここからは、新しい仕組みの話になります。(スライドを指して)新しいアーキテクチャのメインの話は、赤枠で囲ったチャット部分になります。

まず視聴者がデコレーションの設定をすると、LINE LIVEのAPIサーバーにリクエストが行って、LINE LIVE本体のMySQLに設定を保存します。それと同時に、チャットのInternal APIサーバーに設定情報を渡します。ここまでは従来の方法と一緒です。

次に、設定情報を受け取ったチャットのInternal APIサーバーは、RedisにPublishします。そうするとListenしているチャットのWebSocketサーバーは、このPublishされた情報をSubscribeして、ローカルメモリにキャッシュします。

このSubscribeする処理はWebSocketサーバー全台で同時に行われるので、結果的に同じタイミングで全台に設定を反映できることになります。

デコレーションの設定を終えた視聴者がコメントを送信すると、チャットのWebSocketサーバーはローカルメモリのキャッシュに保存されている設定を参照するだけで、ほかの全視聴者にデコレーション情報を載せたコメントを送信できます。

現在、LINE LIVEのチャットのWebSocketサーバー120台で運用していますが、RedisのPub/Subを使って全台に同時に設定の情報を同期することで、どのWebSocketサーバーに接続されている視聴者でも、同じようにコメントを表示できます。

新しく設定を同期する仕組みについては以上になります。この仕組みは、流用可能なように汎用的に作ることを想定しているので、次はこの仕組みを流用して、ほかにどのようなことができる想定でいるかをお伝えします。

(スライドを指して)この図はLINE LIVEが今現在、なにかしらの配信を視聴しているすべての視聴者に対して、なにか通知を同時に送ることを想定したユースケースになります。

LINE LIVEには内部の運用者向けにInternal CMSがありますが、このCMSから通知したい情報をサブミットして、LINE LIVEのAPIサーバーに送ります。

通知情報を受け取ったLINE LIVEのAPIサーバーは、MySQLに操作ログとして通知情報を保存しつつ、チャットのInternal APIサーバーに通知情報を送ります。そしてその通知情報を受け取ったチャットのInternal APIサーバーは、RedisにPublishします。

ListenしているチャットのWebSocketサーバーは、Publishされた通知情報を取得して、今現在なにかしらの配信をしている全視聴者に対して、通知情報を送ります。このように通知情報を送ることで、視聴者全員に対してほぼ遅延なく、同時に通知を送ることが可能になります。

例えばこの場合、先着で何かをプレゼントなどの機能も作れるようになるので、サービスの企画の可能性がより広くなっていくと考えています。これで新しい仕組みの流用をしやすいことを理解してもらえたかと思います。

耐障害性を向上させるために実装したもの

設定情報を同期したり配信したりする仕組みについての話は以上になりますが、ここでもう1つ、RedisのPub/Subで耐障害性を向上させるために実装したものがあるので、この機会にその仕組みも紹介したいと思います。

冒頭で少し話しましたが、インフラやネットワークの障害って突発的に発生するから大変ですよね。頻度こそ低いですが、ハイパーバイザーの障害でRedisノードが影響を受けたことに対して、クライアント側でのフェイルオーバーがうまく動作しなくて、Pub/Sub機能が15分ほど機能しなかったような事例も私たちの中にはあります。

このようなことが発生すると、就業時間中でも休日でもてんやわんやで、かなり大変になります。ただ、ある程度それを見越した作りができていれば、自動復旧できて安心感が増しますよね。そもそも大変な状態のところで少しでも手数を減らせるのは、うれしいことだったりします。

そこで、私たちはSubscriberのコネクションに異常を検知したら自動で復旧する仕組みを作りました。その説明に入っていきます。

(スライドを指して)図のように、複数のSubscriberがRedisにListenしている状態を想定します。LINE LIVEのチャットシステムでいうと、WebSocketサーバーがRedisにListenしている状態ということです。

ここで例えば2台目のSubscriberのコネクションが、なにかしらの理由でListenはしているけれど、RedisからPublish情報を受け取れない、半分フェイルした状態とします。

先ほど話したハイパーバイザーの障害の実例や大規模な障害ではなくても、例えば運悪くサーバー1台程度に対して小規模なネットワーク障害や不安定になることはそれなりの可能性があるので、考慮しておくに越したことはありません。今回は、わかりやすくするために1台がフェイルした状態でお話しします。

この状態の場合、2台目のSubscriberは一定時間Subscribeできない状態が続きますが、そうなったら自身でpingするようにPublishします。

正常にListenできているなら、そのpingをSubscribeできるはずです。ただ、2台目のSubscriberはpingをSubscribeできません。

そこで、自身でpingした状態からSubscribeできないことを検知できるので、それを検知したらRedisに再接続して、正常なListen状態に戻します。これで自動復旧した状態となります。耐障害性に関して工夫した話は以上になります。

新しい仕組みを実装した結果

本日の話は以上となりますが、最後に新しい仕組みを実装した結果をお伝えします。

まず、新機能を含めてユーザーからの評判はなかなかよいものでした。そして、今のところ大きな障害もなく運用できています。また、将来的には私たちはこの仕組みを流用して、新しい機能をすばやく実装していく準備が整いました。もしよいと思っていただけたら、参考にしていただけると幸いです。

これで本日のセッションは終了となります。ご清聴ありがとうございました。