メルクストーリアのレイド用リアルタイムサーバー開発について

2021/12/15に公開

この記事は「Cacalia Studio アドベントカレンダー 2021 エンジニア版」の15日目の記事です。

はじめに

メルクストーリア」 エンジニアの 岸本 です。

昨日に引き続き、メルクストーリアのリアルタイム通信技術を採用した開発の取り組みのご紹介をさせていただきます。

本日は、今月リリースしたばかりのリアルタイムでマルチプレイのできるレイドクエスト(「幻闘」クエスト)に関するリアルタイムサーバーまわりのお話をしたいと思います。

とはいえ、目新しいことはあまりなく、基本的なところは、CEDEC 2021 で発表した内容の延長線上のお話となりますので、そちらの資料も参考にしていただければ幸いです!

また、Kubernetes の話については、8日目と9日目のアドベントカレンダーで紹介がありますので、併せてお読みいただければと思います。

前提

メルクストーリアのリアルタイム通信環境では、Kubernetesを採用し、ステートフルなリアルタイムサーバーの構築するためにAgonesを導入しています。

Agones で管理している リアルタイムサーバー については、ロードバランサーなどを通さず直接インターネットに公開している状態で、ノード (EC2) のスケーリングなどで随時接続先が変動するため、クライアントに リアルタイムサーバー の接続先を予め埋め込んでおくようなことはできません。

そこでクライアントはまず、サービスディスカバリー と呼ばれる API サーバーに接続し、使用したい リアルタイムサーバー の接続先を取得できるようにしています。

今年の3月にリリースしたギルド内コミュニケーション機能のリアルタイム化については、ギルド内のメンバー全員を同じリアルタイムサーバー (以下ギルド用リアルタイムサーバー) に接続させて、サーバー側で各メンバーの状態を一元管理し、クライアント間の同期を行うようにしています。
(チャットなどのルームに相当する感じです。)

サービスディスカバリー は、Kubernetes の API などを活用して、クラスタ内で稼働中の リアルタイムサーバー を管理しつつ、ギルドの ID をキーに リアルタイムサーバー の負荷状況などを判断して、接続先を案内するサーバーとなっています。

今回ご紹介する レイド用リアルタイムサーバー についても基本的なところは同様で、サービスディスカバリー を経由することで、参加メンバー全員が同じ レイド用リアルタイムサーバー に接続して、データ同期を行うようにしています。

ネットワーク構成

今回のレイド機能開発では、既存の ギルド用リアルタイムサーバー とは別で レイド用リアルタイムサーバー を新規追加していて、ソフト的にもハード的にも別々のサーバーになっています。

開発で意識したこと

基本的には ギルド用リアルタイムサーバー とほとんど違いがないため、ほぼほぼコピーしていますが、以下のような部分を意識して開発しました。

ライフサイクルと想定負荷の違い

ギルド用リアルタイムサーバー は、チャットなどのギルド内コミュニケーション用での利用となるため、特に接続可能時間の制限等はなく、短時間での利用もあれば長時間での利用も想定されます。

なるべく接続を維持できるようにする必要はありますが、切断されても再接続すれば問題ないようにしており、デプロイなどのタイミングでサーバーサイドから切断して、新しいサーバーに再接続するような設計になっています。

一方、レイド用リアルタイムサーバー は、レイド終了までの数分~数十分程度の比較的短時間での利用が想定されますが、プレイ中の接続については、絶対に維持する必要があります。

そのため、デプロイなどのタイミングでサーバーサイドから切断するようなことはできず、すべてのプレイが終了するまでサーバーをシャットダウンすることはできません。

どちらもサーバー内部でインスタンスを作成して稼働しているため、アプリケーションのレイヤーでは大きな違いはありませんが、サーバーのライフサイクルの違いで下回りの処理を若干気をつけたかったため、それぞれ別々のサーバーにしました。

また、ギルド用リアルタイムサーバー に比べて通信頻度の多さから高負荷が予想できたため、マイクロサービス的な使い方がしやすい Kubernetes の利点を活かして、ソフト的にもサーバー的にも別々のサーバーとしました。

こちらについては、負荷的な部分でレイド以外のことを気にしなくて良いため、その点は楽だったかな、と思います。

コスト管理 (ソフトリミット)

レイド用リアルタイムサーバー では、1サーバーに複数レイドを収容するように設計しており、負荷試験の結果から1サーバーあたりに収容可能な最大レイド数をソフトリミットとして管理するようにしています。

ギルド用リアルタイムサーバー も同じくソフトリミットを設けて、1サーバーに複数ギルドを収容するようにしていますが、比較的長時間での利用も想定されますし、ギルド単位で出入があるため、なるべくサーバーを再利用するようにしています。

一方で、レイドを考えたとき、比較的短時間での利用が想定されますし、レイド単位にインスタンスも異なるため、ソフトリミットまで収容しつづけて、ソフトリミットを超え次第、次のサーバーに割り当てるようにしています。
サーバー内で稼働しているレイドがすべて終了次第、サーバーをシャットダウンし、必要な時に必要なだけサーバーを起動するようにしてみました。

こちらについては、リリース後の稼働状況を見たところ、1サーバー複数レイドとする場合は、どうしても空きができやすく一時的にサーバーが増えがちなので、ソフトリミット内であれば再利用できるようにしても良かったのかな、と思いました。

開発して思ったこと

リアルタイム通信環境の開発する初期の頃から、「みんなで一緒にリアルタイムでクエスト遊べるようにしたいよねー」という話をしていて、フレーム単位のデータ同期が求められるレイドバトル前提で開発を進めてきました。

社内標準のサーバーサイドは Ruby on Rails ですが、社内注目技術のひとつの gRPC の知見を蓄えておきたかったというのもあり、メルクストーリアのリアルタイム通信環境では、MagicOnion を採用したサーバーサイド C# で開発しました。

MagicOnion を採用したことにより、gRPC ベースの通信部分はとても簡単に実装することができて非常に助かりましたが、.NET のサーバーアプリケーションとしては 1 からの開発であったため、細かい技術検証や基盤となる下地作りには苦労しました…。
また、右も左もわからなかったリアルタイム通信や Kubernetes まわりも理解することが多く、なかなか初期の頃のカロリーは高かったのかなー、と思います。

ゲーム内の部分的な機能での利用に限られるため、利用目的に対して大規模かつ複雑な環境をひとつ構築してしまった気もしますが、比較的一般的な技術を組み合わせることで上手く乗り切れたかと思いますし、マイクロサービス的な思想で小さめのサーバーアプリケーションに分割する前提で共通化なども行ったため、汎用的で小回りが効きやすく、今後の社内開発に活かせるひとつの知見にできたのかな、と思います。

おわりに

ということで、2日間お付き合いいただきありがとうございました!

去年のアドベントカレンダーや今年の CEDEC での発表も加え、この2日間でもメルクストーリアにおけるリアルタイム通信関連の話題をお話させていただきました。
内容としては基本的な部分のみのご紹介となっていますが、皆様の開発で何かの参考になれば幸いです!

引き続き「Cacalia Studio アドベントカレンダー 2021 エンジニア版」をお楽しみいただければと思います。
ありがとうございました!

GitHubで編集を提案
Happy Elements

Discussion