SlideShare a Scribd company logo
アナザーエデンにおける
⾮同期オートセーブを⽤いた
通信待ちストレスのないゲーム体験の実現
グリー株式会社
Wright Flyer Studios事業本部
NT Production部 Engineeringグループ
鈴⽊ 清⼈ ⻄⽥ 綾佑
CEDEC 2017
⾃⼰紹介
• ⻄⽥ 綾佑 (ニシダ リョウスケ)
• グリー株式会社 Wright Flyer Studios事業本部
• エンジニア
• @hosi_mo
2014年 東京⼤学⼤学院 情報理⼯学系研究科修了
同年 グリー株式会社 ⼊社
Wright Flyer StudiosにてLINEタワーライジングの開発を経て、アナザーエデンの開発を担当。
アナザーエデンでは、StateMachineを導⼊したゲームループとイベントハンドラの設計、
UIフレームワークの整備、サウンド基盤、ネットワーク基盤、データストア、アセット管理、オートセーブ周りを
主に担当していました。
⾃⼰紹介
• 鈴⽊ 清⼈ (スズキ キヨト)
• グリー株式会社
• Wright Flyer Studios事業本部
• リードエンジニア
2001年 横浜国⽴⼤学 ⼯学部 電⼦情報⼯学科卒業
2013年 グリー株式会社 ⼊社
グリーが⼤規模なサーバ負荷にどうやって対処しているのかを⾒たいと思って⼊社したら、な
ぜか⾃ら負荷を作り出すための活動をすることに
BIシステム、「天と⼤地と⼥神の魔法」のサーバアプリ開発を経て、「アナザーエデン」の各
種パイプラインの下回り全般を担当
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
• なぜ、オートセーブなのか? (鈴⽊)
• オートセーブのデモとその仕組 (⻄⽥)
• 要素技術 LevelDB/DynamoDB/FlatBuffers (鈴⽊)
• 運⽤の実際 (鈴⽊)
• まとめ
もくじ
スマホゲーム vs コンシューマゲーム
スマホ コンシューマ
ゲームサイクル ホーム -> ゲーム ゲーム -> 設定
運⽤形態 サービス パッケージ
更新頻度 ⾼ 低
1プレイ時間 5分から10分 数⼗分
データ通信頻度 ⾼ 低
ダウンロード 分割か逐次 ⼀括
ユーザデータ サーバ ローカル
差異の解消 ≒ 進化?
スマートフォンゲーム
の⾼度化
コンシューマゲーム
との差異の解消
≒
?
アナザーエデンの主要なテーマ
よりコンシューマゲームっぽい作り⽅をしてみる
• だんだんとクライアント側でできることが増えていく
クライアント vs サーバのパラダイム
ココ
アナザーエデンの場合
スマホ コンシューマ アナザーエデン
ゲームサイクル ホーム -> ゲーム ゲーム -> 設定 ゲーム -> 設定
運⽤形態 サービス パッケージ サービス
更新頻度 ⾼ 低 中
1プレイ時間 5分から10分 数⼗分 数⼗分
データ通信頻度 ⾼ 低 ⾼ (Background)
ダウンロード 分割か逐次 ⼀括 選択性
ユーザデータ サーバ ローカル ハイブリッド
グランドデザイン
バックグラウンド
でのデータ同期
データスキーマ
の共有
クライアントサイド
DBMS
⼀歩踏み込んだ
チート対策
クライアント > サーバ
「クライアント > サーバ」とは?
• ⼤きなコードベース
• データ設計の主体
• クライアント単体で動作
• 開発⼈員の⼤部分を投⼊
• ⼩さなコードベース
• クライアントのスキーマをミラー
• ミラーストレージに過ぎない
• イベントフック主体
• 少⼈数で⼿間をかけずにメンテ
• サーバでデータを作らない。クライアントでどんどん作る
• 作った側から、どんどんサーバに差分を送る
• Dropbox等のオンラインストレージサービスのPC⽤クライアントアプリでは⼀般的
• かなり⾼いレベルでバックグラウンド同期を実現
• もちろん、ゲームでも実現可能である
• 「データの共有」というよりも、「バックアップ」が主眼ならば、なおさら
バックグラウンドでのデータ同期
Dropbox および Dropbox のロゴは、Dropbox, Inc. の商標です。
バックグラウンドで同期することで
通信していないか
のように⾒える
通信状態にプレイ
を邪魔されない
遅いが他の特⻑をもった
DBを選択できる
オートセーブについて
アナザーエデンのオートセーブ
アナザーエデンに最適なオートセーブとは?
• いつ中断しても途中からやり直せる
• 割り込みの多いスマフォでも納得感のある挙動を
オートセーブ : ゴール定義
• プレイヤーの没⼊感を邪魔しない
• ネットワーク環境に依存しない
• いつ中断しても途中からやり直せる
• 割り込みの多いスマフォでも納得感のある挙動を
オートセーブ : アプローチ
• プレイヤーの没⼊感を邪魔しない
• ネットワーク環境に依存しない
キューイングとバックグラウンド通信の組み合わせ
ステートマシンによるオートセーブ制御
• いつ中断しても途中からやり直せる
• 割り込みの多いスマフォでも納得感のある挙動を
オートセーブ : アプローチ
• プレイヤーの没⼊感を邪魔しない
• ネットワーク環境に依存しない
キューイングとバックグラウンド通信の組み合わせ
ステートマシンによるオートセーブ制御
• ゲームプレイ中、左上に「Auto Saving…」と表
⽰されるタイミングがある
• アプリをkillしても、最後に表⽰されたタイミン
グの状態でゲームが復帰する
「Auto Saving…」
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
• 各種設定UIクローズ時
• フィールドのエリア移動時
• luaイベント終了時
• シナリオ進捗時
• バトル終了時
• 1分に1回(プレイ時間が積み上がる)
オートセーブのタイミング
ステートが変わる時にセーブ処理
ExplorerState
FishingState
WarpState
BattleState
EventScriptState
GlobalUIState 装備画⾯などのUI表⽰
イベントスクリプト実⾏
エリア移動中
バトル中
釣り中
プレイヤー操作中
ExplorerStateに戻る
タイミングでオートセーブ
• ユーザデータに変更があるかどうか
• メモリ上のdirty flagを精査
• dirtyなレコードをローカルDBに書き込み
• 差分⼀覧をサーバ通知⽤に別で保存→
オートセーブで何を保存してる?
基本的には次の条件で保存する
msgpack
オートセーブ毎に
msgpackがつくられる
msgpack
msgpack
msgpack
msgpack
• いつ中断しても途中からやり直せる
• 割り込みの多いスマフォでも納得感のある挙動を
オートセーブ : アプローチ
• プレイヤーの没⼊感を邪魔しない
• ネットワーク環境に依存しない
キューイングとバックグラウンド通信の組み合わせ
ステートマシンによるセーブポイント制御
キューイングとバックグラウンド通信
オートセーブの差分情報をサーバに送りたい
Diff
Diff
Diff
Diff
差分情報の等式
クライアントの最新状態 = サーバのミラー状態 + 前回の保存からの差分
この定式を(がんばって)維持することで、⾮同期ながらもデータの⼀貫性を
それなりに⾼い精度で確保できる
• 何かあったときはサーバを優先して、データを巻き戻す
• 巻き戻りのリスクを最⼩化するために、できるだけ⼩さな差分で同期
1.最新の状態
2.ひとつまえの保存状態から変更があったレコー
ド(サーバに送る差分)
1.保存形式はテーブルごとに暗号化したmsgpack
2.msgpackはオートセーブ毎に作成
クライアントからサーバへの同期①
クライアントのローカルDBに、次の2つを保存
• バックグラウンドでどんどん送信する
• 差分はひとつずつ順番に送る
• 1差分、1リクエスト
• ACKがなければ、その差分を送り続ける
• 不整合が起きたら、サーバの最新状態に巻き戻す
クライアントからサーバへの同期②
差分を送信
• 差分は100個までは溜め込み可能
• 100を超えたらタイトルに戻してあげる
• x分間通信失敗し続けた場合も同様
クライアントからサーバへの同期③
タイトル画⾯ではかならず必ず差分を全部
送信してから起動
GameServer
クライアントからサーバへの同期④
AutoSave!
msgpack
Push
diff
Background
}Size = 100
Request Queue
Diff
リクエストごとローカルDBに保存
たくさんリクエストくる
スケーラビリテイだいじ
msgpack
msgpack
msgpack
msgpack
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
ブロッキング通信
プレイヤーの操作を⽌めて通信する必要があるもの
• 有償通貨を使⽤したキャラクター抽選
• 有償通貨を報酬にする機能
• サーバ管理テーブル : ギフト、クエスト報酬etc
いわゆるローディング画⾯
ブロッキング通信とバックグラウンド通信
1. バックグラウンド通信キューの残りを確認
2. バックグラウンド通信を全て実⾏
• 通信キューの数だけAPIコールをゴリゴリ実⾏
3. バックグラウンド通信キューが空になる
4. 晴れてブロッキング通信開始
ブロッキング通信が発生した場合の挙動
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
• が、サーバ側でデータを変更したい場合もある…
• 有償通貨使ったキャラクター抽選はアプリ側でやりたくない
• 補填したい
• サポートツールからデータの補正したい…
サーバからクライアントへの同期①
「サーバはクライアントのミラーストレージである」
GameServer
クライアントからサーバへの同期④
AutoSave!
msgpack
Push
diff
Background
}Size = 100
Request Queue
Diff
リクエストごとローカルDBに保存
たくさんリクエストくる
スケーラビリテイだいじ
msgpack
msgpack
msgpack
msgpack
⾮同期の通信と競合してしまう
サーバからクライアントへの同期③
サーバからデータ更新命令を送信
1. サーバからクライアントへ命令送信(APIコールのレスポンスに⼊れる
• クライアントから完了通知が来るまでサーバの命令は不揮発性
2. クライアントがデータを変更
3. オートセーブ時に差分をサーバへ送信(完了済の命令idも送信
4. サーバは命令を削除
Operation Builder
1.ボタンを押す。画⾯ローディング表⽰
2.ローカルの差分データを全送信(N回APIコール)
3.抽選⽤APIをブロッキングで呼び出す
4.サーバ内で抽選し、OperationBuilderを発⾏し、クライアントへ返信
5.クライアントで、命令どおりのユーザデータの変更処理を実⾏
6.演出表⽰
7.通常のオートセーブに混ぜて、バックグラウンドで変更結果と完了署名を送信
8.サーバに保存していた命令を削除
OperationBuilderの流れ
{
キャラクター抽選の例
OperationBuilderの流れ : 図
diff
API Call
抽選
OperationBuilder
DB 変更
AutoSave
出会い実⾏
命令削除完了報告
• サーバ
• クライアントから完了報告が来るまでは、処理が終わっていな
いものとみなす
• 終わったときちんと⾔ってくるまで、何度も命令を送りなおす
• クライアント側
• 処理完了をかならず差分と⼀緒に保存する
• ⼆回実⾏するのを防ぐ
• ⼆回実⾏してもいいが、実⾏前の状態に巻き戻してからだ
OperationBuilderのキモ
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
オートセーブまとめ
1.ステートマシンによるオートセーブ制御
2.キューイングとバックグラウンド通信の組み合わせ
3.OperationBuilderを⽤いたサーバからのレコード変更命令
4.ブロッキング通信とバックグラウンド通信制御
• キューイングされたバックグラウンド送信⽤の差分を全て送信してから実⾏
LevelDB, DynamoDB, FlatBuffers
依存している⽅々たち
DBMSの選択
モバイルOSで動作する信頼できるフルセットのRDBMSは存在しない?
シンプルな構造のKVSならば存在している
ミラーリングには、
構造の等価性が必要
• Googleが公開しているOSSである
• InfluxDBのような、⼤規模なサー
バサイド⽤のストレージの基礎技
術になっている
• ローカルのファイル構成は、SQLite
のような単⼀ファイルではなく、
複数のファイルから構成されてい
る標準的なDBMSの構成である
クライアントサイドDB LevelDB
• ソートされた主キー
• シンプルなGetter/Setter
• スキャンアクセス⽤のブルームフィルタ
• コンパクトなC++実装
• 毎回ソースからビルドしている
• クエリ単体としてはThread Safe
• 複数レコードの⼀括変更⽤のbatchコ
マンドの提供
マルチプラットフォーム対応のシンプルで信頼性の⾼いKVS
• LevelDBのバグには、開発期間・運⽤期間を通じて出会ったことがない
• だいたい使う側の不備である
• ただ、壊れたことがないか、と⾔われればそんなことはない
• Loggerでログを集めているが、ちょいちょい、ありえないログ
シーケンスの⽅がいらっしゃる
• 正直、よくわからない
• 怪しい場合はクライアントのローカルを全消しして、サーバからデー
タをロードしなおし
LevelDBの信頼性
• ⾼いスケーラビリティ
• 2つの主キーによる「近傍の」データへ
のアクセス可能性
• 基本はデータサイズではなくスループッ
トによる従量課⾦制
• ⾼くもないけど、安くもない
• フルJSONサポート
• ネストされたオブジェクト
• Document Store としての側⾯も
サーバサイドDB DynamoDB
• 主キー以外はスキーマレスだが⽐較的強い型付け
• 強い⼀貫性の部分的なサポート
• 低い応答性能
• 最速でもミリ秒オーダーのレイテンシ
• これは最近出たDAXによってかなり改善され
るらしい
• ストリーミングによる各種処理系へのデータレプ
リケーション
• 更新ログをLambda+Kinesisを経由してS3,
Redshift等に流せる
AWSが提供するManagedなKVS
• レイテンシは問題ではない
• どうせバックグラウンドでダラダラ
送る
• サーバサイドはイベントフック程度
• 通常のブロッキングコールAPI主体
の設計の場合はクリティカルに
• 全差分をログシステムに流せる
• データの復旧や補正もログから取得
して、パッチを作る
• わりと⼿動でやっている
アナザーエデンにとってのDynamoDB
• シンプルなKVSというだけではさすがに困る
• 主キー(ユーザID)にかかわる全デー
タスキャンがある程度の速度で動いて欲
しい
• 他のパターンは要らない
• 要るときはそこだけ別のDBを使う
• 使⽤頻度は⾼くないものの半永続的に管理し
ていくべきデータがある
• トークン系
• データサイズに依存しない仕様が前提に
なる
アナザーエデンにとってのDynamoDB
• スケールアウトの可否はクリティカルである
• クライアント > サーバのため、データ流量をあらかじめ、サーバサイ
ドで設計できない
• 不可能ではないが、しずらいし、ゲームデザインにおける⾃由度が
下がり、本末転倒になる
• 最終的にどんなゲームに落ち着き、どのくらいのトラフィックが発⽣す
るのかわからないが、どんなであれ、それなりのパフォーマンスで動く
ことを保障することが⼤事
• 正直いって作ってるときは、どのくらい売れるかどうかもわからん
し、それ(売れる確率)も含めて育てていくのがプロダクト開発
読み込みが⾼速なバイナリシリアライズフォーマット
バイナリシリアライザ FlatBuffers
• Cocos2d-xで標準的に採⽤
• 強い型付け
• ネスト可能
• Enumサポート
• DSLはデータ構造の記述専⽤
• CとGoのStructの中間くらい
• 各項⽬に対して任意のAttribute
を付与できる(独⾃拡張可能)
• アクセスはオフセットを管理し、
ポインタをずらすだけ
• 読み込みはほぼゼロコスト
• 書き出しは⽐較的苦⼿
• 新しいデータを間に差し
込むと、後ろを全部ズラ
して、インデックスを更
新しないといけない
DSLとして利⽤し、独⾃拡張をだいぶしている
アナザーエデンとFlatBuffers
• マスタデータ
• 値の暗号化を独⾃実装していて、これがなんともかんとも
• ユーザデータ
• FlatBuffersでschemaを書き、msgpackかjsonでIOする
• ローカル保存はmsgpack
• 通信時はJSON
• 独⾃のパーサを実装し、C++とPHPのコードを出⼒している
データスキーマの共有
勝⼿に更新
保存される
データモデルの設計
機能実装
ローカルで
開発
オートセーブの運⽤の実際
メトリックと運⽤作業
• 300万DL突破
• リセマラはわりと少なくて2割程度かな、という印象(ちゃんと調べてない)
• プレイ時間
• 通しでクリアするのに40-60時間くらい
• LevelDBのデータサイズ: 最⼤1MB程度
• DynamoDBのPartition数: 約128くらい (UserData 概算)
• これまでサーバ障害でのサービス停⽌は4回(くらい)
• DynamoDBのキャパシティ管理のオペミス x3: 15分-30分
• Redisのマスターノードダウン時の⾃動FailOverの失敗 x1: 1時間
• バグはそれなりにいっぱい(時間の都合で省略)
アナザーエデンの規模感
ローンチ後のスループットの推移
最⼤ 6K弱
Read
3つに分けたUserDataのうちのひと
Write
バージョンアップ時にRead上昇
4/12 開始
Read/Write Consumed Capacity Units
8/26 現在
実際に消費したスループット
スループット遷移(書込/1週間)
⼟⽇はみんな朝から
⼀⽇中やっている
ある週のスループット
昼休み
22:00-23:00 がピーク
晩御飯
⽉曜 ⾦曜
• キャパシティ設定のスケジューリング(独⾃実装)
• 前⽇までの消費値を⾒て翌⽇の設定を⾃動で決定(賢い)
• なにかイベントがあるときは指定値に設定
• ⼀気に下げすぎないように下限値を指定(絶対値 or ⽐率)
• オートスケーリング
• CloudWatch Alarm + Lambdaで実装
• 条件は最近⼊った本家のものとほぼ同じ(60%を超えたら、1.5倍に)
• 微調整が効くので、いまだにこちら側で運⽤している
• スロットル(詰まり)監視
• DynamoDBは局所的(Partitionごと)に詰まることがある
• ただ、普通はリトライするので、あまり実際の障害と連動しているわけではない
DynamoDB運⽤のためにしていること
まとめ
オートセーブ機構の是⾮
• ようするにオートセーブ機構はGoogle Firebase Realtime Databaseや
AWS Cognitoなどのマネージドサービスの独⾃実装版と⾔えます
• 異なるのは以下の3点でしょう
• FlatBuffersを⽤いることでSchemaをきちんと定義し、中規模のソフト
ウェア開発においてコストやリスクを低減している
• スケールアウトするDBを利⽤し、特性を把握しつつ運⽤することで、
運営のリスクを低減している
• チート対策においては、オートセーブ機構の⼀部として各種の対処をビ
ルトインすることで、ゲーム特有のセキュリティの問題に対処している
まとめ(鈴⽊)
• 何も考えずに全ての通信をブロッキング通信にしてませんか?
• [good] 適切に制御することでバックグラウンドに寄せられるものは多い
• [!] ゲームデザインあってこその通信モデルではある
• 反省 : データが壊れた場合の修復がスマートではなかった
• [bad] データを2系統で持っておくなどすべきだった
• [!] 遠隔で補正するシステムを作っていたため、⼿を動かせば救える
まとめ(⻄⽥)
アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現
本格スマホRPG『アナザーエデン』開発の裏側を包み
隠さずお話します
 〜コード資産も無く、チームとしての経験も豊富ではない中エンジニアはどう挑んだのか〜
グラフデータベースNeo4Jでアセットダウンロードの
構成管理と最適化
8⽉31⽇(⽊) 10:00〜11:00 R311+312 PR : エンジニアリング
9⽉1⽇(⾦) 16:30〜16:55 R304 公募 : エンジニアリング

More Related Content

アナザーエデンにおける非同期オートセーブを用いた通信待ちストレスのないゲーム体験の実現