Combining Transaction Actions
The API reference of each service has a table titled “what you can put in one transaction”. This page collects the background you need in order to read those tables.
The “kind” column
The “kind” column in each service’s action table says which of the three lists of a transaction the action belongs to: verify, consume, or acquire.
It does not always match the name of the action or what it actually does. An action named Verify may be a consume action, and an action that updates nothing may be an acquire action.
How to read the table on each service’s page
Each service’s page carries a table of that service’s operations.
| Column | Meaning |
|---|---|
| Operation | The unit that gets combined. Operations sharing a row are combined into one |
| Repeated in one transaction | What happens when the operations of that row are specified more than once |
| Across a nested transaction | What happens when an update to the same target also arrives from an inner transaction |
| Boundary that separates targets | Differ here and they are different targets, so any number of them may be listed |
Within one boundary, operations from different rows cannot be placed together: the transaction fails. Rows are separate precisely because there is no defined way to compose their values. This failure happens when the actions run in parallel; under the default sequential execution it does not. See Sequential and parallel execution.
The result columns use these words.
| Word | Meaning |
|---|---|
| combined | Merged into one, with the values added up or concatenated |
| combined if equal | Merged into one if they specify the same value; rejected when the transaction is issued if the values differ |
| first wins | Not an error; the second and later ones are silently dropped |
| fails | The transaction fails (400). Retrying does not help |
| no collision | They may be placed together |
Where those five words are not enough, the cell simply states what that service does, as in “the value that arrives later overwrites the other” or “does not touch the progress”.
Verify actions only read, so they may sit alongside any update. The verify row is the one exception to the rule above.
When verify actions are stacked, only those matching in target, verify type, and threshold are gathered into one. Differ in any of the three and each is judged on its own.
Being gathered behaves in one of two ways, depending on the action.
| Behaviour | Result |
|---|---|
| thresholds are added up | Writing exactly the same check twice gives a single check with twice the threshold |
| the second is dropped | The checks are identical, so the result is unchanged |
Each service’s page says which one applies. An action that explicitly passes multiplyValueSpecifyingQuantity as false is excluded from the grouping and is always judged on its own.
“Rejected when the transaction is issued”, in the repeated-in-one-transaction column, means the transaction is refused while it is being put together. Across a nested transaction that check does not run, so the same disagreement surfaces as a failure at run time instead. Both are 400 and neither is fixed by retrying.
If you cannot find a row that matches your case, the combination is allowed. Read the absence of a row as “no restriction”, not as “undefined”. Actions of different services never collide either.
Kinds of failure, and when to retry
| Wording in the table | When it is refused | Status |
|---|---|---|
| The transaction fails | At run time | 400 |
| Rejected when the transaction is issued | Earlier, while the transaction is being put together | 400 |
Both are problems with how the transaction is put together. Retrying does not help.
A conflict (409) only means the state was contended, so it can be retried. Some services have conflicts that a retry cannot clear; where that is the case, their page says so.
Nested transactions
Some actions issue a transaction of their own. These are the ones that do:
a lottery draw (GS2-Lottery), an exchange (GS2-Exchange), enhancing and unleashing (GS2-Enhance), taking a reward (GS2-Mission / GS2-LoginReward / GS2-Idle / GS2-Ranking2), opening a message (GS2-Inbox), using a serial code (GS2-SerialKey), buying from a showcase (GS2-Showcase), releasing a node (GS2-SkillTree), applying a multiplier (GS2-Experience / GS2-Grade), running a script (GS2-Script), and starting a state machine (GS2-StateMachine).
Each service’s combination table is about actions specified directly on one transaction. Updates issued by an inner transaction are not combined with them. This happens in two shapes:
- outer and inner — an action specified directly on the transaction and an update issued by an inner transaction address the same target
- inner and inner — two or more nested actions each touch the same target from inside
The second is the one that gets missed. Drawing ten times, taking several login bonus steps at once, and buying two items from a showcase all take this shape. Sibling inner transactions are not combined either, so they behave exactly like the outer-and-inner case.
In both shapes, the transaction fails under parallel execution where the target does not allow two writes. A collision like this is treated as a malformed transaction, so retrying does not clear it. Under the default sequential execution it does not happen.
How to find out what a nested action does
What a nested action grants or consumes is decided by the master data it refers to: the prize table of a lottery, the rewards of a mission, the campaign of a serial code, the bonus model of a login bonus.
Most of what they grant belongs to GS2-Inventory (items), GS2-Money2 (currency), or GS2-Experience (experience). Check the pages of those services for whether the outer transaction touches the same target.
Sequential and parallel execution
Every “the transaction fails” above – operations from different rows within one boundary, an inner transaction touching the same target as the outer one – comes from running the actions all at once. Which way they run is decided by the transaction setting of the namespace that issues the transaction.
- Under sequential execution (the default,
enableParallelExecutionisfalse) the actions run one at a time and each of them sees the results so far. The same row may be updated by more than one action, so these failures do not happen. The same goes for updates arriving from an inner transaction: they do not collide as long as the namespace issuing the outer transaction runs sequentially. - Under parallel execution (
enableParallelExecutionistrue) the actions run at once against a single snapshot of the data. Two actions that write the same row fail the transaction withdatabase:transaction:same.resource(400). “The transaction fails” in the tables is this case.
You choose parallel execution when you want to cut the response time down, or when one transaction has to contain 21 or more actions. In that case, read the tables and build the transaction so that no two actions touch the same boundary.
Either way this is a namespace setting and affects every transaction that namespace issues. Weigh it against the guarantees you want to keep.
Anything described as “rejected when the transaction is issued” is not cleared by either one. That check happens before execution, when the transaction is being assembled.
The order of execution, and what each action sees
Verify actions run first, then consume actions, then acquire actions. This order is the same either way.
Under sequential execution, each action sees the writes made by the actions that ran ahead of it in the same transaction – from List and Query, and from inside a nested transaction as well. The updates the API that issued the transaction had already made earlier in the same request, including writes made by a pre-script, are visible too. The order within each phase is decided by the action name and then by the target resource, so you cannot choose the execution order by rearranging the actions in the request.
Under parallel execution, every action is judged against the state as of the start of the transaction. Changes made by the other actions of the same transaction are invisible to all of them, so you cannot build “a create action sees the result of a delete” or “a verify action sees the result of an update”.
Verify and consume actions never run after acquire actions. “Consume in the same transaction what you just acquired” is not possible either way. Split the transaction when you need that order.