ソリューションアーキテクトの安川 (@thekentiest)です。
AWS Summit Tokyo 2014はいかがでしたでしょうか?私もAmazon DynamoDBテーブル設計と実践Tipsと題しまして、Amazon DynamoDBの各種Tips、特にマルチプレーヤーオンラインゲームにおけるレイドボスとの対戦のユースケースを例にあげて、Conditional Update等を使うことでRDBでは困難が伴うユースケースもAmazon DynamoDBなら簡単かつ高い性能とスケーラビリティを持って実現できることを紹介しました(講演資料はこちら)。
Amazon DynamoDBのソーシャルゲーム等での利用事例はどんどん増えていて、日本のお客様でもマイネットさんやRipplationさんのなどの事例が実際に公開されています。
本日は我々の同僚であるNate WigerがAWS Big Dataブログに投稿したポストに同様の話題がグローバルの実例付きで上がっていたので、その日本語訳した記事をお届けします。お楽しみ下さい!
Nate WigerはAWSのプリンシパルゲームソリューションアーキテクトです。
Amazon DynamoDBのシニアプロダクトマネージャであるDave Langもこの記事の執筆に貢献しました。
Amazon DynamoDBは急速に伸びている世界中のゲームでデータベースとしての利用が進んでいます。例えばFruit Ninja (Halfbrick Studios)やBattle Camp (PennyPop)などはAmazon DynamoDBのボタンを押すだけでスケールする仕組みを活用して、数百万のユーザがプレイするゲームへとシームレスに成長させて来ました。Tower WarsやTron Evolutionの制作を行ったSupervillain Studiosなどの開発会社でも採用されています。
この記事では、Amazon DynamoDBを使えば、モバイルゲーム向けのスケーラブルで信頼できるデータベースTierをすぐに構築できることをお伝えしたいと思います。設計例を追いながら、スケールするゲームを、1日のコーヒー代よりも安いコストで運用できることをお見せします。また、ある急成長中のお客様が、Amazon DynamoDBを使うことで、時間とコストを節約しながら数百万プレーヤーまでスケールした実例も見て行きたいと思います。
データベースTierの重要性
スケールするアプリケーションの設計をする際、キーになる要素の1つがデータベースTierだと思います。これは書き込みが非常に多いゲームのようなアプリケーションに関しては特に当てはまります。ゲームのデータはプレーヤーがアイテムを手に入れたり、敵を倒したり、ゴールドを受け取ったり、新たなレベルに進んだり、何かを達成した際など、常にアップデートされ続けます。それらの各イベントは全て失われないようにデータベースに書かれる必要があります。もしゲームの状態を失ったとしたら、プレーヤーたちは激怒するでしょう。
ゲームやWebアプリの開発者の方々は、慣れているからという理由で、MySQLなど、オープンソースのリレーショナルデータベースをデータベースTierに利用しがちです。しかし残念なことに、MySQLなどのデータベースは、ゲーム、ソーシャルメディアアプリ、写真共有サイトなどとは要件が異なる、読み込み中心のワークロードが一般的であった時代に設計されています。これがリレーショナルデータベースのクエリの柔軟さの代わりに、高い書き込みスループットと水平スケーリングが可能になるNoSQL技術が広まることになったきっかけと言えるかと思います。
Amazon DynamoDBがゲーム開発者にとってよい選択肢である3つの理由
Amazonが皆様に変わって運用をします
皆様はゲームづくりで忙しいですよね?Amazon DynamoDBは完全な運用サポートと複数データセンターを使った高可用構成が含まれたマネージドサービスです。ソフトウェアのインストールやハードウェアの故障、パフォーマンスのチューニングといったことに頭を悩ませる必要はありません。
Amazon DynamoDBのテーブルの性能を大きくするのも小さくするのも1回APIを呼ぶだけ
Amazon DynamoDBの各テーブルにはスループットのキャパシティが割り当てられます。例えば秒間1,000書き込みと設定すれば、Amazon DynamoDBは裏側でデータベースをスケールさせます。性能要件が変わるごとにキャパシティを更新してもらえれば、Amazon DynamoDBはリソースの再割当てを行います。この伸縮性はゲーム開発者の大きな助けになると思います。もしゲームの人気が出れば、数千人だったプレーヤ数から一気に数百万にスケールさせることもあり得ると思います。同じくらい重要なこととして、瞬時にスケールダウンして戻すことも可能です。もし同じことをシャーディングしたMySQLで行っていることを想像すると、なかなか大変な作業だと思います。
ゲームが成長しても一貫したパフォーマンス
Amazon DynamoDBはどんなスケールにおいても予測可能な、低レイテンシのパフォーマンスを提供します。これはゲームが数百万ユーザまで成長することを考える非常に重要です。その過程でもAmazon DynamoDB自体のパフォーマンスのチューニングに時間を費やす必要はありません。
ゲームデータをAmazon DynamoDBに保存する
ロールプレイングゲームを作っていることを考えてみましょう。ゲームは一般的な機能を備えているものとします。例えば、モンスターと戦って、アイテムを入手し、レベルを上げるといったところです。ユーザの進行具合を保存する必要があるので、各プレーヤのプロファイルを作り、アイテム、レベル、稼いだゴールドなどをKey-valueペアとして記録することとします。データ構造としては例えば下記のようになります:
{
player_id: 3612458,
name: “Gunndor”,
class: “thief”,
gold: 47950,
level: 24,
hp_max: 320,
hp_current: 292,
mp_max: 180,
mp_current: 180,
xp: 582180,
num_plays: 538,
last_play_date: "2014-06-30T16:27:39.921Z"
}
上記の例では、player_id
はユニークな値です。これをMySQL等のリレーショナルデータベースで表現するのは簡単で、各Keyについてカラムを作成すればよいかと思います。それで動作すると思いますが、プライマリキー(player_id
)でアクセスするたびに毎回全カラムのデータが取得されたりインデックスされたりするのはデータベースにとって大きなオーバーヘッドです。一方、一般にプレーヤーのレコードをHPや経験値でクエリすることはほとんどないと思われます。
これがAmazon DynamoDBだとどうなるか見てみましょう。Amazon DynamoDBの場合は、インデックスしたいカラムだけを定義します。上記の場合、player_id
はユニークなので、これをハッシュキーとして設定してプライマリキーに用います。そして"player_profiles"というテーブルを作成し、ハッシュキーの名前を"player_id"とします。Pythonで書くと以下の通りです:
player_profiles = Table.create(‘player_profiles', schema=[
HashKey('player_id', data_type=STRING)
], throughput={
'read': 5,
'write': 5,
},
上記の例では、Read Capacityが5、Write Capacityが5のテーブルを作成しています。これはAWSの無料試用枠に収まる範囲です。マネージメントコンソールでの操作でも簡単に実行できます。
テーブルが作成できたら、プロファイルの保存と取得をしてみましょう:
player_profiles.put_item(data={
'player_id': '3612458',
'name': 'Gunndor',
'class': 'thief’,
...
})
profile = player_profiles.get_item(player_id='3612458')
上記はPythonの例ですが、どのAWS SDKを使った場合でもAmazon DynamoDBの操作は可能です。
プライマリキーの値
リレーショナルデータベースではオートインクリメントされるIntegerがプライマリキーとしてよく使われると思います。大規模なシステムでは、オートインクリメントされるプライマリキーはボトルネックになりがちなので、Amazon DynamoDBのようなNoSQLのデータストアでは採用していません。ではどうやってユニークなplayer_id
を生成すればよいでしょうか?こういった場合、異なるクライアントがそれぞれユニークな値を生成できるという理由でUUIDが用いられます。UUIDは a8098c1a-f86e-11da-bd1a-00112444be1eのような長い文字列で、プライマリキーへのアクセスを一様に分散してくれることから、Amazon DynamoDBでの利用に適しています。こういった特性の文字列を用いれば、Amazon DynamoDBの性能を引き出すことが出来ます。
UUIDの生成はシンプルです:
player_id = uuid.uuid1()
player_profiles.put_item(data={
'player_id': player_id,
'name': 'Gunndor',
'class': 'thief',
...
})
アトミックインクリメント
PutやGetに加え、Amazon DynamoDBはアトミックインクリメントもサポートしています。この機能はオンラインゲームにおけるバグや進捗データのロスの原因となりがちなアプリケーションからの複数のリクエストの衝突を起こさないことから、値の更新を実装する際に便利です。あるプレーヤーが100ゴールド手に入れたら、現在のレコードを確認して、ゴールドの値に加算をして、値を書き戻すといった操作をしなくても、Amazon DynamoDBにただゴールドを100足すように伝えれば自動的に処理してくれます。
キャパシティの調節
Amazon DynamoDBではどれくらいのスループットキャパシティが必要なのか設定できます。でも、もしどのくらい必要かわからない場合はどうすればいいでしょうか?まず、開発を始めた時点では、最初のテーブルは無料試用枠の範囲に収まるように作成しましょう(Write Capcityは5, Read Capacityは10)。トラフィックが増えるに連れ、Amazon DynamoDBのコンソールにあるCloudWatchのグラフで使用状況をモニタリングしながら変更を加えていけばよいでしょう。
もう一つの便利なツールとしてはDynamic DynamoDBという、テーブルのキャパシティを自動でスケールさせる手助けをしてくれるオープンソースのライブラリが有ります。tadaaというお客様では、Dynamic DynamoDBを使ってトラフィックレベルの低下に反応するようにしてコストを削減されています。
本当に数千のプレーヤーを日々のコーヒー代で支えられるの?
Yes! ゲームのデータを保存するユースケースを使ってコスト試算をしてみましょう。月間アクティブユーザ数100,000のゲームがあったとします。多くのプレーヤーは一斉にアクティブになるわけではありませんので、ある時点でその1/10がプレイしているものとします。各プレーヤーのレコードサイズが1KB以下、同時接続する10,000人のプレーヤーが1分間に1回ゲームのステートを保存するものとします。また、各プレーヤーはゲームのステートを1分に1回読み出すものとしましょう。1分は60秒ですから、Amazon DynamoDBのテーブルは秒間167回の書き込みと読み込みをそれぞれサポートする必要があります(10,000 / 60)。実際には多くの場合余裕をもった設定をするでしょうから、繰り上げて秒間200回の書き込みと読み込みを行うものとしつつ、ストレージサイズは50 GBと仮定しましょう。
現在のUS-EAST-1の料金からすると、この設定の1日当たりのおおよそのコストは$4.16!すなわち100,000人のプレーヤーを日々コーヒー1杯分(豪華なエスプレッソドリンクかはともかく)のコストでサポートできるのです。しかもAmazon DynamoDBはAWS無料試用枠を使ってコストをかけずに使いはじめることも出来ます。
顧客事例: Battle Camp
PennyPopが提供する人気のモバイルゲーム、Battle Campでは、Amazon DynamoDBがプライマリのデータストアとして利用されています。Battle Campは累計一千万ダウンロードを超えるアプリで、40以上の国々のアプリランキングでトップ100に入り続けています。他のNoSQLの選択肢をいくつか見て回った後、サーバのメンテナンスやスケーリングではなく、アプリケーションのコーディングに集中したかったという理由でAmazon DynamoDBを選択されたそうです。
Battle Campの開発者たちはまず初めに、ローカルで開発を行うためのオープンソースの実装であるfake_dynamoをダウンロードするところから始めたそうです。(Amazonもローカルで使えるDynamoDBのモックアップをリリースし、サポートしています。)そしてRuby on Railsで使うための独自のORMを構築されました。そのORMによってDynamoDBの操作をシンプルにして、アプリケーションレイヤからはKey-Valueの取得に見えるようにしているとのことです。また、ORMにおいてBodyの値をJSONオブジェクトとした上でGZIP圧縮とBase64エンコードして保存することで、生のJSONオブジェクトを元の10%程度のサイズに落として保存できているそうです。MySQLからDynamoDBへの移行を終えたそうですが、多くのWebアプリケーションのデータベースへのクエリはシンプルなデータの取得と保存に落とし込めたため、独自のORMの構築に要した時間を含め、移行は数週間で完了したそうです。
次の図がBattle CampがどのようにAmazon DynamoDBをそのアーキテクチャに組み込んだのかを示しています。
結果はどうであったか?時間とコストの大きな節約になったとのことです。PennyPopの共同創業者のCharls Juは以下のように語っています:
"Amazon DynamoDBによるコスト削減はその効率と使い勝手の良さによるものだけではなく、メンテナンスのコストが下がったことに依ることも大きいです。リアルタイムに進行する大きなデータ中心のプロジェクトで構築、運用、シャーディングを実行していくことは想像を超えるほど難しく、多くの人手を要します。私達は数百万のプレーヤーが楽しむMMORPGを運用するにあたって未だに2人のサーバエンジニアしか抱えていません。私の知る限り、これほどリーンにこの規模のMMORPGを運用している会社は他にありません。"
また、彼らはDynamoDBが、そのスケーリングの柔軟性のおかげで、MapReduceによる解析とも相性が良いことを実感したそうです。彼らは独自のMapReduceプロトコルを構築し、データの解析を並列実行しているそうです。
最後に一言: データ解析
リアルタイムゲームサービスに加え、Amazon DynamoDBはAmazon Elastic MapReduce (EMR)等の他のAWSサービスともインテグレーションがされています。Amazon EMRもAmazon RedshiftもAmazon DynamoDBからの直接のデータ読み込みに対応しており、解析ワークフローを簡単に構築できるようになっています。もしAWS上で運用するゲームの解析に興味があれば、そのトピックについても今後取り上げたいと思いますので、是非コメントをお寄せ下さい。