クラスター株式会社でSoftware Engineerをしている thara です。
cluster というサービスにおいて、3Dで表現された空間内で各クライアント間の状態をリアルタイムに同期するという機能は、その体験の根幹を担うものです。
clusterでは内部的に、クリエイターが作成した3D空間自体をworld、複数のクライアントの状態が同期されるworldの単位をroomと呼称しており、そのroom内の状態同期を担うバックエンドのサーバーをroom serverと呼んでいます。
この記事では一スタートアップであるクラスターが、2021年半ばまでに3D空間同期という要件をどうやって実現してきたか、そしてそれ以降、room serverがその前提を超えた機能要求を満たすためにどのように進化してきたかを紹介します。
まず、2021年半ばまでを前提に、どのようにroom内の状態の同期をしていたかに触れます。
roomで同期する情報
roomでは、ざっくりと以下のような情報をクライアント間で同期しています。
- roomに入室しているユーザーが使用するアバターの姿勢情報、位置
- roomに入室しているユーザーが音声入力したボイスチャットの音声
- worldに配置されているアイテムの、そのroomでの位置情報
これらは、下図のように、ある特定のクライアントから送信された情報「message」を他のクライアントにブロードキャストする、いわば pubsub の仕組みを用いて同期されます。下図では特定のクライアントから一方的に他のクライアントにmessageを送っていますが、実際には相互にmessageを一定間隔でbroadcastしあうことで各クライアントが認識するroomの状態の同期を取っています。
client状態のpubsubによる同期
このpubsubだけであれば、世に溢れるpubsub serverのソリューションをそのまま使うことができます。現にclusterでは、この仕組みを実現するために MQTT を採用し、サードパーティのMQTTサーバーをそのまま使用していました。
しかし、このMQTTサーバーだけだと、それだけではbrokerとしての機能しかないためにDBや外部APIを元にしたmessageをpublishすることができません。例えば、入室ユーザーの一覧やroomの設定情報などです。
任意のクライアントをホストに選出し、そのクライアントにそれらの責務を担わせる( 分散コンピューティングでいうリーダー)ということもできなくはないですが、MQTTコネクション切断時のホストマイグレーションが困難であることや、そもそもコメントというフリーテキストの送信を特定のクライアントに委譲するのは負荷的にもセキュリティ的にも好ましくない、などの理由でその方法を選択していませんでした。
代わりにclusterでは、手っ取り早い方法として、クライアントではないサーバーサイドの1サービスからmessageをpublishすることでそれを実現していました。このサービスを内部的にはpublisherと呼んでいます。
publisherによるserver状態のbroadcast
これによって、サーバーからpublishされてきた「room入室者」などの情報をクライアントが参照することで受け取ったmessageの送信元の妥当性を検証できるようにもなっています。また、API経由でDBに書き込むことで各クライアントの状態を集約して各クライアントに一貫したmessageを送れるようにもなっています。
2021年半ばまでは、主にこのクライアント間のmessageのpubsubサーバーmessageとのブロードキャストという方法で room内の状態同期を行なっていました。
しかし、clusterが進化していく上で、この方法に限界が見えてきました。
求められる機能要求の高度化
以前よりサードパーティのMQTTサーバーの限界は見えつつあると感じていたのですが、以下のような高度な要求が生まれるにつれ、それ自体が大きな枷となっていました。
- 各クライアントの位置情報に基づくネットワーク帯域最適化
- いわゆるLODですが、テクスチャ解像度などではなく、messageとして送信されうるアバターのボーンなどの情報が対象です
- プレイヤーごとのプレイ状況を保存し、次に訪れたときに引き継いで遊べるセーブ機能 の実装
- スマホだけでも簡単にワールドがつくれる新機能「ワールドクラフト」 の実装
- 実装されたのは2022年ですが、2021年当時から構想はありました
- 通信途絶から回復時の安定性向上
- 到達保障性をメッセージごとにカスタマイズできることでの遅延低減
- 外部データソースへのアクセスによるスループット低下の解消
これらを無理なく実現するためには、サードパーティのMQTTサーバーを使っているだけでは実現できない(あるいは困難な)以下のような課題を解決する必要があります。
- publishされたmessageを加工してブロードキャストする
- publishされたmessageをブロードキャストせずに何かしらの処理を行う
- クライアントネットワーク状況をリアルタイムに把握する
- TCPからUDPへの移行
上記を一度に実現することは非常に困難ですし、可能な限りプロダクト自体の機能追加と並列して実施したいと考えました。
そこで、MQTTとしてのプロトコルは保持しつつも、サードパーティのMQTTサーバーを独自実装に切り替えることで、上記の要求を満たせるようなagilityを確保する「room server」に移行する計画が生まれました。
2021-06 room serverへの移行
clusterはメンテナンスによるサービス停止を行わないポリシーなので、いかに既存の振る舞いへの影響を抑えて漸進的に移行を進めるかが日々の機能開発にとって肝要です。
room serverも例に漏れず、いきなり上記に挙げた機能要求を満たすような新たな実装を1から構築するのではなく、まずはMQTT互換のプロトコルをサポートするroom serverを構築してから、プロトコルに独自拡張を追加したり新たな機能追加をしたりしていきました。
room server自体がMQTTをサポートしpubsubの振る舞いをサポートしているため、サードパーティMQTTサーバー以外のサービスに特別に手を加えずに移行することが可能です。また、world単位のfeature flagを使ってroomに割り当てられるroom serverを切り替えるようにしておき、負荷試験や社内MTGでのドッグフーディング等を経て、2021-06には本番に適用することができました。
publisher統合
サードパーティのMQTTサーバーからroom serverに移行したことで、MQTTのみの構成では別プロセスとして分割する必要があったpublisherを統合できるようになりました。これには、プロセスの起動順序の依存関係の問題を解消したいという意図もありました。
また、room serverからDBに直接アクセスするのはセキュリティの観点で避けたかったため、DBアクセスを別のgRPCサービスに委譲するのも同時に進めました。
これも無停止で実現するために、一時的にpublisherとroom server内でserver publishする機構(internal publisherと呼称)を共存させておき、双方から配信されるmessageが等価であることを検証した上で、room server内のfeature flagによってブロードキャストするmessage生成元を切り替える、という方法を取りました。
LODによる通信量削減
サードパーティのMQTTサーバーからroom serverに移行したことで、今まで困難だったpublishされたmessageへの干渉ができるようになりました。
これにより、room serverはクライアントから送られてきたアバターの姿勢情報を読み取ることでroomのどこにアバターが存在するかを把握できるようになり、アバターの姿勢情報やボイスなどのmessageを他のクライアントに送信する際に、クライアントごとに加工することでデータ通信量を削減できるようになりました。
具体的には、自身との距離を基準に、他者のアバター姿勢情報の圧縮や送信頻度の調整・ボイスパケットのdropなどを実装しました。
結果、2021-07にはイベント100人のアバター表示ができるようになりました。
ブロードキャストしないクライアントからのmessage
クライアントからpublishされたmessageへの干渉には、messageの加工だけではなく、messageそのものをブロードキャストしない、というのも含まれます。
不正なmessageを拒絶するのはもちろん、今までのクライアントから送られてるmessageはpubsub、つまりpublishしたmessageはブロードキャストされて他のクライアントに送られる、という前提を覆して、単にmessageを受け取ってroom server内部で処理を行うとか、publish元のクライアントに対してmessageを送り返す、などということもできるようになりました。
これにより、詳細は省きますが、ネットワーク環境の把握やワールドクラフトが実現できるようになりました。
今後の展望
以上のようにroom serverに移行しそれに伴う機能拡張をしてきましたが、今までサードパーティのMQTTサーバーを前提で設計されたものを新たなroom serverに最適化する、という流れはまだ続いています。
現段階では以下のような変更を考えています。(既に進んでいるものもあります)
- 更なる通信量削減
- Cluster Creator Kitとの連携強化
- UDPベースの独自プロトコルの使用
メタバースの根幹を作るということ
clusterではこのような進化を続けるroom serverだけではなく、メタバースを実現するのに不可欠なプラットフォームの開発・運用を続けています。
ただ一つのメタバースではなく、クリエイターが自由に創造力を発揮できる場を提供するということは、メタバース領域自体の変化に追従しながらもプラットフォームとしての安定性が求められる、というある種矛盾した状況下に置かれているわけですが、そこにはチャレンジするに値する面白さがあります。
クラスターでは、変化と安定のバランスをうまくハンドリングしつつ楽しみながら挑戦をし続けられるソフトウェアエンジニアを募集しています。