2023年4月に開催されたINIAD meets web
せっかくブログをちゃんとしたもので作ったので、まとめてみました。
INIAD meets webって何?

オンラインでやるサークル主催の新入生歓迎会です。
コロナ禍で、対面での活動が制約される中、オンラインでサークルへの勧誘活動ができないかという背景から開催され、今年で4回目のイベントです。
昨年までは、事前収録や編集を行なった上で、それを流すだけにしていましたが、今年は大学内のホールから生配信ということで、事前収録とはまた違った双方向性を持たせることができました。
meets web全体の方針
INIAD meets webは昨年までDjangoを用いて作られていましたが、個人的に以下の理由で嫌いです。
- django-adminがリッチすぎる割に、UIが絶望的にダサい
- 動的型付けなので保守性が低く、(心理的な)メンテナンスコストが高い
技術レベルの平均がそこまで高いチームではなく、コーディング規則などもないので、少なくとも他の学生の書いたコードを保守する自信は微塵もない - そもそもPython遅い
コロナ禍がやや下火になり、今年は対面での活動もできるようになってきてはいるものの、大学のホールを借りて行う新歓イベントですから、言うまでもなく失敗できません。
1から書き直す方針になりましたが、それならとGoを採用しました。
また、部分的ですがマイクロサービス化を行い、スケーラブルなシステムを構築することで、システムの安定稼働を実現させる方針としました。
meets webのバックエンド
前述の通り、meets webは(部分的に)マイクロサービスを意識して構成しました。
API部分は大きく分けてこちらの3つ
イベント管理API (Event Manage API)
参加団体の情報に関するAPIです。
例えば、参加団体の名称や説明文、アイコン画像といった情報を収集します。
内部では後述する放送管理APIと併せて「emapi(イーマピ)」と呼んでいたりします。
この情報は、放送中に表示する団体紹介にも使用しており、このAPIを用いてCRU(Dは未実装)を実現しています。
また、参加団体に向けて質問を送る機能があるのですが、これに対して文章で回答したい場合もこのAPIを使用します。
放送管理API
本番の放送状態に関するAPIです。
例えば、サイト自体の開閉や、現在の配信状況(どの参加団体を紹介しているか)といった情報を管理します。


また、HLSの配信URLもここで設定する他、放送中のコンテンツとして、大学に関連するクイズや、視聴者アンケートといったコンテンツの管理・配信管理も行なっています。


WebSocket API
視聴者と双方向につながるために、WebSocketを用いて情報のやり取りを行うAPIです。
こちらは内部では「wsapi(ダブルサピ)」と呼んでいます。(ネーミングセンスのNASAが光りますね!)
視聴者から参加団体に向けた質問を送ったり、それに対する回答を受け取る他、放送状態の変化といったものも視聴者向けに配信しています。
API間連携
大きく3つに分かれたAPIですが、イベント管理APIと放送管理APIは、多くの情報を共有する(団体の情報など)という都合上、1つのプログラムとして動かすことの方が都合が良かったため、実際には2つのプログラムが連携しているという形になっています。

一方で、wsapiはプロトコルもさることながら、そもそも管理系のAPIとは役割が大きく異なります。
スケーラブルなシステムを構築する上で、役割の違うシステムはなるべく分離することで、ダウンした際の全体への影響を少なくすることを目指しました。
分離の方針として、「このAPIを主に使う対象」を基準として分離を行いました。
その結果、参加団体や主催者のような人が主に使う「イベント管理API + 放送管理API」と、視聴者が主に使う「WebSocket API」という2つのシステムにまとまりました。
wsapiに送られた質問のリクエストは、gRPCを介してemapiに送られて、データベースに書き込まれます。
emapiでデータを保存している理由としては、この質問に対して参加団体が回答するのは「イベント管理API」を用いているためです。
一方で、放送管理APIで状態変更が行われたり、質問に対して回答が行われると、RedisのPub/Subを用いてwsapiに情報を流し、wsapiから各クライアントに向けて状態変更を配信しています。

emapi→wsapiでgRPCを用いなかった理由としては、wsapiも複数台のコンテナにスケールしており、全てのコンテナに対してgRPCのコネクションを張るという実装や動作が負担になると判断したためです。
このような分離を行うことにより、なんらかの理由でemapiを放送中にアップデートしなければならなくなった場合であっても、WebSocketの通信に影響を与えることなく行えるようになりました。
成果と課題
このように、そのシステムが対象とするユーザーを基準に(部分的に)マイクロサービスとすることによって、柔軟な負荷分散を実現することができました。
大体この手の配信企画は「そもそもトラブルで時間通りに始められない、途中で何か手痛くトラブる」がいつもの経験だったのですが、今回は時間通り・大きなトラブルなしで、つつがなく終了できたことにとてもホッとしています。
個人的には、Goを書いていて「エラーを拾いたくなる」気がしており、これまで握り潰したりしがちであったエラーについても、結果的にほぼ全てに対してエラーキャッチを実装することができたので、結果的に安全なAPIが出来上がったのではないかと思っています。
一方で、イベント管理APIと放送管理APIが分離できなかったことが悔やまれます。
あの後実装のアイディアがやや思いついた気がしたので、来年のmeets webに向けて分離を進めていきたいかなとも。
Goという学内で使っている人が少なさそうな言語をチョイスしてしまったので、育成に力を入れていきたい気がしています。
まとめ
今年で4回目となったINIAD meets webは、「安定」を何よりも重視した挑戦をベースに開発した結果、派手に成功したわけではないのですが、「つつがなく終わる」という結果とすることができました。

実はこれまで「紆余曲折あったり、手痛くトラブるけど、最後はド派手に成功する」みたいなことの方が多かったので、この終わり方はやや新鮮でもありました。
WebSocketの接続履歴を元に、視聴者に関するメトリクスを収集していたのですが、(具体的な数字を挙げるのは控えるものの)新入生の大体2人に1人程度が観ていた計算になっています。
構成ガチった割には少ない?やや悔しいです。