Skip to content
This repository has been archived by the owner on Nov 9, 2020. It is now read-only.

Core Transactions

Asaf Kariv edited this page Jan 2, 2018 · 18 revisions

Overview

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.

Usage

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 flow

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.

Creating a new transaction

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.

Associating an operation with a transaction

To associate an operation with a transaction:

  • Set the operation's transactionId property to the id of the created transaction: op.setTransactionId(txid);

Sending the operations to their targets

Send the operations to their targets as usual.

Aborting a transaction

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)

Committing a transaction

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

Limitations

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

Implementation notes

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
Clone this wiki locally