SlideShare a Scribd company logo
DynamoDBによる
    ソーシャルゲーム実装 How
                      To

   2013-03-16 JAWS DAYS 2013 [DEV-02]
   株式会社マイネット
   伊藤 祐策
概要

 『DynamoDB×ソシャゲ』
     をテーマに
 設計と実装のHow To を
惜しむことなく伝授します!
本資料について
以下のURLからいつでもダウンロード可能です


  http://iy-h.com/01/
  読み上げ原稿も置いてあるので
 聞き逃したところがあっても安心!
DynamoDBの紹介
DynamoDBとは?

 Key Value Store型データベース
 NoSQL
     REST   APIでアクセス
   性能課金
     読込性能、書込性能の予約量に応じて時間課
     金
DynamoDBのここがスゴイ

ウルトラスケーラビリ


ティ
 KVS型データベースの最大の強み
 課金すればするほど強くなる!


   超絶耐障害性
 書込み完了と同時に70km以上離れた3箇
    所のデータセンターに分散保存される!
予習
基本的な使い方
   キーを指定してデータへアクセス
     CRUD操作は一通り揃っている
     操作は全てアトミックに処理される

   キーと更新条件を指定してデータを更新する
     データの内容が更新条件に適合しなければ失敗する
     楽観的ロックの実装に必要
他のKVS型DBとの大きな違い
   「レンジキー」というものがある。
   テーブルのキーは以下の2通りから選択できる
     ハッシュキーのみ

     ハッシュキー+レンジキーの組み合わせ
ハッシュキーとは?
   データへアクセスするためのプライマリキー
   ハッシュキーを上手に分散させることができれ
    ば
    概���課金額通りの性能が得られる。
   逆を言えば、1つのハッシュキーにアクセスが
    集中する設計にしてしまうと、パフォーマンス
    が
    低下してしまう。【重要】
レンジキーとは?
   ハッシュキー+レンジキーでプライマリキーに
    なる
   Queryメソッドで範囲検索が可能になる
       「ハッシュキー」と「レンジキーの範囲」を指定し
        て
        複数レコードをまとめて取得することができる
   レンジキーに対するアクセスを分散しても
    負荷が分散されないことに注意
       ハッシュキーでしっかり分散される設計にしよう
基本的なレコード操作
   CRUD
     PutItem ... 作成/置換
     UpdateItem ... 全部更新/部分更新

     DeleteItem ... 削除

     GetItem ... 取得

   複数レコードの取得
     Query ... 1つの���ッシュキーに対する範囲検索
     Scan ... テーブル内の全レコード取得
格納できる値

   値の型は3種類から選べる
     String型
      UTF-8文字列

     Number型
      整数または小数

     Binary型
      用途としては、暗号化されたデータ等
格納できない値

   NULL値
     代わりに属性ごと削除する

   長さ0の文字列
     NULLと同様、属性ごと削除する

   真偽値
     Number型の1と0で代用する
条件付きアップデート
   更新系のメソッドで利用できる
   条件に適合しなければ操作は失敗する
   以下のような条件が指定可能
     「もしレコードが存在しなければ」
     「もしレコードが存在したならば」

     「もしこの属性の値がこの内容と同一であれば」

     「もしこの属性が存在しなければ」
DynamoDBの使いどころ
MySQLの代替手段となり得るか?

   完全には無理。
   以前DynamoDBだけでソーシャルゲーム作って
    みましたが、結論としては色々と無理がある
    のでMySQL等とのハイブリット型にするのが良
    いです。
DynamoDBが苦手なこと
   縦横無尽な検索
     プライマリキー以外のインデックスを張れない
     せいぜいレンジキーで範囲検索ができるくらい

   小規模な集計処理
     集計機能がそもそもないので自前で実装する必要があ
      る
     大規模データならEMR連携という手段が用意されてい
      る
   大きなデータの保存
     1レコードあたり64kBというデータ長制限がある
     素直にS3使いましょう
MySQLとの比較

        DynamoDB   MySQL

データ保全     ◎         ○
検索        ×         ◎
負荷分散      ◎         △
どう使うべきか?
   アプリケーションに要求される機能のうち、
    DynamoDBが苦手なものは他の手段に任せる
     検索はMySQLやCloudSearchへ
     集計はMySQLへ

     大きなデータの保存はS3へ

   残ったものは全てDynamoDBで実装する
MySQLハイブリッド型にする場合

   MySQLのシステムがある日突然消失しても、
    すぐにサービスが再開できるような設計にして
    おく
     MySQL内のデータは全てDynamoDBのデータを本体とし
      たコピーにする。
     データが全て消失しても、MySQLインスタンスを作り
      なおして全データを再投入すれば完全復旧できるよう
      にする。
     「多尐ロストしても構わないデータ」の分別をしっか
      りつけておく
弊社事例
大激闘!キズナバトル
   Androidアプリ
   2012年12月26日リリース
   GvGカードバトル型ゲーム
   最大20人のチームを組んで、
    1日3回開催されるバトルを
    勝ち抜き、最強チームを目指す。
大激闘!キズナバトル
   使用DynamoDBテーブル数は47。
   MySQLとのハイブリット型構成。
   バトル開催時間になるとアクセス量は一気に15倍になる。
                             22時
    1日のアクセス数グラ         19時
    フ
                 12時
DynamoDBの使われ方
   原則全てのデータはDynamoDBで管理
       ユーザー情報
       ユーザーの所有物(カード、アイテム、etc)
       チーム情報
       バトル結果
   MySQLで行なっている処理
       対戦相手のマッチング
       ランキング集計
       チーム検索
       入団希望者検索
実装の基本方針(1)
   ユーザーが1回行動するたびに1レコード作る
       アイテムを使った、カードバトルで対戦した、etc
       ユーザーの行動が全て「証拠」として残されている。
       お問い合わせからクレームが来た時に、何が起こったのかが
        明確に分かるので調査が容易になる。
   レコードは消さない
       保存費用よりWrite性能費用のほうが高い。
         【速報】3月1日にデータ保存料金が75%も値下げされまし
          た!!
       リリース時から全ての歴史が保存されている。
       KVSなのでデータ量がいくら増えてもパフォーマンスに影響
        がない。
実装の基本方針(2)
   ほぼ全ての処理をキューで非同期に実行
       処理が終わるまでのタイムラグは画面エフェクトを表示して待
        たせる
       いかに「ごまかす」かが腕の見せ所
   キャッシュはTAT改善のために使う
       さすがにMemcachedのほうが応答が速い。
       Read性能はかなり安いので節約する意味があまりない。
       m1.smallインスタンス1台の費用でRead性能を366も買えてしま
        う。
テーブル設計
テーブル設計
   スキーマレスだけどスキーマは定義する
   まずはゲームオブジェクトをクラスとして定義
       ユーザー、所有カード、所有アイテム、etc
   1クラス=1スキーマ=1テーブル
       class App_Record_Card extends DynamoDBRecord
   ハッシュキーはユーザーIDで
   レンジキーはオブジェクトインスタンスIDで
       インスタンスIDは日付+時刻+乱数で生成
テーブル定義の例
ユーザー                カード
ユーザーID        100   ユーザーID    100
名前       †ラインハル     インスタンス   1001
             ト†     ID
レベル            15   レベル        10
所有アイテム
ユーザーID        100
所持金         1500G
薬草           32個
実践
残念なお知らせ
   全てのレコード操作メソッドは失敗する可能性がある。
       TCP/IP ネットワークエラーが発生した場合 (結構頻繁)
       Endpoint側に障害が発生した場合 (数回実績あり)
       課金額以上の負荷を与えた場合 (何度もやらかした)
   RDBMSにおける「トランザクション」は提供されていな
    い
       複数レコードを一貫性を保ったまま同時に更新することがで
        きない。
       アプリケーションレイヤで一貫性を保証する実装をしなけれ
        ばならない。
更新対象が1レコードの場合

所有アイテム
ユーザー      100
ID               Case 1:
所持金      1500G
                 薬草を1個購入する
薬草       10個
更新対象が1レコードの場合

所有アイテム                        クエリ内容
ユーザー      100    UpdateItem   ユーザーID     100
ID
                              所持金      -100G
所持金      1500G
                              薬草        +1個
薬草       10個
             【更新条件】
             所持金が1500Gだった
             ら
更新対象が1レコードの場合

所有アイテム
ユーザー       100
ID               更新完了!
所持金      1400G
薬草        11個
更新対象が2レコード以上の場合

所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1500G   ID
                 レベル         10
                 素材カード
Case 2:          ユーザーID     100
カードを強化す          インスタンス    1002
る                ID
                 レベル          1
更新対象が2レコード以上の場合

所有アイテム              強化対象カード
ユーザー          100   ユーザーID     100
ID
                    インスタンス    1001
所持金         1500G   ID
                    レベル         10
       更新           素材カード

         削除         ユーザーID     100
素材カードを消費して強化対 インスタンス          1002
象カードのレベルを1上げる。 ID
費用として500G徴収する。 レベル               1
更新対象が2レコード以上の場合

所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1000G   ID
                 レベル         10
Step1:           素材カード
所持金 -500G        ユーザーID     100
                 インスタンス    1002
                 ID
                 レベル          1
更新対象が2レコード以上の場合

所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1000G   ID
                 レベル         10
Step2:           素材カード
素材カードを削          ユーザーID     100
除                    削除
                 インスタンス    1002
                 ID
                 レベル          1
更新対象が2レコード以上の場合

所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1000G   ID
                 レベル         10
Step3:           素材カード
レベル              ユーザーID     100
アッ・・・                削除済
                 インスタンス    1002
                 ID
                 レベル          1
突然の死
更新対象が2レコード以上の場合

所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1000G   ID
                 レベル         10
残念!!             素材カード
カードの強化処理         ユーザーID     100
は                    削除済
                 インスタンス    1002
これで終わって          ID
                 レベル          1
しまった!
お問い合わせ内容

【ユーザーID】 100
【ユーザー名】 †ラインハルト†
【日時】2013年3月16日 16:25:58
【お問い合わせ内容】
お金とカードだけ取られた!!!
ふざけんな補償しろ!!!
正しい実装パターン
用意するもの

 Webサーバー
 Batchサーバー

 DynamoDB

   Amazon SQS
システム構成

                     3.Enqueue              4.Dequeue



                                   SQS



1.HTTP Request

                 Web Servers                     Batch Servers



                   2.Put Record             5.Update Records

                                  DynamoD
                                     B
2種類のプロセス
   Webリクエスト処理
     HTTPリクエストをトリガーとして実行される処理
     プロセスはApacheによって実行・管理される

     途中でエラーが発生したら503エラーを返して中断さ
      れる
   キュー処理
     SQSへメッセージを送り、メッセージの取り出しを
      トリガーとして実行される処理。
     プロセスはアプリケーション用のユーザーで実行され
      る
     正常終了するまで何度も繰り返し実行される
Amazon SQSを使う
   SQSは、処理の「完遂保証」のために使う
   失敗した時は何度でも再実行されることを保証さ
    せる
   キュー処理は最終的に正常終了に収束するよう
    実装する
       状態遷移図を書いてしっかり机上デバッグ
           但し書いたら負けかなと思ってる
           図が要らないほどシンプルな実装にしよう
キュー処理実装の鉄則

   再実行耐性を持たせる
     同じ処理が2回実行されても
    結果に影響がでないようにする。
   並列実行耐性を持たせる
     同じ処理が2つ以上のプロセスで並行して
    実行されても結果に影響がでないようにする。
再実行耐性の実装方法
   入力内容から処理内容が全て決定されるようにする。
   レコードの更新をする際に、確かに更新されたことが判
    別できるよう「証拠」を残すようにする。
     更新日時を書き込む、ステータス値を変更する、etc。

     レコード内容をみればどこまで処理が終わったかが
      分かるようにする。
   処理済みであればスキップして次の処理へ進むようにす
    る。
   複雑な分岐をさせず、上から流れ落ちるような処理にす
    る。
並列行耐性の実装方法
   条件付きアップデート機能を用いて楽観的ロックを実装
    する。
   更新する前にレコードを「一貫性あり」で読み込む��
   レコードを更新するときは、「読み込んだ時点から他の
    誰にも更新されていなければ」という条件をつける。
       「条件付きアップデート」を使う
       必要であればレコードにバージョン番号を導入する
   更新に失敗した場合は処理を最初からやりなおす。
     → 再実行耐性が実現されていれば問題ないはず!
処理単位のフローチャート

 GetItem()

             NO        UpdateItem()
  処理済み?
                      with Condition
     YES
             YES
                        更新成功?

                              NO
次の処理へ              throw RetryException
キュー処理全体の流れ

  開始

 処理単位1


 処理単位2   流れ落ちるよう
 処理単位3
         に

  終了
実践・改
カードの強化
所有アイテム           強化対象カード
ユーザー       100   ユーザーID     100
ID
                 インスタンス    1001
所持金      1500G   ID
                 レベル         10
                 素材カード
Case 2':         ユーザーID     100
今度こそ             インスタンス    1002
カードを強化す          ID
る                レベル          1
カードの強化
所有アイテム           カード強化依頼
ユーザー       100   ユーザーID     100
ID
                 依頼ID      5001
所持金      1500G
                 強化対象カード   1001
                 素材対象カード   1002
Step1:           強化費用      500G
依頼レコードを          開始済         NO
作成する
カードの強化
所有アイテム              カード強化依頼
ユーザー          100   ユーザーID     100
ID
                    依頼ID      5001
所持金         1500G
                    強化対象カード   1001
未決済      [ 5001 ]
                    素材対象カード   1002
Step2:
            強化費用              500G
依頼IDを所有アイテム
レコードに登録する   開始済                 NO
※STRING_SET型を使う
カードの強化
強化対象カード              カード強化依頼
ユーザーID         100   ユーザーID     100
インスタンス        1001   依頼ID      5001
ID
                     強化対象カード   1001
レベル             10
                     素材対象カード   1002
未処理       [ 5001 ]
Step3:      強化費用               500G
依頼IDを強化対象カー 開始済                  NO
ド
レコードにも登録する
※STRING_SET型を使う
カードの強化
キューメッセージ           カード強化依頼
処理種別     カード強      ユーザーID     100
             化
                   依頼ID      5001
ユーザー        100
ID                 強化対象カード   1001
依頼ID        5001   素材対象カード   1002
Step4:             強化費用      500G
キューメッセージ           開始済         NO
を発行する
カードの強化(キュー処理)
所有アイテム              カード強化依頼
ユーザー          100   ユーザーID     100
ID
                    依頼ID      5001
所持金         1500G
                    強化対象カード   1001
未決済      [ 5001 ]
                    素材対象カード   1002
Step5:              強化費用      500G
レコードを読み込
                    開始済         NO
む
カードの強化(キュー処理)
所有アイテム               カード強化依頼
ユーザー           100   ユーザーID     100
ID
                     依頼ID      5001
所持金          1500G
                     強化対象カード   1001
未決済      [ 5001 ]
                     素材対象カード   1002
Step6:               強化費用      500G
開始済みにする
※無条件UPDATE           開始済        YES
カードの強化(キュー処理)
所有アイテム             カード強化依頼
ユーザー         100   ユーザーID     100
ID
                   依頼ID      5001
所持金        1000G
                   強化対象カード   1001
未決済       (NULL)
                   素材対象カード   1002
Step7:             強化費用      500G
決済する               開始済        YES
※条件付きUPDATEを使う
カードの強化(キュー処理)
素材カード           カード強化依頼
ユーザーID    100   ユーザーID     100
インスタンス   1002   依頼ID      5001
ID
                強化対象カード   1001
レベル         1
                素材対象カード   1002
Step8:          強化費用      500G
素材カードを削除        開始済        YES
カードの強化(キュー処理)
素材カード           カード強化依頼
ユーザーID    100   ユーザーID     100
    削除
インスタンス   1002   依頼ID      5001
ID
                強化対象カード   1001
レベル         1
                素材対象カード   1002
Step8:          強化費用      500G
素材カードを削除        開始済        YES
カードの強化(キュー処理)
強化対象カード              カード強化依頼
ユーザーID         100   ユーザーID     100
インスタンス        1001   依頼ID      5001
ID
                     強化対象カード   1001
レベル             10
                     素材対象カード   1002
未処理       [ 5001 ]
                     強化費用      500G
Step9:
強化対象カードのパラメー 開始済                YES
タを加算する
※条件付きUPDATEを使う
カードの強化(キュー処理)
強化対象カード              カード強化依頼
ユーザーID         100   ユーザーID     100
インスタンス        1001   依頼ID      5001
ID
                     強化対象カード   1001
レベル             11
                     素材対象カード   1002
未処理       ( NULL )
                     強化費用      500G
Step9:
強化対象カードのパラメー 開始済                YES
タを加算する
※条件付きUPDATEを使う
実装の要点
 完遂保証のない処理(Webリクエスト処理)
  と、
  完遂保証のある処理(キュー処理)で、
  実行すべき処理を上手に振り分ける。
 Webリクエスト処理の途中でエラーが発生
  しても、キュー処理の実行が開始されな
  い限り「何も起こらなかった」ことにな
  る。
     その時は仕方なく503エラーを返す
       各レコードにトランザクションIDが残る可能性につい
終
本資料について(再掲)
以下のURLからいつでもダウンロード可能です


  http://iy-h.com/01/

More Related Content

DynamoDBによるソーシャルゲーム実装 How To

  • 1. DynamoDBによる ソーシャルゲーム実装 How To  2013-03-16 JAWS DAYS 2013 [DEV-02]  株式会社マイネット  伊藤 祐策
  • 2. 概要 『DynamoDB×ソシャゲ』 をテーマに 設計と実装のHow To を 惜しむことなく伝授します!
  • 3. 本資料について 以下のURLからいつでもダウンロード可能です http://iy-h.com/01/ 読み上げ原稿も置いてあるので 聞き逃したところがあっても安心!
  • 5. DynamoDBとは?  Key Value Store型データベース  NoSQL  REST APIでアクセス  性能課金  読込性能、書込性能の予約量に応じて時間課 金
  • 6. DynamoDBのここがスゴイ ウルトラスケーラビリ  ティ  KVS型データベースの最大の強み  課金すればするほど強くなる!  超絶耐障害性  書込み完了と同時に70km以上離れた3箇 所のデータセンターに分散保存される!
  • 8. 基本的な使い方  キーを指定してデータへアクセス  CRUD操作は一通り揃っている  操作は全てアトミックに処理される  キーと更新条件を指定してデータを更新する  データの内容が更新条件に適合しなければ失敗する  楽観的ロックの実装に必要
  • 9. 他のKVS型DBとの大きな違い  「レンジキー」というものがある。  テーブルのキーは以下の2通りから選択できる  ハッシュキーのみ  ハッシュキー+レンジキーの組み合わせ
  • 10. ハッシュキーとは?  データへアクセスするためのプライマリキー  ハッシュキーを上手に分散させることができれ ば 概ね課金額通りの性能が得られる。  逆を言えば、1つのハッシュキーにアクセスが 集中する設計にしてしまうと、パフォーマンス が 低下してしまう。【重要】
  • 11. レンジキーとは?  ハッシュキー+レンジキーでプライマリキーに なる  Queryメソッドで範囲検索が可能になる  「ハッシュキー」と「レンジキーの範囲」を指定し て 複数レコードをまとめて取得することができる  レンジキーに対するアクセスを分散しても 負荷が分散されないことに注意  ハッシュキーでしっかり分散される設計にしよう
  • 12. 基本的なレコード操作  CRUD  PutItem ... 作成/置換  UpdateItem ... 全部更新/部分更新  DeleteItem ... 削除  GetItem ... 取得  複数レコードの取得  Query ... 1つのハッシュキーに対する範囲検索  Scan ... テーブル内の全レコード取得
  • 13. 格納できる値  値の型は3種類から選べる  String型  UTF-8文字列  Number型  整数または小数  Binary型  用途としては、暗号化されたデータ等
  • 14. 格納できない値  NULL値  代わりに属性ごと削除する  長さ0の文字列  NULLと同様、属性ごと削除する  真偽値  Number型の1と0で代用する
  • 15. 条件付きアップデート  更新系のメソッドで利用できる  条件に適合しなければ操作は失敗する  以下のような条件が指定可能  「もしレコードが存在しなければ」  「もしレコードが存在したならば」  「もしこの属性の値がこの内容と同一であれば」  「もしこの属性が存在しなければ」
  • 17. MySQLの代替手段となり得るか?  完全には無理。  以前DynamoDBだけでソーシャルゲーム作って みましたが、結論としては色々と無理がある のでMySQL等とのハイブリット型にするのが良 いです。
  • 18. DynamoDBが苦手なこと  縦横無尽な検索  プライマリキー以外のインデックスを張れない  せいぜいレンジキーで範囲検索ができるくらい  小規模な集計処理  集計機能がそもそもないので自前で実装する必要があ る  大規模データならEMR連携という手段が用意されてい る  大きなデータの保存  1レコードあたり64kBというデータ長制限がある  素直にS3使いましょう
  • 19. MySQLとの比較 DynamoDB MySQL データ保全 ◎ ○ 検索 × ◎ 負荷分散 ◎ △
  • 20. どう使うべきか?  アプリケーションに要求される機能のうち、 DynamoDBが苦手なものは他の手段に任せる  検索はMySQLやCloudSearch���  集計はMySQLへ  大きなデータの保存はS3へ  残ったものは全てDynamoDBで実装する
  • 21. MySQLハイブリッド型にする場合  MySQLのシステムがある日突然消失しても、 すぐにサービスが再開できるような設計にして おく  MySQL内のデータは全てDynamoDBのデータを本体とし たコピーにする。  データが全て消失しても、MySQLインスタンスを作り なおして全データを再投入すれば完全復旧できるよう にする。  「多尐ロストしても構わないデータ」の分別をしっか りつけておく
  • 23. 大激闘!キズナバトル  Androidアプリ  2012年12月26日リリース  GvGカードバトル型ゲーム  最大20人のチームを組んで、 1日3回開催されるバトルを 勝ち抜き、最強チームを目指す。
  • 24. 大激闘!キズナバトル  使用DynamoDBテーブル数は47。  MySQLとのハイブリット型構成。  バトル開催時間になるとアクセス量は一気に15倍になる。 22時 1日のアクセス数グラ 19時 フ 12時
  • 25. DynamoDBの使われ方  原則全てのデータはDynamoDBで管理  ユーザー情報  ユーザーの所有物(カード、アイテム、etc)  チーム情報  バトル結果  MySQLで行なっている処理  対戦相手のマッチング  ランキング集計  チーム検索  入団希望者検索
  • 26. 実装の基本方針(1)  ユーザーが1回行動するたびに1レコード作る  アイテムを使った、カードバトルで対戦した、etc  ユーザーの行動が全て「証拠」として残されている。  お問い合わせからクレームが来た時に、何が起こったのかが 明確に分かるので調査が容易になる。  レコードは消さない  保存費用よりWrite性能費用のほうが高い。  【速報】3月1日にデータ保存料金が75%も値下げされまし た!!  リリース時から全ての歴史が保存されている。  KVSなのでデータ量がいくら増えてもパフォーマンスに影響 がない。
  • 27. 実装の基本方針(2)  ほぼ全ての処理をキューで非同期に実行  処理が終わるまでのタイムラグは画面エフェクトを表示して待 たせる  いかに「ごまかす」かが腕の見せ所  キャッシュはTAT改善のために使う  さすがにMemcachedのほうが応答が速い。  Read性能はかなり安いので節約する意味があまりない。  m1.smallインスタンス1台の費用でRead性能を366も買えてしま う。
  • 29. テーブル設計  スキーマレスだけどスキーマは定義する  まずはゲームオブジェクトをクラスとして定義  ユーザー、所有カード、所有アイテム、etc  1クラス=1スキーマ=1テーブル  class App_Record_Card extends DynamoDBRecord  ハッシュキーはユーザーIDで  レンジキーはオブジェクトインスタンスIDで  インスタンスIDは日付+時刻+乱数で生成
  • 30. テーブル定義の例 ユーザー カード ユーザーID 100 ユーザーID 100 名前 †ラインハル インスタンス 1001 ト† ID レベル 15 レベル 10 所有アイテム ユーザーID 100 所持金 1500G 薬草 32個
  • 32. 残念なお知らせ  全てのレコード操作メソッドは失敗する可能性がある。  TCP/IP ネットワークエラーが発生した場合 (結構頻繁)  Endpoint側に障害が発生した場合 (数回実績あり)  課金額以上の負荷を与えた場合 (何度もやらかした)  RDBMSにおける「トランザクション」は提供されていな い  複数レコードを一貫性を保ったまま同時に更新することがで きない。  アプリケーションレイヤで一貫性を保証する実装をしなけれ ばならない。
  • 33. 更新対象が1レコードの場合 所有アイテム ユーザー 100 ID Case 1: 所持金 1500G 薬草を1個購入する 薬草 10個
  • 34. 更新対象が1レコードの場合 所有アイテム クエリ内容 ユーザー 100 UpdateItem ユーザーID 100 ID 所持金 -100G 所持金 1500G 薬草 +1個 薬草 10個 【更新条件】 所持金が1500Gだった ら
  • 35. 更新対象が1レコードの場合 所有アイテム ユーザー 100 ID 更新完了! 所持金 1400G 薬草 11個
  • 36. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1500G ID レベル 10 素材カード Case 2: ユーザーID 100 カードを強化す インスタンス 1002 る ID レベル 1
  • 37. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1500G ID レベル 10 更新 素材カード 削除 ユーザーID 100 素材カードを消費��て強化対 インスタンス 1002 象カードのレベルを1上げる。 ID 費用として500G徴収する。 レベル 1
  • 38. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1000G ID レベル 10 Step1: 素材カード 所持金 -500G ユーザーID 100 インスタンス 1002 ID レベル 1
  • 39. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1000G ID レベル 10 Step2: 素材カード 素材カードを削 ユーザーID 100 除 削除 インスタンス 1002 ID レベル 1
  • 40. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1000G ID レベル 10 Step3: 素材カード レベル ユーザーID 100 アッ・・・ 削除済 インスタンス 1002 ID レベル 1
  • 42. 更新対象が2レコード以上の場合 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1000G ID レベル 10 残念!! 素材カード カードの強化処理 ユーザーID 100 は 削除済 インスタンス 1002 これで終わって ID レベル 1 しまった!
  • 43. お問い合わせ内容 【ユーザーID】 100 【ユーザー名】 †ラインハルト† 【日時】2013年3月16日 16:25:58 【お問い合わせ内容】 お金とカードだけ取られた!!! ふざけんな補償しろ!!!
  • 46. システム構成 3.Enqueue 4.Dequeue SQS 1.HTTP Request Web Servers Batch Servers 2.Put Record 5.Update Records DynamoD B
  • 47. 2種類のプロセス  Webリクエスト処理  HTTPリクエストをトリガーとして実行される処理  プロセスはApacheによって実行・管理される  途中でエラーが発生したら503エラーを返して中断さ れる  キュー処理  SQSへメッセージを送り、メッセージの取り出しを トリガーとして実行される処理。  プロセスはアプリケーション用のユーザーで実行され る  正常終了するまで何度も繰り返し実行される
  • 48. Amazon SQSを使う  SQSは、処理の「完遂保証」のために使う  失敗した時は何度でも再実行されることを保証さ せる  キュー処理は最終的に正常終了に収束するよう 実装する  状態遷移図を書いてしっかり机上デバッグ  但し書いたら負けかなと思ってる  図が要らないほどシンプルな実装にしよう
  • 49. キュー処理実装の鉄則  再実行耐性を持たせる  同じ処理が2回実行されても 結果に影響がでないようにする。  並列実行耐性を持たせる  同じ処理が2つ以上のプロセスで並行して 実行されても結果に影響がでないようにする。
  • 50. 再実行耐性の実装方法  入力内容から処理内容が全て決定されるようにする。  レコードの更新をする際に、確かに更新されたことが判 別できるよう「証拠」を残すようにする。  更新日時を書き込む、ステータス値を変更する、etc。  レコード内容をみればどこまで処理が終わったかが 分かるようにする。  処理済みであればスキップして次の処理へ進むようにす る。  複雑な分岐をさせず、上から流れ落ちるような処理にす る。
  • 51. 並列行耐性の実装方法  条件付きアップデート機能を用いて楽観的ロックを実装 する。  更新する前にレコードを「一貫性あり」で読み込む。  レコードを更新するときは、「読み込んだ時点から他の 誰にも更新されていなければ」という条件をつける。  「条件付きアップデート」を使う  必要であればレコードにバージョン番号を導入する  更新に失敗した場合は処理を最初からやりなおす。 → 再実行耐性が実現されていれば問題ないはず!
  • 52. 処理単位のフローチャート GetItem() NO UpdateItem() 処理済み? with Condition YES YES 更新成功? NO 次の処理へ throw RetryException
  • 53. キュー処理全体の流れ 開始 処理単位1 処理単位2 流れ落ちるよう 処理単位3 に 終了
  • 55. カードの強化 所有アイテム 強化対象カード ユーザー 100 ユーザーID 100 ID インスタンス 1001 所持金 1500G ID レベル 10 素材カード Case 2': ユーザーID 100 今度こそ インスタンス 1002 カードを強化す ID る レベル 1
  • 56. カードの強化 所有アイテム カード強化依頼 ユーザー 100 ユーザーID 100 ID 依頼ID 5001 所持金 1500G 強化対象カード 1001 素材対象カード 1002 Step1: 強化費用 500G 依頼レコードを 開始済 NO 作成する
  • 57. カードの強化 所有アイテム カード強化依頼 ユーザー 100 ユーザーID 100 ID 依頼ID 5001 所持金 1500G 強化対象カード 1001 未決済 [ 5001 ] 素材対象カード 1002 Step2: 強化費用 500G 依頼IDを所有アイテム レコードに登録する 開始済 NO ※STRING_SET型を使う
  • 58. カードの強化 強化対象カード カード強化依頼 ユーザーID 100 ユーザーID 100 インスタンス 1001 依頼ID 5001 ID 強化対象カード 1001 レベル 10 素材対象カード 1002 未処理 [ 5001 ] Step3: 強化費用 500G 依頼IDを強化対象カー 開始済 NO ド レコードにも登録する ※STRING_SET型を使う
  • 59. カードの強化 キューメッセージ カード強化依頼 処理種別 カード強 ユーザーID 100 化 依頼ID 5001 ユーザー 100 ID 強化対象カード 1001 依頼ID 5001 素材対象カード 1002 Step4: 強化費用 500G キューメッセージ 開始済 NO を発行する
  • 60. カードの強化(キュー処理) 所有アイテム カード強化依頼 ユーザー 100 ユーザーID 100 ID 依頼ID 5001 所持金 1500G 強化対象カード 1001 未決済 [ 5001 ] 素材対象カード 1002 Step5: 強化費用 500G レコードを読み込 開始済 NO む
  • 61. カードの強化(キュー処理) 所有アイテム カード強化依頼 ユーザー 100 ユーザーID 100 ID 依頼ID 5001 所持金 1500G 強化対象カード 1001 未決済 [ 5001 ] 素材対象カード 1002 Step6: 強化費用 500G 開始済みにする ※無条件UPDATE 開始済 YES
  • 62. カードの強化(キュー処理) 所有アイテム カード強化依頼 ユーザー 100 ユーザーID 100 ID 依頼ID 5001 所持金 1000G 強化対象カード 1001 未決済 (NULL) 素材対象カード 1002 Step7: 強化費用 500G 決済する 開始済 YES ※条件付きUPDATEを使う
  • 63. カードの強化(キュー処理) 素材カード カード強化依頼 ユーザーID 100 ユーザーID 100 インスタンス 1002 依頼ID 5001 ID 強化対象カード 1001 レベル 1 素材対象カード 1002 Step8: 強化費用 500G 素材カードを削除 開始済 YES
  • 64. カードの強化(キュー処理) 素材カード カード強化依頼 ユーザーID 100 ユーザーID 100 削除 インスタンス 1002 依頼ID 5001 ID 強化対象カード 1001 レベル 1 素材対象カード 1002 Step8: 強化費用 500G 素材カードを削除 開始済 YES
  • 65. カードの強化(キュー処理) 強化対象カード カード強化依頼 ユーザーID 100 ユーザーID 100 インスタンス 1001 依頼ID 5001 ID 強化対象カード 1001 レベル 10 素材対象カード 1002 未処理 [ 5001 ] 強化費用 500G Step9: 強化対象カードのパラメー 開始済 YES タを加算する ※条件付きUPDATEを使う
  • 66. カードの強化(キュー処理) 強化対象カード カード強化依頼 ユーザーID 100 ユーザーID 100 インスタンス 1001 依頼ID 5001 ID 強化対象カード 1001 レベル 11 素材対象カード 1002 未処理 ( NULL ) 強化費用 500G Step9: 強化対象カードのパラメー 開始済 YES タを加算する ※条件付きUPDATEを使う
  • 67. 実装の要点  完遂保証のない処理(Webリクエスト処理) と、 完遂保証のある処理(キュー処理)で、 実行すべき処理を上手に振り分ける。  Webリクエスト処理の途中でエラーが発生 しても、キュー処理の実行が開始されな い限り「何も起こらなかった」ことにな る。  その時は仕方なく503エラーを返す  各レコードにトランザクションIDが残る可能性につい
  • 68.