Search Unity

すでに多くの方々にUnityのクラッシュレポートを収集できる Game Performance Reportingサービスを使っていただいていますが、このサービスの今後について質問をいただきました。安心してください、私たちはすべての投稿を読んでいますよ!そして、皆さんがクールな新機能を欲しがっていることもわかりました。
ぜひ今後もアイディアやリクエストを
引き続き投稿頂ければ嬉しく思います。私たちは過去の数ヶ月にわたり、プロジェクト単位の機能の現状から、数百万のUnity製ゲームをサポートできる超クールなWebアプリケーションへと進化させるべく、インフラの見直しから対応してきました。どんなことをしてきたのか、このブログ記事で解説します。

背景

Unityには内部の開発者が一箇所に集まり1週間かけて様々な新しい機能を開発するハックウィークという活動があるのですが、Unity Game Performanceは1年前に、このハックウィークのプロジェクトとしてスタートしました。ハックウィークの時の目標はシンプルで「何か新しいことをしよう」ということだったのですが、複数の異なるバックグラウンドを持つ人たちでチームを組み、以下の事柄をそれぞれ担当しました:

  • JavaScriptのUI
  • UIを使用する Rails API
  • クラッシュレポートを取り込むパイプライン構築

UIはおそらく一番わかりやすいものでしょう。 すでに developer.cloud.unity3d.com が立ち上がっていることに気づいている方々もいるかと思いますが、このサイトは増え続けるサービスに対する一元的なアクセスを提供することを目標に実装されています。

クラッシュレポートサービスの3つの要素について、過去12ヶ月でもっとも変わったものはクラッシュレポートの取り込みパイプラインです。こうした取り込みの部分の変更は(できれば)あまり目立たない方がよいのですが、私たちはUnityで作られたすべてのゲームで利用出来るようにしたいので、重要な要素ではありました。

以前の動作

もともとの取り込みパイプラインは下記のような形で実装されていました:

Editor Plugin -> Node -> SQS + DynamoDB -> Rails -> MySQL

エディターのプラグインが例外をモニターして、バッチ処理を行ってからNodeに送信します。Nodeはイベントを受け付けたらDynamoDBにこれを保存し、SQSメッセージをRailsに送ってDynamoのどこからイベントを検索するかを指示します。RailsはこれをうけてDynamoに戻り、処理をを行ってデータをMySQLに保存します。このワークフローはセットアップが簡単だったのですが、あまりエレガントなやり方とは呼べませんでした。

その時点では、SQSは結構小さなメッセージ容量の制限(256KB)を持っていました。すべてのサイズの例外を保持できるわけではなかったのです。これがSQSメッセージが単にDynamoのどこにイベントが記録されているかだけを指示する様な構造になっていた理由です。SQSはその後メッセージ容量の制限を2GBまで拡大したので、例外を保存する容量が足りないという私たちの問題は解消されました。当初、私たちはすべてのイベントをDynamoに保存し、もしなにか巨大なポカをやらかしてしまってもイベントをリプレイすればすべてのデータを再インポートして戻せるようにしていました。

サービスを立ち上げたら何が起きたのか

GDC2015で私たちはこのハックウィークプロジェクトをサービスとして立ち上げたのですが、全く予想を上回る状況になってしまいました。私たちはだいたい1日数千程度の例外を受け取る前提でいたのですが、実際には数百万件が送られてきました。わたしたちは毎秒数千の例外を送ってくる幾つかのプロジェクトに対してリミットを設けなければなりませんでした。

運用面での問題を別にしても、私たちのシステムには一つ、大きなボトルネックが存在していました。SQSとDynamoに記録し、Railsで取得し、処理し、データベースに記録する時間です。Railsだけでこのシステムは例外1つあたり75ミリ秒もかかっていたのです!

ただ、このもともとのセッティングにはボジティブな側面もあって、イベントの受け入れと処理が完全に切り分けられている点はよくできていました。このデザインのおかげで一切のイベントの記録漏れを発生させることなく、プログラムの修正やプロセスの再起動といったことを行うことができました。

次に行ったこと

ざっくり言うと、クラッシュレポートの処理は下記のようなステップで成り立っています:

  1. イベントを記録し、
  2. 記録からレコードを作るか既存のレコードを探し、
  3. カウンターをアップデートし、
  4. どのプラットフォームやOSで発生したのかという情報と関連付ける。

私はNodeがあまり好きになれなかったので、まだ学んでいない別のもの(Golang)でのリプレースに着手しました。しかし試してみたものの、改善にはまったくならないということがわかりました。Golang向けのAWSライブラリがまだ未熟だったからです。なので、取り込みパイプライン全体をリプレースして簡単にすることにしました。

わたしのゴールは大体こんな感じのことを書くことでした:

Editor Plugin -> Go -> MySQL

わたしはシンプルで高速に動作するものを求めていました。冗長なログによるディスクスペースアラートとか、酷使されたRubyのプロセスからのメモリーアラートとか、そういうのは要らないのです。最終的にプロセスはこんな感じになりました:

最初の実装はRailsからの単純な移植でした。新しい処理でも以前と同じMySQL select文を使い、同じように行を作成してカウンターをアップデートしました。

最初の最適化はレポート間で重複していたSQL文を削除することでした。これらの重複はSELECT文で、例えば ‘SELECT id FROM operating_systems where name = “Windows 7”’ といった感じのものでした。このような文はアプリケーション側で安全にキャッシュできるので、Hashicorp社の go LRU hashを活用して実装しました。さらにクラッシュのフィンガープリントに対しても同じくキャッシュによる最適化を行い、同じ例外に対して一々データベースに問い合わせなくてよいようにしました。

実現にあたって、LRUハッシュに対してそこそこ多くのロックを実装しなければならなかったので、Go的に好ましい実装とはあまり思えませんでしたが、まぁ動いたのでよしとしました。一つ行ったとことしては finer grain locksを作ったということがあり、これで異なるキーを同時にアップデートできるようにしました。

次に当たったボトルネックは書き込みに関するものでした。カウンターをアップデートする際の個々の書き込みイベントです。私のデータベースは1から100,000,000まで、それは律儀に1回ずつ書き込んでいたのです。

書き込みをバッチ処理したいということはわかっていましたが、同時に効率的にやりたいと考えていました。そこでHashicorp社の LRU ハッシュを生かして、このハッシュ機能の持つ evictフックを使うことにしました。この方法なら、クラッシュレポートが(新規のエントリーが増えることで古くなって)メモリーから落とされる時にデータベースに書き込みに行くということができます。しかしそこで「もしキャッシュ落ちを引き落とすほどの個別のクラッシュレポートが来なかったらどうしよう?」という問題があることに気がつき、LRUハッシュに生存時間(TTL)を加える別のメソッドを実装しました。

TTLは個別のエントリーに対して設定されています。この方法ならTTLによるキャッシュ落ちは個別に発生するため、いきなり何千ものキャッシュ落ちが発生して大量のデータベース書き込みが発生するという、いわゆる Thundering herd 問題を回避できます。

上記のすべての要素を取り入れることで、AWS t2.medium インスタンスのプロセスでおよそ 10,000 リクエスト/秒 を処理できるようになりました。悪くない結果ではないでしょうか。

さらに、私たちはあなたのゲームが地理的に一番近いサーバーにレポートを送れるよう、異なるリージョンにエッジサーバーを配置することも検討しています。これらのサーバーは同じバッチング処理を行い、データベースが存在するエリアに内容を転送します。どれも同じキャッシュ落ちフックを使い、データベースの呼び出しの代わりにHTTPSリクエストを使用します。

ようするに: Game Performance Reporting は最近音沙汰があまりありませんが、別に忘れたわけではありませんよ!このブログ記事が今まで裏側で何が起きていたのかを知る手助けになればと思います。今後もフォーラムにぜひ要望などをお寄せください!

コメント受付を終了しました。

  1. al handu rio peo uru osku mao oeurt hughh hughh hughh ? hanfs.

  2. Why we can’t access directly in our country(Iran) to Unity3d website ?
    Iran and U.S reached to Deal and that deal now is running !

  3. mario viaene

    12月 3, 2015 1:39 pm

    beste
    waarom kan ik niet meer met gamen unity web player vraagt iedere keer om een update als ik update gebeurd er niets kan nog steeds niet gamen .
    beleefde grts dhr mario viaene

    1. Als je Chrome of Firefox gebruikt komt dat omdat de plugin gebruik maakt van NPAPI, welke niet meer ondersteund wordt door deze browsers (weet niet of IExplore of Edge NPAPI ondersteund)

      Misschien is het nog mogelijk om NPAPI aan te zetten met een config tweak?

    2. Holy shzniit, this is so cool thank you.

  4. Wow… there were so few news about it, actually I’ve never heard of that, but it sounds really great.
    Got to check it out as that’s exactly what I’m looking for at the moment as an alternative to Crashlytics.

  5. It’s nice to see you’re improving the service in terms of performance (no pun intended) to make it scale and operate better.

    I wish you had provided features that other services already have, like native exception tracking just to name one.

    We’d really like to replace our current provider, but Unity’s solution simply doesn’t provide enough features right now to justify it.

    1. Chris Lundquist

      12月 3, 2015 1:01 am

      That’s exactly what we have in mind. We had to make a solid platform first though.