LATEST VERSION: 8.1.0 - CHANGELOG
Pivotal GemFire® v8.1

Working with GemFire Cache Transactions

Setting Global Copy on Read

Copy-on-read is not specifically a transaction setting, but if you are using transactions, you probably want copy-on-read behavior. To enable global copy-on-read for all reads, modify cache.xml or use the Java API.

Using cache.xml:
<cache lock-lease="120" lock-timeout="60" search-timeout="300"copy-on-read="true">
API:
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

If you do not have copy-on-read set globally and are using replicated regions, you must explicitly make copies of the cache objects that you are modifying in your transactions. You can use CopyHelper.copy to make these copies.

For example:
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 inside a transaction and you can run a transaction inside a function, as long as your combination of functions and transactions does not result in nested transactions. You cannot call a function from inside a transaction if the function runs its own transaction.

You can also have multiple functions participate within a single transaction.

If you are suspending and resuming a transaction with multiple function calls, all function calls participating in the transaction must execute on the same member.

See Transactional Using a Function Example for an example.

Using Queries and Indexes with Transactions

Queries and indexes reflect the cache and ignore the changes made by ongoing transactions. If you do a query from inside a transaction, the query does not reflect the changes you 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. If you try to use these, you get an IllegalStateException.

Region collection operations include Region.keySet, Region.entrySet, and 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 Region.entrySet operation.

Using Eviction and Expiration Operations

Entry expiration and LRU eviction affect the committed state. They are not part of the transaction, and they cannot be rolled back.

About Eviction

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.

Configure Expiration

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 transactions complete. Once cleanup starts, the manager processes pending expirations. Transaction 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.xml declaration, use a line similar to this:
      <expiration-attributes timeout="300" action="destroy"/>
    • Run your transactions in the member where you configured expiration.

Transactions and Consistent Regions

A transaction that modifies a region having consistency checking 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 GemFire 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 localDestroy or localInvalidate operation against the region. GemFire throws an UnsupportedOperationInTransactionException exception in such cases. An application should use a Destroy or Invalidate operation in place of a localDestroy or localInvalidate when consistency checks are enabled.

Suspending and Resuming Transactions

Pivotal GemFire APIs provide the ability to suspend and resume transactions. 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.

When a transaction is suspended, it loses the transactional view of the cache. None of the previous operations (before calling suspend) are visible to the thread. Subsequently any operations that are performed by the thread do not participate in the suspended 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.

You may optionally use the tryResume method.

If the member with the primary copy of the data crashes, the transactional view that applied to 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, GemFire 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, to modify the default, you could set gemfire.suspendedtxTimeout=60 (time in minutes).

Using Cache Writer and Cache Listener Plug-Ins

All standard Pivotal GemFire application plug-ins work with transactions. In addition, the transaction interface offers specialized plug-ins that support transactional operation.

Note: No direct interaction exists between client transactions and client application plug-ins. When a client runs a transaction, GemFire 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.
The EntryEvent that the callbacks receive has a unique GemFire transaction ID, so the cache listener can associate each event, as it occurs, with a particular transaction. EntryEvents that are not part of a transaction return null instead of 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 netWrite operation 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.

Configuring Transaction Plug-In Event Handlers

GemFire has two types of transaction plug-ins-- Transaction Writers and Transaction Listeners. You can optionally install one transaction writer and one or more transaction listener per cache.

Like JTA global transactions, you can use transaction plug-in event handlers to coordinate GemFire cache transaction activity with an external data store. However, you typically use JTA global transactions when GemFire is running as a peer data store with your external data stores. Transaction writers and listeners are typically used when GemFire 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.

TransactionWriter

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 GemFire 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.

Transaction Listeners

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 GemFire and logged.

If you implement a transaction listener, you can configure one or more listeners as a cache attribute.

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 transaction-listener is URL, which must be a string, as shown in the following cache.xml example.

Note: The cache-transaction-manager allows listeners to be established. This attribute does not install a different transaction manager.
Using cache.xml:
<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());