自己紹介と本セッションで話すこと

R.Y.氏(以下、R.Y.):よろしくお願いします。それでは「リアルタイムサーバーチームが初の大規模ローンチに向けてやってきたこと」というタイトルで発表します。

自己紹介です。株式会社コロプラでバックエンドエンジニアをしています。2020年に新卒入社をしたあと、Prizmという内製のリアルタイムフレームワークを作ったりするチームに所属しています。今回は、このPrizmを使ったゲームのローンチに関わる話をしていこうと思います。

今日話すことについて説明します。少し前に、先ほど説明したPrizmのチームが開発に関わったゲームがリリースされました。このタイトルのPrizmに関わる特徴としては、そもそもPrizmを用いてPvPを実装しているところであったり、あとはOpen Matchというk8s nativeなOSSを利用してマッチメイキングをしているところがあります。

今回はこのPrizmを作っているチームのメンバーとして、このゲームのリリースに向けてやってきたことについて紹介できればなと思います。紹介する内容としては、このリリースに役立ったPrizmの機能や、先ほどもあったような負荷対策、負荷試験についてPrizmチームとして向き合って得られた知見といった2つについて大まかに話せればと思っています。

リアルタイムのゲームを作るためのフレームワーク「Prizm」

まずはリリースに役立ったPrizmの機能についてお話しします。その前に、Prizmについて軽く紹介させてください。Prizmとは、コロプラで開発しているリアルタイムのゲームを作るためのフレームワークです。サーバーはGo、クライアントはUnityで作られていて、サーバーを介してクライアントがroomという単位に分割されます。そのroomの中でクライアントがメッセージを送り合うようなことを想定したアプリのフレームワークになっています。

基本機能としては、クライアントからサーバー、サーバーからクライアントでメッセージを送り合うという機能があったり、あとはサーバー側にロジックを実装するような機能があったりします。他にもいろいろ機能はあるのですが、そちらについては2022年3月に発表したものがあるので、そちらを参照してもらえればと思います。今回は、リリースにあたって特に役立った機能や、リリースを受けて開発した機能を紹介していければなと思います。

Prizmのシミュレート機能

まず1つ目に、通信の遅延やロスをシミュレートする機能について紹介します。Prizmのクライアントに、通信の遅延やロスなどをシミュレートする機能を実装しています。メッセージを更新する前やメッセージを受信したあとにライブラリ側で遅延やロスを挟み込むような機能になっていて、こうすることによってクライアントのアプリだったり、サーバー側から見ると、ネットワークで遅延やロスが起きたのと同じ挙動に見える内容になっています。

(こうした機能が作られた)背景として、コロプラは主にモバイル系のゲームを作っていて、そうした環境では遅延やロスが発生しやすいので、それを前提に作る必要があるということがあります。ただ一方で、開発中は環境が良い社内ネットワークを使って開発するので、こうした問題を認識することや、認識できたとしてもデバッグすることが難しい問題があります。そのためにこういう機能を使っているわけです。

この機能には、開発をする時のアプリにある「デバッグメニュー」みたいなものがあり、そこから「遅延がいくら」とか、「ロスの割合がいくら」みたいなものを設定できるようにしてもらったので、直接開発に携わっているわけではないPrizmチームでも動かせて、「こんな感じの挙動になるんだ」ということを確認できました。

また、リリース後に「ネットワークの調子が悪い時にこういう問題が起きるんじゃないか」みたいなところを検証するためにも使ってもらったようで。いろいろ使い道がある機能になっているのではないかと思っています。

コネクションの切断に対するPrizmの対応

次に、コネクションの切断に対する対応の話をします。前提として、Prizmではリアルタイム通信を実現するために、コネクションを張り続けています。TCPではシンプルにコネクションを張り続けているし、本来コネクションレスなUDPでは、送信元のIPとポートの組を覚えておいて、疑似的に「このIP/ポートから来たのはこの人だ」みたいなことでコネクションを実現しています。

しかし、このようなコネクションは、ゲームの途中で簡単に切れてしまうという問題があります。原因としては、Wi-Fiとモバイル回線の切り替えであったり、モバイル回線でも基地局のハンドオーバーであったり、Wi-Fiのルーターの調子が悪くて通信が一瞬切れてしまうとか。他にもいろいろあると思いますが、その原因はさておき、「切れてしまう」ということが問題になります。

開発中にもコネクションの切断が問題になったので(すが)、(それは)ライブラリ側でサポートができるのではというところで、コネクションが切断してもセッションを維持する機能を実装しました。

やっていることはわりと単純で、TCPやUDPのコネクションが切れた時に、エラーを返すのではなくて、内部的に再接続をしてPrizmレイヤーでのセッションを維持するようなことを実現しました。これによって、ゲームを開発する人たちはL4のコネクションを気にしなくてもよくなる効果が得られました。

もう1つ、切断している間に本来送れるはずだったメッセージが送れなくなってしまうので、そうしたもののうち、Reliableなものについてはライブラリ側でメッセージを再送することで、切断時に起きてしまう嫌なことを防ぐ機能も実装しています。

セッション維持機能の問題点

こうした機能ですが、少し問題点があると感じたので、それについても紹介します。なにかというと、(スライドを示して)このコネクションの再接続に時間が数秒程度かかってしまう問題です。コネクションの切断ですが、そもそも切断をはっきりと取れない場合があるので、Prizmではpingを送り合って、クライアント・サーバーでも、サーバー・クライアントでも状態をお互いに推測することをしています。

この推測ですが、切断(されたこと)で「これはもうダメだな」と判断するまでに、何秒か猶予時間を持つ必要があります。というのも、推測の中身的には「pingを送ったのに返ってこないな」「pingが送られるはずなのに返ってこないな」みたいな感じなので、そこですぐに「ダメだ」と判断することができないからです。たまたまネットワークの調子が悪いだけの時に1回や2回pingが失敗してしまうようなケースはあり得るので、それを排除する必要があります。

何秒か待つところは、ゲーム性によっては許されないことはあると思っています。この前リリースしたゲームはターン制なので、数秒の猶予は許容できるのではないかというところで、こうした実装にしました。ただ、アクション性の強いゲームだったりすると「この数秒も待っていられないよ」という話になると思うので、別の対応を考える必要があるのではないかと思っています。

通信頻度を計測する機能の実装

別の通信頻度の計測についてのお話をしていきます。マルチプレイのゲームを作るにあたって、通信頻度はなるべく最小化したいという前提があります。最小化することによって、インフラコストやクライアント側の消費電力などを減らすことができるので、とてもうれしいです。

しかし、ゲーム開発をしている中で、通信頻度を意識するのはとても難しいという課題があります。原因として、負荷対策よりも「これはそもそもおもしろいゲームなのか」というところの検証を優先することにモチベーションがあったり、開発は社内ネットワークなので通信環境が良くて、たくさん通信をしていても問題が顕在化しづらいことなどがあります。

あとは、とりあえずOnUpdate()のマイクフレームデータを送信すれば動くので、動くものをさらに変えていくようなところも、モチベーションとして湧きづらいです。

ただ、この通信頻度を減らす作業はけっこう重くて、リリース直前に急に「減らしてくれ」と言われても大変なので、事前に早めに気づけるようにしたいところで、計測する仕組みを作るようにしました。

(この時に)「気をつけようね」と定期的にアナウンスするbotになってもいいのですが、意外な理由で通信量が増えるケースがあり、気をつける・気をつけないの問題ではないものもけっこうあったので、「計測したほうがいいよね」というところで計測する仕組みを作りました。

(スライドを示して)「リリースする直前に減らすのは大変」というところについては、いくつか理由があると思っています。まず、そもそもの仕様がたくさん通信しないと厳しいものであって、仕様のレベルで改善をしなきゃいけないとか、通信頻度を減らせるにしても、送っていない間のデータの補完などをしなきゃいけないとか、実装が別で必要になる場合もあるので、早めに発見して早めに対策をしたいところがあったりするので、早めに気づきたいです。

それで「どうやって気づくの?」という話なのですが、サーバーとクライアントのそれぞれに機能を用意しています。サーバーでは、クライアントが通信しすぎていたらワーニングログを出す機能を入れました。クライアントごとに5秒間のリクエスト数、RPSを計測して、一定の値を超えたらワーニングログを出す機能です。

この閾値はデフォルトが10RPSになっているのですが、これは設定で変更可能になっているので、ゲームの仕様に合わせることができるようになっています。サーバー側の機能の意図としては、詳細よりも大まかな傾向をつかむことを目的にしていて、サーバーで大まかな傾向をつかめたら、次のクライアントの機能を使って、より詳細を知る流れを意図しています。

クライアントではなにができるかというと、通信まわりのプロファイルを取得したり、表示したりする機能を入れています。このプロファイルでは、メッセージごとに何回通信しているのか、1秒間にどれだけ通信しているのかといった頻度を知ることができて、サーバーよりも粒度の高い情報を得ることができます。

また、取得するだけではなく、Unityの拡張で表示する機能も入れているので、そのあたりの実装もいらないかたちになっています。こうすることによって、サーバーでは取得しづらい詳細な情報をハンドリングできるので、それぞれ役割を持っていて良い感じなのではないかなと思っています。

Prizmの機能まとめ

機能についてのまとめです。1つ目に、通信の遅延やロスをシミュレートする機能があります。これについてはモバイルのゲームでは特に重要なのではないかと思っています。

2つ目に、セッションを維持する機能を入れました。L4のコネクションが切断されてしまうので、それへの対応です。

3つ目に、通信の頻度を計測する機能を実装しました。これは重要な話ですが気づくのがなかなか難しいので、「計測して気づけるようにしようね」ということもやっていきました。

(次回に続く)