-
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 confloicts 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 successul, 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 as usual.
If one or more of the operations that are part of the transaction has 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 operation that are part of the transaction have succeeded, you typically want to commit the transaction. In order to do rgar:
- 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 = tdix in your query
- To query for documents that does not participate in any transaction, including 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