Running a Transaction in vFabric GemFire

Before you begin, have your members and regions defined where you want to run transactions. If you will use JTA transactions, know how to use them. See Using JTA Transactions with vFabric GemFire for more information on using JTA.

You can run transactions on the application’s distributed system or on a server distributed system. If you run your transaction inside a function in the server distributed system, the steps given here apply to the server system.
  1. Choose whether to run using the GemFire cache transaction manager or using the JTA transaction manager. The JTA manager, obtained through the JTA UserTransaction interface, manages the GemFire transaction and any configured JDBC connection.
  2. Configure the cache copy-on-read behavior in the members hosting the transactional data, or perform cache updates that avoid in-place changes. This allows the transaction manager to control when your cache updates are visible outside the transaction.
  3. In the members hosting the transactional data, configure your regions for your transactions:
    1. For replicated regions, use distributed-ack scope. The region shortcuts specifying REPLICATE use distributed-ack scope. This is particularly important if you have more than one data producer. With one data producer, you can safely use distributed-no-ack.
    2. For partitioned regions, custom partition and colocate data between regions so all the data for any single transaction is hosted by a single data store. If you run the transaction from another member, it will run by proxy in the member hosting the data. The partitioned region must be defined for the application that runs the transaction, but the data can be hosted in a remote member.
    3. For mixed partition and replicated region transactions, make sure your replicated regions are hosted in every member that hosts the partitioned region data. All data for a single transaction must reside in a single host.
    4. If you use delta propagation, set the region attribute cloning-enabled to true. This lets GemFire do conflict checks at commit. Without this, when you run the transaction, you'll get this exception:
      UnsupportedOperationInTransactionException("Delta without 
      cloning cannot be used in transaction");
    5. If you use JTA and want any regions to not participate in the transactions, set the region attribute ignore-jta to true. It is false by default.
  4. Update your event handler implementations in your data stores to handle your transactions.
    1. Cache event handlers are all used for transactions. Cache listeners are called after the commit, instead of after each cache operation, and they receive the conflated transaction events. Cache writers and loaders are called as usual, as the operations are done. Follow these additional guidelines when writing your callbacks:
      • Make sure cache callbacks are transactionally aware. A transactional operation can launch callbacks that are not transactional.
      • Make sure cache listeners will operate properly with the entry event conflation done for transactional operations. Two entry events for the same key are conflated by removing the existing event and adding the new event to the end of the list.
    2. Transaction event handlers are cache-wide. You can install one transaction writer and any number of transaction listeners.
      • Program with synchronization for thread-safety. Your listener and writer methods may be called simultaneously by different threads for different transactions.
      • Keep your transactional callback implementations lightweight and avoid doing anything that might cause them to block.
  5. Program to retrieve the transaction manager.
    • GemFire cache transaction manager:
      CacheTransactionManager txManager =
      		  custRegion.getCache().getCacheTransactionManager();
    • JTA transaction manager:
      Context ctx = cache.getJNDIContext(); 
      UserTransaction txManager = 
      		  (UserTransaction)ctx.lookup("java:/UserTransaction");
  6. Program to run your transaction.
    try {
    		txManager.begin();
    		// ... do work
    		txManager.commit();
    } catch (CommitConflictException conflict) 
    

    1. Start each transaction with a begin operation.
    2. Run the GemFire function and other operations that you want included in the transaction.
      • Consider whether you will want to suspend and resume the transaction. If some operations should not be part of the transaction, you may want to suspend the transaction while performing non-transactional operations. After the non-transactional operations are complete, you can resume the transaction. See Basic Suspend and Resume Transaction Example for an example.
      • If your transaction runs on a mix of partitioned and replicated regions, perform your first transaction operation on a partitioned region. This sets the host for the entire transaction.
      • If you did not configure copy-on-read to true, be sure your cache updates avoid in-place changes.
      • Take into account the behavior of transactional and non-transactional operations. All transactional operations that are run after the begin and before the commit or rollback are included in the transaction.
    3. End each transaction with a commit or a rollback.
      Note: Do not leave any transaction in an uncommitted and unrolled back state. Transactions do not time out, so will remain in the system for the life of your application.
  7. Review all of your code for compatibility with transactions.

    When you commit a transaction, while the commit is taking place, the changes are visible in the distributed cache. This capability is a transition commit. This provides better performance than locking everything to do the transaction updates, but it means that another process accessing data used in the transaction might get some data in the pre-transaction state and some in the post-transaction state. For example, suppose key 1 and 2 are written to in a transaction so both of their values change from A to B. In another thread, it is possible to read key 1 with value B and key 2 with value A, while the transaction is being committed. This is possible due to the nature of GemFire reads. This choice sacrifices atomic visibility in favor of performance; reads do not block writes, and writes do not block reads.