-
Notifications
You must be signed in to change notification settings - Fork 97
Core Transactions
Xenon has built-in support for transactions. A transaction enables a client to perform multiple operations on multiple documents (of the same kind or of different kinds) in a single logical unit, which is isolated from other logical units, and succeeds or fails atomically.
Implementation of core transactions is optimistic. One implication is that a client needs to check the result of a COMMIT request - the request might fail due to conflicts with other transactions.
Note: in addition to the description in this page, one can look at the TestTransactionService class to learn how a client can use transactions. However, please take into account this is test code.
Typical usage flow of transactions:
- A Client creates a new transaction, by sending a POST to the transaction service
- The client associates one or more operations with the transaction, by setting the transactionId property on the operation
- The client sends the operations to their targets
- If one of the operation fails, the client aborts the transaction by constructing and sending an ABORT request to the transaction service
- If all the operations succeed, a client attempts to commit the transaction by constructing and sending a COMMIT request to the transaction service. The client checks whether commit request has succeeded or failed; if it failed, the client can use one of multiple techniques to cope with the failure, like re-trying (starting a new transaction...) or propagating the failure to its client.
To create a new transaction:
- Construct a TransactionServiceState body. If you want to control the id of the generated transaction you should set the body's documentSelfLink field (e.g. String txid = UUID.randomUUID().toString(); state.documentSelfLink = txid;), otherwise the TransactionService will generate a transaction id.
- Send a POST with the body to ServiceUriPaths.CORE_TRANSACTIONS ("/core/transactions"). If successful, a new transaction instance has been created.
To associate an operation with a transaction:
- Set the operation's transactionId property to the id of the created transaction: op.setTransactionId(txid);
Send the operations to their targets as usual.
If one or more of the operations that are part of the transaction have failed, you typically want to abort the transaction. To do that:
- Construct a TransactionService.ResolutionRequest body and set its ResolutionKind field to ABORT: body.resolutionKind = TransactionService.ResolutionKind.ABORT
- Create a PATCH operation with the body and send it to the TransactionService resolution endpoint: UriUtils.buildTransactionResolutionUri(host, txid)
If all operations that are part of the transaction have succeeded, you typically want to commit the transaction. In order to do that:
- Construct a TransactionService.ResolutionRequest body and set its ResolutionKind field to COMMIT: body.resolutionKind = TransactionService.ResolutionKind.COMMIT;
- Create a PATCH operation with the body and send it to the TransactionService resolution endpoint: UriUtils.buildTransactionResolutionUri(host, txid)
- Check whether the commit operation has succeeded. It might have failed with a conflict because other transactions have beaten it to updating some documents
Queries currently do not provide first-class support for transaction awareness. You can use the built-in documentTransactionId field to take transactions into account, for example:
- To query for documents that participate in a given transaction, include documentTransactionId == txid in your query
- To query for documents that do not participate in any transaction, include documentTransactionId == null in your query
Transactions are implemented by TransactionService, which acts as coordinator for transactions. At a high-level:
- When a client creates a new transaction, a new stateful TransactionService instance is created
- A stateful service participating in a transaction maintains a transactional ops log for all transactional operations it receives. It sends those transactional operations to the coordinator.
- At commit time, the coordinator identifies potential conflicts and runs a resolution protocol with peer transactions to deterministically decide which transaction should proceed to a COMMITTED state