Slide 1

Slide 1 text

リアルタイムサーバーチームが 初の大規模ローンチに向けて やってきたこと 株式会社コロプラ R.Y.

Slide 2

Slide 2 text

氏名  : 部署  : 2 自己紹介 ● 2020年4月新卒入社 ● Prizm(内製リアルタイムフレームワーク) チームに所属 R.Y. 技術基盤本部第2バックエンドエンジニア部

Slide 3

Slide 3 text

3 ● 少し前に Prizm チームが開発に関わったゲームがリリースされました ○ 内製リアルタイム通信フレームワーク Prizm を用いた PvP ○ Open Match という k8s native な OSS を利用したマッチメイキング ● Prizm(リアルタイム通信フレームワーク)を開発しているチームのメンバー としてゲームのリリースに向けてやってきたことを話します ○ リリースに役立った Prizm の機能 ○ 負荷対策、負荷試験で得られた知見 今日話すこと

Slide 4

Slide 4 text

4 リリースに役立った Prizm の機能

Slide 5

Slide 5 text

Prizm とは ● コロプラで開発している対人や協力などリアルタイム性のある ゲームのためのフレームワーク ○ サーバー(Go): いわゆるリアルタイムサーバーを作るためのフレームワーク ○ クライアント(Unity): リアルタイムサーバーとの通信の基盤ライブラリ ● サーバーを介してクライアントがメッセージを送り合うことを想定した作り ○ 物理演算などはしない 5 Room A Room B Room C こんなイメージ

Slide 6

Slide 6 text

● 複数のプレイヤーをルーム単位でまとめ、ルーム内でメッセージを送り合う ○ クライアント→サーバー / サーバー→クライアント の両方の経路が存在 ■ サーバーから送信者以外のクライアントに受信したメッセージを送信することで リレーサーバーを実装できる ● サーバー側に任意のロジックを実装 ○ メッセージを受信した際のメッセージハンドラー ○ 一定時間後に処理を実行(Go 言語の time.AfterFunc のような機能) ● ……and more!詳しくは2022/03/17の発表をご参照ください 6 Prizm の基本機能 今回はリリースにあたって特に役立った機能や リリースを受けて開発した機能を紹介

Slide 7

Slide 7 text

● Prizm のクライアントに通信の遅延・ロスをシミュレートする機能を実装 ○ メッセージを送信する前やメッセージを受信した後にライブラリで処理を挟む ○ アプリ的にはネットワーク側の遅延・ロスと同様の挙動に見える 7 通信の遅延・ロスをシミュレートする機能 ア プ リ 実 装 ラ イ ブ ラ リ ライブラリが 送信前や受信後に 遅延やロスを挟む 送信されたパケットが 遅れて届く / 届かなくなる 受信したパケットが 遅れて届く / 届かなくなる クライアント Prizm サーバー

Slide 8

Slide 8 text

● モバイル向けのゲーム⇒遅延・ロスが発生しやすい環境を想定する必要がある ○ 開発中は環境の良い社内ネットワークを使うため 問題を認識しづらかったりデバッグが難しかったりする ● 開発アプリで開けるデバッグメニューから設定できるようにしてもらった ので Prizm チームでも動かせた ● リリース後に起きた問題の調査でも使ってもらっていた 8 通信の遅延・ロスをシミュレートする機能

Slide 9

Slide 9 text

● Prizm ではリアルタイム通信を実現するためコネクションを張り続けている ○ TCP ではコネクションを張りっぱなしにする ■ 送信元、送信先の IP:Port と IP 層のプロトコル番号で区別している ○ UDP はコネクションレスなので送信元を IP:Port の組で区別して擬似的に実現 ● しかしこのようなコネクションはゲームの途中でかんたんに切れてしまう ○ Wi-Fi ↔ モバイル回線の切替 ○ モバイル回線での基地局のハンドオーバー ○ Wi-Fi ルーターの調子が悪くて通信が瞬断 ○ …… ● 開発中にコネクションの切断が問題となったのでライブラリ側に コネクションが切断してもセッションを維持する機能を実装 9 コネクション切断への対応

Slide 10

Slide 10 text

● TCP や UDP のコネクションが切れたときにも内部的に再接続して Prizm レイヤーのセッションを維持する仕組みが実装されている ○ ゲーム開発者はL4のコネクションを気にしなくて良くなる ● 切断している間送れなかった Reliable なメッセージを再送する機能も実装 ○ Unreliable なメッセージはロスしたものと扱うことにして再送しない 10 セッションを維持する機能 再接続の要求が来たぞ メッセージを送り直して あげよう コネクション切れてそう だから再接続しよう メッセージも送り直そう

Slide 11

Slide 11 text

● コネクションの再接続に時間が数秒程度かかる ○ コネクションの切断をはっきりと取れない場合があり ping を送り合って状態を推測 ○ 切断したと判断するまでの猶予時間を何秒か持つ必要がある ■ たまたまネットワークの調子が悪い場合を排除 ○ リリースしたゲームはターン制のゲームなので数秒の猶予は許容できた ■ アクション性の高いゲームだと他の対応を考える必要がある 11 セッションを維持する機能の問題点 ping が来るはずなのに 来ないな…… コネクション切れてそう ping を送ったのに 返ってこないな…… コネクション切れてそう

Slide 12

Slide 12 text

● マルチプレイゲームにおける通信頻度はできるだけ最小化したい ○ インフラコストが減る ○ クライアントの消費電力が減る ● しかしゲーム開発している中で通信頻度を意識することはとても難しい ○ 負荷対策より PoC(面白いゲームになるかの検証)を優先 ○ 開発環境(社内ネットワーク)では問題が顕在化しづらい ○ とりあえず OnUpdate() で送信すれば動く ● 通信頻度をリリース直前に減らすのは大変なので計測する仕組みを強くした ○ 意外な理由で通信量が増えることがあるのでそもそも気をつけるより計測した方がいい ○ 通信頻度を落とせない仕様は早めになんとかしたい ○ フレーム間の補完など追加の実装が必要な場合もあり早期発見が大事 12 通信頻度の計測

Slide 13

Slide 13 text

● サーバー: クライアントが通信しすぎていたらワーニングログを出す機能 ○ クライアントごとの秒間リクエスト数(Request Per Sec, RPS)を 計測し一定の値を超えたらログ出力 ○ しきい値はデフォルト10RPSだが設定で変更可能 ○ 詳細よりも大まかな傾向を掴むためのもの ● クライアント: 通信周りのプロファイルを取得・表示する機能 ○ メッセージごとの通信回数や頻度 ○ ↑を表示する Unity の拡張 ○ 複数のクライアントが繋がるサーバーでは取得・表示しづらい詳細な情報を ハンドリングできる 13 通信の頻度を計測する仕組み

Slide 14

Slide 14 text

● 通信の遅延・ロスをシミュレートする機能 ○ 遅延・ロスが発生しやすいモバイルゲームでは特に重要 ● セッションを維持する機能 ○ L4のコネクションが切断されてしまう現象への対応 ● 通信頻度を計測する機能 ○ マルチプレイゲームの実装で重要だが気をつけるのが難しい点への対応 14 Prizmの機能まとめ

Slide 15

Slide 15 text

15 負荷対策・負荷試験 で得た知見

Slide 16

Slide 16 text

● リリースに向けて実施する負荷試験の一部としてマルチプレイ部分に 注目した負荷試験も実施した ○ Prizm サーバー、Open Match を含めた試験を実施し負荷に耐えるかを検証 ■ Open Match: レートマッチングなどに向いた k8s native なマッチメイキング OSS 16 マルチプレイの負荷試験 DBなど API server Prizm server Open Match bot ここも試験!!

Slide 17

Slide 17 text

● 負荷試験では API やリアルタイム通信のメッセージをランダムに叩くのでは なく、実際のユーザーに近い挙動で試験したい ○ マルチプレイの負荷試験の目的はマッチング→ゲームプレイ→マッチング→……の サイクルをプレイヤーが問題なく遊べるかを検証すること ● そのためユーザーの動きに近いシナリオを実装する必要があった ○ マッチング中にマッチングを再度開始しないようにする ○ 相手のターン中に自分が行動したときのメッセージを送信しないようにする 17 マルチプレイの負荷試験

Slide 18

Slide 18 text

● 負荷試験を実施する中でいくつか苦労したポイントがあるので紹介 ○ 負荷試験 bot を実装する過程で出会った課題 ○ 負荷試験を実施する過程で出会った課題 18 負荷試験で苦労したこと

Slide 19

Slide 19 text

● シナリオを作成するために実施したログの収集方法が原始的すぎた ○ Unity にログを仕込む→ログを awk する→更に手で不要な情報を消す といった流れ ■ 他のメンバーに作業を依頼するときに伝える情報量が増える ■ 本質的ではないポイントでつまづく ● Prizm サーバーにメッセージ受信時に共通で呼ばれる処理をフックできる 機能を実装し GCP Cloud Logging でログを収集できるようにした ○ サーバーにログを仕込む→Cloud Logging でログを絞り込む といった流れで 通信の流れを追えるようになった ■ 他のメンバーに作業依頼するときも Cloud Logging のクエリだけ共有すれば OK ○ 本当にうまく機能するかは次の負荷試験で検証予定だが リリースに向けて必要な機能を発見できたのは収穫と感じた 19 シナリオを作成するためのログ収集について

Slide 20

Slide 20 text

● マッチング / インゲームや自ターン / 相手ターンといったゲームの 進行状態を負荷試験 bot で再現し適切に動作させるのが大変だった ○ 状態遷移図を書いてから実装するなどで対策はした ■ ターン制のゲームだったこともあり正常系の整理はうまく行った ● 正常系の実装より異常系のハンドリングが大変 ○ マッチングに失敗した後の挙動 ○ 対戦相手の通信が不調になって待ちがタイムアウト ○ メッセージが届く / 届かないに依存して遷移が変わる際の挙動 ■ e.g., 「移動した」の後「目的地に到着した」が送られてくる場合 ■ 「目的地に到着した」が遅れて届いた場合次の受信待ちのタイミングで 「目的地に到着した」だけが送られたように見えるケースがある ● 正常系だけ考えていると「移動した」だけを待ってしまう 20 負荷試験 bot のゲーム進行状態のハンドリング

Slide 21

Slide 21 text

● 負荷をかける規模を大きくすると動かなくなる ○ 異常系の想定が甘くて想定していない状態・動作になってしまう ■ botのデバッグは本質ではないので減らしたい…… ○ 規模を大きくするたび問題が出てくる可能性を想定してスケジュールを組む必要が あると感じた ■ コスト的にも問題の切り分け的にもいきなり本番想定規模の試験は 動かしづらいのも難しいポイント ● マッチングがうまくいかない ○ インゲーム・リアルタイムサーバーの問題よりマッチングの問題を解消する 時間の方が長かった印象がある ○ マッチングも規模を大きくするたび問題が出てくる可能性がある ○ Open Match を使う場合はマッチングロジックがボトルネックになってないか注意 21 負荷試験を実施する過程でよく出会った課題

Slide 22

Slide 22 text

● 負荷試験を実施したところ、実施前には気づかなかったが ボトルネックとなっていたポイントがあったので紹介 22 負荷試験をやってみて分かったこと

Slide 23

Slide 23 text

● Pod に割り当てるリソース量を決定するために1Pod あたりにかかる負荷を 増やしていたとき、Pod 単位ではリソースに余裕がある場合でも Node 単位で はリソースが逼迫していることがあった ○ リアルタイムサーバーの処理負荷が小さかったこともあり Pod へのリソース割り当てを 小さくしていたので1台の Node に多くの Pod が乗った ○ Node 単位で捌くリクエスト数が多すぎたりコンテキストスイッチがたくさん発生した ために Pod より先に Node のリソースが逼迫したのではないかと考えている ● サーバーのリソース調整の際は Pod だけではなく Node 単位でもリソースの 使用状況を確認する必要があることが分かった 23 リソースの割り当てについて

Slide 24

Slide 24 text

● Prizm では TCP の暗号化に TLS を用いており接続時にハンドシェイクを実行 ● 負荷試験の結果この TLS ハンドシェイクが思った以上に重いことが分かった ○ 断続的に bot で計測している ping の RTT が遅くなるケースがあった ■ 99.9%値で50ms → 15,000ms ○ リソースは余っているように見えたがサーバーの立ち上げ直後に CPU usage が高くなる 傾向があった ○ Cloud Profiler を見たところ TLS ハンドシェイクの負荷が高いことが分かった ■ ゲームの処理が軽くメッセージ頻度も小さいため相対的に負荷が高くなった ● TLS ハンドシェイクの負荷に合わせたスペック調整やプレイヤーの 流入ペースの調整によって対策した ○ ハンドシェイクがたくさん来るとつらい →ユーザーの流入ペースが減ればハンドシェイクを同時に扱う量が減る 24 TLSハンドシェイクについて

Slide 25

Slide 25 text

● マルチプレイゲームを問題なくプレイできるかを確認するための負荷試験を 実施した ○ 実際のゲームプレイに近いシナリオを用意 ● 負荷試験の準備、実施の過程でいくつかの問題や知見があった ○ ログ収集や異常系のハンドリングなど ○ 規模が大きくなることで bot の挙動やマッチングがうまくいかなくなる ● 負荷試験の結果意外なところに落とし穴があったのが分かった ○ Node 単位での負荷 ○ TLS ハンドシェイクの負荷 25 負荷試験周りのまとめ

Slide 26

Slide 26 text

● ゲームづくりにもっと強く関わっていきたい ○ 今までは Prizm というリアルタイム通信の基盤を作る役割が強かった ○ ゲームのリリースが近づくにつれてマルチプレイゲームを作るには 基盤だけでなくアプリの仕様や実装も難しいし大切だということを感じた ■ マルチプレイゲームの難しさをなんとかハンドリングするアプリの作りが必要 ● Prizm チームそのものが基盤を作るチームからマルチプレイ全体を いい感じにしていくチームに意識を変えていきたい ○ 自他ともに「リアルタイム通信の基盤を作るチーム」「リアルタイムサーバーの チーム」といった認識になっていた ○ ゲームづくりにもっと強く関わるためにこの認識を変えていきたい 26 終わりに: ゲームのリリースに向き合って考えたこと