Game Engine

GS2-SDK for Game Engine のトランザクション処理について

トランザクションとは

GS2 におけるトランザクション処理とは、「消費アクション」「入手アクション」として定義したリソースの交換処理を実行する塊を指します。 トランザクションを発行するAPIは、GS2-Exchange の交換実行関数(Exchange) であったり、GS2-Showcase の商品購入関数(Buy)であったり、GS2-Quest のクエスト開始関数(Start) のようなものがあります。

「消費アクション」はプレイヤーにとってデメリットとなるユーザーデータの操作、逆に「入手アクション」はプレイヤーにとってメリットとなるユーザーデータ操作を指します。

具体的には GS2-Inventory であれば、アイテムの消費が「消費アクション」、アイテムの入手が「入手アクション」となります。 もう少し変わった例を示すとすれば GS2-Inbox のメッセージの開封フラグを立てる操作を「消費アクション」、メッセージに添付されたアイテムを受け取るのが「入手アクション」となります。 GS2 ではユーザーデータを書き換えるトランザクションを発行して実行することを繰り返すことで、ゲームサイクルを実現すると理解してください。

トランザクションとスタンプシート

GS2 では、トランザクション処理を「スタンプシート」という名前で呼んでいました。 そのため、コードやドキュメントの各所にそのような記述がみられます。 近年はこの名称は積極的に用いておらず「トランザクション処理」と呼んでいますが、同じものを指していると理解してください。

類似するワードとして「スタンプタスク」という名称もありますが、こちらはトランザクション内に含まれる「消費アクション」を指していました。

トランザクションの実行

GS2-Exchange の Exchange や、 GS2-Showcase の Buy を呼び出すと、EzTransactionDomain というオブジェクトが返されます。 EzTransactionDomain には Wait 関数が用意されており、この関数を呼び出すことで、トランザクション処理の完了を待つことができます。 ただし、トランザクション処理が完了するまでの時間に保証がないため、タイムアウトを実装することを強く推奨します。

Wait 関数には all という引数が用意されており、こちらに true を指定することで、トランザクション処理内で新しいトランザクションが発行された場合にそのトランザクションの実行完了も待つことが可能です。

Wait 関数を呼び出さない場合は、Gs2Domain::Dispatch を定期的に呼び出す必要があります。

Deep Dive

ここから先はトランザクション処理の詳細な実装について解説します。 多くの開発者は以下に書かれている内容について理解する必要はありません。

消費アクションと入手アクションの実行順番

トランザクションに含まれる「消費アクション」を全て実行すると「入手アクション」が実行できるようになるように制御されています。 そのため、実行順番は「消費アクション」が先で、「入手アクション」が後になります。これはチート耐性を実現するために重要な仕様です。

このような処理の流れを、日本企業が決裁を行う「稟議」にたとえ、 いろいろな役職者(マイクロサービス)に許可をもらい(消費アクションを実行)稟議書にハンコを押してもらい、 全てのマイクロサービスに許可をもらうと本当にやりたかったこと(入手アクション)が実行できるということからスタンプシートと呼んでいました。

トランザクションの実行

GS2が提供しているマイクロサービスは、トランザクションに含まれる「消費アクション」や「入手アクション」を渡すことで、処理を実行する機能が備わっています。 しかし、トランザクションに含まれるアクションをどのマイクロサービスのどのAPIに渡すべきかはアクションの種類によって決定し、実行するのも一苦労です。

そこで、GS2-Distributor マイクロサービスが活躍します。 GS2-Distributor はトランザクションアクションを受け取ると、アクションの内容に応じて適切なマイクロサービスのAPIに転送する処理を持っています。 これによってあなたは何も考えずに、トランザクションデータを GS2-Distributor に渡すだけでトランザクションを実行できます。

エラー処理

次に考えるべきはエラーハンドリングです。GS2 は様々なマイクロサービスを提供しており、障害が発生するときはマイクロサービス単位で発生する可能性があります。 つまり、「消費アクション」の実行に成功したが「入手アクション」の実行に失敗した。というケースがありえます。 この場合は「入手アクション」が成功するまでリトライしなければ、プレイヤーが損をした状態で処理が止まってしまいます。

トランザクション処理の自動実行

かつて GS2 はエラー処理をゲーム開発者に委ねていました。 しかし、生産性の高くないエラー処理を多くのGS2利用者に委ねるのは適切ではないため、 その責任をGS2で担保するようにしようとしたのが「トランザクションの自動実行」機能です。

トランザクションを発行する機能を持つマイクロサービスのネームスペース設定には必ず「TransactionSetting」という項目があります。 そして「TransactionSetting」には「EnableAutoRun」という自動実行を有効化するためのフラグが存在し、現在ではマネージメントコンソールを利用した設定ではデフォルトで有効化されます。

トランザクションの自動実行を有効化すると、GS2-Showcase の Buy のようなトランザクションを発行するAPIを呼び出すと、APIはトランザクションIDのみを応答し、トランザクションのペイロードは応答されません。 代わりに、内部的に GS2-Distributor にトランザクションを実行するようにデータの引き渡しが行われます。 その後、GS2-Distributor は受け取ったトランザクションを実行し、エラーが発生したらリトライを行います。

このようなメカニズムで動作するため、通常は1秒以内に処理は完了しますが、エラーが発生するとリトライが発生する可能性があることから、トランザクション処理の完了までにかかる時間の保証はありません。

自動実行したトランザクションの完了待ち・結果の取得

トランザクションの自動実行を有効にすると、トランザクションが完了したのか、未完了なのかがこのままでは不明確になってしまいます。 そこで GS2-Distributor はトランザクションの自動実行が完了すると、ゲームに通知を送信する仕組みを持っています。

この機能を利用するには、GS2-Distributor に通知を発行する GS2-Gateway のネームスペースを設定し、ゲームは GS2-Gateway のネームスペースに対して通知を受け取るためにユーザーIDを設定する必要があります。 2023年8月以降に作成されたプロジェクトでは、default という名前の GS2-Distributor や GS2-Gateway が自動的に作成されるようになり、SDK で特別な設定をしない場合はこのネームスペースを使用してこれらの処理を行います。

通知の内容には「トランザクションID」が含まれており、「トランザクションID」を指定してトランザクションの実行結果を取得するAPIが GS2-Distributor に用意されています。

複数の入手アクションの実行

説明した通り「消費アクション」は複数設定できるが「入手アクション」が1つしか設定でません。 これには、トランザクションのチート耐性の裏打ちが「全ての消費アクションを実行したら入手アクションを実行できる」という仕組みに起因します。 この仕組みの上で、入手アクションが複数あると色々とややこしくなってしまうということです。

しかし、ゲーム内のリソース増減には入手アクションが複数存在することは様々なケースで考えられます。 そこで、GS2-JobQueue というマイクロサービスが登場します。 GS2-JobQueue は「入手アクション」を遅延実行するための仕組みを提供しています。 そして、GS2-JobQueue には一回のAPI呼び出しで最大10個のジョブを登録できます。

「GS2-JobQueue にジョブを登録する」という入手アクションが存在し、複数の入手アクションを設定した場合はトランザクション発行時にこの入手アクションに内部的に変換しています。 入手アクションの種類が10を超える場合は、「GS2-JobQueue にジョブを登録ジョブを登録する」という形に変換され、最大100個の入手アクションを1つのトランザクションに含めることができます。 2023年8月以降に作成されたプロジェクトでは、default という名前の GS2-JobQueue が作成されるようになっており、このネームスペースを使用して処理するようになっています。 「TransactionSetting」の JobQueueNamespaceId にネームスペースIDを指定することで、任意のキューを利用することができますが、特別な理由がなければ指定する必要はありません。

GS2-JobQueue の実行

GS2-JobQueue の実行についても、トランザクションと同じような過去があります。 GS2-JobQueue に登録されたジョブの実行も明示的に GS2-JobQueue に登録されたジョブの実行APIを呼び出す必要がありました。 ジョブは実行に成功するとジョブキューから削除され、ジョブの実行APIの戻り値にはジョブキューが空になったかが応答値に含まれるため、空になるまで処理を回してもらうことをゲーム開発者の責任として委ねていました。

ジョブの実行に失敗すると、ジョブキューからジョブが削除されないため、キューが空になるまでジョブを繰り返し実行することでリトライを容易に実装できるようにしていたのです。

GS2-JobQueue の自動実行

しかし、これも生産性の高くない処理を多くのGS2利用者に委ねるのは適切ではないため、 その責任をGS2で担保するようにしようとしたのが「ジョブの自動実行」機能です。 GS2-JobQueue のネームスペース設定にはジョブキューの自動実行フラグが用意されており、現在ではデフォルトで有効化されています。

トランザクションと同様に、自動実行が有効化されるとジョブをキューに登録すると自動的に処理が開始され、実行が完了すると GS2-JobQueue のネームスペースに設定した GS2-Gateway に「完了したジョブID」通知します。 GS2-JobQueue は「ジョブID」を指定して、ジョブの実行結果を取得することができます。 2023年8月以降に作成されたプロジェクトでは、default という名前の GS2-Gateway が自動的に作成されるようになり、SDK で特別な設定をしない場合はこのネームスペースを使用してこれらの処理を行います。

EzTransactionDomain::Wait とは何者か

さて、長々と内部処理について説明してきました。 最後に EzTransactionDomain の Wait が何をしているのかを改めて考えてみましょう。

GS2-Showcase::Buy を呼び出すと、トランザクションが発行され、自動実行が有効な場合は GS2-Gateway から発行されたトランザクションIDの完了通知が届くのを待ちます。 自動実行が無効の場合は、返ってきたトランザクションデータを GS2-Distributor に渡して実行を行います。

自動実行の場合はトランザクションの実行結果を GS2-Distributor から取得し、自動実行でない場合は明示的に GS2-Distributor を呼び出した結果を使って、トランザクションの実行結果を取得します。 そこにトランザクション発行処理が含まれていた場合は新しい EzTransactionDomain を作成し、all が true の場合はその EzTransactionDomain::Wait を呼び出します。

実行したトランザクションの「入手アクション」が GS2-JobQueue へのジョブ登録だった場合は、さらに追加の処理があります。 GS2-JobQueue の自動実行が有効だった場合は、GS2-Gateway から登録したジョブIDの実行完了通知が届くのを待ち、自動実行が無効だった場合は明示的に GS2-JobQueue のジョブを実行します。 ジョブの実行結果にトランザクションの発行処理や、GS2-JobQueue へのジョブ登録があった場合は all が true の場合はそれらの処理の完了も待ちます。

Gs2Domain::Dispatch とは何者か

GS2-Gateway から GS2-Distributor のトランザクション実行や、 GS2-JobQueue のジョブ実行完了通知を受け取ると、処理の結果に応じてSDKがもつローカルキャッシュを書き換えます。 これによって、トランザクション処理によって アイテムの所持数量が変動した結果をアイテムの所持数量取得APIを呼び出すことなく最新の値を取得できるようにしています。

EzTransactionDomain::Wait の中で GS2-Gateway からの通知を待つ際に Gs2Domain::Dispatch を呼び出しているため、Wait を使用している場合は明示的に Gs2Domain::Dispatch を呼び出す必要はありません。 しかし、EzTransactionDomain::Wait を呼び出さない、または all の引数に false を指定する場合は Gs2Domain::Dispatch を呼び出さなければ、トランザクションやジョブキューによる実行結果がキャッシュに反映されません。