Transactions by Region Type
Transactions by Region Type
The member running the transaction code is called the transaction initiator.
The member that hosts the data—and the transaction—is called the transactional data host.
Transactions and Partitioned Regions
In partitioned regions, transaction operations are done first on the primary data store then distributed to other members from there, regardless of which member initializes the cache operation. This is the same as is done for normal cache operations on partitioned regions.
- The first, T1, works on data whose primary buckets are stored in M1, so M1 is both initiator and data host for the transaction.
- The second transaction, T2, works on data whose primary buckets are stored in M2, so M1 is the transaction initiator and M2 is the transactional data host.
The transaction is managed on the data host. This includes the transactional view, all operations, and all local cache event handling. In the figure, when T2 is committed, the cache on M2 is updated and the transaction events distributed throughout the system, exactly as if the transaction had originated on M2.
- All partitioned region data managed inside the transaction must use the transactional data host as their primary data store. In the figure above, if transaction T2 tried to put entry W or transaction T1 tried to put entry Z, they would get a TransactionDataNotColocatedException. For information on partitioning your data so it is grouped properly for your transactions, see Understanding Custom Partitioning and Data Colocation. In addition, the data must not be moved during the transaction. Plan any partitioned region rebalancing to avoid rebalancing while transactions are running. See Rebalancing Partitioned Region Data.
- All non-partitioned region data managed inside the transaction must be available on the transactional data host and must be distributed. Operations on regions with local scope are not allowed in transactions with partitioned regions.
The next figure shows a transaction that uses two partitioned regions and one replicated region. As with the single region example, all local event handling is done on the transactional data host.
For a transaction in these data keys to work, the first operation must be on one of the partitioned regions, to establish M2 as the transactional data host. Running the first operation on a key in the replicated region would establish M1 as the transactional data host, and subsequent operations on the partitioned region data would fail with a TransactionDataNotColocated exception.
Transaction on a Partitioned Region with Other Regions:
Transactions and Replicated Regions
For replicated regions, the transaction and its operations are applied to the local member and the resulting transaction state is distributed to other members according to the attributes of each region.
- distributed-ack. Handles transactional conflicts both locally and between members. The distributed-ack scope is designed to protect data consistency. This scope provides the highest level of coordination among transactions in different members. When the commit call returns for a transaction run on all distributed-ack regions, you can be sure that the transaction’s changes have already been sent and processed. In addition, any callbacks in the remote member have been invoked.
Handles transactional conflicts locally, less coordination between members. This
provides the fastest transactions with distributed regions, but doesn't work for all
situations. This scope is appropriate for:
- Applications with only one writer
- Applications with multiple writers that write to different data sets
- local. No distribution, handles transactional conflicts locally. Transactions on regions with local scope have no distribution, but they perform conflict checks in the local member. You can have conflict between two threads when their transactions change the same entry, like object Y in this figure.
Transactions on non-replicated regions (regions that use the old API with DataPolicy EMPTY, NORMAL and PRELOADED) are always transaction initiators, and the transaction data host is always a member with a replicated region. This is similar to the way transactions using the PARTITION_PROXY shortcut are forwarded to members with primary bucket.
Conflicting Transactions in Distributed-Ack Regions
In this series of figures, even after the commit operation is launched, the transaction continues to exist during the data distribution (step 3). The commit does not complete until the changes are made in the remote caches and the application in M1 receives the callbacks to verify that the tasks are complete.
Conflicting Transactions in Distributed-No-Ack Regions
These figures show how using the no-ack scope can produce unexpected results. These two transactions are operating on the same data point, in region B. Since they use no-ack scope, the conflicting changes cross paths and leave the data in an inconsistent state.
Conflicting Transactions with Local Scope
When encountering conflicts with local scope, the first transaction to start the commit
process "wins." The other transaction’s commit fails with a conflict, and its changes are
dropped. In the diagram below, the resulting value for entry "Y" depends on which
transaction commits first.
Transactions and Persistent Regions
gfsh start server --name=server1 --dir=server1_dir \ --J=-Dgemfire.ALLOW_PERSISTENT_TRANSACTIONS=true \
Since GemFire does not provide atomic disk persistence guarantees, the default behavior is to disallow disk-persistent regions from participating in transactions. However, when choosing to enable transactions on persistent regions, you should consider the following:
- GemFire does ensure atomcity for in-memory updates.
- When any failed member is unable to complete the logic triggered by a transaction (including subsequent disk writes), it is removed from the Distributed System and, if restarted, must rebuild its state from surviving nodes that successfully complete the updates.
- The chances of multiple nodes failing to complete the disk writes that result from a transaction commit (due to them crashing for some unrelated reason) are small. The real risk is that the file system buffers holding the persistent updates do not get written to disk in the case of operating system of hardware failure. If only the GemFire process crashes, atomicity still exists. (The overall risk of losing disk updates can also be mitigated by enabling sync'd disk file mode for the Disk Stores, but this incurs a very high performance penalty.)
To mitigate the risk of data not get fully written to disk on all copies of the participating persistent disk stores:
- Make sure you have enough redundant copies of the data. The guarantees of multiple/distributed in-memory copies being (each) atomically updated as part of the Transaction commit sequence can help guard against data corruption.
- When executing transactions on persistent regions, we recommend using the TransactionWriter to log all transactions along with a time stamp. This will allow you to recover in the event that all nodes fail simultaneously while a transaction is being committed. You can use the log to recover the data manually.