新ゲームのローンチ時、ゲームのバックエンド負荷は急激にスパイクする

ごましお氏:それでは「安全なローンチを支える負荷試験」ということでお話しします。よろしくお願いします。私はコロプラの技術基盤本部という部署に所属しています。ふだんは、主に新作ゲームのローンチサポートや社内共通で利用するライブラリの開発・保守などを行っています。

今回の話の前置きから説明しておこうと思います。ゲームのバックエンドの負荷は、ローンチ時に最も急激にスパイクします。これはなぜかというと、みなさんの中にも見たことある方がいるかと思うんですが、例えば「事前登録でアイテムなになにをプレゼント」のような事前登録系のキャンペーンがあったり、最近ではインフルエンサーの方に配信で事前にプレイしてもらうなど、ユーザーさんの期待をなるべく高めてローンチを迎えるようなマーケティング手法が主流になっているためです。

一方で、最もサービスを停止させたくないタイミングでもあるため、ローンチ前の負荷試験は特に重要であると考えています。

そこで本日は、新しいゲームのローンチ時にサービスを停止させないために、コロプラで行っているローンチ前の負荷試験について紹介していきたいと思います。

(スライドを示して)こちらは、とあるゲームのローンチ時のアクセス数の推移です。22時ぐらいの時間帯が山になっていたり、反対に深夜の4時ぐらいの時間帯が谷になっていたりということがわかると思いますが、一番急激にアクセスが増えているタイミングは、ローンチ直後の1時間ぐらいだということが伝わるかと思います。

今回は、ここの負荷を乗り切るための負荷試験についてお話をしていきたいと思います。

負荷試験の流れ

(スライドを示して)実際の試験の流れはこんな感じです。まずは自分たちでゲームをプレイしてログを収集します。次に試験に使うクライアント、試験シナリオみたいなものの実装を行っていきます。それから複数人でのプレイログを収集します。さらに、実際に試験を実施し、その試験結果を評価し、規模を大きくしながら試験の実施と評価を繰り返していく流れになっています。

最後に番外です。データベースのウォームアップと書いています。時間がもし余れば少し触れるかもしれませんが、ちょっと触れられない気もするので、その場合は後ほど公開する資料などで確認してもらえればと思います。

ゲームをプレイしてログを収集する

それではさっそく、1番のゲームをプレイしてログを収集するフェーズから話していきたいと思います。

こちらでやることは、単純にゲームを実際にプレイしてログを収集するということだけです。ですが、この時点でのゲームの開発状況の想定としては、まずゲームのコア部分のプレイサイクルができていること。その際、レベルデザインは完成していなくてもOKとしています。

(スライドを示して)例えば右のフローのように、入会して、チュートリアルがあって、あとはクエストと強化を繰り返していくというのがゲームの基本の流れです。それが確認できる状態なら、このフェーズとしてはOKということです。

ここでの目的は、まずゲームの特性を把握することと、試験クライアントの実装に必要な情報を収集することの2点です。

試験クライアントの実装に必要な情報というのは、例えばゲームでどんな順番でAPIが呼び出されるのかだったり、APIごとの実際のリクエストパラメーターはどんなものなのかといった内容です。ここで集めた材料を基に、次のフェーズの試験クライアントの実装を行っていく流れになります。

試験クライアントを実装する

続いて、試験クライアントを実装するフェーズについて話していきます。コロプラでは、ゲームローンチ前の負荷試験に使うクライアントは、gamebotと呼んでいる独自のフレームワークを使って開発しています。

こちらの特徴は、まずAPIサーバーと実際のゲームクライアントのやり取りに使うAPI定義というものが存在するんですが、これを基に試験クライアントのAPI呼び出しコードを自動生成する機能を持っていることです。また、実際の負荷試験の規模を与えるパラメーターで指定できます。基本的に、社内での負荷試験に最適化された負荷試験用のフレームワークになっています。

(スライドを示して)こちらは、「具体的にこんな感じのコードを書くことになります」という紹介です。ここではSampleScenarioというシナリオを実装する場合を考えてみます。Runというメソッドが、指定されたユーザー数分、100人なら100人分のゴルーチンで並行に実際は実行されると考えてください。

複雑なことはやっていません。処理を上から順に説明していくと、まず、Prometheusで取得したいメトリクスがあればその呼び出しをしています。ここでは起動時にカウンターをインクリメントして、終了時にデクリメントとしているので、このカウンターを使えば現在起動中のワーカー数、ユーザー数がメトリクスとして取れるという意味です。

それから、InterceptorとしてNewHandleBearerTokenというものを用意しています。例えばBearerトークンを処理するような、各APIで共通する仕組みのようなものを挟みたければ、このようなかたちで、いわゆるMiddlewareパターン的に挟むことが可能になっています。

そして「//REST API呼び出ししたり」と書かれている部分が実際にAPI呼び出しをする部分です。ApiUserRegisterという関数が、先ほど説明したAPI定義から自動生成した関数です。これに同じくAPI定義から自動生成したApiUserRegisterRequestというリクエスト構造体の中身を埋めて渡したり、ApiUserRegisterResponseというレスポンスを受け取る構造体を渡したりすることで、API呼び出しを実行できます。

Prizmという単語が出てきますが、今回、Prizmについての詳細は割愛させてください。簡単に説明すると、リアルタイム通信機能を提供する内製のフレームワークです。Prizmについての紹介は別の勉強会で話しているので、詳しくはそちらの資料などを確認してもらえればと思います。

要は「リアルタイム通信の部分も1つの試験シナリオとして実装に組み込むことができますよ」ということが、今回gamebotとして紹介したいことでした。

ここではAPIサーバーからPrizmの接続情報を取得して、それを基にPrizmとコネクションを確立し、最終的にTCP、UDPそれぞれでPingを送るというサンプル実装になっています。

これは今回の紹介用なのでとてもシンプルな中身ですが、実際は自分たちでプレイしたログを基にこのシナリオを作っていくことになるので、けっこう大変な作業になります。

Q&A なぜ自前で内製フレームワークを作っているのか

このgamebotという内製フレームワークを使って作っている理由について、Q&A形式で説明していこうと思います。まず「なんで自前で作っているんですか?」という疑問があると思います。

負荷試験のツールは調べるといろいろと世の中に存在しています。GatlingとかJMeterとかLocustとか、みなさんが聞いたことあるものもあると思うんですが、なぜそれらを使わないのかという話です。

これは「独自の要件に合わせやすいから」という回答になります。APIの呼び出し部分を自動生成できるとか、パラメーターで負荷をシミュレート可能だとか、リトライ戦略など細かいカスタマイズがゲームに合わせて行えるとか。

リアルタイムゲームサーバーであるPrizmとも接続できるとか、クライアントサイドのメトリクスをPrometheusに連携できるとか、GKE環境にそのままデプロイできるとかが特に社内の要件にマッチしていて便利なので、gamebotを使って自作しています。

Q&A 収集したログからシナリオを自動生成するアプローチについて

続いて「自作するのはわかったけど、例えば収集したログからシナリオを自動生成できたら便利じゃないですか?」と思った方もいるかもしれません。

これについては、以前はそういうアプローチも実はありました。私の入社以前の話ではありますが、プレイしたログを特定のフォーマットで出力するようにしておいて、それをファイルとして渡すと順番に実行してくれる試験をやっていたことも過去にはありました。(ただ)これには便利な面もありつつ、課題もあったと聞いています。

例えばアプリケーションの修正をすると、基本的にログを取り直さないといけないとか、結果がランダムになり得るAPI呼び出しの結果に再現性を持たせることがけっこう大変だったりが難しいポイントでした。

gamebotとのいいとこ取りのアイデアとして、初期実装だけこのログから自動生成して、あとは人力でメンテナンスしていくというハイブリッドな形式はありかもと思っていますが、現状はログから自動生成はやっていないです。

Q&A なぜGolangで実装しているのか

続いて「なんでGolangで実装しているんですか?」と(いう疑問です)。「UnityのゲームならC#でそのまま動かしたらいいんじゃないですか?」と思った方も、もしかしたらいるかもしれません。

これについては、単純にGolangで始めて、それ以降特に検証とかをしたことがないというだけではあるんですが、メリットがあるかもしれないと思っている部分もあります。

仮に、クライアントのコードを部分的にでもそのまま負荷試験に使うことができれば、ゲームとシナリオで二重管理みたいなことをする必要もなくなります。また、仕様変更とかが仮にあってもそのまま負荷試験に反映できるので、そういう意味でメリットが大きいように思います。という意味で、検討する価値はあるかもと考えています。

一方で、クライアントの実装時点で、通信に関連する処理をなるべくUnityと疎結合になるように考慮してもらったり、クライアント開発にもある程度制限をかけないと難しそうだと思っています。ということで、これは現状未検証ですが、検討はしていきたいと思っているという回答になります。

試験クライアントの実装について、ちょっと細かく説明してしまったんですが、ここからサクサクいきたいと思います。

(次回に続く)