Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
メタバースプラットフォーム 「INSPIX WORLD」はPHPもC++もまとめてC#に統一...
Search
Pulse Co., Ltd.
September 21, 2023
Programming
1
2.8k
メタバースプラットフォーム 「INSPIX WORLD」はPHPもC++もまとめてC#に統一! ~MagicOnionが支えるバックエンド最適化手法~
8/26開催 CEDEC2023にて登壇した資料となります。
Pulseが展開する仮想空間『INSPIX WORLD』のエンジニアリーダーによる
当該プロジェクトの大改修についてご紹介します!
Pulse Co., Ltd.
September 21, 2023
Tweet
Share
Other Decks in Programming
See All in Programming
macOS でできる リアルタイム動画像処理
biacco42
8
2.2k
Macとオーディオ再生 2024/11/02
yusukeito
0
300
讓數據說話:用 Python、Prometheus 和 Grafana 講故事
eddie
0
380
ActiveSupport::Notifications supporting instrumentation of Rails apps with OpenTelemetry
ymtdzzz
1
160
Sidekiqで実現する 長時間非同期処理の中断と再開 / Pausing and Resuming Long-Running Asynchronous Jobs with Sidekiq
hypermkt
6
2.9k
Outline View in SwiftUI
1024jp
1
260
Amazon Bedrock Agentsを用いてアプリ開発してみた!
har1101
0
290
Server Driven Compose With Firebase
skydoves
0
430
色々なIaCツールを実際に触って比較してみる
iriikeita
0
310
プロジェクト新規参入者のリードタイム短縮の観点から見る、品質の高いコードとアーキテクチャを保つメリット
d_endo
1
1.1k
GitHub Actionsのキャッシュと手を挙げることの大切さとそれに必要なこと
satoshi256kbyte
5
410
Tuning GraphQL on Rails
pyama86
2
1.2k
Featured
See All Featured
Automating Front-end Workflow
addyosmani
1366
200k
BBQ
matthewcrist
85
9.3k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.3k
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
The Invisible Side of Design
smashingmag
297
50k
Thoughts on Productivity
jonyablonski
67
4.3k
Building Applications with DynamoDB
mza
90
6.1k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
A designer walks into a library…
pauljervisheath
202
24k
Building Your Own Lightsaber
phodgson
102
6.1k
Transcript
メタバースプラットフォーム 「INSPIX WORLD」はPHPもC++もまとめてC#に統一! ~MagicOnionが支えるバックエンド最適化手法~ 2023.08.23 小柴 祐二 / 河合 宜文
❖ INSPIX WORLDの光と闇 ➢ 長期運用に向かない問題 ➢ 当初のサーバー設計 ❖ 再開発でC#大統一へ ➢
APIとDBの改修 ➢ ログ構造の改修 ➢ 開発環境の統一化 ❖ リアルタイムサーバーの大改修 ➢ MagicOnionとLogicLooper ➢ フルスケーラブルメタバースアーキテクチャ ➢ INSPIX WORLDにおける設計とパフォーマンス ❖ まとめ アジェンダ
小柴 祐二 / Koshiba Yuji パルス株式会社 メタバース事業部 エンジニアリーダー 韓国PCオンラインゲームパブリッシャーにて MMORPGの開発・運用
現在(INSPIX WORLD) 2020年 ・エンジニアとしてコンテンツ開発に参画 2021年 ・エンジニアリーダーに就任 ・講演内容に関わるサーバーサイド大改修を行う 自己紹介
自己紹介 株式会社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
INSPIX WORLDとは ボイスチャットで楽しめる人狼ゲームや脱出ゲームなど 様々なコンテンツを鋭意開発中 3Dメタバースプラットフォーム アバター&ゲームで多彩なコミュニケーション ※2023年8月現在:新バージョンに向けて長期メンテナンスを実施中です
ヒプノシスマイク 予選ライブチケット販売数(全て有料) INSPIX WORLDの歴史 2021年4月にアーリーアクセスを開始 初手のVRライブは好調にスタート まるで順風満帆なスタート しかし… その後、技術統括となり全体を見渡した際に 深淵を垣間見てしまう…
本当はヤバかったINSPIX WORLD 結果、メタバースとして長期運用に向かない状態になっていた UGC廃止後も再設計を行わず開発を継続 着手当初は ユーザー生成コンテンツ (以下、UGC)を想定 スケジュールの都合上 パフォーマンスは二の次で開発 ver.1.0
その後、プロジェクト方針で仕 様を変更することに モックver. UGC時代の処 理 UGC時代の処理 新機能① 新機能②
不要な機能・仕様が多数 拡張開発が必ず工数大 データロックが多発 操作不可の待ち時間が 高頻度で発生 APIレスポンス速度が遅く リアルタイム性が悪い アップデートコスト増大 ユーザー体感が悪い 快適さ皆無のサーバー設計
幾度とない仕様変更による不要機能が多く アップデートを行う際には必ず仕様見直しを測る事態に lock! 基本 工数 見直し 工数 基本 工数 処理待ち… やりたい事があっても根幹の問題で”開発自体が行えない”事態も発生 長期運用に向かなかった原因
リアルタイムサーバーの 許容接続者数に上限があった メタバース空間として遊んでほしいが 多数のプレイヤー接続に対応できない 情報保持など追加の処理が必要になり クライアント側の開発工数を圧迫する事態に キリが無くなってくるのでこの辺で… 初期の設計では接続人数を無制限にするため ロジックをほぼ持たせず 同期や通知のリレー等のサーバーとして設計
サーバー主導でイベント発火ができない クライアント主導での実装が必要だった その他にも問題は多数 リレーサーバー形式だった為、クライアントホス ト型としての実装しかできなかった
課題を解決するために… 改造までのストーリー サーバーサイドを全て作り直すしかない … メタバース&運用に対して 課題になるポイントを整理 大規模な改修を決定 サーバーサイドのPHP⇒C#への マイグレーションが決定 マイグレーションのリファクタリングと同時に新規
開発も並走することが決定 API総数200以上のマイグレーション& 新規開発で1からの再設計も行う事に
目指すべきものを再整理 高速でストレスのないプレイ体験 急激なユーザー増加にも耐えうるスケーラブルなシステム 機能拡張・拡充がスムーズに行える設計 問題を解決するために目標設定
リリース当初のサーバー設計
リリース初期の INSPIX WORLD のサーバー構成 C# クライアント C++ PHP 当初のサーバー構成図
クライアントはリアルタイムサーバーと通信する際、 このリバースプロキシへを通じて通信 C# クライアント 当初のサーバー構成図 C++ PHP
AreaServerはルーム内の位置情報を管理して同期を行うためのサーバーになり、 ロジックは持たず、同じルームに所属していユーザー間との同期通知に専念します。 C# クライアント 当初のサーバー構成図 ルーム内の位置情報同期に専念 C++ PHP
コンテンツへの参加や招待機能など 基本的なユーザーの行動はAPIを経由してやりとりを行います。 C# クライアント 当初のサーバー構成図 C++ PHP
WorldServerは、接続している全ユーザーの接続先情報・管理や通知に必要な全ての接続グループ情報を保 持しています。 全ての情報はこのサーバーが保持しているため、本構成での要となります。 C# クライアント 当初のサーバー構成図 C++ PHP 全てのセッション情報を保持
クライアントへの通知、APIからWorldサーバーへ送信し APIから受信した情報を元に、RevProxyを経由してクライアントへメッセージを送信 C# クライアント 当初のサーバー構成図 C++ PHP
開発途中で大規模なメタバースアプリへ方針が変更となりましたが、 モックで制作した基本設計をベースにサーバーが構築されており各所に問題を抱える状態に C# クライアント C++ PHP リレーサーバー形式による 実装コストの増大 当初のサーバー構成図
開発途中で大規模なメタバースアプリへ方針が変更となりましたが、 モックで制作した基本設計をベースにサーバーが構築されており各所に問題を抱える状態に C# クライアント C++ PHP 単一障害点の発生 当初のサーバー構成図
開発途中で大規模なメタバースアプリへ方針が変更となりましたが、 モックで制作した基本設計をベースにサーバーが構築されており各所に問題を抱える状態に C# クライアント コネクション非維持による 処理速度の低下 メタバースアプリに適していない データ構造等 当初のサーバー構成図 C++
PHP
開発途中で大規模なメタバースアプリへ方針が変更となりましたが、 モックで制作した基本設計をベースにサーバーが構築されており各所に問題を抱える状態に C# クライアント 言語が異なる事で管理が煩雑化 当初のサーバー構成図 C++ PHP
全てにおいて詰んでました…
処理速度が遅くなるなど直接的な影響をユーザーに及ぼしている 当初の設計ではメタバースとしての長期運用が難しい バックエンドを丸ごとイチから再設計
別の言語で作成されていたAPI、リアルタイムサーバーを全てC#に統一することで IDEの統一、クライアント、サーバー間でのコード共有などもC#で開発を完結! クライアントの開発はUnityを使用 リアルタイムサーバーは MagicOnionを採用しC#へ バックエンドを全てC#へ クライアントとの連携向上のため、API サーバーをC#へ インターフェース実装でクライアントとサーバー間の コード共有が可能になり並行開発等も容易に
バックエンドを全てC#統一することで 同一プロジェクトとして管理できるように APIをPHPからC#に改修決定 JSONからMessagePackへの変更で クライアント⇔API⇔リアルタイムサーバー全ての 通信プロトコルを共通化 MagicOnion製作者であるCysharp社の河合さんにも バックエンドの監修に参画していただけることに PHPもC++もまとめてC#に統一!で全部解決
まずはAPIとDBを改修
APIサーバー:やるべきこと 言語の違いによる連携コストの発生 送受信ともに言語の違いからデータ変換周 りで度々トラブルがあった。 バグの調査も言語の違いからお互いに頼り きり状態に APIもC#に書き換えプロトコルを共通化 サーバー⇔クライアントの開発効率を考慮 リアルタイムサーバー同様に言語を C#に共通
化 データベース:やるべきこと 各DBの役割や情報を整理 色々な箇所に散らばっていた 状況を整理し、DB毎の役割を分別 一般的なMMOのようなDB構造へ 分別したデータを整理 スキーマを正しく分離することで 一元管理と負荷分散を行えるように APIとDBの改修について
アイテムの上限数が統一されていない アイテムIDが目まぐるしく変わる 水平分割すべきではないデータの無用な分割 … 無制限では延々と アイテムが増加 通信時間が増大していく 所持上限や倉庫機能を作成 長時間プレイの負荷を軽減 A
12345 例えば”トレード”を行うと アイテムのユニークIDが変動 調査が困難×追跡不可能 A B C 常に12345 アイテムのユニークIDを固定 ログを柔軟に追跡できる改修 欲しいデータ 構造整理を行えておらず 不要なデータも参照 余計な負荷が発生 参照データ スキーマを整理し 必要な参照のみを 行う形で最適化 B 67890 改善された内容の一部
データ参照時、Redisを都度参照しておりロスが多く発生 cache data A B メタバースには”多数のユーザーが存在” ⇒キャッシュから参照するように調整 ロスを最大限削減できよう改修 C D
APIからリアルタイムサーバーへのコネクションを都度生成&破棄していた コネクション生成 破棄 リアルタイムサーバーへの RPS(秒間リクエスト数)の向上 ⇒リアルタイムサーバーとの セッションを維持するように改修 処理速度が大幅に減少 cache APCuのキャッシュ機構:未実装 Redis 常に全件取得 改善された内容の一部
モック時代の不要なデータが大量:膨大な処理負荷に UGC時代の処理 新機能① 新機能② 新機能③ ・UGCベースの不要な処理残 ・削除せずに新機能を追加 削除もしづらい状態& 無駄な処理負荷が増大 不要なコード破棄&再整理
・200個のAPIを移植 ・100個のAPIを新規追加 異なる言語によってClientとServer間で実装難度が高くなっていた ClientとServerがそれぞれ インターフェースを実装し パラメータ表を確認するアナログ管理 ヒューマンエラーの多発 C#に統一したことで PJ内のプロトコルの共通化 (JSONからMessagePackに移行) ⇒エラーが激減&効率が大幅向上 CL SV Error! 改善された内容の一部
前提 旧構成の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%に
新構成の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人以上の リクエストを許容できる結果に
APIとDBのある程度の最適化 DB構造も変えたし、運営効率をあげる為にも ログ構造も改修しよう 長期運用を行うにはログ分析ができないといけない… 完了!
長期運用を見据えたログ構造の最適化
▼ユーザーログをDBに書き込み API ログの書き込み DB 書き込み完了 書き込み終わるまで APIの処理は止まる APIサーバーからDBへログを直接書き込む形式 一連の動作の中で書き込み待ちが発生 運用が長期化する=データサイズの肥大化
運用コスト面での懸念もある 数ミリ秒単位の小さい遅延だがより良いパフォーマンスを実現したかった 当初のログ保存 ログ保存方法の変更
AzureEventHubs(以下Hub)とAzureDataExplorerを採用したことで APIでログをQueueに詰め込んで非同期でHubへログを送るように改善 ⇒APIからDBへの書き込み時に発生していた遅延が0となり負荷を軽減することもできた 非同期でのログ保存により、APIサーバーの応答性と低レイテンシが 確保され最大限のパフォーマンスに ログデータの保存方法を変更 連携により非同期の ログ保存/バッファリングが できるように ▼AzureDataExplorerを用いて
・リアルタイムに 解析ができるよう最適化 ・分析ツールも 同時に連携させ分析も可能 ログ保存方法の変更
「アイテムの取得」「売却」「取引」など、一連のログが1か所に保存されており ログの種別が増えた場合はカラムが増えてしまう構造に… 1つのカテゴリに 同種のログを詰め込んでいた 不要なデータが多く 状況把握も適切に行えない dataがNullになっていて不要 分析やサポートが 難しい状態 Statusでのアイテム取得情報が
分かりづらい ログ種別が増えると カラムが増えてしまい可読性が落ちる 不要データが含まれている 当初のログ構造 運営や分析を前提としたログ構造になっていなかった
旧ログ 新ログ 細分化されたログ (参考として一部のみ記載 ) ・同種のログに詰め込む形式を破棄 ・ログの細分化を行うよう改善 まずはログの構成整理:”ログの細分化”に着手 Item -
アイテム情報 購入 獲得 売却 使用情報 などの全情報 ItemBuy - アイテムの購入 ItemGet - アイテム獲得 ItemSell - アイテム売却 ItemUse - アイテム使用 Room - ルーム情報 RoomIn - ルーム入室 RoomOut - ルーム退室 一定のフォーマットを前提として細分化することでログ結合が可能となり 後述の”管理ツール”でのデータ取得/解析が容易にできるよう最適化 入室 退室 などの全情報 ログ構造の刷新
不要データが含まれている どのメソッドを経由して [ItemGet - アイテム獲得]ログの参考 だれが どのアイテムを いくつ獲得・いくつ持っている なにで どこで
例えば「アイテム獲得を調査したい」と考えた際にどのような情報が必要になるか 運営的な視点も加えてログ構成を検討 ログ構造の刷新
カスタマーサポート等でログを解析する為に、 運営チームが欲しい情報を取得できるよう ”管理ツール”を開発 その際下記3つを必須項目として開発を進めることになりました ログの細分化により 情報が整理しやすくなった 管理ツールによる データの取得/解析の効率化 NEXT 専門知識不要・誰でも簡単
完成した ツール 三原則 最新の情報が常に取得可能 抽出情報を自由に選べる 管理ツールの開発
日時を選択 どんなログを抽出するか どのユーザーを対象に (AccountIDやCharacterID) どの情報を対象に (InstanceIDやItemID等) ログを時系列で表示 管理ツールで何ができるのか:参考 管理ツールの開発
ガチャ ※ゲーム画面は開発中のものとなります ログを時系列に表示 ガチャ/ゲーム報酬のログ種を選択 トレード 売却 これらの行動も 全てリアルタイム確認可能 管理ツールで何ができるのか:参考 ゲーム
管理ツールの開発
ユーザーアクションの流れ 例:様々なアイテムの獲得 トレードや売却などの各種行動 ・誰でも簡単にユーザー行動を 全て時系列でログとして可視化 ・調査やカスタマーサポートなどが 迅速に行える 管理ツールの開発
C#統一による開発環境の効率化
デバッグを行うだけでも複雑な手順が求められていた 移行前 ・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へ移行
環境構築の作業コストが非常に高かった PHPにて制作 C#統一後 Vagrantにて仮想のLinux環境を作成し Dockerコンテナを展開 環境によって異なるトラブルが多発 解決のために多くのリソースを割く事に 再開発時にDocker Desktopでダイレクトに コンテナを展開する方向に
VisualStudioとの連携により環境構築が Dockerのインストールのみでできるように 環境破棄から再生成間での構築時間が 10倍以上早くなる結果に 開発環境がVagrantからDockerへ移行
言語が異なる事でプロジェクト内のコード管理が煩雑化していた CL:C# SV:PHP,C++ Clientでのデータ利用は ステップを挟んで実装する必要があった CL/SV:C# C#統一により同一のソリューションで ・定数やクラス、ロジック等を ClientとServerで共通化 ・APIとリアルタイムサーバー間で定数やロジックも共通化
⇒開発効率が大幅に向上 JsonUtilityで変換 変換時にデータ不整合等の連携エラーが多発 C#統一化による共通プロジェクト化
・ネットワーク通信 ・ストレージ利用の最適化 ⇒異なるプラットフォームとの データ交換がスムーズに行える ライブラリとなっている ※Githubより https://github.com/MessagePack-CSharp/ MessagePack-CSharp MessagePack for
C# C#向けに最適化された、 JSONと比べ高速なデータ処理 / コンパクトなデータサイズを実現 できるハイパフォーマンスのシリアライザです。 後述するMagicOnionでもMessagePack for C#が採用されています。 MessagePackとは…
ここから本題 重要なリアルタイムサーバーの改修を…
とにかく大人数を捌く事を重視し、最小限のリアルタイムサーバー台数で 最大限の人数効率を出すために”リレーサーバー形式”を採用 再開発のため、問題点を一旦整理 ユーザー同士のゲームプレイなど、コミュニケーション重視に切り替えた結果、 様々な機能で”クライアントホスト型”での実装が必要に クライアント側の実装コストが悪くなる状態へ 新規コンテンツ開発&アップデートに対して コストが異常に高くなってしまう結果に 問題点の整理
INSPIX WORLDはリレーサーバー形式のクライアントホスト型だった ホスト兼サーバー ゲスト ゲスト ゲスト クライアントのうち1台がホストとなり 同時にサーバーとしての役割も担う ▼主な特徴 ・サーバーをクライアントが担っているので
サーバー費用が抑えられる ・クライアント側での処理が複雑になる ・クライアント改造などのチートを防げない 等 リレーサーバー リレーサーバーを介してゲストとの 情報送受信を行い、ゲーム進行の同期 簡易図解 リレーサーバー形式のクライアントホスト型とは
リレーサーバー形式のクライアントホスト型で開発はかなり複雑に… INSPIX WORLDの人狼ゲームは”8人プレイ” ホスト ゲスト ゲスト ゲスト ゲスト 8人全員のデータを常に同期 ▼ホストが切断
復帰した元ホスト ▼復帰した元ホストに新ホストがデータを復元 …… 基本方針:復帰した際にゲームに戻れることが必要だった ゲスト 新ホスト ホスト ▼ゲーム継続のためゲストを新ホストに 新ホスト ▼多くの懸念 ・プレイ時間中、常に多人数の情報の保持/同期 ・データの整合性が取れないとゲームが進行しない ・”切断⇒復帰”時のプレイヤーデータの修復のため ホスト以外も含めて常に情報の同期が必要だった 修正やアップデートが慎重になり 対応コストが増大していく クライアントホスト型における懸念
MagicOnionとLogicLooperを組み合わせた構成で サーバーを構築することに ユーザー同士が様々な体験を共有できる メタバース空間を実現する為に サーバー主導でリアルタイム通信を成立させたい
MagicOnion & LogicLooperとは
”MagicOnion”はこれらの要件を満たすだけでなく C#への言語統一が出来るなど多くのメリットがある事から採用を決定! 通信フレームワークは超重要! 多数のユーザーが 同じ空間を共有できる”同期処理” ストレスなく遊べる ユーザー体験に直結する”処理速度” メタバースアプリで重要なこと
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
MagicOnionのメリット ・APIサーバー同様にMessagePackが利用可能となり、同一プロジェクトで管理可能に ・インタフェース定義された C#ファイルをCL⇔SV間で共有することができ開発効率向上に寄与 ・CL側からgRPC経由でサーバーメソッドを呼び出すだけで簡単に通信処理を実行できる MagicOnionが向いているゲーム ・プレイヤーが戦術や戦略をリアルタイムで指定しユニットに指示を与える ・指示に合わせたユニットの動きを迅速に同期 上記は一例:リアルタイムで多人数がプレイするゲームに最も適しています MO
リアルタイムストラテジー ・多数のプレイヤーが同時に参加するオンラインゲーム ・リアルタイムな対戦や協力プレイ、 チャット等の通信を効率的に行う事ができる INSPIX WORLD:仕組み的に近似 MagicOnion
ただ、MagicOnionは高速・多人数でのリアルタイム通信は実現できるものの、サー バ側の複雑なロジックを効率よく実装するには別の何かが欲しい。 LogicLooperの登場
State等で管理すればLooperがゲーム進行を管理することも可能 CLにロジックを持たせなければチートの抑制にも繋がる LogicLooperとは .NETでのサーバーアプリケーションでループを使用した プログラミングモデルを実装するためのライブラリ 主にサーバーサイドにロジックがあるゲームサーバーのようなユースケースにフォーカスしています LogicLooper MagicOnion クライアント Updateのようにフレーム毎に処理を繰り返す
引用:https://github.com/Cysharp/LogicLooper/blob/master/README.ja.md State変更 State変更 通知を受けて 次の進行へ 変更後の 処理を通知 サーバーサイドでゲーム進行を行う為のロジックを実装することができ CPUコア数に応じてスレッドに負荷分散が可能 Example LogicLooper
LogicLooperが向いているゲーム LogicLooperのメリット ・ゲーム進行を制御する為のロジックループを簡単に実装できる ・ゲーム動作をリアルタイムに制御する事が可能です ・CPUコア数に応じてマルチスレッド処理が出来る為、高頻度の計算処理等に適している ・複雑なゲームパートをリアルタイムで進行させる必要があるゲームでは 計算や非同期の処理を Logiclooperで行う事でゲーム進行が簡潔に実装できる リアルタイムマルチプレイヤーゲーム 非同期処理やタスクのスケジューリングが効率的に出来るため
高頻度でデータの同期と非同期処理が必要になるようなゲームでは 同期やデータ更新をスムーズに行う事が可能になる シミュレーションゲーム INSPIX WORLD LogicLooper
通信コストを抑えて、且つ水平分割可能な仕組みが必要に MagicOnion & LogicLooperでの実装 ①複数のユーザーを同一空間上で同期させる場合同じMagicOnionに接続する必要があり 接続人数を増加する為にはサーバースペックが要求され運用コストが高くなってしまう ②別のMagicOnionに接続しているユーザーとの同期にはMagicOnion同士のセッションを 常時繋ぐ必要があり通信コストが増大してしまう MagicOnionとLogicLooperのみで実装を進めた場合、スケーラブルをどうするかが肝に…
ではどうするか…を考えていたら 河合さんからタイムリーな連絡が!!
フルスケーラブルメタバースアーキテクチャが 完成しました!!
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)
いわゆるDedicated Serverと同じく、ステートやロジックを持った単一サーバーにクライアントが繋ぎに行く構成。 1ゲームセッション(ルーム)に対するサーバーIPの管理に工夫が必要、特に Kubernetes環境の場合に難しくな る。 ステートフルサーバー Client Client Client MagicOnion
(LogicLooper) 単一サーバー接続 LogicLooperを同居させて ステートを持たせる Good: シンプル Bad: クライアントの接続先管理が別途必要 Bad: 単一サーバーに集約されるのでスケーリン グしづらい、1セッション1プロセスの場合は、コス ト面のバランスが悪いことも
メタバースアーキテクチャ MagicOnion (State) Client Client Client MagicOnion MagicOnion (State) MagicOnion
MagicOnion ロジックサーバーを単体で分離して、 ワールド全体のステート管理、ゲーム ループを用いたフレーム間のバッチ処理 などを行う PubSub 繋いでいるクライアント固有のステート管理(そのユーザーに不可視のデー タであればカリングして送信しないようにする、など) LogicLooper ステートレスサーバーの管理の容易さとステートフルサーバーの機能性の両立といういいとこ取り(?) クライアントとの送受信処理とロジック処理を分離することでスケーリング性も高まった
メタバースアーキテクチャ MagicOnion (State) Client Client Client MagicOnion MagicOnion (State) MagicOnion
MagicOnion ロジックサーバーを単体で分離して、 ワールド全体のステート管理、ゲーム ループを用いたフレーム間のバッチ処理 などを行う 繋いでいるクライアント固有のステート管理(そのユーザーに不可視のデー タであればカリングして送信しないようにする、など) LogicLooper ステートレスサーバーの管理の容易さとステートフルサーバーの機能性の両立といういいとこ取り(?) クライアントとの送受信処理とロジック処理を分離することでスケーリング性も高まった PubSub ただしMagicOnionとLogicLooperを繋ぐPubSubの パフォーマンスは依然として気になる ……
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 特にメタバース的な座標データの配列な どで数百倍のパフォーマンス向上
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…)
このアーキテクチャでメタバースを実現する
INSPIX WORLDで採用しているメタバースアーキテクチャで構成したリアルタイムサーバーは完全なるフルス ケーラブル構成となり下記のようなフローになります。 新INSPIX WORLDの構成 C# クライアント C# C#
MagicOnionはClientとNATS&LogicLooperの間に立ち リバースプロキシの役割を果たします クライアントが接続する MagicOnionはリ バースプロキシの役割を果たす 新INSPIX WORLDの構成 C# クライアント C#
C#
LogicLooperはゲームループのロジックに専念させる 接続先はNATSに任せるので気にしない 新INSPIX WORLDの構成 C# クライアント C# C# ゲームループのロジックに専念
C# クライアント MagicOnionとLogicLooperは直接繋がっておらず、お互いがどのマシンを把握していません ここの通信を担保する為に、 NATSの存在が欠かせません 直接は接続していない 新INSPIX WORLDの構成
LogicLooperからのメッセージ、MagicOnionからのメッセージはNATSを経由することでPub/Subにて間接的に メッセージのやり取りを行う事で各々の役割が明確に分離され実装がシンプルになります Publish/Subscribe 新INSPIX WORLDの構成 C# クライアント
今後追加されるコンテンツでは1~200人以上が参加できるような物もあり アトラクションでは切断後の復帰機能も必須となる INSPIX WORLDは現在ロビー、ハウジング可能なマイルーム、人狼ゲームや脱出ゲームが 実装されており、その他のコンテンツは Ver3.0にて追加される予定となります ロビー 200人 マイルーム 8人
脱出ゲーム 4人 人狼ゲーム 8人 追加コンテンツ 追加コンテンツは複数 鋭意開発中 1~200人 INSPIX WORLDのコンテンツ
メタバースアーキテクチャの設計の概要について 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
メタバースアーキテクチャの設計の概要について 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
メタバースアーキテクチャの設計の概要について 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
メタバースアーキテクチャの設計の概要について 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を介してデータ送信
メタバースアーキテクチャの設計の概要について 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は複数同時に持つ場合も
メタバースアーキテクチャの設計の概要について 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を実装して使い分ける形に
同一の空間に1500体のアバターを生成し、同期処理が正常に行えるかの検証を行いました クライアント/サーバー共にまだ最適化を は行っていませんが、 同期は問題ない結果となりました 1500人の同一空間同期テスト 図2:アバターが動いている様子 図1:ゲーム画面と接続人数 アーキテクチャ採用後の同期数テスト
LogicLooperによるロジックサーバーの負荷分散 ▼ゲーム進行等のロジックを全て LogicLooperに任せることができるようになった ・CPUのコア数に応じたマルチスレッド処理で プロセス内で複数のロジックが効率的に並列実行されるように ・一つのLogicLooperに負荷が大きくかかったとしても オートスケールにより台数を増やせば簡潔に解決可能 ・更にクライアントホストが持っていたゲーム同期ロジックが Serverに移動したことで、ホスト端末の負荷も軽減しました。 ロジックの開発に専念
前述した設計において、同期周りを MagicOnionのStreamingHubにまとめることで ・共通での処理ができるようになり、各コンテンツ開発においては それらのアバター同期等を考慮せずロジックのみの開発に専念出来るようになった ⇒新規開発及びアップデートにおける人的コストの削減に繋がりました リアルタイムサーバーの改修後
市場的にサーバーエンジニアが少ないこともあり人員不足の解消にも寄与!? サーバー& クライアント サーバー クライアント サーバー CL SV or クライアントとサーバーの並行作業または単一での作業完結が可能に
機能実装時、担当者の不在や作業の遅れによって担当者の手が止まってしまう事もあったが MagicOnionの導入によりInterfaceを共有することで 両者の作業を並行して進めることができるようになり開発効率の大きな向上に 旧:実装 フロー 担当1 クライアント 担当2 基本的にSVが先に実装を行い次にクライアントが作業 新:実装 フロー 担当 担当 MagicOnionの利用で並行作業が可能に エンジニアによっては両作業を 1人でも可能に CL、SV共に同じInterfaceを実装することで共通化 共通化したことでCL⇔SV間での実装精度も向上 C# リアルタイムサーバーの改修後
各サーバーのCPUを監視し、あらかじめ設定された 閾値を超えると自動でスケールが行われる ⇒ゲームへのユーザー接続上限数を実質 ”無制限”にすることが可能になった NATSはクラスタ化可能 自動でスケールできるが最適化は必須! MagicOnion+NATS+LogicLooperを用いて理論上人数無制限のオートスケールが可能に 自動でスケール リアルタイムサーバーの改修後
まとめ
高速でストレスのないプレイ体験 急激なユーザー増加にも耐えうるスケーラブルなシステム 機能拡張・拡充がスムーズに行える設計 まとめ 最適化によりINSPIX WORLDが掲げた目標を達成 クライアントホスト型からの脱却及び C#統一化による開発効率向上 APIやDBの改修によるデータ構造及びリアルタイムサーバーの改修 メタバースアーキテクチャで実現
まとめ 当たり前の形にする為に時には再開発の提案も必要に! プロジェクトの長期運用を目指さない開発はいないと思いますが、 当初の方針から大きく舵が切られてしまう事も多々あると思います。 そんな時は、一度全てを捨て再開発する勇気も時には必要です C#大統一によるプロジェクト統一化 C#への統一に起因してコードやプロトコル等あらゆる共通化を進めた事で クライアントとサーバー間の作業効率やミスの削減に繋がり、 再開発もスピード感を持って対応できる環境が構築できる結果となりました フルスケーラブルメタバースアーキテクチャでメタバースを実現
技術の革新が進み、モバイル端末でも据え置き機と変わらないクオリティに なってきたとはいえ、人数がボトルネックになりがちなメタバース … そんな問題も解決できるかもしれないこのアーキテクチャを是非試してみてください!
ご清聴ありがとうございました!