はじめに
どうもこんばんは!ゆうだいです!
Streamerioアドベントカレンダー4日目です!今日も元気にやっていきます!
さて、では改めてですが
まず最初にですが、Streamerioってなに?という方は一日目の記事をご覧ください!
Streamerioってどんなゲーム?
また興味を持ってくれた方はこちらも見ていただけると嬉しいです!
同時接続300人に耐える技術!フロント編!
そして今日は同時接続300人に耐える技術!サーバー編!#2ということで#1が気になることはこちらをご覧ください!
さて、では始めていきます。
筆者は記事を書き慣れていないので読みづらい部分も度々あるかと思われます。
気になる点があれば、コメントやフィードバックをいただけると嬉しいです。
また今回工夫点と書き紹介している内容ですが、経験のあるエンジニアの方なら当たり前だよと感じる内容かもしれません。お手柔らかにみていただけると幸いです。
同時接続300人に耐える技術!サーバー編!#2
昨日に引き続きですが、サーバーのお話をしていきます!
現状の構成はこちらです!

さて、昨日書いた通り現状の構成には問題があります。
まずこちらをご覧ください。こちらは技育展の発表の朝にチームメンバーから送られてきた写真になります。

なにかがリミットに達している画像ですね。
ぱっとこれがなんなのかわかる方は少ないと思うので早々にネタバラシをすると、今回Redisのデプロイを行っているUpstashの利用枠が尽きたという画像になります。
今回学生のみのチームということでスポンサーがいるわけでもないし、できるだけお金がかからない構成で行こう!ということでUpstashというRedisがデプロイでき、無料枠があるサービスを使用することにしました。(他の技術選定もできるだけお金がかからない!というのが基準です。)
こちら大体コマンド利用の無料枠が一ヶ月50万となっています。それが超えてしまったという感じです。当日はなんとかなったのですが、だいぶまずいです。
ということでどこにリクエストがかかっているのだろうかということで、原因調査をすることにしました。
原因調査
想定は技育展当日の発表最中のデモ約2分間でどれだけ使ったかというのを考えます。
あのときの大体の目安を振り返ると以下になります。
- 視聴者数: 約140人
- 秒間リクエスト: 約90/s
- 合計リクエスト数: 90*120=10800
- 秒間ボタン押された数: 約400/s
- 合計ボタン押された数: 累計約4万程度
こんな感じです。
ではUpstashへのリクエストが発生するタイミングを洗い出します。
上のシステム構成図から見ると発生するタイミングはEventCountとPub/Subの利用のタイミングです。
詳しく見るとEventCountは視聴者全体でのボタン押された回数をカウントするものなので、Webフロントからのリクエストが来る度にリクエストが発生しています。
処理を見るとRedisに関わる処理はこのあたりが動きます。
- 視聴者数取得、更新(2コマンド)
- イベントのカウント取得、更新(2コマンド)
ざっとコードを読み返しただけなので抜けがあるかもですが、だいたい1リクエストのたびに4程度呼ばれている感じです。
またイベントの数が視聴者全体でのしきい値を超えるとPub/Subへの発火が起こるのですがここはしきい値が一定ではないのでだいたいでゲーム全体で2000程度起こったのではないかと想定しています。
ではまとめると、
10800(合計リクエスト数) * 4 + 2000でおそらく42000程度はあの発表のタイミングで起きていたのではないかと試算しました。
さて、リミットについて改めて思い出します。そう50万です。
つまりこの規模でゲームを行うとおそらく12回も動かせば月の制限が来てしまうという状況になっているわけです。
これはあまりよろしくないです。だいぶ。
サーバー耐える!とかいいながらでも12回しかできないけどね、なんて言いたくないですし。
問題の解決のための検討
ではこの問題を解決しなければならないとなったので、解決策を暇なタイミングでずっと考えていたので話します。
まず今回ネックになっているのは、視聴者からのリクエストが飛ぶたびにRedisにコマンドが飛ぶことです。しかも4回も。
この前のデモだと毎秒360コマンドが飛んでいたことになります。そりゃすぐ尽きますね。
ということで、前回の記事ではサーバに状態を持たせたくないと話しましたが、仕方がないのでHTTPサーバに多少の状態を持たせることにします。
具体的にはグルーバルな変数を用意し、現在Redisにもたせていた役割を一時的にもたせます。
そして次にですが、そのままだとスケーリングした際にうまくカウントが取れなくなるので、一定間隔ごとに同期をさせることにします。
同期には今までは過労死寸前だったRedisを使います。具体的な実装のイメージだと、サーバー起動のタイミングで毎秒程度の頻度でRedisにアクセスを行い変数の内容を同期させるゴルーチンを作る感じです。
これによりリクエストの度に4回起動されていたコマンドが1秒間に1回まで落ちるはずです。つまりデモのときを想定すると360/sがなんとびっくり2/sまで落ちるはずです。(あのときは2台のサーバが起動していたため2/s)
あとこれは希望ですが、リクエストの度のRedisアクセスが消えるのでレイテンシもがっつり改善されないかなとも思っています。
さて、これがもししっかりと実現すればだいぶリクエスト問題は改善されるはずです。
ただこれだけだと満足できなかったのでもっと突き詰めます。
次のターゲットはPub/Subです。
あのときはだいたい2000程度Unityへのイベントが発生したのではないかと考えているので秒間でいうと20/sはコマンド起動が発生したはずです。
HTTPサーバが頑張って2/sまで落としたのでこちらも甘えていたら駄目です。
ということでWebSocketサーバは1秒毎にRedisにイベントが何回発生したかというのを確認するという形にすることにしました。
これにより20/sが1/sまで落ちるはずです。
まとめ
多分最終構成はこんな感じになるはずです。

(※DBサーバも正直毎度リクエスト送るのいらなくね?となったので消しています。)
この構成でいくとあの時380/s発生していたコマンドの起動数がきっと3/sまで落ちるんじゃないか!と思っています。
これにより月に12回しか遊べなかったものが多分1500回ほどまでいけるんじゃないかと考えています。まるで魔法です。
ただまだこういう構成はどうかな〜とふわっと考えただけで、現状のRedisのデータの入れ方などとガラッと変えないといけないので、詳細部分は変わる可能性があるし、なんなら実現しない可能性だってあります。
そのあたりはメンバーみんなで頑張っていこうかなという感じです!
おわりに
さて、本日の記事は以上となります!
今回は構想をお話しただけなので全然穴がたくさんありそう!と思っています、何か気になった部分や指摘事項があれば教えていただけるととても嬉しいです!
明日は「DBで苦しんだこと」を書いていく予定です!
それではとても寒くなってきているので体調にお気をつけください!