TransactionsOverHotRod

Introduction

There are several use cases that require the Hot Rod protocol to support transactions.

  • JPA-like API as an in-VM API to communicate with a remote data grid tier
  • To support an embedded, in-VM cache configured with a RemoteCacheStore, writing through to a remote data grid, to participate in JTA transactions.
  • as a general mean of being able to access an Infinispan grid in a transactional manner, through Hot Rod

 

This is tracked in JIRA by:  ISPN-375

Local or global transactions?

This section attempts to outline the difference between local and global transactions.

Local transactions

 

As an example of a local transaction, let us consider the way JDBC drivers handle transactions.  A JDBC connection pool provides the effect of local transactions by setting the connetion's autocommit property to false.

 

Connection conn = getConnection();
conn.setAutoCommit(false);
// do several database updates
...
//this ends the transaction, one-phase commit
conn.commit();

 

Global transactions (XA)

 

Global transactions might encapsulate transactional resources distribute across multiple JVMs. Global transactions are specified by Java Transaction API. Here is an example of a global transaction that spreads over two RDBMS:

 

Connection connToA = getConnectionToDatabaseA();
Connection connToB = getConnectionToDatabaseB();
UserTransaction ut = getUserTransaction(); //most likely from JNDI
ut.begin();
//update things in A
connToA.prepareStatement("update...");
//update things in B
connToB.prepareStatement("update...");
//after commit both A and B were updated or none. This is achieved through a 2-phase-protocol
ut.commit();

Do we need global or local?

JPA's transaction demarcation allows a user to choose to either use JDBC style transactions (local transactions) or use JTA style transactions. JPA encourages people towards JTA and declarative demarcation (more on JPA's transaction demarcation). This requires Hot Rod to be able to participate to participate in global JTA/XA transactions.

 

Further on, it is  possible (and not difficult) to build local transactions on top of global transaction. This can be achieved the same way Infinispan (in embedded mode) does: local transactions is Infinispan are implemented through batching.

 

//start local transactions
cache.startBatch(); 
//do stuff
...
 //this would commit/rollback the local transaction
cache.endBatch(true);
Infinispan's batching functionality (i.e. local transaction) is implemented by starting a JTA transaction (i.e. global transaction) within BatchingInterceptor.

Infinispan, JTA and XA

Infinispan has support for global transactions. Internally it achieve this by registering an XAResource implementation to the transaction associated with the calling thread.

Design Draft

Hot Rod request header

The server identifies whether an incoming request is associated with a transaction by inspecting the request header, which contains the transaction's id in the following format:

 

     [TX_TYPE][TX_ID]


TX_TYPE is a 1 byte field, containing one of the following well-known supported transaction types:

       0: Non-transactional call, or client does not support transactions.  The subsequent TX_ID field will be omitted.

       1: X/Open XA transaction ID (XID).  This is a well-known, fixed-size format.

TX_TYPE is extensible and more types may be supported in future, such as WS-TX.

 

TX_ID is a fixed-length byte array containing the transaction ID.  The length and format of this transaction ID depends on the value of the TX_TYPE field.  For example, TX_TYPE of 0 would mean a 0-length TX_ID.

 

Hot Rod clients that support transactions

Client implementations that wish to support transactions must implement the following, additional operations.

 

  • PrepareTx
    • this operation is sent by client to each node touched by the transaction. It's client's responsibility to track the list of touched nodes.
    • opcode request: 0x0B
    • opcode response: 0x0C
  • CommitTx
    • must go to the same set of nodes to which PrepareTx was sent
    • opcode request: 0x0D
    • opcode response: 0x0E
  • RollbackTx
    • must go to the same set of nodes to which PrepareTx was sent
    • opcode request: 0x0F
    • opcode response: 0x20
  • StartTx
    • An optimisation. Could be useful if initiating server-side Txs is costly and could be called preemptively/asynchronously by the client, when transaction is started.
    • opcode request: 0x21
    • opcode response: 0x22
  • RecoverTx
    • Returns the list of all prepared or heuristically finished transactions. Transaction id flag from this operation is ignored.
    • opcode request: 0x23
    • opcode response: 0x24

 

The exact operation signature is to be defined and will most likely map X/Open XA specification for corresponding operations.

 

Rules

Clients implementing Hot Rod's transactional capabilities must adhere to the following rules:

 

  • Key stickiness.  If a key is accessed within the scope of a transaction, subsequent operations on the key must be directed to the same endpoint from where they key was originally retrieved.
  • If a node which has seen interaction within the scope of a transaction fails, the transaction must be aborted.
  • Clients should not expect the data grid to provide repeatable read guarantees within the scope of a transaction.  Clients should implement repeatable read internally if this is required, perhaps by making use of versioned operations such as those detailed in Consistent Concurrent Updates With Hot Rod Versioned Operations.

Reference

Hot Rod spec

Java Transaction API (JTA)

X/Open XA specification

Consistent Concurrent Updates With Hot Rod Versioned Operations