ソーシャルゲームにおける...
TRANSCRIPT
ソーシャルゲームにおけるAWS/MongoDB利用事例
Masakazu MatsushitaCyberagent, Inc.
•松下 雅和 / Masakazu Matsushita•@matsukaz•Cyberagent, Inc. - SocialApps Division- Ameba Pico (2011/01~)•海外向けピグ- Animal Land (2011/03~)•DevLOVE Staff
About Me
•MongoDB概要•Animal Land概要•AWS利用時のポイント•MongoDB利用時のポイント•発生した障害•課題•まとめ
Agenda
MongoDB概要
•スキーマレス•BSON形式(Binary JSON)> db.users.save( { name : "Alex", age : 20 } );> db.users.save( { name : "Beth" } );> db.users.find();{ _id : ObjectId("..."), name : "Alex", age : 20 }{ _id : ObjectId("..."), name : "Beth" }
> db.items.save( { id : "item01", name : "hoge", price : 1 } );> db.items.find();{ _id : ObjectId("..."), id : "item01", name : "hoge", price : 1 }
Document-oriented
•ドキュメント内のどの属性にもIndexを作成可能•Unique Index、複合Indexにも対応
Full Index Support
•ReplicaSetにより高可用性を実現Replication
mongod(PRIMARY)
mongod(SECONDARY)
mongod(SECONDARY)
•ReplicaSetにより高可用性を実現Replication
mongod(DOWN)
mongod(PRIMARY)
mongod(SECONDARY)
•ReplicaSetにより高可用性を実現Replication
mongod(RECOVERING)
mongod(SECONDARY)
mongod(PRIMARY)
•ReplicaSetにより高可用性を実現Replication
mongod(SECONDARY)
mongod(SECONDARY)
mongod(PRIMARY)
•指定したShard Keyで水平分割Auto-Sharding
Shard 1users (shardkey=name)Alex
items (shardkey=id)
Beth ChrisDaniel Fred GeorgeMaria Nancy Phil
item01 item02 item03
Shard 2•指定したShard Keyで水平分割
Auto-ShardingShard 1users (shardkey=name)Alex
items (shardkey=id)
Beth ChrisDaniel Fred GeorgeMaria Nancy Phil
item01 item02 item03
Shard 2users (shardkey=name)Maria Nancy Phil
•指定したShard Keyで水平分割Auto-Sharding
Shard 1users (shardkey=name)Alex
items (shardkey=id)
Beth ChrisDaniel Fred GeorgeMaria Nancy Phil
item01 item02 item03・データ量の偏りを元に自動でマイグレーション・マイグレーションはchunkと呼ばれるShard Keyの特定範囲(ここではMaria~Phil)
Shard 2users (shardkey=name)Maria Nancy Phil
•指定したShard Keyで水平分割Auto-Sharding
Shard 1users (shardkey=name)Alex
items (shardkey=id)
mongocclient
mongosを介すことで、クライアントはShard構成を意識する必要なし
Shard情報
Beth ChrisDaniel Fred George
item01 item02 item03
clientmongos mongocmongoc
Shard情報を保持
•field selectionQuerying
•sort
•skip, limit
•cursor
> db.items.find({id : "item01"}, {"name" : 1});
> db.items.find({}).sort({"name" : 1});
> db.items.find({}).skip(20).limit(10);
> var cur = db.items.find({});> cur.forEach(function(data){ print(data.name) });
Animal Land概要
Demo
https://apps.facebook.com/animal-land/
• 2011/03から開発スタート•初ミーティングが 3.11•本格的な開発は2011/05から• 2011/12/09 リリース
開発期間
• Producer × 2•Designer × 1• Flash Developer × 3• Engineer × 4 + α
開発メンバー
•Amazon Web Services- EC2 (Amazon Linux, Instance Store)- EBS
利用サービス
m1.large m2.2xlargeMemory 7.5 GB 34.2 GBECU 4 13
Storage 850 GB 850 GBI/O 高速 高速
•Amazon Web Services- S3- CloudFront- Route53- Elastic Load Balancing
•Google Apps• Kontagent
利用サービス
システム間のつながり
Flash
iframe
Commandサーバ
Webサーバ
JSON
各種API呼び出し
AMF : Actionscript Message Format
課金コールバック
HTML
AMF
•RemoteObjectを利用すると、HTTPセッションが使われてステートフルになってしまう-スケールアウトしづらい•NetConnectionを直接利用することで、ステートレスにして解決
AMF利用時の注意
サーバ構成
Shard
Web
ELB
nginxTomcatmongos
CommandnginxTomcatmongos
×3 ×4
MongoDB
ELBS3
CloudFront
Route 53
admin
Tomcatmongos
monitor
ビルド
jenkinsMaven
バッチバッチMySQL
×5
L
mongod
MongoDBmongod
MongoDBmongod
MongoDBmongoc
×3
MySQLMySQL
MySQLMySQL
memcachedmemcached
×2XX
muninnagios
nginx
m1.largem2.2xlarge
redmine
SVNEBS EBS
EBS
Secondary EBS
EBS EBS
EBS
L
XX
XX
L
L
XX
L
L L
L
L
L
XX
• nginx 1.0.x• Tomcat 7.0.x•MongoDB 2.0.4•MySQL 5.5.x•memcached 1.4.x
ミドルウェア
• Spring Framework、Spring MVC•BlazeDS、Spring Flex• Spring Data - MongoDB 1.0.0 M4•mongo-java-driver 2.6.5• Ehcache• spymemcached•RestFB•MyBatis
フレームワーク/ライブラリ
AWS利用時のポイント
•環境ごとに適切なリージョンを選択-開発環境は効率を重視して東京-本番環境はCalifornia(Virginiaは障害が多いと判断)
• 1年以上使うインスタンスはReservedにするとかなりお得
EC2
•AMIを作成しておくとサーバ追加が楽- image作成
EC2
ec2-bundle-vol -d [image出力先] --privatekey [EC2インスタンス作成時に利用した秘密鍵] --cert [アクセス証明書] --user [AWSの口座番号]
- S3へアップロードec2-upload-bundle --bucket [S3のバケット] --manifest [作成したimageのマニフェストファイル] --access-key [アクセスキー] --secret-key [シークレットキー]
-管理画面でimageを登録
•必要に応じてEBSを利用-別のインスタンスにマウントし直すことが出来るので、いろんな用途で使える- SNAPSHOTでバックアップ可能
EC2
•注意点-別インスタンスで利用されていたIPアドレスが再利用される•外部公開のサーバは、各種ログで異常を確認しておいた方が良い
EC2
•注意点-同じスペックでもI/O性能に差がある•ストレージで利用するサーバは、ツールなどで検証しておいた方が良い
EC2
bonnie++の分析結果の一部
対象連続書き込み連続書き込み連続書き込み連続書き込み連続書き込み連続書き込み 連続読み込み連続読み込み連続読み込み連続読み込み
対象 Char単位Char単位 Block単位Block単位 RewriteRewrite Char単位Char単位 Block単位Block単位対象K/sec CPU K/sec CPU K/sec CPU K/sec CPU K/sec CPU
m2.2xlarge 55484 68 71216 12 61546 7 86701 99 3034488 99m2.2xlarge 81659 99 245211 47 133788 17 87943 99 3154005 99ebsマウント 81476 98 186995 34 129284 16 83300 99 3090866 99
•注意点-サーバ再起動がスケジューリングされることがある•大抵は1W~2W程度の猶予があり、期間内に自分で再起動すれば良い
-稀にサーバ停止もスケジューリングされる・・・•代替サーバを立てて移行が必要
EC2
運用中
•同じEC2インスタンスは複数のELBにはつなげられない• EC2インスタンスを全て切り離すと、次回つなげた際にELBの起動時間がかかる-メンテ時はSorryサーバをつなぐ運用
Elastic Load Balancing
Web Web Sorry
ELBメンテ中
Web Web Sorry
ELB
•CloudFrontのキャッシュクリアは難しい•キャッシュクリアをしない運用- S3のパスにバージョン番号を入れる
S3 + CloudFront
https://github.com/pcorliss/s3cmd-modification
・[S3バケット]/swf/1.0.3/main.swf・[S3バケット]/image/1.0.8/icon/xxx.png
-バージョン毎に全ファイルをUploadするため、並列Uploadで効率化
MongoDB利用時のポイント
•Ameba Picoでの利用実績•Animal Landの要件に合う-複雑なデータ構造 (街のグリッド情報、ユーザ/建築物のパラメータ、etc)-順次処理中心で、同時更新が少ない-メンテナンスレス•データ構造の動的変更•耐障害性、スケールアウト
MongoDB採用の理由
•MongoDBが苦手な部分は他の方法で解決-特性に合わないデータは他のDBで •信頼性が必要な課金関連はMySQL•一時的なデータはmemcached-トランザクションは利用しない
MongoDB採用の理由
•トランザクションがいらないデータ構造-ユーザ情報などは、1ドキュメントで保持して一括更新
{ "facebookId" : xxx, "status" : { "lv" : 10, "coin" : 9999, ... }, "layerInfo" : "1|1|0|1|2|1|1|3|1|1|4|0...", "structure" : { "1|1" : { "id" : xxx, "depth" : 3, "width" : 3, ... }, ... }, "inventory" : [ { "id" : xxx, "size" : xxx, ... }, ... ], "animal" : [ { "id" : xxx, "color" : 0, "pos" : "20|20", ... } ], ...}
ユーザ情報イメージ
アプリケーション開発時
•データ量を可能な限り削減-街のグリッド情報を1つのデータで保持するなど (プログラム側で展開)-設計時の構造(500 KB)"layerInfo" : { "1|1" : 0, "1|2" : 1, ....}
"layerInfo" : "1|1|0|1|2|1|1|3|1|1|4|0..."
-現在の構造(50 KB)
アプリケーション開発時
•フィールド数は多すぎないように- 2万以上のフィールド (144x144の街のグリッド情報 )を持つと、find()にかかる時間は5秒以上に・・・-データ量だけでなく、BSONのパースにかかる時間も考慮する
アプリケーション開発時
• Shard Keyは以下の方針で決定-カーディナリティが低い値は使わない-よく使われるデータがメモリ上に乗る-使われないデータはメモリ上に乗らない-参照や更新が多いデータはバランスよく各Shardに分散- Targetedオペレーション
での利用を意識 (後述)
おすすめ書籍 →
アプリケーション開発時
•Globalは極力避けて、Targeted中心に- Shard Keyでデータを操作- Shard Key以外の操作はIndexを利用
Operation Typedb.foo.find({ShardKey : 1}) Targeteddb.foo.find({ShardKey : 1, NonShardKey : 1}) Targeteddb.foo.find({NonShardKey : 1}) Globaldb.foo.find() Globaldb.foo.insert(<object>) Targeteddb.foo.update({ShardKey : 1}, <object>)db.foo.remove({ShardKey : 1})
Targeted
db.foo.update({NonShardKey : 1}, <object>)db.foo.remove({NonShardKey : 1})
Global
アプリケーション開発時
•更新頻度をなるべく減らす-マスタ情報はサーバ上でキャッシュ-ユーザ操作はまとめて処理 (5秒に1度)• Flash側でキューの仕組みを用意•サーバ側はキューから取り出してシーケンシャルに処理
Flash Commandサーバ
まとめて送信
キュー
キューで保持 シーケンシャル処理ユーザ操作
アプリケーション開発時
•O/D(?)マッパーで開発を効率化- Spring Data - MongoDBとラッパークラスを用意して効率よく開発@Autowiredprotected MongoTemplate mongoTemplate;public void insert(T entity) { mongoTemplate.save(entity);}
-オブジェクトをそのまま利用できるので、RDBのO/Rマッパーより扱いやすい- Javaでも言うほど大変じゃない
アプリケーション開発時
•ロールバックは出来ない前提で実装-ある程度のデータ不整合は諦める-必ずユーザが不利益を被らないようにする
アプリケーション開発時
•従来のDBと同様に見積もり-データ量/1ユーザ (50 KB)-想定ユーザ数 (DAU 70万)-データの更新頻度-各サーバの最大同時接続数-各サーバの台数、スペック、コスト-想定ユーザ数を超えた場合のスケールのポイント整理
インフラ構築時
•性能検証-帯域幅を確認 (350Mbps程度)-MongoDBを検証 (MongoDB 2.0.1、ReplicaSetのShardを2つ、ジャーナリング有効)•参照/更新の処理性能•マイグレーション時の性能比較• Javaドライバー経由の性能比較-アプリケーションを通した性能を確認
インフラ構築時
•障害を想定した動作検証- mongodが落ちた場合• PRIMARYへ昇格するか•起動したらデータが同期されるか• SECONDARYが落ちても問題ないか- mongocが落ちた場合•全体の動作に影響がないか-バックアップから戻せるか
→ 全て問題ナシ
インフラ構築時
•ReplicaSet、Shardの構成と配置を決定-メモリサイズを大きめ-ディスクI/Oが高速- mongocはmongodと別サーバに-信頼性を高めるためにEBS利用 (負荷軽減のためSECONDARY専用に)-見積もりに合わせたサーバ台数
インフラ構築時
•ジャーナリングを有効化-障害時のデータロストの危険性を軽減-パフォーマンスは低下するので注意
• oplogsizeを10GBに設定-デフォルトは全体の5%で大きいため-障害発生から復旧作業にとりかかるまでの予定時間から算出
インフラ構築時
•必要なIndexはあらかじめ作成しておく-通常のIndex作成中は、全てのオペレーションがブロックされてしまうため- 20万件のデータ (それぞれ50KB程度) にIndexを貼ると、2分程度かかる-あとから追加する場合は、メンテ中に作成するか、バックグラウンドで作成
インフラ構築時
•コネクションプールをチューニング
項目 意味 値connectionsPerHost コネクション数 100threadsAllowedToBlockForConnectionMultiplier
1コネクション辺りの接続待ち数 4
※ mongosに対するコネクションプール
- nginxのworker数、TomcatのThread数とのバランスを考慮して設定-プールが足りなくなったときのエラー (Out of Semaphores) に注意
上記例では、100 + (100 * 4) = 500
インフラ構築時
• db.printShardingStatus()でShard間のchunkの偏りを定期的にチェック-必要があれば手動でchunk移動
•新しいCollectionの追加は慎重に-最初はPrimary Shardにデータが偏り、急激な負荷がかかる可能性があるため
•それ以外は監視任せでOK
運用時
発生した障害
•mongoc x 1 & mongod x 1 (SECONDARY) が落ちた•原因はEC2インスタンスの仮想サーバホストの障害•プロセスを起動するだけで復旧•サービスへの影響なし
MongoDBがダウン
•mongos上でmoveChunkしたら、他のmongosからはデータが見えなくなった- Shard情報がうまく伝わらないことがあるらしい-普段のマイグレーションでは問題ないので、手動で行ったときはmongos再起動
Shard情報の不整合
•アプリから数値を入れると32-bit integerだが、コンソールから数値を入れるとdoubleとして扱われ、型変換に失敗-入れる場合はNumberIntを利用
※ まだドキュメントには書いてない
数値の型変換エラー
> db.foo.save( { val : 1 } );> db.foo.save( { val : NumberInt(2) } );
> db.foo.find( { val : { $type : 16 } } );{ _id : ObjectId("..."), val : 2 }
1 : double16 : int
•mongoimportを実行したら、データ件数が1件減った•ヘッダ行を無視する --headerline オプションが誤動作している模様-再現性が不明(一部環境でのみ発生)- mongodumpを利用&件数を常に確認
mongoimportの不具合
•movePrimaryを実行したら、Shard情報が更新されないままデータが別Shardに移動してしまい、データが見えない状態に-数万件のデータがロストしかけた-この問題は、当時ドキュメントに書いてなかった(今はこの問題が警告されている)
データロストしかけた
•数分間のネットワーク障害•サーバへのPINGも通らない状態•AWS Service Health Dashboardを見て復旧を祈る・・・
AWSネットワーク障害
http://status.aws.amazon.com/
課題
•MongoDBはやはり難しい•当面はメンテを入れてバックアップ•以下のSECONDARYバックアップも検討中1. balancerをオフに2. SECONDARYでwrite lockをかけて
ディレクトリ毎バックアップ3. SECONDARYのロックを解除
オンライン・バックアップ
•バージョンがどんどん上がるので、サービスを運用しているとアップグレードのタイミングが難しい-基本的には、Release Noteで問題のあるバグがFixされてたら対応
MongoDBアップグレード
• Shard追加は可能だが、追加時のマイグレーションがパフォーマンスに与える影響が読めない- Picoではパフォーマンスの影響が問題となったため、メンテ中に対応している
Shardの追加
•ユーザ情報のデータサイズが大きかったため、デフォルトの64MBだとマイグレーションが多発•データサイズに合わせて調整する必要がある- Collectionごとにchunksizeが設定出来ればいいのに・・・(まだない)
最適なchunksize
•ユーザ情報を1つにまとめたため、データの分析がやりづらい•必要に応じてIndexを貼って対応中-MapReduceも試したが、検証時にパフォーマンスに影響が出たので利用していない
データの分析
まとめ
•スタートアップに最適•サーバ構成の変更にも対応しやすい-簡単に出来すぎるので、サーバ管理はしっかりと(特にSecurity Group)
•積極的に機能追加/改善されてる•多少の障害は許容が必要(障害検知と早期対応が大事)•コストは定期的に確認
AWS
•学習コストが低い•使いどころがあっていれば、開発も運用もすごく楽•RDBの単純な置換えはNG•絶賛開発中のプロダクトなので、いろいろ覚悟して使う
MongoDBについて
ご清聴ありがとうございました
https://apps.facebook.com/animal-land/