Transaction Settings

Design principles for Transaction Setting (V2) in Game Server Services

The microservices provided by GS2 generally have a field named transactionSettingV2 in their namespace configuration. How the transactions issued by that namespace are executed is decided by this setting.

TransactionSettingV2 has the following structure:

TypeConditionRequiredDefaultLimitationDescription
distributorNamespaceIdstring
“grn:gs2:{region}:{ownerId}:distributor:default”~ 1024 charactersThe GS2-Distributor namespace used to execute transactions
enableParallelExecutionbool
falseWhether to execute the actions in parallel instead of sequentially

These two fields are all there is to set. Everything else about how a transaction runs is fixed to the recommended configuration, so whichever you choose, the following three things always hold:

  • The transaction is executed by the server the moment it is issued. The client does not have to execute a stamp sheet.
  • It succeeds or fails as a whole. When an action fails, the actions that already ran are undone with it and nothing is left applied.
  • By the time the API that issued the transaction returns its response, the transaction has finished executing.

These three also apply to the pre-script of the API that issued the transaction. For example, if you have a script configured for product purchases, the data that script rewrote by calling the GS2 API, and any transaction the script itself issued, are applied together when the product purchase API succeeds.

Field Explanation

distributorNamespaceId

The GS2-Distributor namespace used to execute the transaction. Unless you want to separate transaction execution per purpose, the default namespace prepared with the project is enough.

enableParallelExecution

Decides whether the actions in a transaction run one at a time or all at once. In practice this is the only field you actually choose.

Sequential execution (default, false)

Each action works on top of what the actions before it wrote. The actions run in verify, consume, acquire order, one at a time, and this is what lets you:

  • update the same row from more than one action in a single transaction
  • read what an earlier action wrote, including from List and Query and from inside a nested transaction
  • read the updates the API that issued the transaction had already made earlier in the same request. Writes made by a pre-script are included, and they merge with the actions’ writes in order into a single commit
  • use %{Gs2Xxx:ActionName.path[0].field} to feed the result of an earlier verify or consume action into the parameters of a later verify, consume or acquire action

Only the results of verify and consume actions can be referenced with %{...}; the result of an acquire action cannot. When the same action name appears more than once, the first one to run wins. A placeholder that cannot be resolved is left as-is in the parameter, which can fail validation if the parameter is a numeric field.

The cost is:

  • The response time is the sum of the time taken by each action, not the time of the slowest single action.
  • A transaction may contain at most 20 actions. Issuing more fails immediately.
  • Execution stops at the first action that fails. The phase that was running returns its results up to the failed action, and the phases after it come back empty. The 5xx errors that a later action would have produced under parallel execution are never observed, which narrows the conditions under which a client should decide to retry.

The order within each phase is decided by the action name and then by the target resource. It is not the order the actions were written in, so you cannot choose the execution order by rearranging them. Consume actions always run before acquire actions, whatever their names.

Parallel execution (true)

The actions run in parallel against a single snapshot of the data. The response time is that of the slowest single action, and there is no limit on the number of actions.

The cost is:

  • An action cannot read what the other actions wrote.
  • Two actions that write the same row fail the transaction with database:transaction:same.resource (400).
  • %{...} may reference only the results of an earlier phase (verify, then consume, then acquire). A reference to an action in the same phase is left unresolved, and a phase that contains %{...} waits for the earlier phases to complete, which lengthens the response time by that amount.

Enable this only when you can guarantee that no two actions in the same transaction write the same data.

What does not change

This setting does not change what happens when the transaction is issued. Actions against the same resource are still folded into one, still dropped, or still rejected with an error at that point, under sequential and parallel execution alike. What changes is only what happens once the actions reach execution. For which actions may be specified together in one transaction, see Combining transaction actions.

Flowchart for deciding which one to use

graph TD
  Start["Configure transactionSettingV2"] --> Q3{"Can you guarantee that no two<br/>actions write the same data?"}
  Q3 -- Cannot guarantee --> Sequential["enableParallelExecution = false (default)"]
  Q3 -- Can guarantee --> Q4{"Does a later action reference<br/>the result of an earlier action?"}
  Q4 -- References one in the same phase --> Sequential
  Q4 -- No reference, or references<br/>only an earlier phase --> Q1{"21 or more actions, or<br/>response time to cut down?"}
  Q1 -- Neither --> Sequential
  Q1 -- Either --> Parallel["enableParallelExecution = true"]

When in doubt, leave it at the default sequential execution. Sequential execution places the fewest constraints on how a transaction may be built, and most of the combinations that fail under parallel execution succeed under it.

Note that sequential execution cannot issue 21 or more actions, so if you exceed the limit and also cannot avoid writing the same data, split the transaction.

Relationship with the old TransactionSetting

Before TransactionSettingV2 existed, how a transaction was executed was specified with the transactionSetting field of the namespace configuration. transactionSetting is obsolete. It is kept for namespaces created before TransactionSettingV2 existed, and it applies only while TransactionSettingV2 is unset. Do not use it on a newly created namespace.

transactionSetting exposes each part of transaction execution as a separate field – AutoRun, AtomicCommit, asynchronous execution using GS2-Distributor, batch application of script results, asynchronous processing of acquire actions via GS2-JobQueue, and sequential execution – so combinations other than the recommended one can be built. TransactionSettingV2 is that recommended combination expressed as a single setting.

Setting TransactionSettingV2 fixes the fields of transactionSetting as follows:

transactionSetting fieldValue under TransactionSettingV2
enableAutoRuntrue
enableAtomicCommittrue
enableSequentialExecutionthe negation of enableParallelExecution
transactionUseDistributorfalse
commitScriptResultInUseDistributorfalse
acquireActionUseJobQueuefalse

A namespace that uses TransactionSettingV2 therefore cannot:

  • execute a transaction as a client-run stamp sheet
  • run AutoRun asynchronously via GS2-Distributor
  • fold acquire actions into GS2-JobQueue

Keep using transactionSetting only on a namespace that already depends on one of these behaviours. Most of the conflicts those settings used to work around are resolved by sequential execution instead: a conflict between a pre-script’s writes and the transaction disappears because the updates made in the same request merge into a single commit, and a conflict between a consume action and an acquire action, or between two acquire actions, disappears because the same row may be updated by more than one action.


Combining Transaction Actions

Which actions may be placed in one transaction together. Background for reading the combination tables in the API reference of each service.