見出し画像

【インターンレポート】プロセカを支えるリアルタイム通信エンジン「Diarkis」の試験シナリオ作成

はじめに

はじめまして!
3月の平日に,1ヶ月間の就業型インターンシップ「CA Tech JOB」に参加させていただきました,高橋拓也(@TAK848)です。
普段はPlayGroundというコミュニティに所属してバックエンドコースの運営をしたり,ISUCONや・ハッカソンなどに参加したりしています。
僕はサーバーエンジニアとして,株式会社Colorful Paletteに配属いただき,「プロジェクトセカイ カラフルステージ! feat. 初音ミク(以下,プロセカ)」の開発チームに参加しました。
今回は,1ヶ月にかけて行った,リアルタイム通信エンジンの試験シナリオ作成と実行について紹介します。

インターンについて

今回僕が参加したのは,「CA Tech JOB」という1ヶ月の就業型インターンシップです。サイバーエージェントの様々な部署の中から配属をいただき,トレーナーさんを筆頭に,チームのメンバーと実際の業務をこなしていきます。配属された会社や部署についてはもちろん,サイバーエージェントの各事業部についてまで幅広く解像度を高め,今後のキャリアについて考えていく機会をふんだんに提供していただくことができます。
面接の際には,「golangを書きたい!大規模に運営されているサービスの設計・運用といった知見を実際に見て・書いて学びながら,未知の技術にも挑戦したい!」という希望をお伝えし,面接・面談を通してそれにそった配属・仕事に入ることができました。インターン前からプロセカはよくプレイしていたのもあり,入る前からワクワクでした。

プロセカのリアルタイム通信

プロセカでは,ユーザー同士のリアルタイム通信のエンジンとして「Diarkis」が採用されています。
これにより,以下のような機能が実現しています。

  • バーチャルライブ・コネクトライブ

ユーザーのアバターの位置情報や状態を複数人で共有することで,最大100人で同時にキャラクターたちのライブを観戦できます。さらに,部屋同士でスタンプやメッセージを共有し合うこともできます。コネクトライブでは,ペンライトの色によるユーザー間での投票機能も実現しています。

  • みんなでライブ

完全ランダムでマッチングするフリールームと,近い総合力の人とマッチングするベテランルームがあり,いずれも5人1組で協力してリズムゲームのプレイができます。スタンプなどでのコミュニケーションや,スコア・コンボ数などの共有といった機能などが実現されています。

  • ランクマッチ

7段階のランクがあり,一定期間のシーズンでどこまで上位に上がることが出来るかを競い合います。毎試合,プレイヤーのランクに応じたマッチングを行い,1vs1でライバルとリズムゲームの精度を競います。みんなでライブ同様にスタンプなどでのコミュニケーション機能がある他,ライブ中は優勢/劣勢の状況がリアルタイムで更新され緊張感の持ったプレイができます。

Diarkisの概要

プロセカでは,主にDiarkisの「Room」機能がメインで使用されており,これによってバーチャルライブやコネクトライブでの最大100人の同時視聴や,最大5人でのライブプレイ・ランクマッチなどが実現されています。
Diarkisの通信における大まかな流れは,

  1. Podに接続

  2. 条件に基づいたマッチング

  3. Room作成/入室/移動・接続中のPodに存在しないRoomが割り当てられたらPodの再接続

  4. プレイヤーやRoomなどの情報同期(ゲームのメイン部分)

  5. 退室・切断

となっています。
基本はUDPのプロトコルに乗せたdiarkis独自のパケットによって通信が成されています。
RoomやPlayerの同期状態といった,ゲーム進行の上で消失しては困る重要な通信に関しては,RUDP(Reliable UDP)を組み合わせることで,ほぼ確実に共有できるようになっています。これにより,自分や他の入室が完了したことや,Broadcastされた情報をクライアント側で簡単に検知することができます。
また,Room以上に広い範囲で情報を共有できる「Group」機能も局所的に使用されており,ペンライトでの投票機能や,Roomを超えたスタンプ・メッセージの共有に使用されています。

シナリオの作成

作成の経緯

過去,コネクトライブにて,ペンライトでの投票機能が起点となり,過負荷でPodが複数台落ちてしまったという事案がありました。該当PodのRoomに入室していたユーザーは一斉に追い出されてしまう結果となりました。
その検証と改善のために,ユーザーが実際にペンライトでの投票を行うクライアントを再現したbotツールがありました。指定した数だけbotを召喚し,それぞれのbot達が独立したクライアントとしてたち振る舞って接続・マッチング・入室・行動・切断の一連の流れを行うことのできるプログラムです。それぞれの状態に番号をつけて順々に実行させ,コンソールに出力されるメトリクスから今どの状態のbotが何体ずついるかといった情報を確認できるようになっています。そして,ペンライトでの投票の開始タイミングはOS側からsignal(SIGUSR)を送ることで制御できるようになっていました。
そのツールにより,修正前と修正後の両方で,3/19のコネクトライブに向けて負荷試験を行い比較する様子も見学させていただきました。修正前は,一気に負荷が上がってしまって障害が発生する様子を再現でき,修正後では負荷も落ち着きつつ集計もできていて大丈夫そうだね,という確認を念入りに行っていました。

今回の僕のタスクとしては,バグ発見や修正はもちろんのこと,上記の障害を起こさないためのチューニング,さらには限界値を知ることを目的として,Diarkisによる各機能の試験シナリオを,クライアントの通信・挙動を可能な限り再現する形で作成するというものでした。

作業の流れ

僕が今回のインターンでメインで行った作業になります。
トレーナーさんから直接,Diarkisによる通信の概要・ログや通信の確認方法を教えてもらいつつ,ペンライトの投票試験のコードを読んで理解していくところから始まりました。
設定用のconfigを調節しながら実際にbot処理が実行される様子を実際に実行して確認することで,まずはコードの理解を深めました。アーキテクチャとしては,適度なくらいにクリーンアーキテクチャを取り込んだもので勉強になりました。
おおまかな手順としては,以下の通りでした。

  1. まずは検証端末で実際に機能にアクセスしてみて,クライアントとサーバーのログからどのようなコマンドがどのようなフローで呼ばれているか確認し,シーケンス図にまとめる。

  2. 該当コマンドのリクエストやレスポンスがあれば,サーバー側のコードに手を加えてpayloadをログ出力するようにし,どのような通信をおこなっているか確認する。

  3. シーケンシャルにStateを用意し,各状態の実際の処理を書き起こし,シナリオを作り上げていく。

  4. テスト用の環境に対し,実際にbot数を少なめにして試してみる。上手くいかない箇所があればログやbotがどこで詰まっているのかを確認するなどして調整する。

バーチャルライブのシナリオ作成

まずはバーチャルライブのシナリオを作成しました。入室してから,ライブの会場が開場するまでは適当な頻度で行動をし,開場したらGroupにもJoin・終了したら順次Group退出・Room退出という一連の流れを実装しました。

一番の山場は入室処理でした。
マッチング条件を指定してリクエストをかけて,RoomにJoinするのですが,何も制御せずに次の処理に進むと,Joinが完了していない段階で次の通信を使用としてエラーになってしまう事象に苦労しました。コマンド実行のレスポンスとは別に,Joinしたことを伝えてくれるHandlerが動作していることを教えていただいたことで,乗り越えることが出来ました。
ユーザーの移動や行動頻度は人によってまちまちなので,configから行動頻度を指定できるようにすることで,試験のリアリティを高められるよう工夫もしました。

自由に移動したり,アクションを起こしたりできる

ライブ会場の開場・終了といったタイミングは,最初はConfigに指定した時限で切り替わるようにしていました。ですが実際の試験では,クライアント側の負荷とbot数の都合もあり,時間では無くsignalでタイミングも制御したいよねという話になり,結果configによって2通りのやり方でbotを動かすことができるようになりました。

みんなでライブのシナリオ作成

これが一番の苦労でした。バーチャルライブでは,ライブ会場の開場・終了を検知して,プログラム全体としての会場の状態を制御するだけだったので,botそれぞれに細かい制御は不要でした。
ですがライブ機能では,マッチングして部屋が出来たのち,部屋それぞれで楽曲選択・難易度選択やローディング中といった状態を保持し,全員が楽曲選択完了したら次へ行くなどといった制御をする必要があります。そして制御のロジックは基本クライアント側が保持するようになっており,botツールでもそれを再現する必要がありました。

全員が難易度を選択したら次へ進む,といった制御が必要

サーバー側は,Roomのマッチングや情報共有機能などはあれど,状態同期はバイト列をそのまま受け取って保持・配信するようになっています。これにより,サーバー実装に依存せずクライアント側が自由に素早く機能を実装したり修正したりするといったことが可能になっています。
一方バイト列故,検証端末での通信ログを見ても何のデータなのか分かりません。そのため,クライアントエンジニアの方に実際にどのような通信・情報のやりとりを行っているかヒアリングをし,さらに詳細にログをとる方法を教えてもらいつつ,できる限り処理を再現する形で実装を組みことにしました。

Playerが今どこのステップにいるかといった情報は各々が自分のタイミングでBroadcastし,受け取ったbroadcastデータによって逐一ユーザーの状態を更新する,といった実装によって実現しました。そのためのデータ共有用のフォーマットをbotのクライアント側で作成し,messagepackによってMarshal・Unmarshalすることで管理を行いました。
また,Roomには一人Ownerが定められています。その代表者が中心となって,Player全員の状態がxxになったら部屋の状態をyyに進める,といった処理を行うことで,みんなでライブ機能の完全なシミュレーションを行うことに成功しました。
曲選択や難易度選択の時間制限・情報更新が来ない場合のタイムアウト処理などもgoroutineとchannelを生かしながら自分で用意して,できる限り再現したのは一推しポイントです。golangならではの並行処理の手軽さを感じられました。

ランクマッチのシナリオ作成

最後に取り組んだシナリオになります。
部屋やユーザーの状態同期は,基本みんなでライブのものをほぼそのまま使用できたのですが,マッチング部分が複雑になっており苦労しました。
みんなでライブでは,最初の処理の時に,Roomを作成するか,既存の部屋に入室するかを決定しており,その場で完結します。
一方ランクマッチでは,まずは1人1人が部屋を作った上で,半数は入室を待ち,半数はマッチング処理によって部屋が見つかったら自分の部屋を抜けて破棄し移動する,といった処理になっています。うまく2人のユーザーがルームに入ったことを検知したら,マッチング処理を終了するという処理を実現するのが非常に複雑で苦労しました。
リクエスト内容も正しいはずなのにずっとマッチしないという事象があったのですが,ログを出力しながらた賢明に調査しては試し,以下のような空データを入れておく必要があったことに気付くことができました。

[]map[string]byte{
{}, // 空のmapを入れる必要があった…!
},

botツールのさらなる改善

botツールを考えなしに大量ユーザーで走らせてしまうと,同じタイミングで同じリクエストを大量に送るDDos攻撃のような状況になってしまいます。プロセカでは,できる限り負荷がかかるタイミングが分散するような工夫が凝らされているため,Configを調節することで,特定の動作をa秒からb秒の間のランダム秒待機してから行う,といったばらつきを再現できるようにしたり,行動するコマンドの送信確率を調節できるようにしたりして,実際のユーザーの動作による負荷バランスを再現しやすいように工夫しました。
また,botのState・接続状態がうまく管理できておらず,botが規定数以上に急激に増殖するバグがありました。そのため,KeyにユーザーID・Valueにシナリオの状態番号を格納するsyncmapを用いて管理する方法に変更し,defer文も活用しメトリクス管理を正常化・召還時のbot数チェックを追加するなどして対処することができました。これによる集計形式への変更で発生する計算量はO(n)であり,1台あたりのbot数(=n)は多くても2万くらいであることからも,安全という判断がすぐできて変更に踏み切れました。

負荷試験

予定では試験シナリオを作成するくらいで終わるかなと言う想定だったみたいなのですが,早く準備ができてきたこともあり,実際に試験を実行しました。

実行

負荷試験用に用意したVM上で実施しました。ツールとしてはシンプルなCLIツールで,それぞれのVMにSSH接続し,バイナリやconfigをscpで転送して実行するという手段をとりました。複数窓に同じ入力ができるiTerm2のBroadcast機能がとても便利でした。ツールの出力とGrafanaを監視しながら,まず最初にVM1台・少ないbot数で実行し,botが実際の挙動に近づくようコードやパラメータを調整しながら,徐々に台数・bot数を上げていきました。
ローカルでbot数10程度で試していた頃には見られなかった,再接続が発生してしまう現象や,負荷がかかりすぎると雪崩のように切断され再起不能になってしまう症状などが見えてきました。
そのため,負荷試験で一般的とされる,Ramp-Up機能をトレーナーさんから提案いただいて追加し,botの召喚ペースを細かく制御できるようにしたことで,負荷の高いマッチング・入室処理の並行処理限界や,bot全体数の上限などを探ることがやりやすくなりました。
また,同じ条件でもPod数を増やしスケールさせたときに上限はきちんと上がるかの確認もできました。(meshネットワークを活用するものだと,Pod数を増やすほど逆に不安定になることもあるみたいです。)

見つかった課題

この試験により,見つけられた課題は例えば以下です。

  1. 一定時間内に入室できる人数に限界がある

  2. 負荷が高まってくると,レスポンスが急激に遅くなる

  3. 一度に大量の処理を流し負荷を高めすぎると,一斉にユーザーが切断されて再起不能になってしまう

  4. スケールしたPodごとに作成されるルーム・負荷のバランスが悪い

1番に関しては,Ramp-Upにより,上限を探ることまではできました。
2・3番に関しては,Pod数を増やせばある程度上限が上がることが確認できたのは良い収穫であったと思います。あとはさらにチューニングや負荷軽減が進むと良いなと思います。
4番のPodのバランスが悪いという点に関しては既知のもので,今回作成した試験をもとにこれからも検証いただけるとのことでした!
今回は,厳密な定量的評価や修正こそ行き着けなかったものの,自分の作ったシナリオが動き,課題の洗い出しと修正への糸口を作ることができて良かったです。

残せた提案

他にも試験シナリオ作成において検証していく過程で,ランクマッチのマッチング・移動のタイミングにおいて,より良いユーザー体験を届けるための改善ポイントを見つけ,提案を残すことができました。

おわりに

リアルタイム通信と負荷試験を組み合わせた難しいテーマに,希望のgolangで挑戦し,設計も学びながら実装・試験までやり遂げることができて良かったです。また,業務だけで無く,インフラの概観であったり,ゲームならではのマスターデータやキャッシュと言った工夫を学んだり,メンテナンスを見学させてもらったりもできて,非常に勉強になりました。bot数を増やしたことで問題が起きてきてしまうという,サービスの規模が大きくなると現れてくるような苦労と調整も味わうことができて,貴重な経験でした。質問も交流も非常にしやすい環境で,チームのみなさんが暖かく接してくださって非常にありがたかったです。

トレーナーさんとの毎週の1on1では,フィードバックはもちろんのこと,チューニングや設計・運用・ロジックをどこに持たせるかといった話やChatGPTなどの技術トピック・業界や会社についてなど,非常に勉強になるトピックを話すことができてありがたかったです。個人的に,新しい技術への向き合い方など共感できる部分が非常に多かったです!

人事の方々には,ABEMAやgoのプロダクトを詳しく知りたいという希望に対し,急遽CA.goというイベントへの参加を手配してくださったり,他の事業部の様子も知りたい希望に対し,他の事業部の方々とのランチの設定をいただいたりと,たくさんの手配をいただき,相談にも乗っていただきました。気になったこと・やりたいことがあれば言ってくれれば何でも手配するよ!という姿勢で大変ありがたかったです。

1ヶ月という期間はあっという間でしたが,技術的成長はもちろん,実際に業務を行ったり様々な人と交流したりする中で,会社の解像度を高めつつ,キャリアについて深く考えるきっかけまでいただきました。これらは非常に様々な方々のご協力によって成り立ったものです。

改めまして,トレーナーさん,人事の方々をはじめ,サーバーチームの皆さん,カラパレ社員の皆さん,ランチをご一緒した社員の方々などなど,関わってくださった皆様のおかげで非常に充実した1ヶ月を過ごすことができました。改めまして,誠にありがとうございました!

さいごに,リアルタイム通信環境の試験やインターンの雰囲気など,少しでも伝わっていたら幸いです。