※資料内の参照リンクを選択し閲覧する場合は、ダウンロードをお願いいたします
\積極的に技術発信を行なっております/ ▽ Twitter/COLOPL_Tech https://twitter.com/colopl_tech
▽ connpassページ http://colopl.connpass.com
▽ COLOPL Tech Blog http://blog.colopl.dev
リアルタイムサーバーチームが初の大規模ローンチに向けてやってきたこと株式会社コロプラ R.Y.
View Slide
氏名 :部署 :2自己紹介● 2020年4月新卒入社● Prizm(内製リアルタイムフレームワーク)チームに所属R.Y.技術基盤本部第2バックエンドエンジニア部
3● 少し前に Prizm チームが開発に関わったゲームがリリースされました○ 内製リアルタイム通信フレームワーク Prizm を用いた PvP○ Open Match という k8s native な OSS を利用したマッチメイキング● Prizm(リアルタイム通信フレームワーク)を開発しているチームのメンバーとしてゲームのリリースに向けてやってきたことを話します○ リリースに役立った Prizm の機能○ 負荷対策、負荷試験で得られた知見今日話すこと
4リリースに役立ったPrizm の機能
Prizm とは● コロプラで開発している対人や協力などリアルタイム性のあるゲームのためのフレームワーク○ サーバー(Go): いわゆるリアルタイムサーバーを作るためのフレームワーク○ クライアント(Unity): リアルタイムサーバーとの通信の基盤ライブラリ● サーバーを介してクライアントがメッセージを送り合うことを想定した作り○ 物理演算などはしない5Room A Room B Room Cこんなイメージ
● 複数のプレイヤーをルーム単位でまとめ、ルーム内でメッセージを送り合う○ クライアント→サーバー / サーバー→クライアント の両方の経路が存在■ サーバーから送信者以外のクライアントに受信したメッセージを送信することでリレーサーバーを実装できる● サーバー側に任意のロジックを実装○ メッセージを受信した際のメッセージハンドラー○ 一定時間後に処理を実行(Go 言語の time.AfterFunc のような機能)● ……and more!詳しくは2022/03/17の発表をご参照ください6Prizm の基本機能今回はリリースにあたって特に役立った機能やリリースを受けて開発した機能を紹介
● Prizm のクライアントに通信の遅延・ロスをシミュレートする機能を実装○ メッセージを送信する前やメッセージを受信した後にライブラリで処理を挟む○ アプリ的にはネットワーク側の遅延・ロスと同様の挙動に見える7通信の遅延・ロスをシミュレートする機能アプリ実装ライブラリライブラリが送信前や受信後に遅延やロスを挟む送信されたパケットが遅れて届く / 届かなくなる受信したパケットが遅れて届く / 届かなくなるクライアント Prizm サーバー
● モバイル向けのゲーム⇒遅延・ロスが発生しやすい環境を想定する必要がある○ 開発中は環境の良い社内ネットワークを使うため問題を認識しづらかったりデバッグが難しかったりする● 開発アプリで開けるデバッグメニューから設定できるようにしてもらったので Prizm チームでも動かせた● リリース後に起きた問題の調査でも使ってもらっていた8通信の遅延・ロスをシミュレートする機能
● Prizm ではリアルタイム通信を実現するためコネクションを張り続けている○ TCP ではコネクションを張りっぱなしにする■ 送信元、送信先の IP:Port と IP 層のプロトコル番号で区別している○ UDP はコネクションレスなので送信元を IP:Port の組で区別して擬似的に実現● しかしこのようなコネクションはゲームの途中でかんたんに切れてしまう○ Wi-Fi ↔ モバイル回線の切替○ モバイル回線での基地局のハンドオーバー○ Wi-Fi ルーターの調子が悪くて通信が瞬断○ ……● 開発中にコネクションの切断が問題となったのでライブラリ側にコネクションが切断してもセッションを維持する機能を実装9コネクション切断への対応
● TCP や UDP のコネクションが切れたときにも内部的に再接続してPrizm レイヤーのセッションを維持する仕組みが実装されている○ ゲーム開発者はL4のコネクションを気にしなくて良くなる● 切断している間送れなかった Reliable なメッセージを再送する機能も実装○ Unreliable なメッセージはロスしたものと扱うことにして再送しない10セッションを維持する機能再接続の要求が来たぞメッセージを送り直してあげようコネクション切れてそうだから再接続しようメッセージも送り直そう
● コネクションの再接続に時間が数秒程度かかる○ コネクションの切断をはっきりと取れない場合があり ping を送り合って状態を推測○ 切断したと判断するまでの猶予時間を何秒か持つ必要がある■ たまたまネットワークの調子が悪い場合を排除○ リリースしたゲームはターン制のゲームなので数秒の猶予は許容できた■ アクション性の高いゲームだと他の対応を考える必要がある11セッションを維持する機能の問題点ping が来るはずなのに来ないな……コネクション切れてそうping を送ったのに返ってこないな……コネクション切れてそう
● マルチプレイゲームにおける通信頻度はできるだけ最小化したい○ インフラコストが減る○ クライアントの消費電力が減る● しかしゲーム開発している中で通信頻度を意識することはとても難しい○ 負荷対策より PoC(面白いゲームになるかの検証)を優先○ 開発環境(社内ネットワーク)では問題が顕在化しづらい○ とりあえず OnUpdate() で送信すれば動く● 通信頻度をリリース直前に減らすのは大変なので計測する仕組みを強くした○ 意外な理由で通信量が増えることがあるのでそもそも気をつけるより計測した方がいい○ 通信頻度を落とせない仕様は早めになんとかしたい○ フレーム間の補完など追加の実装が必要な場合もあり早期発見が大事12通信頻度の計測
● サーバー: クライアントが通信しすぎていたらワーニングログを出す機能○ クライアントごとの秒間リクエスト数(Request Per Sec, RPS)を計測し一定の値を超えたらログ出力○ しきい値はデフォルト10RPSだが設定で変更可能○ 詳細よりも大まかな傾向を掴むためのもの● クライアント: 通信周りのプロファイルを取得・表示する機能○ メッセージごとの通信回数や頻度○ ↑を表示する Unity の拡張○ 複数のクライアントが繋がるサーバーでは取得・表示しづらい詳細な情報をハンドリングできる13通信の頻度を計測する仕組み
● 通信の遅延・ロスをシミュレートする機能○ 遅延・ロスが発生しやすいモバイルゲームでは特に重要● セッションを維持する機能○ L4のコネクションが切断されてしまう現象への対応● 通信頻度を計測する機能○ マルチプレイゲームの実装で重要だが気をつけるのが難しい点への対応14Prizmの機能まとめ
15負荷対策・負荷試験で得た知見
● リリースに向けて実施する負荷試験の一部としてマルチプレイ部分に注目した負荷試験も実施した○ Prizm サーバー、Open Match を含めた試験を実施し負荷に耐えるかを検証■ Open Match: レートマッチングなどに向いた k8s native なマッチメイキング OSS16マルチプレイの負荷試験DBなどAPI serverPrizm server Open Matchbotここも試験!!
● 負荷試験では API やリアルタイム通信のメッセージをランダムに叩くのではなく、実際のユーザーに近い挙動で試験したい○ マルチプレイの負荷試験の目的はマッチング→ゲームプレイ→マッチング→……のサイクルをプレイヤーが問題なく遊べるかを検証すること● そのためユーザーの動きに近いシナリオを実装する必要があった○ マッチング中にマッチングを再度開始しないようにする○ 相手のターン中に自分が行動したときのメッセージを送信しないようにする17マルチプレイの負荷試験
● 負荷試験を実施する中でいくつか苦労したポイントがあるので紹介○ 負荷試験 bot を実装する過程で出会った課題○ 負荷試験を実施する過程で出会った課題18負荷試験で苦労したこと
● シナリオを作成するために実施したログの収集方法が原始的すぎた○ Unity にログを仕込む→ログを awk する→更に手で不要な情報を消す といった流れ■ 他のメンバーに作業を依頼するときに伝える情報量が増える■ 本質的ではないポイントでつまづく● Prizm サーバーにメッセージ受信時に共通で呼ばれる処理をフックできる機能を実装し GCP Cloud Logging でログを収集できるようにした○ サーバーにログを仕込む→Cloud Logging でログを絞り込む といった流れで通信の流れを追えるようになった■ 他のメンバーに作業依頼するときも Cloud Logging のクエリだけ共有すれば OK○ 本当にうまく機能するかは次の負荷試験で検証予定だがリリースに向けて必要な機能を発見できたのは収穫と感じた19シナリオを作成するためのログ収集について
● マッチング / インゲームや自ターン / 相手ターンといったゲームの進行状態を負荷試験 bot で再現し適切に動作させるのが大変だった○ 状態遷移図を書いてから実装するなどで対策はした■ ターン制のゲームだったこともあり正常系の整理はうまく行った● 正常系の実装より異常系のハンドリングが大変○ マッチングに失敗した後の挙動○ 対戦相手の通信が不調になって待ちがタイムアウト○ メッセージが届く / 届かないに依存して遷移が変わる際の挙動■ e.g., 「移動した」の後「目的地に到着した」が送られてくる場合■ 「目的地に到着した」が遅れて届いた場合次の受信待ちのタイミングで「目的地に到着した」だけが送られたように見えるケースがある● 正常系だけ考えていると「移動した」だけを待ってしまう20負荷試験 bot のゲーム進行状態のハンドリング
● 負荷をかける規模を大きくすると動かなくなる○ 異常系の想定が甘くて想定していない状態・動作になってしまう■ botのデバッグは本質ではないので減らしたい……○ 規模を大きくするたび問題が出てくる可能性を想定してスケジュールを組む必要があると感じた■ コスト的にも問題の切り分け的にもいきなり本番想定規模の試験は動かしづらいのも難しいポイント● マッチングがうまくいかない○ インゲーム・リアルタイムサーバーの問題よりマッチングの問題を解消する時間の方が長かった印象がある○ マッチングも規模を大きくするたび問題が出てくる可能性がある○ Open Match を使う場合はマッチングロジックがボトルネックになってないか注意21負荷試験を実施する過程でよく出会った課題
● 負荷試験を実施したところ、実施前には気づかなかったがボトルネックとなっていたポイントがあったので紹介22負荷試験をやってみて分かったこと
● Pod に割り当てるリソース量を決定するために1Pod あたりにかかる負荷を増やしていたとき、Pod 単位ではリソースに余裕がある場合でも Node 単位ではリソースが逼迫していることがあった○ リアルタイムサーバーの処理負荷が小さかったこともあり Pod へのリソース割り当てを小さくしていたので1台の Node に多くの Pod が乗った○ Node 単位で捌くリクエスト数が多すぎたりコンテキストスイッチがたくさん発生したために Pod より先に Node のリソースが逼迫したのではないかと考えている● サーバーのリソース調整の際は Pod だけではなく Node 単位でもリソースの使用状況を確認する必要があることが分かった23リソースの割り当てについて
● Prizm では TCP の暗号化に TLS を用いており接続時にハンドシェイクを実行● 負荷試験の結果この TLS ハンドシェイクが思った以上に重いことが分かった○ 断続的に bot で計測している ping の RTT が遅くなるケースがあった■ 99.9%値で50ms → 15,000ms○ リソースは余っているように見えたがサーバーの立ち上げ直後に CPU usage が高くなる傾向があった○ Cloud Profiler を見たところ TLS ハンドシェイクの負荷が高いことが分かった■ ゲームの処理が軽くメッセージ頻度も小さいため相対的に負荷が高くなった● TLS ハンドシェイクの負荷に合わせたスペック調整やプレイヤーの流入ペースの調整によって対策した○ ハンドシェイクがたくさん来るとつらい→ユーザーの流入ペースが減ればハンドシェイクを同時に扱う量が減る24TLSハンドシェイクについて
● マルチプレイゲームを問題なくプレイできるかを確認するための負荷試験を実施した○ 実際のゲームプレイに近いシナリオを用意● 負荷試験の準備、実施の過程でいくつかの問題や知見があった○ ログ収集や異常系のハンドリングなど○ 規模が大きくなることで bot の挙動やマッチングがうまくいかなくなる● 負荷試験の結果意外なところに落とし穴があったのが分かった○ Node 単位での負荷○ TLS ハンドシェイクの負荷25負荷試験周りのまとめ
● ゲームづくりにもっと強く関わっていきたい○ 今までは Prizm というリアルタイム通信の基盤を作る役割が強かった○ ゲームのリリースが近づくにつれてマルチプレイゲームを作るには基盤だけでなくアプリの仕様や実装も難しいし大切だということを感じた■ マルチプレイゲームの難しさをなんとかハンドリングするアプリの作りが必要● Prizm チームそのものが基盤を作るチームからマルチプレイ全体をいい感じにしていくチームに意識を変えていきたい○ 自他ともに「リアルタイム通信の基盤を作るチーム」「リアルタイムサーバーのチーム」といった認識になっていた○ ゲームづくりにもっと強く関わるためにこの認識を変えていきたい26終わりに: ゲームのリリースに向き合って考えたこと