こんにちは。ヤフーで社外向け技術イベントの開発責任者をしている今谷です。今回の記事では、2022年2月3日・4日に開催された「Yahoo! JAPAN Tech Conference 2022」の公式サイトや配信サイトをつくる上で採用した技術や開発の裏側についてご紹介します。
Yahoo! JAPAN Tech Conferenceについて
Yahoo! JAPAN Tech Conferenceは、Yahoo! JAPAN のエンジニアやデザイナー、データサイエンティストなどさまざまなクリエイターが発表・講演を行う技術カンファレンスです。5回目の開催となった今回のカンファレンスは昨年と同じく、オンラインのみでの開催となりました。そのため、カンファレンスの公式サイトのほか、カンファレンス当日に参加者がセッションを視聴するための配信サイトの開発を行いました。
開発の期間や体制
カンファレンスを主催するDeveloper Relationsから依頼を受け、SWATが開発協力を行いました。SWATとは、特定のサービスに所属せずに全社横断で技術支援を行っているチームです。技術的に高難易度な課題や緊急を要する課題を解決することをミッションとした開発組織で、全社的に重要度が高く緊急性のある案件に幅広く対応しています。
Developer RelationsとSWATの双方のチームに所属する今谷がプロジェクトマネージャーとなり、SWATチームから開発に参加した3名のエンジニアと共に開発を行いました。2021年10月から案件がスタートし、2021年12月頃から本格的に開発が進行していきました。
システムの全体構成
こちらが今回採用したシステムの構成です。配信サイトのフロントエンドにはReact、Next.js、TypeScriptを採用しており、バックエンドにはJava、Spring Bootを採用しています。管理者用の社内ツールも提供しており、こちらもReact、Next.js、TypeScriptを採用していました。それぞれ詳しくご紹介します。
工夫したこと
管理者ツールの提供について
まず、管理者ツールについてご紹介します。今回のために開発した社内ツールで、参加者の事前登録数をデイリーで集計したり、カンファレンス当日のセッション評価・アンケート・スタンプといった集計データをCSVでダウンロードしたり、参加者からフィードバックや質問・感想をリアルタイムに確認できる機能を備えていました。
そのほか、現在配信されているセッションのタイトルや概要といった情報をリアルタイムに切り替えたり、運営からのお知らせを掲出できる機能も実装しました。こちらは技術的な内容を含むため以降で詳しくご紹介します。
このツールは開発メンバー以外のカンファレンスを運営するメンバーの利用も想定していたため、ツールにログインしてデータを表示できる権限とツールを操作してデータの更新ができる権限を分けて設計していました。開発メンバーは日時の集計をする必要がなくなり開発業務に集中できましたし、運営メンバーもデータの集計・分析が任意のタイミングでスムーズに行えるようになりました。
開発工数と技術選定について
今回のカンファレンスでは、参加者に向けて以下の機能を提供していました。
- 現在放送されているセッション情報のリアルタイム切り替え
- セッション間の休憩時間に回答できる、セッションの5段階評価
- セッションに対するスタンプの送信
- 他の参加者が送信したスタンプを各参加者の画面にリアルタイム描画
- 送信されたスタンプの合計数のリアルタイム更新
- スタンプ画面アニメーションの非表示機能
- セッションに対する質問・意見の送信
- 終了したセッションをタイムテーブル上からグレーアウト
- イベント全体に対するアンケート回答
参加者は配信を視聴している途中でブラウザーをリロードすることなく最新の情報がリアルタイムに確認できることが必須であったため、実装にいくつか工夫が必要でした。
開発内で検討を進め、RedisのPub/Sub機能とSSE(Server-Sent Events)を採用して設計する方針としました。管理者ツールからRedisのPub/Sub機能でリアルタイムにFrontendインスタンスにデータを送信し、SSE接続を介して、各ブラウザーが持つローカルステートを更新する流れです。
一般的にこのような「リアルタイムに」「複数の対象にデータを流す」が目的のケースでは、RedisのようなNoSQL製品もしくはメッセージキュー(MQ)製品を採用することが多いと思います。RDBMSではNoSQLやメッセージキューに比べて、書き込み・読み込みに関する性能の考慮や、読み込みの際のポーリングが必要になるためです。
そもそも、RedisにPub/Sub機能があること自体があまり知られていないかもしれませんが、このような設計を行ったのは次のような理由からです。まず一番に、リアルタイムに状態を変更したいデータをすべてRedisに集約することで、障害発生時のデバッグや調査にかかるコストを最小にできます。また、共通実装を増やすことで全体の開発工数の削減にもつながります。WebSocketやロングポーリングを採用しなかったのは、今回の要件の一部で社内のプラットフォームとの相性や要件とマッチしなかったためです。
そのような背景もあり、想定される負荷やスケーラビリティ、開発スケジュールや工数をベースに検討を行い、今回はRedisのPub/Sub機能とSSE接続の併用を採用する方針となりました。もちろん一般的なケースではもう少しベターな設計があると考えています。
リアルタイムな状態反映について
現在配信されているセッションのタイトルや概要といった情報を管理するため、以下の情報をRecoilのグローバルステートとして扱いました。また、カンファレンスの状態をステートに持つことで、カンファレンスの開催前後でコードのデプロイなく、トップページの導線や見た目の切り替えを実現していました。これにより、開催直前の大型リリースを避けられましたし、網羅的なシナリオテストを実施することもできました。
- 配信中のセッションID
- 配信の遅延の有無と遅延メッセージ
- カンファレンスのステータス
- カンファレンス開始前
- 開場後
- セッション中
- 休憩中
- 障害発生中
- 閉場後
- カンファレンス終了後
ステートの初期値は画面ロード時にバックエンドのAPIから得た値を正として扱い、ロード以降にあった更新をRedisのPub/Sub機能とSSE接続で受け取った値を正としました。その後、React HooksのuseEffect・useStateでハンドリングを行いprops経由でコンポーネントに渡すことで、リアルタイムな画面更新を実現しました。
スタンプの描画について
スタンプによる演出は見た目も華やかでカンファレンス全体の体験に影響すると考えていたため、特に時間をかけて検討し実装を進めたコンポーネントです。前回のカンファレンスではチャットによるテキストでのコミュニケーションが中心でしたが、今回はより華やかでインタラクティブな演出を目指してスタンプ機能の採用を決めました。せっかく配信サイトを訪れてくれた参加者が、配信を視聴するだけで終わるのではなく、何かイベントを楽しんでもらう要素をつくりたいという強い想いがあり、このような機能を追加しました。
ビジュアルデザインを担当するデザイナーと検討を行い、最終的に5種類のスタンプから好きなスタンプを選んで送信できる仕様としました。参加者がスタンプを押下すると、対応するスタンプ画像が画面上にアニメーションされます。他の参加者が送信したスタンプも、自分の画面上でリアルタイムにアニメーションされます。負荷対策や画面描画について意識して開発をしていた箇所を紹介いたします。
a. 負荷対策について
負荷試験を行い得られたデータを参考値としブラウザー側の負荷対策として、20個以上のスタンプを画面に描画させない制御を入れました。スタンプ機能はその性質上、複数人が同じタイミングでスタンプを連打することが想定される機能です。スタンプが画面いっぱいに埋まってしまうことでレンダリング負荷が増えたり、画面がスタンプで埋まることで肝心の発表が見えなくなってしまうことを防ぐなど、配信を視聴するユーザー体験に影響が出ないような対策を考えていました。またバックエンド側の負荷対策として、Redis側では1秒間隔でスタンプ数を保持しており、RedisのPub/SubとSSE接続によってリアルタイムにブラウザー側にスタンプ情報を渡していました。
結果として、イベント当日には最大84rpsでスタンプ投稿をさばくことができ、2日間のイベントを通して合計で25,000個以上のスタンプを保存できました。また、スタンプの投稿数をスタンプの種類ごとに時系列なデータとして蓄積していたため、どのセッションのどのタイミングでどんなスタンプが増えたかを振り返ることができました。
b. 画面描画について
それぞれのスタンプが独立した自然なアニメーションで表示されるように、スタンプの軌跡はGIF画像ではなくCSSアニメーションで装飾をしました。SCSSのrandom()関数を使って表示の遅延秒数がランダムになるよう指定した上で、より自然な軌跡を描くためcubic-bezier()関数を採用しました。cubic-bezier()はアニメーション中の速さの変化量をベジェ曲線のX軸とY軸を使って数学的に記述できるイージング関数です。これにより、アニメーションの表示や速度に関してより自然な動きを持たせることができました。
また、参加者によってはスタンプ機能より配信の視聴に集中したい方もいると考えていました。そのため、参加者自身でスタンプのオプトアウトができるように非表示ボタンも提供していました。
c. データの送受信について
データの流れは上記の図のようになっています。参加者によってスタンプが押されたとき、POSTリクエストを受け取ったFrontendインスタンスがRedisの値を確認。すでにスタンプがたまっている場合には1秒後にPublishを実行します。ブラウザーとSSEで接続しているFrontendインスタンスでは、SubscribeしているRedisのチャンネルにPublishがあったとき、たまっているスタンプのデータを受け取り、SSEで接続されているブラウザーに対して1秒間分のスタンプのデータを送ります。仮にPublishをする責任のあるFrontendインスタンスがPublishに失敗したとしても、他のFrontendインスタンスが前回のPublish時からの経過秒数を確認し、タイムアウトが起きていた際には代わりにPublishするような仕組みを採用していました。
さいごに
今回のようなシステムは、カンファレンス当日にたくさんのユーザーが利用することに特徴があります。定期的にシステムの改修を行ったり、運用による実績を重ねて改善を繰り返すものではないため、イベントごとの要件に合わせて0から再設計する必要があります。実際に今回提供した機能のほとんどすべては、3カ月かけて準備しても開催中の2日間しか利用されない機能です。かつ、タイトな開発スケジュールの中で、より工数が少なくデバッグもしやすく安定してスケールさせられる設計が求められます。
今回このような状況の中で大きな目立った不具合もなく、カンファレンスにご参加いただいた方からの好意的なフィードバックもたくさん得られて良かったと思います。提供した機能についても、さまざまなカットでの数値計測ができました。次回以降のイベントに向けて、データに基づいた精度の高い機能開発や仕様の検討ができそうです。
今回、Yahoo! JAPAN Tech Conference 2022 にご参加いただいた皆さま、改めましてありがとうございました。また次回のご参加も、心よりお待ちしております。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました
- 今谷 祐通
- CTO室 SWAT / Developer Relations
- 狩野 和博
- CTO室 SWAT
- 栗田 優輝
- CTO室 SWAT
- 藤田 智也
- CTO室 SWAT