Transactions

This section describes StreamBase® transactional functionality in more detail.

Local and Distributed Transactions

Transactions may be either local or distributed.

Local transactions are used on a single node even if they span multiple JVMs on the node.

Distributed transactions are used between StreamBase® nodes. When a transaction spans StreamBase® nodes a global transaction is started on the node that initiates the distributed work. The initiating node acts as the transaction coordinator. There is no independent transaction coordinator in StreamBase®. All StreamBase® nodes act as a transaction coordinator for distributed work that they initiate.

Distributed transaction

Distributed transaction

Nodes may be added to a distributed transaction not only by the node that initiated the distributed transaction, but by any node that participates in the distributed transaction.

Distributed transaction node participants

Distributed transaction node participants

Figure 2, “Distributed transaction node participants” shows how nodes are added to a distributed transaction. In this diagram Node 1 starts a local transaction, LT1, and then initiates a global transaction, GT, to Node 2. Node 2 starts a local transaction, LT2, on behalf of the global transaction GT and then initiates work on Node 3 in the same global transaction GT. Node 3 initiates another local transaction LT3, and then initiates work on Node 4, which starts another local transaction, LT4.

The response back from each of the nodes contains information on the local transaction that was started on the node, and any other nodes that started local transactions. This allows the initiating node, to determine which nodes need to be committed when the global transaction is committed. This is shown in the diagram in the commit processing on Node 1 — a commit is sent to all four nodes, even though Node 1 only initiated a global transaction to Node 2.

There is no programmatic difference between local and distributed transactions. StreamBase® initiates the appropriate transaction type transparently depending on whether local or remote objects are in the transaction. There is a difference in how deadlocks are detected. See Deadlock Detection.

Prepare

Distributed transactions optionally have a prepare phase. A prepare phase provides a mechanism to integrate with external transactional resources. A failure in any of the prepare notifiers causes the transaction to rollback.

A prepare phase is used if there are any updates in a transaction, or transaction notifiers (see Transaction Notifiers) are installed for a transaction. The transaction notifiers can be installed on any node that is participating in a distributed transaction. If no updates are done in a transaction, or no transaction notifiers are installed, the prepare phase is skipped to optimize the performance of distributed transactions by eliminating the additional network I/O required with prepares.

Distributed transaction with prepare

Distributed transaction with prepare

See also Deferred Write Protocol.

Transaction Notifiers

Applications can optionally install transaction notifiers that are called during the prepare phase and when the transaction commits or rolls back. Transaction notifiers can be used to integrate with external systems — both transactional and non-transactional. Transaction notifiers are executed on the node on which they were installed. A distributed transaction may have transaction notifiers installed on multiple nodes by the application. In this case, the notifiers are executed on each node on which they were installed.

Distributed transaction notifiers

Distributed transaction notifiers

Isolation

StreamBase® transactions support the following transaction isolation levels for objects:

  • Serializable — modifications are only visible outside of the current transaction when it commits. Transaction read locks are taken for the duration of the transaction to ensure read consistency. All writes are blocked while a transaction read lock is held.

  • Read Committed Snapshot (Default) — modifications are only visible outside of the current transaction when it commits. Snapshots are taken from the last committed transaction (i.e. It is not a dirty read) to ensure read consistency during a transaction. No transaction read locks are taken during the transaction allowing object modifications to occur while reading an object. Read consistency is provided by the snapshot data across all fields in an object.

Both object isolation levels, serializable and read committed — snapshot, provide consistent, or repeatable reads during a transaction on the same node. This means that the same object field read multiple times in a transaction returns the same value. Read consistency is not guaranteed across nodes. See State Conflicts for details on how data inconsistencies are handled.

Extents always use this transaction isolation level:

  • Read Committed Snapshot — extent iterations and cardinality will return inconsistent results in the same transaction if other transactions create or delete objects in an extent.

Locking

Transaction locks are used to maintain data consistency for the duration of a transaction. Transaction locks are only taken on objects. The transaction isolation level impacts the locking that occurs during a transaction. A serializable transaction isolation takes both transaction read and transaction write locks. A read committed snapshot transaction isolation level only takes transaction write locks, no transaction read locks are taken.

A transaction lock is taken on an object when a field is accessed (serializable transaction isolation only) or modified. The transaction lock is released when the transaction commits or rolls back. Executing a method on an object does not take a transaction lock unless an object field is accessed (serializable transaction isolation only) or modified in the method. This implies that multiple threads can be running the same method on the same object at the same time.

No transaction locks are taken on extents when objects are created or deleted. This allows better parallelism for object creation and deletion, but it does have implications for transactional isolation.

StreamBase® supports multiple reader, single writer transaction locks. For example, multiple concurrent transactions can read the same object fields, but only a single transaction can modify an object field.

When a transaction is using a serializable transaction isolation, transaction read locks can be promoted to a transaction write lock if an object field is read, and then the field is modified in the same transaction. A transaction read lock would be taken on the initial field read and then promoted to a transaction write lock when the field is written. If multiple transactions attempt to promote a transaction read lock on the same object, all transactions, but one, will generate a promotion deadlock. A promotion deadlock causes the transaction to rollback, dropping its transaction locks. The transaction is then replayed causing the transaction to reacquire the transaction locks.

Distributed objects support the same transaction locking as objects on the local node.

State Conflicts

A state conflict is reported by StreamBase® when an object modification (create, write, delete) operation from a remote node detects that the data on the local node has changed underneath it. This is possible in a distributed system because the object may be modified from multiple nodes in the system. State conflicts can occur with both the standard distributed transaction protocol and the deferred write protocol (see Deferred Write Protocol).

If a state conflict is detected an error is returned to the remote node where the object state is discarded, the transaction rolled back, and then replayed. The affect of this is that the object state will be resynchronized on the remote node. The application is never aware that a state conflict occurred. The only impact is on application performance.

Figure 5, “State conflict” shows an example of a state conflict. The sequence diagram shows these steps:

  1. Transaction T1 on node 1 reads an object from node 2 and commits.

  2. Transaction T2 on node 3 reads the same object from node 2 and commits.

  3. Transaction T3 on node 3 modifies the object on node 2 and commits.

  4. Transaction T4 on node 1 attempts to modify the same object on node 2, but the object has changed since the last time it was read onto node 1. A state conflict is detected and node 1 is instructed to rollback transaction T4 and to discard all object state.

  5. Transaction T4 is replayed on node 1 as T5. The object state is first refreshed from node 2, and then the object is successfully modified.

State conflict

State conflict

Deadlock Detection

Since transactions are running simultaneously, it is possible to have deadlocks in applications. StreamBase® automatically detects deadlocks and handles them in the following manner:

  • the transaction that detected the deadlock is chosen as the victim, this transaction is rolled back and replayed.

  • another transaction waiting on a transaction lock that was released is chosen as the winner and allowed to complete.

Deadlock detection

Deadlock detection

Figure 6, “Deadlock detection” shows a deadlock caused by these actions:

  1. Transaction 1 requests, and is granted, a read lock on Object 1.

  2. Transaction 2 requests, and is granted, a read lock on Object 2.

  3. Transaction 1 requests, but is not granted, a write lock on Object 2. The write lock is not granted because of the read lock held on Object 2 by Transaction 2. Objects cannot be modified while other transactions are reading the object.

  4. Transaction 2 requests, but is not granted, a write lock on Object 1. This is a deadlock because both transactions would block indefinitely waiting for the other to complete. Transaction 2 is chosen as the victim and rolled back.

  5. Transaction 1 is granted the requested write lock on Object 2 because Transaction 2's read lock on Object 2 was released when Transaction 2 was rolled back.

Notice that both transactions are attempting to promote a read lock to a write lock. This deadlock can be avoided by taking the write lock initially, instead of promoting from a read lock.

Deadlock detection and resolution is transparent to the application programmer, but deadlocks are expensive in both responsiveness and machine resources so they should be avoided.

Local transactions detect deadlocks immediately in the execution path. There is no timeout value associated with local transactions.

Distributed transactions use a configurable time-out value to detect deadlocks. If a lock cannot be obtained on a remote node within the configured time-out period, the distributed transaction is rolled back, releasing all locks. The transaction is then restarted.

Distributed deadlock detection

Distributed deadlock detection

Because distributed deadlock detection is based on a time-out, applications with distributed deadlocks will perform poorly because the configured time-out has to be large enough to ensure that there are never any false deadlocks reported during normal application processing.

Transaction Logging

To support rollback of a transaction, all object modifications must be logged. The StreamBase® logging mechanism is done in memory by keeping a copy of the before image of any changes. Any object references that are no longer referenced in a transaction are protected from garbage collection so they are still available if the current transaction rolls back.

If the current transaction commits, all logged data is discarded and any reference locks to deleted objects are released.

If the current transaction rolls back, the original state of all objects is restored. Any objects created in the transaction are released to allow them to be garbage collected.