トランザクション設定

Game Server Services のトランザクション設定の設計方針

GS2 が提供するマイクロサービスには概ねネームスペース設定に TransactionSetting というフィールドが存在します。 TransactionSetting は以下の構造を持ちます。

有効化条件必須デフォルト値の制限説明
enableAutoRunbool
false発行したトランザクションをサーバーサイドで自動的に実行するか
enableAtomicCommitbool{enableAutoRun} == true
falseトランザクションの実行をアトミックにコミットするか
enableAutoRun が true であれば 必須
transactionUseDistributorbool{enableAtomicCommit} == true
falseトランザクションを非同期処理で実行する
enableAtomicCommit が true であれば 必須
commitScriptResultInUseDistributorbool{transactionUseDistributor} == true
falseスクリプトの結果コミット処理を非同期処理で実行するか
transactionUseDistributor が true であれば 必須
acquireActionUseJobQueuebool{enableAtomicCommit} == true
false入手アクションを実行する際に GS2-JobQueue を使用するか
enableAtomicCommit が true であれば 必須
distributorNamespaceIdstring
“grn:gs2:{region}:{ownerId}:distributor:default”~ 1024文字トランザクションの実行に使用する GS2-Distributor ネームスペース
queueNamespaceIdstring
“grn:gs2:{region}:{ownerId}:queue:default”~ 1024文字トランザクションの実行に使用する GS2-JobQueue のネームスペース

フィールドが多く、その用途も複雑ですのでこちらのドキュメントで詳細を説明します。

フィールドの解説

enableAutoRun

発行したトランザクションを自動実行するかを設定します。 ここで false にする理由は今や殆どありません。

かつて GS2 ではトランザクションを自動実行する仕組みがなく、発行されたトランザクションを手動で各マイクロサービスにリクエストして処理するのが主たるトランザクションの実行方法でしたが、今となってはトランザクションが途中で失敗した時のエラーハンドリングを複雑にするだけです。

enableAtomicCommit

GS2 のトランザクションシステムの歴史の中でも比較的後発な機能がこの AtomicCommit 機能です。 AtomicCommit 機能がないころには発行されたトランザクションは非同期処理で実行されるもので、例えば GS2-Showcase で商品を購入するAPIを呼び出した段階では購入処理は完了しておらず、非同期処理で実行されるトランザクションの結果が返ってきて初めてトランザクションが成功したのか、失敗したのかがわかりました。 このような設計の課題は、トランザクションの実行過程でエラーが発生した時にありました。

消費アクションに成功した後、入手アクションの実行に失敗したとして、それがリトライする価値のあるサーバーエラーだった場合は、自動的にリトライされてトランザクションを完了するようにサーバーサイドで努力するのが自動実行の仕組みです。 しかし、入手アクションが所持数量オーバーであったり、消費アクションが複数設定されているうちの1つがリソース不足だったりして、リトライしても意味がない状況になるとその段階でトランザクションは失敗になりますが、成功してしまった消費アクションは実行されたままになることがありました。

このような問題を解決するために用意されたのが AtomicCommit で、GS2-Showcase の商品を購入するAPIの応答を返す前に消費アクションや入手アクションを実行し、すべてが成功したら結果をデータベースに反映するような挙動をするようになります。 この設定をすることで、トランザクションが中途半端な状態で実行されてしまう問題を回避することができます。 そのため、新しく GS2 を利用したゲームを開発する際には AtomicCommit を有効にして開発を進めることを強く推奨しています。

ただし、AtomicCommit は万能ではなく、消費アクションや入手アクションを同時に実行するためアクションないで同一のリソースに対して更新処理を行おうとするとエラーになります。 そのようなトランザクションを実行する際には AtomicCommit を有効にできない点に注意が必要です。

また、AtomicCommit を有効にすると、その効力はトランザクションの実行だけでなく、そのAPIで実行される事前スクリプトにも処理が適用されます。 例えば、商品購入時のスクリプトを設定している場合、そのスクリプトでGS2のAPIを呼び出してデータを書き換える処理や、スクリプトが発行したトランザクションも GS2-Showcase の商品購入APIが成功した時に結果が反映されるようになります。

transactionUseDistributor

enableAtomicCommit を有効にした状態で利用できるオプションです。 トランザクションの実行を非同期処理で行うようにする設定です。この設定をすることで、消費アクションと入手アクションは AtomicCommit として処理されますが、その完了を待たずに GS2-Showcase の商品購入API は応答を返します。 トランザクションの結果を待つ必要がない時に有効化したり、商品購入時のスクリプトで書き換える内容と、トランザクションで書き換える内容が衝突する場合は有効化するような用途を想定しています。

commitScriptResultInUseDistributor

transactionUseDistributor を有効にした時に利用できるオプションです。 事前スクリプトによって書き換えられる処理も非同期処理で実行される消費アクションと入手アクションのトランザクションに含めることができます。 これによって、スクリプトの実行結果だけが反映されることを防ぎつつ、トランザクションの実行を非同期処理にすることが可能となります。

acquireActionUseJobQueue

enableAtomicCommit を有効にした状態で利用できるオプションです。 発行するトランザクションの入手アクションを GS2-JobQueue へのジョブ登録化し、非同期処理で入手アクションを実行するように設定ができます。 この設定によって、消費アクションと入手アクションが競合する場合や、複数設定された入手アクション間で競合する場合に問題を回避することが可能となります。

distributorNamespaceId

トランザクションを自動実行する際に使用する GS2-Distributor のネームスペースを設定します。

queueNamespaceId

トランザクションの入手アクションをジョブキュー経由で実行する際に使用する GS2-JobQueue のネームスペースを設定します。

どのトランザクションシステムを利用するべきか判断するフローチャート

graph TD
  Start --> 1st["enableAutoRun = true, enableAtomicCommit = true"]
  1st --> Async{"トランザクションの実行結果をゲームで使用する必要があるか"}
  Async -- 待つ必要がない --> UseDistributor2["transactionUseDistributor = true"]
  Async -- 待つ必要がある --> Conflict{"競合エラーが発生するか"}
  Conflict -- 競合していない --> End
  Conflict -- 競合した--> 2nd{"競合している内容を調査"}
  2nd -- 競合しているのがスクリプトとトランザクション --> UseDistributor["transactionUseDistributor = true"]
  2nd -- トランザクションの消費アクション同士 --> NotAtomicCommit["enableAtomicCommit = false"]
  2nd -- トランザクションの消費アクションと入手アクション ----> UseJobQueue["acquireActionUseJobQueue = true"]
  2nd -- トランザクションの入手アクション同士 --> UseJobQueue
  UseDistributor2 --> 3rd{"事前スクリプトを設定しているか"}
  3rd -- 設定していない --> Conflict
  3rd -- 設定している --> UseScript{"スクリプトの結果もトランザクションと一緒に反映して欲しいか"}
  UseScript -- しなくてよい --> Conflict
  UseScript -- してほしい --> CommitScriptResultInUseDistributor["commitScriptResultInUseDistributor = true"]
  CommitScriptResultInUseDistributor --> Conflict
  NotAtomicCommit --> End
  UseJobQueue --> End