Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unityのための高速かつクラウドネイティブな構造化ロギング及びテレメトリ収集基盤【DeNA ...

DeNA_Tech
March 17, 2022

Unityのための高速かつクラウドネイティブな構造化ロギング及びテレメトリ収集基盤【DeNA TechCon 2022】

ゲーム開発の大規模化に伴って、成果物の検証や効率的なデバッグのためにもLogs/Traces/Metricsといったテレメトリの重要性は増してきています。

サーバーアプリケーションにおいてはOpenTelemetryと各種APMサービスを導入すれば計測やモニタリングが気軽に行える環境が整ってきています。一方で、ゲームクライアントにおいては動作速度や動作モデルの問題から、適切なシステムが存在せず、全てを独自に実装するのも大きなコストがかかるという問題がありました。

DeNAでは、Unity製のゲームクライアントのLogs/Traces/Metricsの3種のテレメトリを高速に計測し、Cloud Logging / Cloud Trace / BigQueryに保存するシステムをGCPを活用してフルマネージドな形で実現し、この問題の解決を目指しています。

本セッションでは、このシステムのアーキテクチャについては勿論、OpenTelemetryを始めとしたテレメトリ計測分野の動向、MessagePack for C#の低レベルAPIの活用による構造化ログの実現、terraformによるプロビジョニングなど、オーバービューから実装上の工夫まで幅広い範囲で紹介します。

資料内でのリンク集:
p32, https://github.com/open-telemetry/opentelemetry-proto
p36, https://cloud.google.com/blog/products/data-analytics/bigquery-now-natively-supports-semi-structured-data
p47, https://www.slideshare.net/dena_tech/real-time-remote-debugging
p50, https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/gRPC.md

◆ You Tube
https://youtu.be/HZJRvYcToSQ

◆ You Tube チャンネル登録はこちら↓
https://youtube.com/c/denatech?sub_confirmation=1

◆ Twitter
https://twitter.com/DeNAxTech

◆ DeNA Engineering
https://engineering.dena.com/

◆ DeNA Engineer Blog
https://engineering.dena.com/blog/

◆ DeNA TechCon 2022 公式サイト
https://techcon2022.dena.dev/spring/

DeNA_Tech

March 17, 2022
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. 自己紹介 • 大竹 悠人 (Haruto Otake) a.k.a @Trapezoid • 経歴

    ◦ 2009~2013 ドワンゴ ▪ サーバからクライアントまで、領域を問わない新規開発 ◦ 2013~ DeNA ▪ ブラウザやUnity製ゲームの新規開発/運用 ▪ 現在は横断部門から現場の技術的課題の解決をアシストする傍ら、 Unity用ライブラリ/SDK開発に従事
  2. テレメトリとは何か、そしてなぜそれを持ち込むのか • 遠隔地にあるノード上で動作するアプリケーションの状態や環境情報を各々が自律的に取得し、 通信によって一元化して管理/分析を行えるようにする手法 • ソフトウェアにおいては、主にLogs, Traces, Metricsという3種類のデータを取り扱う テレメトリとは何か なぜテレメトリを持ち込むのか

    • モバイルゲームクライアントはまさに遠隔地にあるノードで動作するアプリケーション • アプリケーションの状態の把握が必要になる問題はモバイルゲームクライアント開発でも 数多く存在する • 見えている問題ありきで解決するのではなく、既に整理された概念で問題を再定義するこ とで、今後発生しうる同領域の問題を解決していく為の礎とする
  3. Traces • システムのシーケンスを追跡するイベント ◦ 開始と終了のある区間で構成される ◦ 一つの区間はSpanとして表現される ◦ Spanは親となるSpanを持つことができる ◦

    ルートとなるSpanに連なるSpan群がTrace • サーバーAPIのリクエスト/レスポンス, アプリケー ション内のスタックトレース(の一部)など
  4. 現代で取り扱われる高度なテレメトリ Structured Logging (構造化ロギング) • 単なるテキストでなく、オブジェクトとして表現できるような構造を持ったLog • 正規表現等ではなく、ログを構成するフィールドを使って分析ができる ◦ 直感的なフィルタリングや分類を行える

    • GoogleのDapper論文に端を発する、分散システムにおける全体のシーケンスを可視化するよ うなTrace ◦ 複数のシステムを跨るシーケンスを、Traceに親子関係をもたせることで表現 ◦ 通信時に相手から現在のSpanをIDとして受け取り、それを親としたSpanを生成 • 分散システムのなかでどのシステムのどの箇所に時間が掛かったか/問題が起こったかを直 感的に把握できる Distributed Tracing (分散トレーシング)
  5. テレメトリの実現手段 • データを集積して分析するので、これを可能にするプラットフォームが必要 • GCPのCloud Logging/Trace/Monitoring, New Relic, Datadog等の多数のベンダがSaaSを提供している ◦

    Logs/Traces/Metricsを統合的に扱って、収集から分析までを行えるものが多い • Grafana, Loki, Tempo等のOSSソフトウェアで構築することも可能だが、選択肢は少ない テレメトリを実現するプラットフォーム テレメトリを実現するライブラリ • 各々のSaaSのSDKを利用すると、 SaaSやライブラリに対してベンダーロックされてしまうリスクがある ◦ ベンダ依存部分を抽象化したOSSを利用することで回避可能 • 抽象化を行うOSSはOpenCensus, OpenTracingの両プロジェクトが合流した OpenTelemetryが業界標準になりつつある
  6. OpenTelemetry • テレメトリを行うためのオープンな共通仕様であり、実装 ◦ Logs,Traces,Metrics全ての計測、収集、エクスポートをカバーできる ◦ 様々な言語向けのライブラリ、プロトコル、パイプラインを構成するためのCollectorなどがある • 多くのクラウドベンダやテレメトリプラットフォームがOpenTelemetryへの対応を進めている ◦

    とはいえまだ発展途上で、OpenCensus/OpenTracingからの移行期にある ◦ Loggingに関してはまだdraft及びbetaの段階。対応している実装もサービスも少なめ ◦ Traces, Metricsは仕様、実装共に概ねstable。対応している実装もサービスも豊富
  7. テレメトリを実現することで解決する課題 • Editorやlogcat上でその場で確認するのに留まっていて、事後の確認が困難 • 大量のLogから見たい情報をフィルタリングする手段が少ない • 実機や他人の環境など、離れた場所でのLogを見ることが難しい • Logsの観測の実現によって解決可能 出力されるLogをその場でしか活用できていない

    • ゲームへのAIの応用のため、機械学習に必要なデータの出力を埋め込みたい ◦ テキストベースのLogから抽出するのは性能的にも保守性的にも問題が多い ◦ 収集できる環境を整える事自体が導入の大きな障壁になる ◦ 構造化されたデータ構造で、かつタイトル横断で使える枠組みが必要 • Logsの観測と構造化ロギングの実現によって解決可能 機械学習等へのLogの応用が難しい
  8. テレメトリを実現することで解決する課題 サーバAPI呼び出しを含む複雑なシーケンスのボトルネックの分析が難しい 開発中のフレームレート等のパフォーマンス低下を見過ごしやすい • サーバAPIの呼び出しはレイテンシが大きい為、可能な限り並列に呼び出してユーザの待ち時間を短くしたい ◦ 並列に呼び出しを行うと、単純なLogベースでは全容を把握することが非常に困難になる ◦ クリティカルセクションやボトルネックを特定するのが難しくなり、待ち時間の増加につながる •

    Tracesの観測と分散トレーシングの実現によって解決可能 • 実行時のパフォーマンスは開発が進むにつれて低くなることが多い ◦ リリース後にも継続して機能追加を続けるモバイルゲームでは顕著 ◦ 属人的な観測ベースでは見過ごされて、深刻な問題になってから発覚する • Metricsの観測とオートパイロットテストのによって解決可能
  9. モバイルゲームクライアントでテレメトリを実現するための要件 テレメトリ計測によるパフォーマンス影響を可能な限り小さくすること • 特にメインスレッドを専有すると、ゲームの体験に影響を及ぼし、動作確認の精度を下げてしまう • 負荷を嫌ってテレメトリの計測自体を躊躇すると、本当に残したい情報を十分に残すことができなくなる • メインスレッドの負荷を避けつつ、高スループットも出せる必要がある 不安定な環境でもフェイルセーフに動作すること •

    クライアントは突然終了することがあり、終端処理を安全に行える保証がない • 通信環境も基本的には無線環境が前提で不安定 ◦ テレメトリの送信は失敗する可能性があるし、常時接続は常に前提にはできない ◦ ネットワーク接続は多くのリソースを消費する • テレメトリは突然アプリケーションが終了しても安全に記録でき、 なるべく冪等性をもって再送信ができる必要がある
  10. プラットフォームの選定 選定基準 分析と開発の2用途を想定 • クエリ言語やWebUIに実用性があるか • 繋ぎこみが可能なAPIインターフェースを持つか • 想定する流量とAPI利用方法で繋ぎこんだ時に、サービスのQuotaや費用は許容範囲に収まるか •

    選定自体はやり直せる想定で、一旦ベストなものではなく選びやすい順に適応できるかを考える指針 • 分析やAI活用は各々のプラットフォームではなく、BigQueryから全て取得できるようにする • 開発で活用するにはクエリができるだけでなく、可能な限り使いやすいコンソールが必要 ◦ 物量も需要も高いロギングでは特に重要
  11. Logsの選定 • Google Cloud Loggingを選択 ◦ 様々な属性は勿論、Structured Logの内容でもフィルタ可能な強力なクエリ言語とWebUIを持つ ◦ DeNAのゲーム事業ではGCPを多く活用しており、気軽に採用もしやすい環境

    • APIインターフェースやQuotaは十分要件を満たす ◦ 一度に複数のLogを書き出せるentries.writeというAPIが存在する ◦ entries.writeはプロジェクトあたり毎分120000回、1回10MBまで実行可能 • 分析用としてBigQueryにもミラーリングを行う ◦ LogSinkでGCP側で完結してBigQueryへ転送することも可能だが、利用しない ▪ 開発用と分析用のプラットフォームの選定の独立性を保つため ▪ 構造化ロギング時のスキーマ構成にも難がある ◦ Cloud LoggingへのInsertと並行して、BigQueryにもInsertを独自に行う
  12. Tracesの選定 • Google Cloud Traceを選択 ◦ Cloud Loggingと連携が可能(Traceに関連するLogを直接表示したり、リンクを表示できる) • Quotaは十分要件を満たす

    ◦ 複数のTraceを書き出すPatchTracesがプロジェクトあたり毎分4800回、一回25000Spanまで実行可能 ◦ 1日あたりの 3,000,000~5,000,000,000 個のSpanを取り込み可能 ◦ Logsに比べてTracesは量も少ないので、必要十分と判断 • 分析用としてBigQueryにもミラーリングを行う ◦ TraceSinkでGCP側で完結してBigQueryへ転送することも可能だが、 構築の自動化が複雑になるうえLogs/Metricsとの統一性も無くなるので、 同様の方法を取ることに
  13. Metricsの選定 • Cloud Monitoringの利用を検討したが、サーバ監視用の為いくつか致命的な問題が見つかり、断念 ◦ 計測した値同士に5秒以上の間隔が必要 ◦ APIからは時系列毎に1つのデータしか書き込めない為、バッチ処理が不可能 • BigQueryのみを利用することを選択

    ◦ 開発に使いやすいUIあるとは言い難いが、他に比べて開発で閲覧する要求は低かった ▪ データを取って分析を可能にすることをまずは優先する ◦ 必要に応じてDataStudioやGrafanaによる可視化を検討する
  14. テレメトリ収集基盤 Gretel • Game-client REvolutionary TELemetry • Unityで作られたモバイルゲームクライアントのテレメトリ収集を行うDeNAの社内システム/ライブラリ ◦ 開発時のテレメトリ収集をファーストターゲットとしている

    • Unity及び.NET6向けのクライアントライブラリと、.NET6向けのサーバによって構成 • Logs/Traces/Metricsの三種のテレメトリを低いベンダーロックリスクで全てサポート • モバイルゲームクライアントでの利用に耐える高速なランタイムパフォーマンス • オートスケールするクラウドネイティブなインフラ構成 • terraformによる環境構築の自動化
  15. サーバ及び中間フォーマットの設計方針 クラウドネイティブな構成にする 高速化を優先しつつ、OpenTelemetryの仕様を手本にする • 安価かつメンテナンスフリーに、開発規模に依らないでスケールさせたい • 回収できるデータの規模がサーバが理由で制限されることがなくなる • 今後として、プロダクションでのテレメトリ回収も視野に入れられる •

    OpenTelemetryは実装だけでなく、各テレメトリが満たすべき仕様も定義している ◦ 積み重ねられた知見を取り込める • 中間フォーマットをOpenTelemetryを参考にする ◦ OpenTelemetryの仕様に落とし込めるような仕様にする ◦ 業界標準なので、有用なプラットフォームが現れたときの移行が簡単になる
  16. Gretelの全体アーキテクチャ • クライアントは計測したテレメトリを 中間フォーマットで出力しファイルシステムに保存 ◦ 基本的にファイルに追記し、随時Rotate • 保存されたテレメトリを定期的にサーバ側(Exporter) で処理できるように送信 ◦

    シーン遷移時など ◦ 起動時には、前回終了時に送っていないデータ を送信 • Exporterは送信されてきた中間フォーマットのテレメ トリを読み取り、各々のプラットフォームのAPIが要 求する形式に変換してExport
  17. 中間フォーマットの策定 • OpenTelemetryのgRPCによるプロトコル(OTLP)の定義を元に 中間フォーマットを策定 ◦ テレメトリはどのような属性を持つべきなのか、とい う知見がOTLPには詰まっている ◦ あくまで速度重視で、直接OTLPを採用はしない ◦

    Logs/Traces/Metrics全てに定義されている • ワイヤフォーマットとしてMessagePackを採用 ◦ MessagePack for C#が非常に高速 ◦ フォーマットはスキーマレスなので柔軟な運用が可能 • ヘッダによるシリアライズ量の削減 ◦ Headerのあとに任意の個数のデータを順に書いていく ◦ データ間で共通するデータはHeaderに保持する ▪ 共通タグや任意のIDなど ◦ Headerが変化した場合、ファイルを新しく作る ◦ アプリケーションの中断に強いフォーマットになる • Logs, Metrics, Traces全ての中間フォーマットに同様の構造を 採用
  18. サーバ設計 • サーバはGCPのサービスを使って構築 ◦ Cloud Storage ◦ Cloud Run ◦

    Cloud Tasks ◦ Cloud Functions • 常時起動するサーバを持たず、負荷に合わせて オートスケールするServerless構成 ◦ 全て従量課金になり、安価に導入できる 完全にServerlessで クラウドネイティブな構成を実現
  19. サーバ設計 1. クライアントから送られる中間フォーマットの テレメトリはCloud StorageにPUTされる ◦ 署名付きURLでPUTを利用 2. Cloud Storageの更新をTriggerにCloud

    Functions を起動し、Cloud Tasksにキューを積む ◦ Cloud FunctionsのTriggerはat least once なので重複の可能性をここで排除 ◦ エラー時の一定のリトライも行う 3. Cloud TasksがCloud Run上のExporterを起動 ◦ Cloud TasksのHTTP Targetを用いる ◦ 受け取った中間データを前処理した上で 各々の形式に変換して、バックエンドに Insert
  20. サーバ実装 • C#(.NET 6, ASP.NET)で実装 ◦ クライアントとの間でスキーマ定義を含む コアロジック(Gretel.Core)を共有 • System.Threading.Channelsによる並列化とバッチ化

    ◦ ParseとConvert/Exportを並列実行 ◦ 一定の件数までConvertした時点でExportを実 行するバッチ化を施し、Exportする件数を固定 ◦ APIアクセス中にCPUをできるだけ遊ばせない
  21. BigQueryでのNative JSON DataTypeの利用 • 構造化ログやタグ情報は共通したスキーマのない、Dictionary的な構造を持つ ◦ これを扱うにはBigQueryでもダイナミックなスキーマが必要 • BigQueryはカラムを足していく事もできるが、フィールド名が同じで型が違うログが存在した場合に書き込みが できない上、カラムを消せない

    • 先日パブリックプレビューになった、BigQueryのNative JSON DateTypeを利用 ◦ JSONの値をフィールドやクエリの中で扱えるようになる、BigQueryの新たなプリミティブ型 ◦ 文字列との相互変換は勿論、JSON PathでJSON型の値から内包する値を抽出することもできる ◦ スキーマ定義上でJSON型として宣言することでJSONとしてValidな値しか入らなくなる ◦ 該当するフィールドに文字列としてJSONを与えることでInsertはそのまま行える • 構造化ログやタグ情報の柔軟な運用を安全に行える
  22. terraformによるプロビジョニング • ゲームタイトル毎に完全に分離された環境を用意したい ◦ terraformによってInfrastructure as Codeを実践 ◦ タイトル毎の独立した環境構築が容易に可能 •

    サービスアカウントの切り分けによるアクセス制限 ◦ クライアントからテレメトリをCloud Storageに アップロードする専用のサービスアカウントを自動的に作成し、組み込むJSONをファイルとして出力 ◦ 他の権限を与えないことで、ビルドに認証用JSONを組み込む際のリスクを低減
  23. クライアントの実装方針 • 頻度もデータ量も多いLogsを最も優先 • Metricsも毎フレーム単位で実行するため次点で優先 • Tracesはデータ量は場合によって多いものの、頻度は非常に低いので優先度は最低に ◦ 速度が問題になったらLogsと同様の戦略を取る パフォーマンスは呼び出し頻度やデータ量を考えて優先度をつけて対応する

    テレメトリに隣接する既存の実装や標準を活かす • .NETのテレメトリAPIインターフェースであるSystem.Diagnostics.DiagnosticSourceに対応 ◦ System.Diagnostics.Metricsはかなり薄い実装なので、参考にするのみ • ZLoggerをベースに利用して、魔改造して実装する ◦ C#でGeneric Hostベースで超高速なLoggerを実現する、@neueccさん発のOSS ◦ 目指す物が非常に似ていて、下回りの実装はLogs以外の実装にも活用できる ◦ 一方で、ZLoggerはテキスト出力が主眼な為拡張する形は難しい
  24. Logs : クライアント設計 • 高パフォーマンスの実現のための全体の構造は ZLoggerのものと殆ど変わらない • System.Threading.Channelsでのサブスレッド化 ◦ ChannelによってLogEntryを書き込み、サブス

    レッド上からそれを読み出す ◦ サブスレッド上でシリアライズ ◦ メインスレッドは殆どメモリコピーしかしない • LogEntryのPooling ◦ 型引数毎にLogEntryをPoolingして実質的なゼロ アロケーションを実現 ◦ LogEntryはGenericに実装することで、その先の 実装が高効率に多態的に振る舞えるようにする
  25. Logs : ZLoggerとの差分 • テレメトリの中間フォーマットでの出力 ◦ ZLoggerではZStringでテキストとして書き出す 部分を、MessagePack for C#でバイナリとして

    書き出す ◦ ZLoggerでは扱わないメタ情報を付加 ▪ Attributes, Trace, ◦ 任意のサイズに至るかヘッダが変化したら、 ログファイルを自動的にローテーションして ヘッダを書き出す • 拡張メソッドで実現されるLogging Methodの構造 ◦ ILoggerではなくwrraperとなるstructに対する 拡張メソッドに変更 ▪ ILogger.Logが直接呼ばれると多くの情報 が失われてしまうので、その回避策 ◦ Conditional属性でリリースビルド時に (特定のシンボルを定義してなければ)呼び出し 自体が消去が行われるメソッドも定義
  26. Logs : ZLoggerとの差分 • MessagePack for C#による構造化ロギングへの対応 ◦ ZLoggerは構造化ロギングにSystem.Text.Jsonを使う ため、Unityでは構造化ロギングは非対応

    ◦ MessagePack for C# によって 構造化ログのシリアライズを行うようにする • 文字列フォーマット(string.Format)処理のサーバへの移譲 ◦ ZLoggerはZStringを使うことで文字列フォーマット 処理を高速化している ◦ 文字列フォーマット自体をクライアントで行わない ◦ フォーマット書式とフォーマット引数をそのまま MessagePackにシリアライズする ◦ 文字列フォーマットはサーバ側でパース後に行う
  27. Logs : ロギングの呼び出し • 様々なパラメータを渡せる ◦ LogLevel(ログの重要度) ◦ EventId(ログの種別) ◦

    Payload(構造化ログを表すオブジェクト) ◦ Attribute(属性を表すオブジェクト) ◦ Message (テキストログのフォーマット文字列) ◦ Args(テキストログのフォーマット引数) ◦ ActivityContext(ログ出力時のTrace) ◦ CallerInfo(呼び出し元ファイル名/行数/関数名) • CallerInfo属性によるロギング時のコードパスの記録 ◦ StackTraceの取得は非常に重いが、 Logからコードパスを辿れることは非常に有用 ◦ Caller(FilePath/LineNumber/Member)属性を 関数の省略可能な引数に付ける ◦ 呼び出し元のファイルパス/行数/メンバ名が コンパイル時に埋めこまれる ▪ 限定的だが低コストでコードパスを残せる
  28. Logs : ロギングのバリエーション • 場合によって省略したいパラメータも存在する ◦ 複数のメソッド名とオーバーロードの 両面でバリエーションを持たせる • メソッド名のバリエーション

    ◦ ユースケースレベル ◦ Release~/Debug~ ▪ Conditional属性による呼び出し除去 ◦ ~LogExeption~ ▪ 例外(Exception)の記録 ◦ ~LogMessage~ ▪ Payloadを伴わないテキストログ ◦ ~WithAttribute ▪ ログ個別のAttributeの指定 • オーバーロードでのバリエーション ◦ EventIdの省略(構造化ログの型からEventIdを自動決定)
  29. Logs : オーバーロードの衝突 • フォーマット引数部は複数の型引数を持つ ◦ 引数の数ごとにオーバーロードを定義 ◦ 高速化の為にも必須の構造 •

    複数の型引数でのオーバーロードの定義と、 デフォルト引数は非常に相性が悪い ◦ 呼び出し時に型引数を推論させての オーバーロード解決ができず、使いづらくなる • フォーマット引数部をまとめてValueTuple化 ◦ 1引数にまとまるので、隣接する引数と干渉しない ◦ Tupleリテラルを使える上、型推論が可能 ◦ Tupleリテラル(x,y,z)は1引数では使えないが、1引数で は無駄な丸括弧になる ◦ 1引数の時だけValueTupleを使わないで定義すると、 呼び出し側の記述を統一でき、推論上の問題も解決
  30. Logs : 非対称なシリアライズ • 構造化ログはログ種別毎にC#の型を定義して扱いたい ◦ 扱いやすい上、事前生成されるパフォーマンス の高い実装を利用できる • サーバではクライアントのスキーマを把握しなくても

    パースできるようにしたい ◦ 変更毎にスキーマのデプロイが必要になると、 並行開発に耐えるアジリティを実現できない • クライアントではスキーマベースにシリアライズし て、サーバではスキーマレスにパースする ◦ MessagePackはJSON同様のスキーマレスなファ イルフォーマット ▪ Mapモードでスキーマを利用すれば、C# のプリミティブ型によるDictionaryとして Parseすることが可能 ◦ DictionaryをJSONや各SDK側で要求される構造 体を表す型に変換して最終的に利用する
  31. Logs : UnityEditor用のLog Console • 特にLogsに関しては、UnityEditorからリアルタイム に確認できることが実用上求められる ◦ UnityEditor上にUIToolkitを用いて専用のLog Consoleを実装

    ◦ 数十万件のログ表示も高速に処理可能 • コンソールに必要な一通りの機能を完備 • ログの表示 ◦ フィルタ機能付きのリスト表示 ◦ ログごとの詳細表示 • フィルタ機能 ◦ LogLevel, EventId, LogNameによる複数選択可 能なフィルタ ◦ Message,Payload,StackTrace,Caller, TraceId,SpanIdでの文字列一致フィルタ ▪ Messageはハイライト付き • 新規ログへの自動スクロール(Tail) • Editor Preview開始時の自動クリア(Clear on Play)
  32. Logs : UnityEditor用のLog Consoleの実装 • 前述のLogConsoleに対してログを出力する実装 ◦ Loggerからの出力先の一つとして構成可能 ◦ 必要に応じてStackTraceを残すことも可能

    • Editor上でTCPでTLVベースの独自プロトコルの Consoleサーバを実装し、フロントエンドから転送 ◦ 実機ビルド時でもLogConsoleが利用可能 ◦ 中間フォーマットのデータを転送 ◦ Editorのドメインリロード時にListenし、 受信したログがEditorWindow上に表示 ◦ EditorPreviewではlocalhostに接続する • ファイル出力時と同様にサブスレッド化 • USB接続された実機とPC間のTCP通信の経路を担保 ◦ TechCon2020での講演で紹介したdevwire によるリバースポートフォワード over USB ◦ もちろんWiFiなども経路として利用可能
  33. Logs : マーケティング分析ログの送信 • プロダクション環境において、マーケティング分析用のログを出力する ◦ マーケティング分析用のログは、社内mBaaSであるLCXを通して扱うように現状の社内基盤は整っている ◦ LCX SDKを通してログを出力するように、Loggerの出力先の一つとして構成可能

    ◦ プロダクションの分析ログも開発のログの間での実装方法の差分を最小化できる • 中間フォーマットでの出力を行わない ◦ ログエントリをLCX SDKが要求する形式にコンバートし、SDKを通して直接送信する ◦ 送信頻度は高くないため、パフォーマンスはあまり問題にはならない
  34. Traces : クライアント設計 • System.Diagnostics.DiagnosticSourceのActivitySourceを利用 ◦ StartActivityからその返り値のActivityをDisposeする までの区間がTraceとして処理される ◦ ActivityのContextプロパティをStartActivity時に与え

    ることで、Traceの親子関係を設定できる • Activityの開始や終了はActivitySourceを通じて監視できる ◦ これを利用して終了時にTraceとして記録する ◦ シリアライザ以下の出力部分の実装はLogsのものを共 用して、ローテーションを実現 • ゲーム向けのActivity管理を実現するActivityStackを実装 ◦ ActivitySourceは親が無指定の場合、AsyncLocalな状 態に基づいて直近のActivityを自動的に親に設定する ◦ コールスタックに紐付いたTraceを残すには合理的 ◦ ゲームはコールスタックを超えた状態が殆どなので、 これを無視できるStack型の管理機構を実現する
  35. Traces : 分散トレーシングの実現 • 分散トレーシングを実現して、ゲームサーバとの通 信内容,タイミングをTraceに残しつつ、クライアン トとサーバのボトルネックを一気通貫で分析する • DeNAの社内ゲームサーバ基盤(Takasho)との連携 ◦

    gRPC/OpenCensus/Cloud Traceという構成 • OpenCensusのgRPC向けの実装では、リクエストの grpc-trace-binヘッダをパースして親のTraceとして 解釈するようになっている • Takasho SDKを拡張して透過的に分散トレーシング を実現するインテグレーション層を実装 ◦ Takasho SDKにインテグレートして、リクエ ストとレスポンスを自動的にTraceに残す ◦ 作成中のTraceのIDをgRPCリクエストの grpc-trace-binヘッダとして埋め込む ◦ Takashoサーバ側は何も変更をすることなく 分散トレーシングを実現できた クライアント側Trace サーバ側 Trace
  36. Metrics : クライアント設計 • System.Diagnostics.Metricsの設計を下敷きに独自実装 ◦ 計測項目ごとのInstrumentとそれを追跡するMeter • Instrumentに対して必要な間隔で測定した値を Reportして蓄積し、定期的にFlush

    • 値そのものではなく分布として集計するHistogram ◦ fpsなどの瞬間的なスパイクを逃したくないもの • 時間あたりの値を計測するCounter ◦ リクエスト数, GC発生数など • その瞬間の値を計測するGauge ◦ メモリ使用率など
  37. Metrics : ヒストグラムの逐次処理 • 瞬間的にスパイクが考えられる値は統計的に処理したい ◦ 値をすべて報告するとデータ量が膨大になる ◦ データ量に比例しない計算量で統計処理を行いたい •

    Bucketベースのヒストグラム計算を採用 ◦ 任意の区間で標本化を行う ◦ N個の昇順の境界値で区切ったN+1個の区間(Bucket) の単位に標本化して、分布(それぞれの個数)を記録 ◦ Bucketに属するかを求めてカウントをインクリメン トするのみで、計算量がデータ量に比例しない(O(1)) ◦ Sum/Min/Max等のO(1)で計算可能な項目も計算 ◦ 区間を求めるロジックはBurst化 • 分布の状態で中間フォーマット化 ◦ 分布とBucketの閾値を送信 ◦ サーバー側で分布から%tileを必要に応じて計算 ◦ 分布情報のまま受け入れるプラットフォームが数多 く存在する
  38. 任意の型を含む多態性のあるシリアライズ • 構造化ログで任意の型を与える部分はログ情報のス キーマの一部でしかない ◦ MessagePack.Unionによる動的なシリアライズ を行うには、interface側に全ての任意型を列挙 する必要がある ◦ フォーマット文字列のパラメータの配列も任意

    の型が入りうるので同様 • MessagePack for C#のUnionやMessagePackSerializer は使わず、Low Level APIを利用 ◦ MessagePackWriterで要素数をヘッダとして書 き込み、続いて要素の内容を順に書き込む ◦ 型を任意にしたい部分を型パラメータと共に受 け取り、Formatterを取得してシリアライズ ◦ T4でパラメータ数毎にオーバーロードを実装 • サーバ側からはスキーマレスに扱う ◦ Dictionary<string,object>やobject[]にパース
  39. Formatterの高速化 • 任意の型のシリアライザをFormatterを独自に実装する ことで、EventId,TraceId等の既存の構造体を直接扱う ◦ ResolverでそのFormatterの参照を得られるように する必要がある • MessagePackFormatter属性をフィールドに指定して、 利用するFormatterを明示的に指定する

    ◦ Formatterの解決を省略できる ◦ 実体型でのnonvirtualなメソッド呼び出しは IL2CPP後にvtableを経由しないので高速 ◦ 独自のFormatterでAggresiveInliningを指定すして いる場合、inline化も可能 • Nullableの場合も上記のvtable回避/inline後を誘発させる ◦ Genericsの実装共有の関係で、Formatterも値型で あることが条件になる
  40. UTF16文字列のシリアライズ • MessagePackの文字列はUTF8 ◦ C#のstringはUTF16なため、シリアライズ時にUTF8に 変換された上で書き込まれる ◦ できればこのコストも回避したい • UTF16のままbyte列としてstringを書き出すFormatterを実装

    ◦ MessagePackのextフォーマットとして書き出す ◦ 任意のTypeCodeと共に任意のbyte列を独自の フォーマットとして組み込めるMessagePackの仕様 • サーバ側では個別のFormatterによる解決が期待できない ◦ object型用のFormatterを通してデシリアライズする必 要があるため、標準のFormatterをforkして改造 ◦ 上記の独自フォーマットをstringとして デシリアライズできるようにする ◦ MessagePackバイナリからJSON変換を行う際にも 同様の改造が必要
  41. 達成できたパフォーマンス • 300件/frame,60fpsで1000frameの間Android(GalaxyS9) でログ出力するパフォーマンステスト(Unity 2020.3.28f1) ◦ UnityEngine.Debug.LogFormat, ZLoggerとの間で 1frame中のLogのメインスレッド専有時間を比較 ◦

    3パラメータの文字列フォーマットを伴うLog ◦ Gretelでは文字列フォーマットと同時に構造化 ログも出力する際のパフォーマンスも計測 ◦ Gretel, ZLoggerはテスト後のサブスレッド側の残 処理の所要時間も比較(LoggerFactoryのDispose) • 結果 ◦ メインスレッド負荷はDebug.LogFormat比で1/200 ◦ 現実的な密度で構造化ロギングを行ってもリアルタイム処理が可能
  42. • リアルタイム処理できるスループットを追試 ◦ 実機での構造化ロギングで400~500件/frame ◦ 実機での通常ログで2000~3000件/frame ◦ 実機でのZLoggerで3000~4000/frame • Editorではいずれも4000件/frameも捌いた

    • 通常ログではZLoggerと遜色ないパフォーマンスを発揮 • ログを過不足無く残すには十分に超高速と言えるパ フォーマンスを達成した
  43. 達成したこと • モバイルゲームに対して、サーバ分野でメジャーになったテレメトリの概念を持ち込む基盤を実装した ◦ ミクロな実装では高いパフォーマンスを実現するナイーブな実装を行いつつ、 マクロなアーキテクチャでは柔軟性とスケーラビリティを両立するクラウドネイティブな構成にできた • 仕様面ではOpenTelemetry、実装面ではMessagePack for C#,

    ZLoggerといったOSSを活用することで、規模に対して 短い期間で実装しきることができた ◦ 構想から完成まで、全体で概ね2人月程度 • アプリケーションの状態の把握という領域で生じる諸問題に対して、解決の見通しを立てることが出来た
  44. これからの展望 • 基盤が完成したばかりで、まだ実際のゲームタイトルに組み込めていない ◦ 組み込んで利用実績を積み、フィードバックを得て、実用上の問題点をシューティングしていくのを優先 • .NET Standard 2.1 /

    Unity 2021.2への対応 • 開発利用のためのプラットフォーム選定に関しては、それぞれの進歩を加味して選択肢を適宜再考していく ◦ サーバ側にOTLP Exporterを実装すれば幅広い選択肢が取れる。特にMetricsは再考の余地あり • (記録の粒度を下げた上での)プロダクション環境での利用のための実装 ◦ 利用者全体でなく、同意を得た上で一定割合でのみ計測する必要がある ◦ サーバサイドでユーザ認証及びQuotaを設ける等のセキュリティ強化 • 社内の技術資産との連携の推進 ◦ リアルタイムサーバとの分散トレーシング ◦ 様々な内製開発用ツールの利用統計のLogsとしての記録
  45. 謝辞・注釈 • 本講演のプロダクトはMessagePack for C#, ZLogger, ZStringに大きく助けられて成立しています ◦ @neuecc 様

    並びにCysharp社様、いつも素晴らしいOSSをありがとうございます! • このスライドは、CNCFと提携しておらず、CNCFが後援しているものでもありません。 (This slide is not affiliated with or otherwise sponsored by CNCF.)