Working with Geode Cache Transactions
This section contains guidelines and additional information on working with Geode and its cache transactions.
As many entry operations return a reference to the cache entry, copy-on-read avoids problems within a transaction setting. To enable global copy-on-read for all reads, modify the
cache.xml file or use the corresponding Java API call.
<cache lock-lease="120" lock-timeout="60" search-timeout="300" copy-on-read="true">
Cache c = CacheFactory.getInstance(system); c.setCopyOnRead(true);
The copy-on-read attribute and the operations affected by the attribute setting are discussed in detail in Managing Data Entries.
Making a Safe Change Within a Transaction Using CopyHelper.copy
copy-on-read is not globally set, and the cache uses replicated regions, explicitly make copies of the cache objects that are to be modified within a transaction. The
CopyHelper.copy method makes copies:
CacheTransactionManager cTxMgr = cache.getCacheTransactionManager(); cTxMgr.begin(); Object o = (StringBuffer) r.get("stringBuf"); StringBuffer s = (StringBuffer) CopyHelper.copy(o); s.append("Changes unseen before commit. Read Committed."); r.put("stringBuf", s); cTxMgr.commit();
Transactions and Functions
You can run a function from inside a transaction and you can nest a transaction within a function, as long as your combination of functions and transactions does not result in nested transactions. See Function Execution for more about functions.
A single transaction may contain multiple functions.
If you are suspending and resuming a transaction with multiple function calls, all functions in the transaction must execute on the same member.
See Transaction Embedded within a Function Example for an example.
Using Queries and Indexes with Transactions
Queries and indexes reflect the cache contents and ignore the changes made by ongoing transactions. If you do a query from inside a transaction, the query does not reflect the changes made inside that transaction.
Collections and Region.Entry Instances in Transactions
Collections and region entries used in a transaction must be created inside the transaction. After the transaction has completed, the application can no longer use any region entry or collection or associated iterator created within the transaction. An attempted use outside of the transaction will throw an
Region collection operations include
Region.values. You can create instances of
Region.Entry through the
Region.getEntry operation or by looking at the contents of the result returned by a
Using Eviction and Expiration Operations
Entry expiration and LRU eviction affect the committed state. They are not part of a transaction, and therefore they cannot be rolled back.
LRU eviction operations do not cause write conflicts with existing transactions, despite destroying or invalidating entries. LRU eviction is deferred on entries modified by the transaction until the commit completes. Because anything touched by the transaction has had its LRU clock reset, eviction of those entries is not likely to happen immediately after the commit.
When a transaction commits its changes in a region with distributed scope, the operation can invoke eviction controllers in the remote caches, as well as in the local cache.
Local expiration actions do not cause write conflicts, but distributed expiration can cause conflicts and prevent transactions from committing in the members receiving the distributed operation.
When you are using transactions on local, preloaded or empty regions, make expiration local if possible. For every instance of that region, configure an expiration action of local invalidate or local destroy. In a cache.xml declaration, use a line similar to this:
<expiration-attributes timeout="60" action="local-invalidate" />
In regions modified by a transaction, local expiration is suspended. Expiration operations are batched and deferred per region until the transaction completes. Once cleanup starts, the manager processes pending expirations. Transactions that need to change the region wait until the expirations are complete.
With partitioned and replicated regions, you cannot use local expiration. When you are using distributed expiration, the expiration is not suspended during a transaction, and expiration operations distributed from another member can cause write conflicts. In replicated regions, you can avoid conflicts by setting up your distributed system this way:
- Choose an instance of the region to drive region-wide expiration. Use a replicated region, if there is one.
Configure distributed expiration only in that region instance. The expiration action must be either invalidate or destroy. In a
cache.xmlfile declaration, use a line similar to this:
<expiration-attributes timeout="300" action="destroy" />
Run the transactions from the member in which expiration is configured.
Transactions and Consistent Regions
A transaction that modifies a region in which consistency checking is enabled generates all necessary version information for region updates when the transaction commits.
If a transaction modifies a normal, preloaded or empty region, the transaction is first delegated to a Geode member that holds a replicate for the region. This behavior is similar to the transactional behavior for partitioned regions, where the partitioned region transaction is forwarded to a member that hosts the primary for the partitioned region update.
The limitation for transactions with a normal, preloaded or empty region is that, when consistency checking is enabled, a transaction cannot perform a
localInvalidate operation against the region. Geode throws an
UnsupportedOperationInTransactionException exception in such cases. An application should use a
Invalidate operation in place of a
localInvalidate when consistency checks are enabled.
Suspending and Resuming Transactions
CacheTransactionManager API provides the ability to suspend and resume transactions with the
resume methods. The ability to suspend and resume is useful when a thread must perform some operations that should not be part of the transaction before the transaction can complete. A complex use case of suspend and resume implements a transaction that spans clients in which only one client at a time will not be suspended.
Once a transaction is suspended, it loses the transactional view of the cache. None of the operations done within the transaction are visible to the thread. Any operations that are performed by the thread while the transaction is suspended are not part of the transaction.
When a transaction is resumed, the resuming thread assumes the transactional view. A transaction that is suspended on a member must be resumed on the same member.
Before resuming a transaction, you may want to check if the transaction exists on the member and whether it is suspended. The
tryResume method implements this check and resume as an atomic step.
If the member with the primary copy of the data crashes, the transactional view associated with that data is lost. The secondary member for the data will not be able to resume any transactions suspended on the crashed member. You will need to take remedial steps to retry the transaction on a new primary copy of the data.
If a suspended transaction is not touched for a period of time, Geode cleans it up automatically. By default, the timeout for a suspended transaction is 30 minutes and can be configured using the system property
gemfire.suspendedtxTimeout. For example,
gemfire.suspendedtxTimeout=60 specifies a timeout of 60 minutes.
See Basic Suspend and Resume Transaction Example for a sample code fragment that suspends and resumes a transaction.
Using Cache Writer and Cache Listener Plug-Ins
All standard Geode application plug-ins work with transactions. In addition, the transaction interface offers specialized plug-ins that support transactional operation.
No direct interaction exists between client transactions and client application plug-ins. When a client runs a transaction, Geode calls the plug-ins that are installed on the transaction’s server delegate and its server host. Client application plug-ins are not called for operations inside the transaction or for the transaction as a whole. When the transaction is committed, the changes to the server cache are sent to the client cache according to client interest registration. These events can result in calls to the client’s
CacheListeners, as with any other events received from the server.
EntryEvent that a callback receives has a unique Geode transaction ID, so the cache listener can associate each event, as it occurs, with a particular transaction. The transaction ID of an
EntryEvent that is not part of a transaction is null to distinguish it from a transaction ID.
CacheLoader. When a cache loader is called by a transaction operation, values loaded by the cache loader may cause a write conflict when the transaction commits.
CacheWriter. During a transaction, if a cache writer exists, its methods are invoked as usual for all operations, as the operations are called in the transactions. The
netWriteoperation is not used. The only cache writer used is the one in the member where the transactional data resides.
CacheListener. The cache listener callbacks - local and remote - are triggered after the transaction commits. The system sends the conflated transaction events, in the order they were stored.
For more information on writing cache event handlers, see Implementing Cache Event Handlers.
Geode has two types of transaction plug-ins: Transaction Writers and Transaction Listeners. You can optionally install one transaction writer and one or more transaction listeners per cache.
Like JTA global transactions, you can use transaction plug-in event handlers to coordinate Geode cache transaction activity with an external data store. However, you typically use JTA global transactions when Geode is running as a peer data store with your external data stores. Transaction writers and listeners are typically used when Geode is acting as a front end cache to your backend database.
Note: You can also use transaction plug-in event handlers when running JTA global transactions.
When you commit a transaction, if a transaction writer is installed in the cache where the data updates were performed, it is called. The writer can do whatever work you need, including aborting the transaction.
The transaction writer is the last place that an application can rollback a transaction. If the transaction writer throws any exception, the transaction is rolled back. For example, you might use a transaction writer to update a backend data source before the Geode cache transaction completes the commit. If the backend data source update fails, the transaction writer implementation can throw a TransactionWriterException to veto the transaction.
A typical usage scenario would be to use the transaction writer to prepare the commit on the external database. Then in a transaction listener, you can apply the commit on the database.
When the transaction ends, its thread calls the transaction listener to perform the appropriate follow-up for successful commits, failed commits, or voluntary rollbacks. The transaction that caused the listener to be called no longer exists by the time the listener code executes.
Transaction listeners have access to the transactional view and thus are not affected by non-transactional update operations.
TransactionListener methods cannot make transactional changes or cause a rollback. They can, however, start a new transaction. Multiple transactions on the same cache can cause concurrent invocation of
TransactionListener methods, so implement methods that do the appropriate synchronization of the multiple threads for thread-safe operation.
A transaction listener can preserve the result of a transaction, perhaps to compare with other transactions, or for reference in case of a failed commit. When a commit fails and the transaction ends, the application cannot just retry the transaction, but must build up the data again. For most applications, the most efficient action is just to start a new transaction and go back through the application logic again.
The rollback and failed commit operations are local to the member where the transactional operations are run. When a successful commit writes to a distributed or partitioned region, however, the transaction results are distributed to other members the same as other updates. The transaction listener on the receiving members reflect the changes the transaction makes in that member, not the originating member. Any exceptions thrown by the transaction listener are caught by Geode and logged.
To configure a transaction listener, add a
cache-transaction-manager configuration to the cache definition and define one or more instances of
transaction-listener there. The only parameter to this
URL, which must be a string, as shown in the following cache.xml example.
cache-transaction-manager allows listeners to be established. This attribute does not install a different transaction manager.
<cache search-timeout="60"> <cache-transaction-manager> <transaction-listener> <class-name>com.company.data.MyTransactionListener</class-name> <parameter name="URL"> <string>jdbc:cloudscape:rmi:MyData</string> </parameter> </transaction-listener> <transaction-listener> . . . </transaction-listener> </cache-transaction-manager> . . . </cache>
Using the Java API:
CacheTransactionManager manager = cache.getCacheTransactionManager(); manager.addListener(new LoggingTransactionListener());