現在( 株) グラニでは、C#を用いて「神獄のヴァルハラゲート」と「モンスターハンター ロア オブ カード」という2つのソーシャルゲームを開発・運営しています。本連載の第1回 では、なぜC#を使っているのか、なぜC#が良いのかについて紹介しました。最終回である今回は、具体的にどのようなアーキテクチャ・プログラムで開発されているかを紹介していきます。
AWS上のWindows
インフラ環境は完全にクラウドに置き、すべてAWS(Amazon Web Services )上で構成しています。AWSというとLinuxの印象が強いと思いますが、Windowsのインスタンスも提供されていて、Linuxと比較しても遜色なく動きます。グラニがAWSを選択した理由には、早い時期から東京リージョンがあったこと、豊富な機能、そしてRDS(Relational Database Service )の存在が挙げられます。
システム構成は図1 のようになっています。アプリケーションサーバはEC2 のWindows Server 2012で、IIS(Internet Information Services )8 を使ってASP.NET MVC 5(.NET Framework 4.5)をホストしています。また、データベースはRDS上のMySQL 5.6で、補助としてEC2上のAmazon LinuxでRedisを立てています。この構成で「1億/日」のPV数、「 10,000リクエスト/秒」の瞬間最大ページリクエスト、「 25,000クエリ/秒」の瞬間最大クエリ数、「 230,000コマンド/秒」のRedisの瞬間最大コマンド発行回数という、かなり高負荷のアプリケーションをさばいています。
図1 「 神獄のヴァルハラゲート」のシステム構成図
データベース構成の設計思想
昨今、NoSQLのブームも落ち着いてきました。たとえば、AWSであれば無限にスケールアウトするDynamoDBの性能は魅力的ですが、グラニではRDS上のRDBMSのみを採用しています。これは、現状のグラニの使い方では、多くの理由からRDBMSに利があるためです。
まず、RDBMSにはデータベース用のIDE(グラニではHeidiSQLを採用)やプロファイラといった周辺ツールが充実しています。それらを介した設定やSQLを使ったカジュアルな解析のしやすさ、アドホックなデータの弄りやすさは大きな利点です。パフォーマンス面から見ても、RDBMSは単純なクエリなら1ミリ秒以下で返すので、単発のクエリのレイテンシという点では、現状のDynamoDBはRDBMSに劣ると考えられます。
さらに、RDSなら手放しで障害を管理できる安心感があります。性能的に限界で苦しいときには上級インスタンスが現れるので、ダメなときはスケールアップで何とかなるという事実も心強いです。スケールアップには限界がありますが、ヴァルハラゲートの規模でも十分成立していることを考えると、多くの場合はRDBMSとRDSを選ばない理由のほうがないのでは?と思います。
グラニでは、RDSとしてMySQL 5.6を採用しています。C#と相性が良いのは間違いなくMicrosoft SQLServerです。しかし、RDSで提供されている状態ではMySQLのほうが機能は上回っています。SQL Serverを使いたいとは思っていますが、あくまでC#+RDSと考えてMySQLを選びました。
さて、スケールアップが容易とはいえ1台では支えきれないので、データベースを複数台に分散させる必要があります。分割方法としては、テーブル内のIDなどで分散させる水平分割と、テーブル単位で別データベースに割り振る垂直分割が考えられます。水平分割には無限にスケール可能というメリットがありますが、データベース用のIDEなどの既存のツールがまともに使えなくなるというデメリットがあります。このデメリットは大き過ぎると判断したため、グラニでは垂直分割を採用し、垂直分割に収まるような切り分け方をしています。
アプリケーションサイドでのログ取り
グラニではパフォーマンス、特にTTFB(Time To First Byte )を重要視しています。ほかのソーシャルゲームと比較しても、ヴァルハラゲートの応答速度は業界屈指の速さと自負しています。そのために何よりも重視しているのは、本番環境と開発環境でほぼ同一の応答速度を保つ、ということです。もちろん、開発環境ではデバッグビルドになるので完全には同一になりませんが、特にデータベースやRedisとのネットワーク遅延で引っ張られないように細心の注意を払っています。これは、速度に対する意識は肌感覚が重要だと考えているからです。開発環境が遅いと感覚は養われません。「 遅いのは開発環境が遅いせい」となると、改善も遅れる(そしておそらくそれは見送られて、実際に改善されることはなくなる)ようになります。ほぼ同一の環境を簡単に作れる点も、クラウドを使う理由の1つです。
また、外部通信(HTTP、SQL、Redis)はすべてアプリケーションサイドで記録しています。データベース側で記録しようとすると性能低下の可能性があり、常時記録するわけにはいかないこともありますが、アプリケーションサイドで行えば負荷はかからず、より多くの付随する情報を記録できます。フックするポイントをすべて押さえ、処理速度やクエリ、キーをログに書き込んでいます(リスト1 ) 。
リスト1 HTTP通信をログ化する例
public class HttpProfilingHandler : DelegatingHandler
{
static readonly Logger httpLogger = NLog.LogManager.GetLogger("Http");
public HttpProfilingHandler() : base(new HttpClientHandler()) { }
public HttpProfilingHandler(HttpMessageHandler innerHandler) : base(innerHandler){ }
protected override async Task SendAsync(
HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// 通信の前後をStopwatchで測る
var sw = Stopwatch.StartNew();
var result = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
sw.Stop();
// 次に好きなようにログを仕込む、たとえばJSON化
httpLogger.Trace(ApplicationPerformanceLog.ToJson(
DateTime.Now, request.Method.ToString(),
request.RequestUri.ToString(),
sw.ElapsedMilliseconds));
return result;
}
}
こうして溜めたログはSumo Logic で収集と解析を行い、日常的な速度改善のほか、障害時の原因究明に役立てています(図2 ) 。傾向がつかめれば十分な場合が多いため、収集対象のサーバを限定してログ容量を削減しています。
図2 Sumo Logicの解析画面。独自クエリで柔軟に解析でき、グラフ化も可能
さまざまなデータの可視化
アプリケーション速度の改善や開発効率化のためには、あらゆるデータが可視化されている必要があります。さまざまなデータを記録したとしても、「 開発者」がそのデータを簡単に見られなければ改善にはつながりません。グラニでは、前述のように本番環境はSumo Logicなどで可視化しています。しかし、開発環境ではもっと近いところでデータを見られる必要があると考え、ASP.NET向けの診断ツールGlimpse を利用しています。
Glimpseを使うと、Webページの詳細なデータがHUD[1] で表示されます。履歴を保持しているため、表示ページのないハンドラやJSONを返すAPIなども確認できます。本連載の第1回では、Redisへの非同期アクセスの可視化にGlimpseを利用していると紹介しましたが、実際にはもっと多くの情報をカスタマイズして表示しています。もちろん、HTTP、SQL、Redisのように外部へアクセスする部分に関しては、タイムラインにすべて表示しています。特にRedisは、キー、コマンド、同一キーの重複時警告、送受信オブジェクトのダンプ、オブジェクトサイズ、Expire残り時間、通信時間といった、考えつくあらゆるデータを常に確認できるようにしています。
SQLについては、クエリや使ったパラメータ、通信時間は当然として、ページ表示に使用したすべてのクエリにexplainをかけて表示しています(図3 ) 。気になるクエリだけ手動でexplainをかけて確認するという流れでは、面倒でやらなくなってしまったり、確認漏れが生じたりしてしまいます。すべてを常に表示すれば漏れは絶対に発生しません。さらに、明らかに危険なクエリ(Using filesort;
など)を警告表示すれば、事故を開発中に未然に防げます。すべてのクエリにexplainをかけると、通信時間が上乗せされて開発環境が遅くなり、開発にストレスがかかると思われるかもしれませんが、C#の並列実行・非同期構文を使ってexplainの実行をまとめているため、画面表示速度には影響しません。
図3 Glimpse上で作成した独自タブによるSQLのexplain表示
普通のやつらの上を行け
Paul Grahamが「普通のやつらの上を行け」とLISPを選んだように[2] 、グラニではC#こそが大きなアドバンテージを得られる最大の武器であると信じ、突き進んできました。これは結果で実証されるべきだと考えています。理屈上できる、プロトタイプがある、というだけではまったく意味がありません。ヒットして初めて、負荷的な面でも、開発効率的な面でも、C#の強さを証明できたと言えます。
さいわい「神獄のヴァルハラゲート」はヒットタイトルになりました。しかし、やれることはまだまだたくさん残っています。表立ってBtoC分野でC#を使っているという話はあまり聞きません。だからこそグラニは第一人者となり、あとに続く会社が現れるよう、また技術的な模範となるよう、多くの成果を持ってC#の最先端を突き進み続けます。ぜひとも今後の動向に注目してください。