Transactions in Depth

This section describes StreamBase transactional functionality in more detail.

Local and Distributed Transactions

Transactions can 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 TIBCO 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


Nodes can 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


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 roll back.

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


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 can 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


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. This is the default transaction isolation level.

  • Read Committed - Snapshot — modifications are only visible outside of the current transaction when it commits. Snapshots are taken from the last committed transaction (that is, 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 — extent iterations and cardinality return inconsistent results in the same transaction if other transactions create or delete objects in an extent.

Locking

Transaction locks maintain data consistency for the duration of a transaction. Transaction locks are only taken on objects. The transaction isolation level affects 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 executing 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. See the StreamBase Java Developer's Guide for details.

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

StreamBase reports a state conflict 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 can 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 StreamBase detects a state conflict, an error is returned to the remote node where the object state is discarded, the transaction rolled back, and then replayed. This causes the object state to resynchronize 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


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


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. See the StreamBase Java Developer's Guide for details on how to use explicit locking to avoid lock promotion deadlocks.

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 time-out 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 rolls back, releasing all locks. The transaction then restarts.

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 must 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, StreamBase discards all logged data and releases any reference locks to deleted objects.

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.