Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

メタバースプラットフォーム 「INSPIX WORLD」はPHPもC++もまとめてC#に統一...

Pulse Co., Ltd.
September 21, 2023

メタバースプラットフォーム 「INSPIX WORLD」はPHPもC++もまとめてC#に統一! ~MagicOnionが支えるバックエンド最適化手法~

8/26開催 CEDEC2023にて登壇した資料となります。
Pulseが展開する仮想空間『INSPIX WORLD』のエンジニアリーダーによる
当該プロジェクトの大改修についてご紹介します!

Pulse Co., Ltd.

September 21, 2023
Tweet

Other Decks in Programming

Transcript

  1. ❖ INSPIX WORLDの光と闇 ➢ 長期運用に向かない問題 ➢ 当初のサーバー設計 ❖ 再開発でC#大統一へ ➢

    APIとDBの改修 ➢ ログ構造の改修 ➢ 開発環境の統一化 ❖ リアルタイムサーバーの大改修 ➢ MagicOnionとLogicLooper ➢ フルスケーラブルメタバースアーキテクチャ ➢ INSPIX WORLDにおける設計とパフォーマンス ❖ まとめ アジェンダ
  2. 小柴 祐二 / Koshiba Yuji パルス株式会社 メタバース事業部 エンジニアリーダー 韓国PCオンラインゲームパブリッシャーにて MMORPGの開発・運用

    現在(INSPIX WORLD) 2020年 ・エンジニアとしてコンテンツ開発に参画 2021年 ・エンジニアリーダーに就任 ・講演内容に関わるサーバーサイド大改修を行う 自己紹介
  3. 自己紹介 株式会社Cysharp CEO/CTO @neuecc Microsoft MVP for Developer Technologies(C#) 2011-

    CEDEC AWARDS 2022 エンジニアリング部門優秀賞 「.NETのクラスライブラリ設計 改訂新版」監訳 多数のC#向けOSS開発 MessagePack for C# MagicOnion MemoryPack UniRx UniTask AlterNats 河合 宜文 / Kawai Yoshifumi
  4. 不要な機能・仕様が多数 拡張開発が必ず工数大 データロックが多発 操作不可の待ち時間が 高頻度で発生 APIレスポンス速度が遅く リアルタイム性が悪い アップデートコスト増大 ユーザー体感が悪い 快適さ皆無のサーバー設計

    幾度とない仕様変更による不要機能が多く アップデートを行う際には必ず仕様見直しを測る事態に lock! 基本 工数 見直し 工数 基本 工数 処理待ち… やりたい事があっても根幹の問題で”開発自体が行えない”事態も発生 長期運用に向かなかった原因
  5. 別の言語で作成されていたAPI、リアルタイムサーバーを全てC#に統一することで IDEの統一、クライアント、サーバー間でのコード共有などもC#で開発を完結! クライアントの開発はUnityを使用 リアルタイムサーバーは MagicOnionを採用しC#へ バックエンドを全てC#へ クライアントとの連携向上のため、API サーバーをC#へ インターフェース実装でクライアントとサーバー間の コード共有が可能になり並行開発等も容易に

    バックエンドを全てC#統一することで 同一プロジェクトとして管理できるように APIをPHPからC#に改修決定 JSONからMessagePackへの変更で クライアント⇔API⇔リアルタイムサーバー全ての 通信プロトコルを共通化 MagicOnion製作者であるCysharp社の河合さんにも バックエンドの監修に参画していただけることに PHPもC++もまとめてC#に統一!で全部解決
  6. APIサーバー:やるべきこと 言語の違いによる連携コストの発生 送受信ともに言語の違いからデータ変換周 りで度々トラブルがあった。 バグの調査も言語の違いからお互いに頼り きり状態に APIもC#に書き換えプロトコルを共通化 サーバー⇔クライアントの開発効率を考慮 リアルタイムサーバー同様に言語を C#に共通

    化 データベース:やるべきこと 各DBの役割や情報を整理 色々な箇所に散らばっていた 状況を整理し、DB毎の役割を分別 一般的なMMOのようなDB構造へ 分別したデータを整理 スキーマを正しく分離することで 一元管理と負荷分散を行えるように APIとDBの改修について
  7. アイテムの上限数が統一されていない アイテムIDが目まぐるしく変わる 水平分割すべきではないデータの無用な分割 … 無制限では延々と アイテムが増加  通信時間が増大していく 所持上限や倉庫機能を作成 長時間プレイの負荷を軽減 A

    12345 例えば”トレード”を行うと アイテムのユニークIDが変動  調査が困難×追跡不可能 A B C 常に12345 アイテムのユニークIDを固定 ログを柔軟に追跡できる改修 欲しいデータ 構造整理を行えておらず 不要なデータも参照  余計な負荷が発生 参照データ スキーマを整理し 必要な参照のみを 行う形で最適化 B 67890 改善された内容の一部
  8. データ参照時、Redisを都度参照しておりロスが多く発生 cache data A B メタバースには”多数のユーザーが存在” ⇒キャッシュから参照するように調整  ロスを最大限削減できよう改修 C D

    APIからリアルタイムサーバーへのコネクションを都度生成&破棄していた コネクション生成 破棄 リアルタイムサーバーへの RPS(秒間リクエスト数)の向上 ⇒リアルタイムサーバーとの  セッションを維持するように改修 処理速度が大幅に減少 cache APCuのキャッシュ機構:未実装 Redis 常に全件取得 改善された内容の一部
  9. モック時代の不要なデータが大量:膨大な処理負荷に UGC時代の処理 新機能① 新機能② 新機能③ ・UGCベースの不要な処理残 ・削除せずに新機能を追加  削除もしづらい状態&  無駄な処理負荷が増大 不要なコード破棄&再整理

    ・200個のAPIを移植 ・100個のAPIを新規追加 異なる言語によってClientとServer間で実装難度が高くなっていた ClientとServerがそれぞれ インターフェースを実装し パラメータ表を確認するアナログ管理  ヒューマンエラーの多発 C#に統一したことで PJ内のプロトコルの共通化 (JSONからMessagePackに移行) ⇒エラーが激減&効率が大幅向上 CL SV Error! 改善された内容の一部
  10. 前提 旧構成のAPI検証画面 ここまでの改修を終えた後、旧サーバー構成と新サーバー構成でパフォーマンス比較を実施 ・旧サーバー構成と新サーバー構成を構築、マシンは同スペックに設定 ・テストシナリオ:アカウント作成 → ログイン → キャラクター作成まで 2,000人

    5,000人 10,000人 仮想環境:同時接続人数 10,000人 5,000人 10,000人 5,000人 10,000人 5,000人 5,000人以上ではエラーが発生 レスポンス速度も遅い結果に 2,000人では特にエラーは見られず 旧構成+仮想スペックでは “3,000人”が限度 エラー レスポンス 秒間Request数:366 (2000人) 改修によるパフォーマンス比較 APIサーバー DB 2,000人 DB負荷がほぼ100%に
  11. 新構成のAPI検証画面 レスポンス エラー 2,000人 5,000人 10,000人 エラーなし 10,000人 10,000人までエラーは見られず レスポンスも旧構成と比べて極端に低くなった

    API及びDBの総合的な改修で、10,000人のリクエストも許容できる最適化を達成 また、50,000人でも殆どエラーが発生せずリクエストを受け付けることが出来た 秒間Request数:604 (2000人) 改修によるパフォーマンス比較 5,000人 2,000人 APIサーバー DB DB負荷は最大60%で打ち止め 新構成では10,000人以上の リクエストを許容できる結果に
  12. 旧ログ 新ログ 細分化されたログ (参考として一部のみ記載 ) ・同種のログに詰め込む形式を破棄 ・ログの細分化を行うよう改善 まずはログの構成整理:”ログの細分化”に着手 Item -

    アイテム情報 購入 獲得 売却 使用情報 などの全情報 ItemBuy - アイテムの購入 ItemGet - アイテム獲得 ItemSell - アイテム売却 ItemUse - アイテム使用 Room - ルーム情報 RoomIn - ルーム入室 RoomOut - ルーム退室 一定のフォーマットを前提として細分化することでログ結合が可能となり 後述の”管理ツール”でのデータ取得/解析が容易にできるよう最適化 入室 退室 などの全情報 ログ構造の刷新
  13. 不要データが含まれている どのメソッドを経由して [ItemGet - アイテム獲得]ログの参考 だれが どのアイテムを いくつ獲得・いくつ持っている なにで どこで

    例えば「アイテム獲得を調査したい」と考えた際にどのような情報が必要になるか 運営的な視点も加えてログ構成を検討 ログ構造の刷新
  14. デバッグを行うだけでも複雑な手順が求められていた 移行前 ・PHP Stormはバックエンドの  デバッグを行う際に  ツールを挟むなど複雑な手順が必要 ・XDebugのVer.とも合わせる必要があり  インストールが必要になる ・開発効率が多少なりとも落ちる C#統一後

    Visual Studio(VS)に 一本化 ・C#に統一したことで  VSひとつで完結したデバッグが可能に ・IDEの変更でClientもServerも同じ  Editorで作業、連携効率が向上した ・INSPIX WORLDではAzureを採用しており  MS社製でビルド&デプロイが容易に回せる Copyright © 2023 JetBrains s.r.o. PhpStorm and the PhpStorm logo are registered trademarks of JetBrains s.r.o. IDE(統合開発環境)をPHPStormからVisualStudioへ移行
  15. 環境構築の作業コストが非常に高かった PHPにて制作 C#統一後 Vagrantにて仮想のLinux環境を作成し Dockerコンテナを展開 環境によって異なるトラブルが多発 解決のために多くのリソースを割く事に 再開発時にDocker Desktopでダイレクトに コンテナを展開する方向に

    VisualStudioとの連携により環境構築が Dockerのインストールのみでできるように 環境破棄から再生成間での構築時間が 10倍以上早くなる結果に 開発環境がVagrantからDockerへ移行
  16. ・ネットワーク通信 ・ストレージ利用の最適化 ⇒異なるプラットフォームとの  データ交換がスムーズに行える  ライブラリとなっている ※Githubより https://github.com/MessagePack-CSharp/ MessagePack-CSharp MessagePack for

    C# C#向けに最適化された、 JSONと比べ高速なデータ処理  / コンパクトなデータサイズを実現 できるハイパフォーマンスのシリアライザです。 後述するMagicOnionでもMessagePack for C#が採用されています。 MessagePackとは…
  17. INSPIX WORLDはリレーサーバー形式のクライアントホスト型だった ホスト兼サーバー ゲスト ゲスト ゲスト クライアントのうち1台がホストとなり 同時にサーバーとしての役割も担う ▼主な特徴 ・サーバーをクライアントが担っているので

     サーバー費用が抑えられる ・クライアント側での処理が複雑になる ・クライアント改造などのチートを防げない 等 リレーサーバー リレーサーバーを介してゲストとの 情報送受信を行い、ゲーム進行の同期 簡易図解 リレーサーバー形式のクライアントホスト型とは
  18. リレーサーバー形式のクライアントホスト型で開発はかなり複雑に… INSPIX WORLDの人狼ゲームは”8人プレイ” ホスト ゲスト ゲスト ゲスト ゲスト 8人全員のデータを常に同期 ▼ホストが切断

    復帰した元ホスト ▼復帰した元ホストに新ホストがデータを復元 …… 基本方針:復帰した際にゲームに戻れることが必要だった ゲスト 新ホスト ホスト ▼ゲーム継続のためゲストを新ホストに 新ホスト ▼多くの懸念 ・プレイ時間中、常に多人数の情報の保持/同期 ・データの整合性が取れないとゲームが進行しない ・”切断⇒復帰”時のプレイヤーデータの修復のため  ホスト以外も含めて常に情報の同期が必要だった 修正やアップデートが慎重になり 対応コストが増大していく クライアントホスト型における懸念
  19. MagicOnionで利用できるAPI ”Service”と”StreamingHub”の2種のAPIが利用可能で、それぞれ特徴があります UnaryRPC(1Request - 1Response)  リクエストを行ったクライアントにのみレスポンスを返せる Service リアルタイム通信に特化 コネクションを常に維持しCL⇔SVで自由にデータが送受信可能 全体へのブロードキャストもできる

    StreamingHub CL MagicOnion Request Response MagicOnion Message Broadcast Requestを送った人にResponseを返すのみ CLがサーバーを介して相互通信 MagicOnionとは CLとSV間のリアルタイム通信を容易に実現するためのフレームワーク ⇒バイナリ形式のシリアライズと gRPCプロトコルを用いて簡潔なコードで  高速なリアルタイム通信を実現することができます CL MagicOnion https://github.com/Cysharp/MagicOnion
  20. APIサーバーはステートレスだが、リアルタイムサーバーもステートレスにできるものもある。例えば Chatなどの メッセージの伝搬や、クライアント間のリレーには向いている。しかし状態 (ステート)を持てないため、ロジックを サーバー側に実装する場合は向いていない ステートレスサーバー MagicOnion (State) Client Client

    Client MagicOnion (Stateless) MagicOnion (State) MagicOnion (Stateless) MagicOnion (Stateless) ロードバランサー PubSub クライアントは異なる MagicOnion サーバーに繋ぐ Good: クライアントの接続先の調整不要 Bad: ステートフルにしづらい Bad: PubSubのパフォーマンスが気になる 各サーバーの裏の PubSubミドルウェアに よってメッセージを繋ぐ gRPC(HTTP/2)
  21. いわゆるDedicated Serverと同じく、ステートやロジックを持った単一サーバーにクライアントが繋ぎに行く構成。 1ゲームセッション(ルーム)に対するサーバーIPの管理に工夫が必要、特に Kubernetes環境の場合に難しくな る。 ステートフルサーバー Client Client Client MagicOnion

    (LogicLooper) 単一サーバー接続 LogicLooperを同居させて ステートを持たせる Good: シンプル Bad: クライアントの接続先管理が別途必要 Bad: 単一サーバーに集約されるのでスケーリン グしづらい、1セッション1プロセスの場合は、コス ト面のバランスが悪いことも
  22. メタバースアーキテクチャ MagicOnion (State) Client Client Client MagicOnion MagicOnion (State) MagicOnion

    MagicOnion ロジックサーバーを単体で分離して、 ワールド全体のステート管理、ゲーム ループを用いたフレーム間のバッチ処理 などを行う PubSub 繋いでいるクライアント固有のステート管理(そのユーザーに不可視のデー タであればカリングして送信しないようにする、など) LogicLooper ステートレスサーバーの管理の容易さとステートフルサーバーの機能性の両立といういいとこ取り(?) クライアントとの送受信処理とロジック処理を分離することでスケーリング性も高まった
  23. メタバースアーキテクチャ MagicOnion (State) Client Client Client MagicOnion MagicOnion (State) MagicOnion

    MagicOnion ロジックサーバーを単体で分離して、 ワールド全体のステート管理、ゲーム ループを用いたフレーム間のバッチ処理 などを行う 繋いでいるクライアント固有のステート管理(そのユーザーに不可視のデー タであればカリングして送信しないようにする、など) LogicLooper ステートレスサーバーの管理の容易さとステートフルサーバーの機能性の両立といういいとこ取り(?) クライアントとの送受信処理とロジック処理を分離することでスケーリング性も高まった PubSub ただしMagicOnionとLogicLooperを繋ぐPubSubの パフォーマンスは依然として気になる ……
  24. NATS / AlterNats / MemoryPack PubSub部分のパフォーマンスを高めれば接点が増えることによるロスを打ち消せる 一般的に使われるRedis PubSubではなく、PubSub専用の高速なミドルウェア NATSを採用 更にクライアント側の性能を高めるために独自クライアント

    (AlterNats)を開発(後に公式クライアントに採用 ) 同時期に、より高速なシリアライザー MemoryPackを開発 これらの組み合わせで PubSub自体のロスを極限まで抑えた MagicOnion MagicOnion MagicOnion NATS LogicLooper AlterNats(nats.net.v2) + MemoryPack AlterNats(nats.net.v2) + MemoryPack https://nats.io/ https://github.com/Cysharp/AlterNats/ https://github.com/Cysharp/MemoryPack 特にメタバース的な座標データの配列な どで数百倍のパフォーマンス向上
  25. Dedicated(Headless) Server vs .NET Server Unity Dedicated(Headless) Server クライアントアプリケーションをそのままサーバーでホスティング 利点:

    クライアント開発をそのままシームレスにサーバーに持ち込める PhysicsやAIロジック、NavMeshなどをそのままサーバーで実行可能 欠点: ビルドやデプロイがサーバー開発用環境に比べて複雑 パッケージマネージャの不足など 3rd Party Libraryの利用が難しい ロギング・モニタリング・ DB通信など基本的なライブラリ郡からして不足気味 サーバー専用と比べてパフォーマンス上の無駄が多い 特にUnityの場合はランタイムが古いため性能面でかなり差が出る Dedicated Serverに上げた欠点は全てない(利点) 今回のメタバースアーキテクチャのような自由度の高い構成を実現可能 PhysicsやAIロジック、NavMeshなどの話は何らかの解決が必要 ある程度のところはC#同士の共有で解決できるところもある(できないものもある) クライアントがC#なら.NETを選ばない理由ないのでは!? .NET Server(MagicOnion, LogicLooper) 餅は餅屋、みたいなところがあるので、原則 .NET Serverで組んだほうがいいと個人的には思います 他言語でのServer(Node.js, C++, Go, Rust, etc…)
  26. メタバースアーキテクチャの設計の概要について MagicOnionではどのような通信を行うかの定義を StreamingHubで行っており、 サーバ上の対応する処理との通信を行います。 INSPIX WORLDでは用途に分けて大まかに 3種類のStreaming Hubを用意しています。 クライアントA BasicHub

    マイルームHub (ContentsHub) ModuleHub クライアントB BasicHub 人狼Hub (ContentsHub) ModuleHub MagicOnion BasicProcess マイルームHub (ContentsHub) ModuleHub 人狼Hub (ContentsHub) NATS LogicLooper マイルームLogic マイルームProcess (ContentsProcess) ModuleProcess 人狼Process (ContentsProcess) ModuleProcess 人狼Logic WebAPI
  27. メタバースアーキテクチャの設計の概要について BasicHubはMagicOnionとクライアント間の通信に関する基本的な処理を行うためのものです。 リアルタイム通信全体の開始と終了、認証や通信が中断した際の再接続などを行います。 また、WebAPIから送られてきた通知も BasicHubを通じて受け取ります。 クライアントA BasicHub マイルームHub (ContentsHub) ModuleHub

    クライアントB BasicHub 人狼Hub (ContentsHub) ModuleHub MagicOnion BasicProcess マイルームHub (ContentsHub) ModuleHub 人狼Hub (ContentsHub) NATS LogicLooper マイルームLogic マイルームProcess (ContentsProcess) ModuleProcess 人狼Process (ContentsProcess) ModuleProcess 人狼Logic WebAPI
  28. メタバースアーキテクチャの設計の概要について ContentsHubはLogicLooper上に存在するコンテンツの処理との通信を行うためのものです。 INSPIX WORLDではマイルームでの家具配置の変更や人狼ゲームでのステート制御など、ユーザーがその 時利用しているコンテンツに応じた通信を行います。 クライアントA BasicHub マイルームHub (ContentsHub) ModuleHub

    クライアントB BasicHub 人狼Hub (ContentsHub) ModuleHub MagicOnion BasicProcess マイルームHub (ContentsHub) ModuleHub 人狼Hub (ContentsHub) NATS LogicLooper マイルームLogic マイルームProcess (ContentsProcess) ModuleProcess 人狼Process (ContentsProcess) ModuleProcess 人狼Logic WebAPI
  29. メタバースアーキテクチャの設計の概要について ContentsHubはLogicLooper上に存在するコンテンツの処理との通信を行うためのものです。 INSPIX WORLDではマイルームでの家具配置の変更や人狼ゲームでのステート制御など、ユーザーがその 時利用しているコンテンツに応じた通信を行います。 クライアントA BasicHub マイルームHub (ContentsHub) ModuleHub

    クライアントB BasicHub 人狼Hub (ContentsHub) ModuleHub MagicOnion BasicProcess マイルームHub (ContentsHub) ModuleHub 人狼Hub (ContentsHub) NATS LogicLooper マイルームLogic マイルームProcess (ContentsProcess) ModuleProcess 人狼Process (ContentsProcess) ModuleProcess 人狼Logic WebAPI 処理の内容が異なる為コンテンツ毎に StreamingHubを作成 MagicOnionではデータ中継時に ユーザーに不要なものはカリングできる 対応したHubを持つクライアントへ NATSを介してデータ送信
  30. メタバースアーキテクチャの設計の概要について ContentsHubは複数同時に持つ場合もあります。 通信は全てMO経由で一本化されているため、通信のコネクションが増えるといった事はありません。 クライアントA BasicHub マイルームHub (ContentsHub) ModuleHub MagicOnion BasicProcess

    マイルームHub (ContentsHub) ModuleHub マッチングHub (ContentsHub) NATS LogicLooper コンテンツLogic マイルームProcess (ContentsProcess) ModuleProcess マッチングProcess (ContentsProcess) マッチングLogic WebAPI マッチングHub (ContentsHub) ContentsHubは複数同時に持つ場合も
  31. メタバースアーキテクチャの設計の概要について ModuleHubはアバターの座標同期やアニメーション同期といった、複数のコンテンツで共通する 処理を行うための物です。サーバ側では各コンテンツのロジック上で動作します。 クライアントA BasicHub マイルームHub (ContentsHub) アバター同期Hub (ModuleHub) MagicOnion

    BasicProcess マイルームHub (ContentsHub) 人狼Hub (ContentsHub) NATS LogicLooper マイルームLogic マイルームProcess (ContentsProcess) アバター同期Process ModuleProcess 人狼Process (ContentsProcess) 人狼Logic WebAPI アバター同期Hub (ModuleHub) アバター同期Process ModuleProcess コンテンツの処理とは 別の共通処理を行うために実装 アバターの位置同期はどのコンテンツを プレイしていようと共通処理を行うため 別途実装を行うのは非効率 複数コンテンツで共通する処理は 機能毎にModuleHubを実装して使い分ける形に
  32. LogicLooperによるロジックサーバーの負荷分散 ▼ゲーム進行等のロジックを全て LogicLooperに任せることができるようになった ・CPUのコア数に応じたマルチスレッド処理で  プロセス内で複数のロジックが効率的に並列実行されるように ・一つのLogicLooperに負荷が大きくかかったとしても  オートスケールにより台数を増やせば簡潔に解決可能 ・更にクライアントホストが持っていたゲーム同期ロジックが  Serverに移動したことで、ホスト端末の負荷も軽減しました。 ロジックの開発に専念

    前述した設計において、同期周りを MagicOnionのStreamingHubにまとめることで ・共通での処理ができるようになり、各コンテンツ開発においては  それらのアバター同期等を考慮せずロジックのみの開発に専念出来るようになった  ⇒新規開発及びアップデートにおける人的コストの削減に繋がりました リアルタイムサーバーの改修後
  33. 市場的にサーバーエンジニアが少ないこともあり人員不足の解消にも寄与!? サーバー& クライアント サーバー クライアント サーバー CL SV or クライアントとサーバーの並行作業または単一での作業完結が可能に

    機能実装時、担当者の不在や作業の遅れによって担当者の手が止まってしまう事もあったが MagicOnionの導入によりInterfaceを共有することで 両者の作業を並行して進めることができるようになり開発効率の大きな向上に 旧:実装 フロー 担当1 クライアント 担当2 基本的にSVが先に実装を行い次にクライアントが作業 新:実装 フロー 担当 担当 MagicOnionの利用で並行作業が可能に エンジニアによっては両作業を 1人でも可能に CL、SV共に同じInterfaceを実装することで共通化 共通化したことでCL⇔SV間での実装精度も向上 C# リアルタイムサーバーの改修後