Slide 1

Slide 1 text

©MIXI Agones 上に作るQUIC を使った音声通信機能 開発本部 CTO室 たんぽぽグループ 古城 秀隆

Slide 2

Slide 2 text

©MIXI 開発メンバー 2 開発本部CTO室たんぽぽグループ 加藤 亮 @lyokato 古城 秀隆 @hdtkkj

Slide 3

Slide 3 text

©MIXI Agenda 1. 課題 ● 制作の経緯と解決したかった課題 2. Architecture ● クライアントの音声サーバへの接続フロー ● クラウド上での構成 3. Application ● サーバ/クライアントの構成 ● 音声データが作成されて送受信をするデータフロー 4. サービス導入 ● 技術先行のプロダクトをサービスに組み込む上で行ったこと 5. 今後について 3

Slide 4

Slide 4 text

©MIXI 課題 4

Slide 5

Slide 5 text

©MIXI 過去のシステム ● WebRTC SFUによる音声通信のシステムを開発しており、 XFLAG PARK や モンストフリークなどのゲー ムのユーザイベントに使用 ○ 音声同時接続、数万人のイベントにも使用 ○ Go 製 (pion base) 5 Node001 Node002 room2 room1 Data Store stats reservation room3 Manager API

Slide 6

Slide 6 text

©MIXI 過去のシステム ● ノードを複数並べて、各ノードに複数のルームが共存する設計 ○ ノードはクラウド上のインスタンス ○ この他にも関連のコンポーネントがあって、管理するものが少し多い ○ (もう少し詳しい仕組みは公開資料の Appendixに記載) 6 Node001 Node002 room2 room1 Data Store stats reservation room3 Manager API

Slide 7

Slide 7 text

©MIXI 改善したい点 ● コンテナ仮想化 ● ノード作成時にDNS登録やセットアップの処理が発生 ○ コンテナをDeployのみでスケールも行いたい ● 1 roomを一つのコンテナとして運用したい ■ 音声サーバのコードから room管理を除外できる ● ノードの管理とクライアントの接続先の管理の簡略化 ○ データベースにノードや接続先の情報を保存していた ○ 自前で管理しているものをなるべく減らしたい 7 VM Node001 room2 room1 VM Node002 room3 Container001 Container002 Container003 room1 room2 room3

Slide 8

Slide 8 text

©MIXI アイデア ● 音声用サーバに必要な要件はゲームサーバと似ている ○ 必要に応じてリソースを確保 /解放する ○ 参加者の接続先の管理ができる必要がある ● ゲームサーバをKubernetes上でホスティングする良いソリューションはないか? ● Agones ○ コンテナ型仮想化でホスティング ○ オートスケールの仕組みもあるので、スケールの設定も可能 8

Slide 9

Slide 9 text

©MIXI Agones ● Kubernetes上でゲームサーバをホスティングするためのライブラリ ○ 中身はKubernetesのCRDとカスタムコントローラ, SDK ● 提供される主な機能 ○ ゲームサーバのライフサイクルと在庫の管理 ○ podの状態を変更するためのSDK ○ podごとに異なる外部portの割り当て 9 Agones controller state: allocated port: 9800 state: ready port: 9801 state: ready port: 9802 state: ready port: 9803 game server SDK sidecar Kubernetes API fleet Scheduled Ready Allocated Shutdown

Slide 10

Slide 10 text

©MIXI 改善したい点 ● 複数サービスに基盤的に提供 ○ 複数のサービスが相乗りでき、使用量を補足できる仕組み ○ 無停止でバージョンアップができるようにしたい ● 通信量の削減 ○ 一番コストに関係してくる部分でできる限り減らしたい ○ 接続人数が増えるとそれぞれ下りのデータが一人分増えるので人数が多い部屋だと通信量が どんどん増えてしまう 10 Room 001 Room 001 参加者: 3名 上り: 3本 下り: 6本 参加者: 4名 上り: 4本 下り: 12本

Slide 11

Slide 11 text

©MIXI 技術的投資 ● たんぽぽグループの現在のミッションは弊社で必要になりそうな技術に対する知見を深め、 それをサービスに還元していくこと ○ 昔は「刺身の上にたんぽぽを乗せるような仕事をなくす」でした (技術的負債を解消する ) ● 音声通信は弊社で届けたい価値を提供する上で必要な要素 ○ 音声通信に関する知見を深めておきたい ● 既存の問題の解決と同時に新規技術の検証をまとめて行いたい ○ 通信周りの新しいプロトコルも確認しておきたい ■ QUICを選択(使ってみたい ) 11

Slide 12

Slide 12 text

©MIXI Architecture 12

Slide 13

Slide 13 text

©MIXI 接続フロー 13 servcie api allocator proxy (api server) ku allocator (Service) Agones controller aria-server state: ready port: 9800 aria-server state: ready port: 9801 Kubernetes aria side service side Agones

Slide 14

Slide 14 text

©MIXI 接続フロー 14 servcie api allocator proxy ku allocator Agones controller aria-server state: ready port: 9800 aria-server state: ready port: 9801 Kubernetes aria side service side Agones ● 特定のクライアントを特定のルームに参加させたい ● serviceは allocator proxyにリクエスト ○ parameter は roomIDとuserID (string) ○ このリクエストは認証有 ● allocator proxyは serviceIDとroomIDで複数の Kubernetes cluster からConsistentHashigによりクラス タを決定する ○ roomとclusterのマッピングは持ちたくない ○ クラスタ入れ替えのために少し工夫が必要

Slide 15

Slide 15 text

©MIXI 接続フロー 15 servcie api allocator proxy ku allocator Agones controller aria-server state: ready port: 9800 aria-server state: allocated port: 9801 aria-server state: ready port: 9802 Kubernetes aria side service side Agones ● allocator は serviceIDとroomIDを使ってAgonesに対してPodの確保を依頼 ○ StateAllocationFilter を使用することでFindOrAllocate をリクエスト ○ 後から参加するユーザも同じ roomへの接続を誘導 ● pod allocationの際にAgonesのサーバのメタデータに必要な情報を更新 ○ serviceID, roomID, 認証のSecret ● 確保された音声サーバは内部で SDK経由でメタデータの更新を watchしており、内部の configurationを更新

Slide 16

Slide 16 text

©MIXI 接続フロー 16 servcie api allocator proxy ku allocator Agones controller aria-server state: ready port: 9800 Kubernetes Agones aria side service side aria-server state: allocated port: 9801 aria-server state: ready port: 9802 ● allocator はagonesから帰ってきた情報から tokenを作成 ○ serviceID, roomID, userIDや有効期限を含んだ署名付きの JWT ● tokenと接続先の情報を返却

Slide 17

Slide 17 text

©MIXI 接続フロー 17 servcie api allocator proxy ku allocator Agones controller aria-server state: ready port: 9800 Kubernetes aria side service side Agones aria-server state: allocated port: 9801 ● client は受け取ったtokenを使って音声サーバと接続を開始 ● 音声サーバはtokenの検証を行い、クライアントの認証を実施 ○ 認証が成功すれば接続は完了です ● 音声サーバにクライアントからの接続がこなかった場合は、 一定期間経過後に音声サーバが自らリソースを解放します aria-server state: ready port: 9802

Slide 18

Slide 18 text

©MIXI マルチクラスタの運用 ● マルチクラスタにした理由 ○ Kubernetesはpodがある一定数を超えるとパフォーマンスが低下する ○ Agonesの処理がボトルネックになったときに逃げ道が欲しい ● ConsistentHashを使ったルーティングのデメリット ○ 要素の数や順番が変わってしまうと一貫性がなくなる ○ 単純にクラスターを増やそうとすると変更前の podへの接続ができなくなる ● 2つのCLUSTER Configurationを使ってクラスタの入れ替えをサポート ○ CURRENT_CLUSTERS ■ 通常使用しているクラスターのリスト ○ OLD_CLUSTERS ■ 過去に使用していたクラスターのリスト 18

Slide 19

Slide 19 text

©MIXI マルチクラスタの運用 ● allocation-proxy はOLD_CLUSTERSの設定がある場合は、そのリストを使ってから Find ○ 起動済みのpodがあった場合はそちらを接続先として返します ○ 見つからない場合は通常の CURRENT_CLUSTERSへのFindOrAllocate を実施します ● OLD_CLUSTERSのpodの切断が確認できたら OLD_CLUSTERSの設定を削除します ○ クラスター内のメタデータをフィルタしたりログから確認が可能 19 allocator proxy current Kubernetes clusters old Kubernetes clusters Find FindOrAllocate

Slide 20

Slide 20 text

©MIXI クラウド上の構成 20 aria-allocator-proxy Cloud Run Kubernetes / aria-cluster-0 Kubernetes / aria-cluster-1 Kubernetes / aria-cluster-N aria-allocator Service/Deployment agones-system controller Service/Deployment aria-server(Ready) Pod aria-server(Allocated) Pod aria-server(Allocated) Pod fleet (aria-server) Agones Cloud Storage BigQuery Logging

Slide 21

Slide 21 text

©MIXI Application 21

Slide 22

Slide 22 text

©MIXI server & client 22 Server quiche Rust で各環境向けにビルドした library quiche Unity (C#) Win OSX Android iOS Framework Bundle

Slide 23

Slide 23 text

©MIXI server ● サーバはElixir+Rustで実装 ○ 採用理由は ■ 弊社にElixirの知見があること ■ Discordも同様の構成で実績あり ■ RustのQUICライブラリ、cloudflare/quiche を使用したかった ● ここまでQUICと言ってきたが、正確には WebTransport Over http/3 ● WebTransportの実装はドラフトを参照して、 quicheにパッチを当てて利用 ○ Elixirから WebTransport Server を動かすためのフレームワークは公開しています ■ https://github.com/xflagstudio/requiem ○ コネクションの管理や Agonesとの通信をElixirで実装 ○ QUICのパケット処理をRustで実装 ○ 録音機能も実装 23

Slide 24

Slide 24 text

©MIXI client ● 音声パケットの処理部分は Rustで実装 ○ QUICパケットの処理はサーバと同様に cloudflare/quicheを使用することで内部実装の一致を保 証 ■ QUICは比較的新しいプロトコルなので、異なるライブラリ間でのトラブルを回避 ● 使用するサービスのエンジニアが開発環境でも動作確認が必要なため、 Win, OSXもサポート ○ OSXはAppleSillicon移行期のためBundleで (x86_64, arm64) 両対応 ● iOSは静的ライブラリにビルドしたものを Framework化してうえで関数呼び出ししています ○ サービス側のクライアントで使用する BoringSSL/OpenSSLとのシンボル衝突を回避するため 24

Slide 25

Slide 25 text

©MIXI 音声データフロー 25 Unity Recorder Resampler Buffering Packetizer Codec Denoiser GameObject AudioPlayer (UnityAudio, CRIWare) Audio Mixier QUIC Server Member Management Jitter Buffer Network Voice Processing 音声データフロー

Slide 26

Slide 26 text

©MIXI 音声データフロー 26 Unity Recorder Resampler Buffering Packetizer Codec Denoiser GameObject AudioPlayer (UnityAudio, CRIWare) Audio Mixier QUIC Server Member Management Jitter Buffer Network Voice Processing ● AudioInput 部分 ● Recorder ○ Bluetoothの処理やエコキャンの処理を担当している ● Resampler ○ マイクデバイスごとに異なるサンプリングレートのデータを OPUSで必要な48,000Hzに統一 ● マイク関連はトラブルが多いので別ライブラリとして 作成している ■ Serenade ■ (抱えていた問題はAppendix に記載しておきます )

Slide 27

Slide 27 text

©MIXI 音声データフロー 27 Unity Recorder Resampler Buffer Packetizer Codec Denoiser GameObject AudioPlayer (UnityAudio, CRIWare) Audio Mixier QUIC Server Member Management Jitter Buffer Network Voice Processing ● データ処理部分 ● 適切なフレーミングのため Bufferがある ● DenoiserにはRNNoise ● CodecはOPUSを使用 ○ FEC(前方誤り訂正)とDTX(不連続送信)を 設定している ○ DTXで通信量を削減 ● Packetizerでパケットを作成している ○ RTPをアレンジしたものを使用

Slide 28

Slide 28 text

©MIXI 音声データフロー 28 Unity Recorder Resampler Buffering Packetizer Codec Denoiser GameObject AudioPlayer (UnityAudio, CRIWare) Audio Mixier QUIC Server Member Management Jitter Buffer Network Voice Processing ● 通信部分 ● 認証情報や接続維持、切断検知はストリーム上で行う ● PacketizerからもらったPacketはデータグラム として送信される ● 送信は自分の分だけだが、受信は roomのメンバーの人数だけ受け取る

Slide 29

Slide 29 text

©MIXI 音声データフロー 29 Unity Recorder Resampler Buffering Packetizer Codec Denoiser GameObject AudioPlayer (UnityAudio, CRIWare) Audio Mixier QUIC Server Member Management Jitter Buffer Network Voice Processing ● 受け取ったPacketからroom内のメンバーごとに バッファにデータを整理する ● JitterBufferでパケットの遅延や順序ずれを吸収 ○ 一定期間とどかなかったパケットはロスト扱い ○ 取り出す時にデコード ● UnityのGameObjectにアタッチできるAudioPlayer コンポーネントを用意

Slide 30

Slide 30 text

©MIXI サービス導入 30

Slide 31

Slide 31 text

©MIXI サービス導入のために ● 技術先攻のプロダクト開発 ● 開発本部内の定期的な共有会でこういうものを作っていると共有 ○ 開発本部は各サービスのサポートに人を出している部署なので、 各プロダクトなどにアサインされた人が音声機能が必要なときに声をかけてくれます ● 音声機能を必要としているプロジェクトに相談 ○ 他社のサービスと料金や機能面での比較を行い、説明をしに向かいます ○ さすがに予算面で敗北していたり機能が不足している場合は、話が通りません ● 導入のためにサービスの開発に参加 ○ 音声接続の実装だったり、音声接続までに必要なサービス開発の手伝いも行います ● サービス側の開発メンバーと協力して、バグを潰しながら安定動作の検証をしていきます。 31

Slide 32

Slide 32 text

©MIXI サービス導入 ● 音声システムは無事、昨年の 7月にリリースされた モンストシリーズのアクションスマホゲームアプリ 「ゴーストスクランブル」で使用されています。 32

Slide 33

Slide 33 text

©MIXI Special Thanks !!! ● 技術的なチャレンジの意味を理解し、導入の決断をしてくれたマネジメントの方々 ● 今回は試験的な導入であったため、全てのインフラをゴーストスクランブル内で管理しており、 日々運用してくださっているサーバエンジニアの方々 ● クライアント側のパフォーマンス問題などを一緒に調査解決してくださった クライアントチームの方々 ● 端末ごとのスペックの違いによって発生する問題を検証してくださった QAチームの方々 33

Slide 34

Slide 34 text

©MIXI 今後 34

Slide 35

Slide 35 text

©MIXI 今後 ● 実装済みで導入準備中の機能 ○ 少人数部屋用のpure Rust version ■ 少人数の部屋の場合 CPUコアの割り当ても少なく、あまりロックをかけずに一定感覚ごとに まとめてパケットを処理し効率よく CPUを使うような設計 ● やりたいこと ○ Agones の最新への追従 ○ 社内で気軽にサービスから使える環境の構築 ○ QUICの知見を生かして、ゲームのリレーサーバの実装 ● メンバーが少なく他のプロジェクトに手が取られていて .... ○ 仲間が増えると良いなと思っています .... 35

Slide 36

Slide 36 text

©MIXI ご清聴ありがとうございました 36

Slide 37

Slide 37 text

©MIXI

Slide 38

Slide 38 text

©MIXI Appendix 38

Slide 39

Slide 39 text

©MIXI 旧チャットの構成 (1)  39 MatchingAPI Node001 Node002 Data Store group2 group1 nodeは自分の状態(接続数など)を定期的に更新する これが node の health check になる stats reservation group1 is node1. group2 is node2.

Slide 40

Slide 40 text

©MIXI 40 MatchingAPI Node001 Node002 Data Store group2 group1 クライアントは接続時に MatchingAPIから 接続先を取得し、対象の nodeに接続 MatchingAPIはnodeの状態から接続先を決定し、 groupIDと接続先のマッピングを保存し、 クライアントに返す stats reservation group1 is node1. group2 is node2. group3 どこ? 旧チャットの構成 (2)

Slide 41

Slide 41 text

©MIXI 41 MatchingAPI Node001 Node002 Data Store group2 group1 stats reservation group1 is node1. group2 is node2. group3 同じgroupに接続するクライアントは MatchingAPIから既に存在する接続先を 取得して繋ぎにいく group3 どこ? 旧チャットの構成 (3)

Slide 42

Slide 42 text

©MIXI 42 MatchingAPI Node001 Node002 Data Store group2 group1 stats reservation group1 is node1. group2 is node2. group3 is node3. group3 同じgroupに接続するクライアントは MatchingAPIから既に存在する接続先をも らって繋ぎにいく 同じhostで複数のチャットグループに機能を提供することで、 connectionMapping, roomMappingなどの管理データが増えロックも存在する 旧チャットの構成 (4)

Slide 43

Slide 43 text

©MIXI 43 MatchingAPI Node001 Node002 Dead! Data Store group1 stats reservation group1 is node1. group2 is node2. group3 is node3. group3 nodeを追加するとサーバが readyになるとstats が更新されて振り分け対象になる Node003 New! Reconciler Worker statsの更新時刻を監視し、更新がない nodeを削除し、その nodeに紐づくreservationを削除する これで障害nodeへの接続を排除する 旧チャットの構成 (5)

Slide 44

Slide 44 text

©MIXI マイク関連のトラブル (1) 44 ● 録音開始する際にStreamをどう扱うかのmodeの選択が可能 ● NormalModeのまま録音するとAEC、AGC、NSの恩恵が受けられない ● CommunicationModeで開始すると、メインがVoiceCallになり、ゲーム側音量の調整ができなくなる Game内の音楽 Music Stream VoiceCall Stream Game内の音楽 Music Stream VoiceCall Stream マイク入力 Game内の音楽 ● 録音を開始すると、OS から見て音のStreamが二つになります 簡略化したAndroidの例

Slide 45

Slide 45 text

©MIXI マイク関連のトラブル (2) 45 ボイスチャットのみの場合は問題にならないことが多いが、ゲーム音楽など他に鳴らしたい音がある場合は、デバ イスごとの固有の問題も含めて制御が非常に難しいものになります。 このような問題の対応を aria側と切り離すために音声入力部分を別ライブラリとして実装しています