Slide 1

Slide 1 text

アナザーエデンにおける ⾮同期オートセーブを⽤いた 通信待ちストレスのないゲーム体験の実現 グリー株式会社 Wright Flyer Studios事業本部 NT Production部 Engineeringグループ 鈴⽊ 清⼈ ⻄⽥ 綾佑 CEDEC 2017

Slide 2

Slide 2 text

⾃⼰紹介 • ⻄⽥ 綾佑 (ニシダ リョウスケ) • グリー株式会社 Wright Flyer Studios事業本部 • エンジニア • @hosi_mo 2014年 東京⼤学⼤学院 情報理⼯学系研究科修了 同年 グリー株式会社 ⼊社 Wright Flyer StudiosにてLINEタワーライジングの開発を経て、アナザーエデンの開発を担当。 アナザーエデンでは、StateMachineを導⼊したゲームループとイベントハンドラの設計、 UIフレームワークの整備、サウンド基盤、ネットワーク基盤、データストア、アセット管理、オートセーブ周りを 主に担当していました。

Slide 3

Slide 3 text

⾃⼰紹介 • 鈴⽊ 清⼈ (スズキ キヨト) • グリー株式会社 • Wright Flyer Studios事業本部 • リードエンジニア 2001年 横浜国⽴⼤学 ⼯学部 電⼦情報⼯学科卒業 2013年 グリー株式会社 ⼊社 グリーが⼤規模なサーバ負荷にどうやって対処しているのかを⾒たいと思って⼊社したら、な ぜか⾃ら負荷を作り出すための活動をすることに BIシステム、「天と⼤地と⼥神の魔法」のサーバアプリ開発を経て、「アナザーエデン」の各 種パイプラインの下回り全般を担当

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

• なぜ、オートセーブなのか? (鈴⽊) • オートセーブのデモとその仕組 (⻄⽥) • 要素技術 LevelDB/DynamoDB/FlatBuffers (鈴⽊) • 運⽤の実際 (鈴⽊) • まとめ もくじ

Slide 6

Slide 6 text

スマホゲーム vs コンシューマゲーム スマホ コンシューマ ゲームサイクル ホーム -> ゲーム ゲーム -> 設定 運⽤形態 サービス パッケージ 更新頻度 ⾼ 低 1プレイ時間 5分から10分 数⼗分 データ通信頻度 ⾼ 低 ダウンロード 分割か逐次 ⼀括 ユーザデータ サーバ ローカル

Slide 7

Slide 7 text

差異の解消 ≒ 進化? スマートフォンゲーム の⾼度化 コンシューマゲーム との差異の解消 ≒ ? アナザーエデンの主要なテーマ よりコンシューマゲームっぽい作り⽅をしてみる

Slide 8

Slide 8 text

• だんだんとクライアント側でできることが増えていく クライアント vs サーバのパラダイム ココ

Slide 9

Slide 9 text

アナザーエデンの場合 スマホ コンシューマ アナザーエデン ゲームサイクル ホーム -> ゲーム ゲーム -> 設定 ゲーム -> 設定 運⽤形態 サービス パッケージ サービス 更新頻度 ⾼ 低 中 1プレイ時間 5分から10分 数⼗分 数⼗分 データ通信頻度 ⾼ 低 ⾼ (Background) ダウンロード 分割か逐次 ⼀括 選択性 ユーザデータ サーバ ローカル ハイブリッド

Slide 10

Slide 10 text

グランドデザイン バックグラウンド でのデータ同期 データスキーマ の共有 クライアントサイド DBMS ⼀歩踏み込んだ チート対策 クライアント > サーバ

Slide 11

Slide 11 text

「クライアント > サーバ」とは? • ⼤きなコードベース • データ設計の主体 • クライアント単体で動作 • 開発⼈員の⼤部分を投⼊ • ⼩さなコードベース • クライアントのスキーマをミラー • ミラーストレージに過ぎない • イベントフック主体 • 少⼈数で⼿間をかけずにメンテ

Slide 12

Slide 12 text

• サーバでデータを作らない。クライアントでどんどん作る • 作った側から、どんどんサーバに差分を送る • Dropbox等のオンラインストレージサービスのPC⽤クライアントアプリでは⼀般的 • かなり⾼いレベルでバックグラウンド同期を実現 • もちろん、ゲームでも実現可能である • 「データの共有」というよりも、「バックアップ」が主眼ならば、なおさら バックグラウンドでのデータ同期 Dropbox および Dropbox のロゴは、Dropbox, Inc. の商標です。

Slide 13

Slide 13 text

バックグラウンドで同期することで 通信していないか のように⾒える 通信状態にプレイ を邪魔されない 遅いが他の特⻑をもった DBを選択できる

Slide 14

Slide 14 text

オートセーブについて

Slide 15

Slide 15 text

アナザーエデンのオートセーブ アナザーエデンに最適なオートセーブとは?

Slide 16

Slide 16 text

• いつ中断しても途中からやり直せる • 割り込みの多いスマフォでも納得感のある挙動を オートセーブ : ゴール定義 • プレイヤーの没⼊感を邪魔しない • ネットワーク環境に依存しない

Slide 17

Slide 17 text

• いつ中断しても途中からやり直せる • 割り込みの多いスマフォでも納得感のある挙動を オートセーブ : アプローチ • プレイヤーの没⼊感を邪魔しない • ネットワーク環境に依存しない キューイングとバックグラウンド通信の組み合わせ ステートマシンによるオートセーブ制御

Slide 18

Slide 18 text

• いつ中断しても途中からやり直せる • 割り込みの多いスマフォでも納得感のある挙動を オートセーブ : アプローチ • プレイヤーの没⼊感を邪魔しない • ネットワーク環境に依存しない キューイングとバックグラウンド通信の組み合わせ ステートマシンによるオートセーブ制御

Slide 19

Slide 19 text

• ゲームプレイ中、左上に「Auto Saving…」と表 ⽰されるタイミングがある • アプリをkillしても、最後に表⽰されたタイミン グの状態でゲームが復帰する ʮAuto Saving…ʯ

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

• 各種設定UIクローズ時 • フィールドのエリア移動時 • luaイベント終了時 • シナリオ進捗時 • バトル終了時 • 1分に1回(プレイ時間が積み上がる) オートセーブのタイミング

Slide 22

Slide 22 text

ステートが変わる時にセーブ処理 ExplorerState FishingState WarpState BattleState EventScriptState GlobalUIState 装備画⾯などのUI表⽰ イベントスクリプト実⾏ エリア移動中 バトル中 釣り中 プレイヤー操作中 ExplorerStateに戻る タイミングでオートセーブ

Slide 23

Slide 23 text

• ユーザデータに変更があるかどうか • メモリ上のdirty flagを精査 • dirtyなレコードをローカルDBに書き込み • 差分⼀覧をサーバ通知⽤に別で保存→ オートセーブで何を保存してる? 基本的には次の条件で保存する msgpack オートセーブ毎に msgpackがつくられる msgpack msgpack msgpack msgpack

Slide 24

Slide 24 text

• いつ中断しても途中からやり直せる • 割り込みの多いスマフォでも納得感のある挙動を オートセーブ : アプローチ • プレイヤーの没⼊感を邪魔しない • ネットワーク環境に依存しない キューイングとバックグラウンド通信の組み合わせ ステートマシンによるセーブポイント制御

Slide 25

Slide 25 text

キューイングとバックグラウンド通信 オートセーブの差分情報をサーバに送りたい Diff Diff Diff Diff

Slide 26

Slide 26 text

差分情報の等式 クライアントの最新状態 = サーバのミラー状態 + 前回の保存からの差分 この定式を(がんばって)維持することで、⾮同期ながらもデータの⼀貫性を それなりに⾼い精度で確保できる • 何かあったときはサーバを優先して、データを巻き戻す • 巻き戻りのリスクを最⼩化するために、できるだけ⼩さな差分で同期

Slide 27

Slide 27 text

1.最新の状態 2.ひとつまえの保存状態から変更があったレコー ド(サーバに送る差分) 1.保存形式はテーブルごとに暗号化したmsgpack 2.msgpackはオートセーブ毎に作成 クライアントからサーバへの同期① クライアントのローカルDBに、次の2つを保存

Slide 28

Slide 28 text

• バックグラウンドでどんどん送信する • 差分はひとつずつ順番に送る • 1差分、1リクエスト • ACKがなければ、その差分を送り続ける • 不整合が起きたら、サーバの最新状態に巻き戻す クライアントからサーバへの同期② 差分を送信

Slide 29

Slide 29 text

• 差分は100個までは溜め込み可能 • 100を超えたらタイトルに戻してあげる • x分間通信失敗し続けた場合も同様 クライアントからサーバへの同期③ タイトル画⾯ではかならず必ず差分を全部 送信してから起動

Slide 30

Slide 30 text

GameServer クライアントからサーバへの同期④ AutoSave! msgpack Push diff Background }Size = 100 Request Queue Diff リクエストごとローカルDBに保存 たくさんリクエストくる スケーラビリテイだいじ msgpack msgpack msgpack msgpack

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

ブロッキング通信 プレイヤーの操作を⽌めて通信する必要があるもの • 有償通貨を使⽤したキャラクター抽選 • 有償通貨を報酬にする機能 • サーバ管理テーブル : ギフト、クエスト報酬etc いわゆるローディング画⾯

Slide 33

Slide 33 text

ブロッキング通信とバックグラウンド通信 1. バックグラウンド通信キューの残りを確認 2. バックグラウンド通信を全て実⾏ • 通信キューの数だけAPIコールをゴリゴリ実⾏ 3. バックグラウンド通信キューが空になる 4. 晴れてブロッキング通信開始 ブロッキング通信が発⽣した場合の挙動

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

• が、サーバ側でデータを変更したい場合もある… • 有償通貨使ったキャラクター抽選はアプリ側でやりたくない • 補填したい • サポートツールからデータの補正したい… サーバからクライアントへの同期① 「サーバはクライアントのミラーストレージである」

Slide 36

Slide 36 text

GameServer クライアントからサーバへの同期④ AutoSave! msgpack Push diff Background }Size = 100 Request Queue Diff リクエストごとローカルDBに保存 たくさんリクエストくる スケーラビリテイだいじ msgpack msgpack msgpack msgpack ⾮同期の通信と競合してしまう

Slide 37

Slide 37 text

サーバからクライアントへの同期③ サーバからデータ更新命令を送信 1. サーバからクライアントへ命令送信(APIコールのレスポンスに⼊れる • クライアントから完了通知が来るまでサーバの命令は不揮発性 2. クライアントがデータを変更 3. オートセーブ時に差分をサーバへ送信(完了済の命令idも送信 4. サーバは命令を削除 Operation Builder

Slide 38

Slide 38 text

1.ボタンを押す。画⾯ローディング表⽰ 2.ローカルの差分データを全送信(N回APIコール) 3.抽選⽤APIをブロッキングで呼び出す 4.サーバ内で抽選し、OperationBuilderを発⾏し、クライアントへ返信 5.クライアントで、命令どおりのユーザデータの変更処理を実⾏ 6.演出表⽰ 7.通常のオートセーブに混ぜて、バックグラウンドで変更結果と完了署名を送信 8.サーバに保存していた命令を削除 OperationBuilderの流れ { キャラクター抽選の例

Slide 39

Slide 39 text

OperationBuilderの流れ : 図 diff API Call 抽選 OperationBuilder DB 変更 AutoSave 出会い実⾏ 命令削除 完了報告

Slide 40

Slide 40 text

• サーバ • クライアントから完了報告が来るまでは、処理が終わっていな いものとみなす • 終わったときちんと⾔ってくるまで、何度も命令を送りなおす • クライアント側 • 処理完了をかならず差分と⼀緒に保存する • ⼆回実⾏するのを防ぐ • ⼆回実⾏してもいいが、実⾏前の状態に巻き戻してからだ OperationBuilderのキモ

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

オートセーブまとめ 1.ステートマシンによるオートセーブ制御 2.キューイングとバックグラウンド通信の組み合わせ 3.OperationBuilderを⽤いたサーバからのレコード変更命令 4.ブロッキング通信とバックグラウンド通信制御 • キューイングされたバックグラウンド送信⽤の差分を全て送信してから実⾏

Slide 43

Slide 43 text

LevelDB, DynamoDB, FlatBuffers 依存している⽅々たち

Slide 44

Slide 44 text

DBMSの選択 モバイルOSで動作する信頼できるフルセットのRDBMSは存在しない? シンプルな構造のKVSならば存在している ミラーリングには、 構造の等価性が必要

Slide 45

Slide 45 text

• Googleが公開しているOSSである • InfluxDBのような、⼤規模なサー バサイド⽤のストレージの基礎技 術になっている • ローカルのファイル構成は、SQLite のような単⼀ファイルではなく、 複数のファイルから構成されてい る標準的なDBMSの構成である クライアントサイドDB LevelDB • ソートされた主キー • シンプルなGetter/Setter • スキャンアクセス⽤のブルームフィルタ • コンパクトなC++実装 • 毎回ソースからビルドしている • クエリ単体としてはThread Safe • 複数レコードの⼀括変更⽤のbatchコ マンドの提供 マルチプラットフォーム対応のシンプルで信頼性の⾼いKVS

Slide 46

Slide 46 text

• LevelDBのバグには、開発期間・運⽤期間を通じて出会ったことがない • だいたい使う側の不備である • ただ、壊れたことがないか、と⾔われればそんなことはない • Loggerでログを集めているが、ちょいちょい、ありえないログ シーケンスの⽅がいらっしゃる • 正直、よくわからない • 怪しい場合はクライアントのローカルを全消しして、サーバからデー タをロードしなおし LevelDBの信頼性

Slide 47

Slide 47 text

• ⾼いスケーラビリティ • 2つの主キーによる「近傍の」データへ のアクセス可能性 • 基本はデータサイズではなくスループッ トによる従量課⾦制 • ⾼くもないけど、安くもない • フルJSONサポート • ネストされたオブジェクト • Document Store としての側⾯も サーバサイドDB DynamoDB • 主キー以外はスキーマレスだが⽐較的強い型付け • 強い⼀貫性の部分的なサポート • 低い応答性能 • 最速でもミリ秒オーダーのレイテンシ • これは最近出たDAXによってかなり改善され るらしい • ストリーミングによる各種処理系へのデータレプ リケーション • 更新ログをLambda+Kinesisを経由してS3, Redshift等に流せる AWSが提供するManagedなKVS

Slide 48

Slide 48 text

• レイテンシは問題ではない • どうせバックグラウンドでダラダラ 送る • サーバサイドはイベントフック程度 • 通常のブロッキングコールAPI主体 の設計の場合はクリティカルに • 全差分をログシステムに流せる • データの復旧や補正もログから取得 して、パッチを作る • わりと⼿動でやっている アナザーエデンにとってのDynamoDB • シンプルなKVSというだけではさすがに困る • 主キー(ユーザID)にかかわる全デー タスキャンがある程度の速度で動いて欲 しい • 他のパターンは要らない • 要るときはそこだけ別のDBを使う • 使⽤頻度は⾼くないものの半永続的に管理し ていくべきデータがある • トークン系 • データサイズに依存しない仕様が前提に なる

Slide 49

Slide 49 text

アナザーエデンにとってのDynamoDB • スケールアウトの可否はクリティカルである • クライアント > サーバのため、データ流量をあらかじめ、サーバサイ ドで設計できない • 不可能ではないが、しずらいし、ゲームデザインにおける⾃由度が 下がり、本末転倒になる • 最終的にどんなゲームに落ち着き、どのくらいのトラフィックが発⽣す るのかわからないが、どんなであれ、それなりのパフォーマンスで動く ことを保障することが⼤事 • 正直いって作ってるときは、どのくらい売れるかどうかもわからん し、それ(売れる確率)も含めて育てていくのがプロダクト開発

Slide 50

Slide 50 text

読み込みが⾼速なバイナリシリアライズフォーマット バイナリシリアライザ FlatBuffers • Cocos2d-xで標準的に採⽤ • 強い型付け • ネスト可能 • Enumサポート • DSLはデータ構造の記述専⽤ • CとGoのStructの中間くらい • 各項⽬に対して任意のAttribute を付与できる(独⾃拡張可能) • アクセスはオフセットを管理し、 ポインタをずらすだけ • 読み込みはほぼゼロコスト • 書き出しは⽐較的苦⼿ • 新しいデータを間に差し 込むと、後ろを全部ズラ して、インデックスを更 新しないといけない

Slide 51

Slide 51 text

DSLとして利⽤し、独⾃拡張をだいぶしている アナザーエデンとFlatBuffers • マスタデータ • 値の暗号化を独⾃実装していて、これがなんともかんとも • ユーザデータ • FlatBuffersでschemaを書き、msgpackかjsonでIOする • ローカル保存はmsgpack • 通信時はJSON • 独⾃のパーサを実装し、C++とPHPのコードを出⼒している

Slide 52

Slide 52 text

データスキーマの共有 勝⼿に更新 保存される データモデルの設計 機能実装 ローカルで 開発

Slide 53

Slide 53 text

オートセーブの運⽤の実際 メトリックと運⽤作業

Slide 54

Slide 54 text

• 300万DL突破 • リセマラはわりと少なくて2割程度かな、という印象(ちゃんと調べてない) • プレイ時間 • 通しでクリアするのに40-60時間くらい • LevelDBのデータサイズ: 最⼤1MB程度 • DynamoDBのPartition数: 約128くらい (UserData 概算) • これまでサーバ障害でのサービス停⽌は4回(くらい) • DynamoDBのキャパシティ管理のオペミス x3: 15分-30分 • Redisのマスターノードダウン時の⾃動FailOverの失敗 x1: 1時間 • バグはそれなりにいっぱい(時間の都合で省略) アナザーエデンの規模感

Slide 55

Slide 55 text

ローンチ後のスループットの推移 最⼤ 6K弱 Read 3つに分けたUserDataのうちのひと Write バージョンアップ時にRead上昇 4/12 開始 Read/Write Consumed Capacity Units 8/26 現在 実際に消費したスループット

Slide 56

Slide 56 text

スループット遷移(書込/1週間) ⼟⽇はみんな朝から ⼀⽇中やっている ある週のスループット 昼休み 22:00-23:00 がピーク 晩御飯 ⽉曜 ⾦曜

Slide 57

Slide 57 text

• キャパシティ設定のスケジューリング(独⾃実装) • 前⽇までの消費値を⾒て翌⽇の設定を⾃動で決定(賢い) • なにかイベントがあるときは指定値に設定 • ⼀気に下げすぎないように下限値を指定(絶対値 or ⽐率) • オートスケーリング • CloudWatch Alarm + Lambdaで実装 • 条件は最近⼊った本家のものとほぼ同じ(60%を超えたら、1.5倍に) • 微調整が効くので、いまだにこちら側で運⽤している • スロットル(詰まり)監視 • DynamoDBは局所的(Partitionごと)に詰まることがある • ただ、普通はリトライするので、あまり実際の障害と連動しているわけではない DynamoDB運⽤のためにしていること

Slide 58

Slide 58 text

まとめ オートセーブ機構の是⾮

Slide 59

Slide 59 text

• ようするにオートセーブ機構はGoogle Firebase Realtime Databaseや AWS Cognitoなどのマネージドサービスの独⾃実装版と⾔えます • 異なるのは以下の3点でしょう • FlatBuffersを⽤いることでSchemaをきちんと定義し、中規模のソフト ウェア開発においてコストやリスクを低減している • スケールアウトするDBを利⽤し、特性を把握しつつ運⽤することで、 運営のリスクを低減している • チート対策においては、オートセーブ機構の⼀部として各種の対処をビ ルトインすることで、ゲーム特有のセキュリティの問題に対処している まとめ(鈴⽊)

Slide 60

Slide 60 text

• 何も考えずに全ての通信をブロッキング通信にしてませんか? • [good] 適切に制御することでバックグラウンドに寄せられるものは多い • [!] ゲームデザインあってこその通信モデルではある • 反省 : データが壊れた場合の修復がスマートではなかった • [bad] データを2系統で持っておくなどすべきだった • [!] 遠隔で補正するシステムを作っていたため、⼿を動かせば救える まとめ(⻄⽥)

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

本格スマホRPG『アナザーエデン』開発の裏側を包み 隠さずお話します  〜コード資産も無く、チームとしての経験も豊富ではない中エンジニアはどう挑んだのか〜 グラフデータベースNeo4Jでアセットダウンロードの 構成管理と最適化 8⽉31⽇(⽊) 10:00〜11:00 R311+312 PR : エンジニアリング 9⽉1⽇(⾦) 16:30〜16:55 R304 公募 : エンジニアリング