本記事は オルトプラス Advent Calendar 2023 の12/20の記事です。
はじめに
はじめましての方は初めまして。
初めてじゃない方はおはようこんにちわこんばんわ。
オルトプラスの自称賑やかし担当兼サーバーサイドエンジニアすがやです。
今回は同時接続の負荷試験を実施した時の体験談に関しての記事になります。
仕様や構成等
今回のゴール地点は最大8000人が同時接続できる汎用的なロビー機能になります。
この8000人はランダムに移動を繰り返したりアクションを起こしたりします。
仕様ツールは下記の通りです。
内容 | 採用しているもの |
---|---|
環境と言語 | Node.js + TypeScript |
負荷試験ツール | k6 |
インフラ | AWS環境 |
上記を用いて、nodejsのインスタンスを複数作成し、ALBで接続先をある程度分散させ、Redisのpub/subの仕組みを用いて、ユーザーがどのインスタンスに接続しても座標等の共有が可能なようにしました。
実際に負荷試験をかけて起きた主な不具合
- エモートを1回送信すると、複数回エモートが受信される。
- 負荷試験中に突然止まる。
- エモートを送信してもすぐに受信されない。
- 画面がカクツク。
☆原因と解決の道のり
1. エモートを1回送信すると、複数回エモートが受信される。
こちらの現象はローカルの環境では確認できず負荷試験環境でのみ確認することが出来ました。
エモートは複数種類あるのですが特定のエモートではなくランダムに現象が起きました。
この現象が起きる場合と起きない場合がありました(こういうの困る。本当に困る)
そこでログを確認し送信が1回であることを確認しました。
受信側のログで複数回出ているログを確認しました。
色々試した結果インスタンスが複数ある場合に怪しいのではないかと仮説を立てました。
実際のところ起きていた現象と手順は以下になります。
1. ルームにAさんが1番のインスタンスから接続します。
2. Bさんが2番のインスタンスから接続します。
3. Aさんがエモート送信します。
この手順を行うと仕組み上1番のインスタンスと2番のインスタンスそれぞれでエモート情報を受信していることが分かりました。
そのためBさんはエモートが2回受信されるという不具合につながります。
プログラムで該当箇所を確認ご修正して解決しました。
2. 負荷試験中に突然止まる。
こちらもローカルでは再現できませんでした。
ただ負荷試験環境で試したりログを確認しているうちにトリガーがあることに気づきました。
ダミーユーザーの接続が切れたタイミングで動かなくなっているかもしれない。
原因となる接続を切ったタイミングでログを追加して確認したところ切断時の処理に原因を発見しました。
socketList.splice(index, 1);
socketListには接続者一覧の情報があるんですが誰かが切断すると削除されてしまうようになってました。
切断時の処理に関して別のPJのコードを流用していたのですが、運用実績のあるコードとして扱っていたのでなかなか見つかりにくかったですね。
3. エモートを送信してもすぐに受信されない。4. 画面がカクツク。
こちらに関してはどこかのタイミングで負荷が高すぎるため起きている現象なので試験方法を変えるところから検証を始めました。
高負荷を解決するまでの道のり
不具合の修正後は高負荷状態を解決するための試行錯誤が始まります。
今までは一度に8000人のダミーユーザーを追加していたのですが、200人程度から動作確認することから始めました。
が…
200人の同時接続すら実現していないことがわかりました。
なので最初の目標としてまず200人の同接を目指すことにしました。
原因としては一部データの送受信時にpub/subを経由するのに時間がかかっていました。
これを解決するために試したことは以下の通りです。
-
負荷試験環境のサーバースペックを上げる
⇒特に効果なし -
Redisのインスタンスを1台から10台に変更してpub/sub経由を分散させる
⇒AWS側を色々確認して見ると使用しているpub/subで経由するRedisのインスタンスが偏っていそうでした。
しかし、理由がわからなかったためこちらはAWSに問い合わせをし、回答を要約するとwebsocketの仕様のようでした。
この偏りについてはドメイン+ALB+ECSタスクの組を増やして、ある程度アプリ側で接続先を均等化することで解決します。 -
まだまだ解決しなさそうなので一度Redisを経由しない場合どれだけ違いが出るか検証をしました。
理論上はRedisを経由しなければ最速で座標等の情報が共有がされるはずだからです。
⇒そこまで違いは出なかったのでRedisが悪いわけではないことが分かりました。 -
送信&受信データの確認をしました。
負荷試験時は秒間8回の座標データを送っていたのですが一旦秒間1回に変更しました。
これで200人が問題なく動くことを確認できました。
⇒ということで通信データを精査することにしました。
ちなみにこの対応で2000人が同接可能な状態で、目標まであと6000人です。 -
送信データの精査
よくよく送信データを観察すると座標の移動がないのダミーユーザーの座標も送っていました。
そのため座標が変わらない場合は送信しないように対応しました。
また、setTimeoutを使って送信に関しては非同期に実行するようにしました。
⇒この改修で6000人まで接続可能になり、目標まであと2000人です。 -
Clusterモジュールの導入
色々インターネットで色々調べているうちにClusterモジュールの記事を見つけたのでこちらの導入を行いました。
劇的な変更が見られました。
⇒こちらの対応で8000人を達成して目標クリアしました
全体を通じた感想
一部の不具合は運用実績のあるコードを信用したため発見するのが割と困難になりました。
流用することによって工数を削減できるのは確かなのですが、今回の様なこともあるため、今後も気を付けていきたいなとは思います。
負荷試験で使用したk6というツールを今回初めて使用しました。
今まであまり負荷試験自体作業として携わったことがないため、良い経験になったと思います。