RSS .92| RSS 2.0| ATOM 0.3
  • Home
  • About
  •  

    A Simple Illustration of Terracotta Locking Strategies

    June 28th, 2009

    If you are familiar with Terracotta then you know that its hallmark feature is  data sharing between Java programs running on different virtual machines.  Making use of this very powerful capability requires that the the programs that  share the data use Java synchronization to prevent conflicts between operations that access the shared data from corrupting the data or returning incomplete results.  Terracotta data sharing also requires that Terracotta be configured to detect and honour the Java synchronization instructions. In this post we refer to the combination of Java synchronization and Terracotta configuration as a “locking strategy”.

    The choice of locking strategy can have a profound impact on a Terracotta application’s  performance.  Even in a very simple application, there may be several points of data contention at which locking strategies are required, and several possible locking strategies for each contention point.   This post provides a very brief illustration of how locking strategies are implemented with Terracotta, and of the impact that a small change in locking strategy can have on  application performance.

    This post barely scratches the surface of Terracotta’s remarkable data sharing capabilities, and it completely bypasses many other powerful and important features of the product.  Readers are advised to regard  this post as a very simple illustration of some basic principles of working with Terracotta, and nothing more.

    Readers should also understand that this post demonstrates use of Terracotta’s low level concurrency features.  Many Terracotta users will find themselves using Terracotta’s integration modules (TIMS), which provide out-of-the-box integration with productivity frameworks such as Hibernate and Spring.  The intent of the developers of these integration modules seems to be to shield users of the module as much as possible from the kinds of low level concurrency concerns that feature prominently in this post.

    As a starting point, we create two Java programs from three classes:
    A – a data POJO.
    TCLockingExampleMain – a program that creates and instance of A and updates A’s data field.
    TCLockingExampleReporter – a program that indicates whether or not our data is shareable.

    Here is the source code for all three classes prior to implementing any locking strategy:

    package tcLockingExample;

    public class A {
    int primInt;

    public void primIntInc() {
    this.primInt++;
    }
    }

    package tcTLockingExample;

    public class TCLockingExampleMain {
    static A aInstance = new A();

    /**
    * @param args
    */
    public static void main(String[] args) {
    for (int x = 0; x < 10; x++ ) {
    aInstance.primIntInc();
    System.out.println(“aInstance.primInt: ” + aInstance.primInt);
    }
    }
    }

    package tcTLockingExample;

    import java.util.Date;

    public class TCLockingExampleReporter {

    /**
    * @param args
    */
    public static void main(String[] args) {
    System.out.println(new Date() + ” TCLockingChoicesMain.aInstance.primInt:” + TCLockingExampleMain.aInstance.primInt);
    }
    }

    We will be tinkering with the first two classes as the example progresses.

    The first step is to establish that the programs work as expected when run without Terracotta.  As the following output illustrates, both programs run:

    aInstance.primInt: 1
    aInstance.primInt: 2
    aInstance.primInt: 3
    aInstance.primInt: 4
    aInstance.primInt: 5
    aInstance.primInt: 6
    aInstance.primInt: 7
    aInstance.primInt: 8
    aInstance.primInt: 9
    aInstance.primInt: 1

    but, of course, there is no data sharing between them:

    Sun Jun 07 17:48:16 BST 2009 TCLockingChoicesMain.aInstance.primInt:0

    Next we run each prgram as a Terracotta application, but without configuring Terracotta to share data between them.  The results are the same:

    2009-06-07 18:06:48,029 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:06:48,334 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:06:48,494 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:06:49,907 INFO – Connection successfully established to server at 192.168.1.20:9510
    aInstance.primInt: 1
    aInstance.primInt: 2
    aInstance.primInt: 3
    aInstance.primInt: 4
    aInstance.primInt: 5
    aInstance.primInt: 6
    aInstance.primInt: 7
    aInstance.primInt: 8
    aInstance.primInt: 9
    aInstance.primInt: 10

    and from TCLockingExampleReporter:

    2009-06-07 18:09:03,467 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:09:03,772 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:09:03,905 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:09:05,199 INFO – Connection successfully established to server at 192.168.1.20:9510
    Sun Jun 07 18:09:05 BST 2009 TCLockingChoicesMain.aInstance.primInt:0

    Next we configure Terracotta to be aware of all three classes, and set a trap for ourselves by establishing the instance of A  in TCLockingExampleMain as a root class (a Terracotta root is an object that is identified as shared by the application’s Terracotta configuration):

    <dso>
    <instrumented-classes>
    <include>
    <class-expression>tcTLockingExample.A</class-expression>
    </include>
    <include>
    <class-expression>tcTLockingExample.TCLockingExampleMain</class-expression>
    </include>
    <include>
    <class-expression>tcTLockingExample.TCLockingExampleReporter</class-expression>
    </include>
    </instrumented-classes>
    <roots>
    <root>
    <field-name>tcTLockingExample.TCLockingExampleMain.aInstance</field-name>
    </root>
    </roots>
    </dso>

    This is a trap because, having established  TCLockingExampleMain.aInstance as a shared object, we are obliged to implement a locking strategy wherever we write to it in our code.  Because we have not done this,   TCLockingExampleMain fails:

    2009-06-07 18:13:48,927 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:13:49,240 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:13:49,371 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:13:50,845 INFO – Connection successfully established to server at 192.168.1.20:9510
    com.tc.object.tx.UnlockedSharedObjectException:
    *********************************************************************
    Attempt to access a shared object outside the scope of a shared lock.
    All access to shared objects must be within the scope of one or more
    shared locks defined in your Terracotta configuration.

    Caused by Thread: main in VM(0)
    Shared Object Type: tcTLockingExample.A

    The cause may be one or more of the following:
    * Terracotta locking was not configured for the shared code.
    * The code itself does not have synchronization that Terracotta
    can use as a boundary.
    * The class doing the locking must be included for instrumentation.
    * The object was first locked, then shared.

    For more information on how to solve this issue, see:

    http://www.terracotta.org/usoe

    *********************************************************************

    at com.tc.object.tx.ClientTransactionManagerImpl.getTransaction(ClientTransactionManagerImpl.java:360)
    at com.tc.object.tx.ClientTransactionManagerImpl.fieldChanged(ClientTransactionManagerImpl.java:653)
    at com.tc.object.TCObjectImpl.objectFieldChanged(TCObjectImpl.java:317)
    at com.tc.object.TCObjectImpl.intFieldChanged(TCObjectImpl.java:357)
    at tcTLockingExample.A.__tc_setprimInt(A.java)
    at tcTLockingExample.A.primIntInc(A.java:7)
    at tcTLockingExample.TCLockingExampleMain.main(TCLockingExampleMain.java:11)
    Exception in thread “main” com.tc.object.tx.UnlockedSharedObjectException:
    *********************************************************************
    Attempt to access a shared object outside the scope of a shared lock.
    All access to shared objects must be within the scope of one or more
    shared locks defined in your Terracotta configuration.

    Caused by Thread: main in VM(0)
    Shared Object Type: tcTLockingExample.A

    The cause may be one or more of the following: . . .

    To fix this we will implement our first locking strategy, by synchronizing the method in A that writes to the data member of the shared instance:
    package tcLockingExample;

    public class A {
    int primInt;

    synchronized public void primIntInc() {
    this.primInt++;
    }
    }

    and configuring Terracotta to apply its locking to that method:

    <dso>
    <instrumented-classes>
    <include>
    <class-expression>tcLockingExample.A</class-expression>
    </include>
    <include>
    <class-expression>tcLockingExample.TCLockingExampleMain</class-expression>
    </include>
    <include>
    <class-expression>tcLockingExample.TCLockingExampleReporter</class-expression>
    </include>
    </instrumented-classes>
    <roots>
    <root>
    <field-name>tcLockingExample.TCLockingExampleMain.aInstance</field-name>
    </root>
    </roots>
    <locks>
    <autolock>
    <method-expression>void tcLockingExample.A.primIntInc()</method-expression>
    <lock-level>write</lock-level>
    </autolock>
    </locks>
    </dso>

    Now TCLockingExampleMain works:

    2009-06-07 18:24:22,965 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:24:23,284 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:24:23,414 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:24:25,324 INFO – Connection successfully established to server at 192.168.1.20:9510
    aInstance.primInt: 1
    aInstance.primInt: 2
    aInstance.primInt: 3
    aInstance.primInt: 4
    aInstance.primInt: 5
    aInstance.primInt: 6
    aInstance.primInt: 7
    aInstance.primInt: 8
    aInstance.primInt: 9
    aInstance.primInt: 10

    and TCLockingExampleReporter shows that the data is being shared:

    2009-06-07 18:27:21,486 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:27:21,804 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:27:21,933 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:27:23,271 INFO – Connection successfully established to server at 192.168.1.20:9510
    Sun Jun 07 18:27:23 BST 2009 TCLockingChoicesMain.aInstance.primInt:10

    Next we try a different locking strategy.  We remove the synchronization from A’s method, and instead synchronize TCLockingExampleMain’s write operation on the root object:

    package tcLockingExample;

    public class TCLockingExampleMain {
    static A aInstance = new A();

    /**
    * @param args
    */
    public static void main(String[] args) {
    for (int x = 0; x < 10; x++ ) {
    synchronized(aInstance) {
    aInstance.primIntInc();
    }
    System.out.println(“aInstance.primInt: ” + aInstance.primInt);
    }
    }
    }

    We also update the Terracotta configuration, applying a Terracotta autolock (which means that Terracotta will add its locking wherever it sees Java synchronization) to TCLockingExampleMain’s main() method instead of A’s incrementer method:

    <dso>
    <instrumented-classes>
    <include>
    <class-expression>tcLockingExample.A</class-expression>
    </include>
    <include>
    <class-expression>tcLockingExample.TCLockingExampleMain</class-expression>
    </include>
    </instrumented-classes>
    <roots>
    <root>
    <field-name>tcLockingExample.TCLockingExampleMain.aInstance</field-name>
    </root>
    </roots>
    <locks>
    <autolock>
    <method-expression>void tcLockingExample.TCLockingExampleMain.main(java.lang.String[])</method-expression>
    <lock-level>write</lock-level>
    </autolock>
    </locks>
    </dso>

    This strategy also works:
    2009-06-07 18:45:39,099 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:45:39,425 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:45:39,555 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:45:41,441 INFO – Connection successfully established to server at 192.168.1.20:9510
    aInstance.primInt: 1
    aInstance.primInt: 2
    aInstance.primInt: 3
    aInstance.primInt: 4
    aInstance.primInt: 5
    aInstance.primInt: 6
    aInstance.primInt: 7
    aInstance.primInt: 8
    aInstance.primInt: 9
    aInstance.primInt: 10

    and data sharing is enabled:
    2009-06-07 18:44:17,498 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 18:44:17,816 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 18:44:17,951 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 18:44:19,710 INFO – Connection successfully established to server at 192.168.1.20:9510
    Sun Jun 07 18:44:19 BST 2009 TCLockingChoicesMain.aInstance.primInt:10

    Finally we’ll take a quick look at how the choice of locking strategy can affect performance.  To see this, we change  TCLockingExampleMain so it increments A’s integer member a million times instead of ten as in previous executions.  We also add some code to tell us how long the program took to do the million iterations:

    package tcLockingExample;

    import java.util.Date;

    public class TCLockingExampleMain {
    static A aInstance = new A();

    /**
    * @param args
    */
    public static void main(String[] args) {
    Date startTime = new Date();
    for (int x = 0; x < 1000000; x++) {
    aInstance.primIntInc();
    }
    Date endTime = new Date();
    System.out.println(“startTime: ” + startTime + ” endTime: ” + endTime
    + ” elapsed: ”
    + ((endTime.getTime() – startTime.getTime()) / 1000)
    + ” seconds”);
    System.out.println(“aInstance.primInt: ” + aInstance.primInt);
    }
    }

    When we run this program we see that the million iterations take around 26 seconds:

    2009-06-07 19:16:09,091 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 19:16:09,410 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 19:16:09,544 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 19:16:11,022 INFO – Connection successfully established to server at 192.168.1.20:9510
    startTime: Sun Jun 07 19:16:11 BST 2009 endTime: Sun Jun 07 19:16:37 BST 2009 elapsed: 26 seconds
    aInstance.primInt: 1000000

    Next we try our second locking strategy with a million iterations:

    package tcLockingExample;

    import java.util.Date;

    public class TCLockingExampleMain {
    static A aInstance = new A();

    /**
    * @param args
    */
    public static void main(String[] args) {
    Date startTime = new Date();
    for (int x = 0; x < 1000000; x++) {
    synchronized (aInstance) {
    aInstance.primIntInc();
    }
    }
    Date endTime = new Date();
    System.out.println(“startTime: ” + startTime + ” endTime: ” + endTime
    + ” elapsed: ”
    + ((endTime.getTime() – startTime.getTime()) / 1000)
    + ” seconds”);
    System.out.println(“aInstance.primInt: ” + aInstance.primInt);
    }
    }

    It takes about the same amount of time:

    2009-06-07 19:21:08,279 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 19:21:08,602 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 19:21:08,737 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 19:21:10,266 INFO – Connection successfully established to server at 192.168.1.20:9510
    startTime: Sun Jun 07 19:21:10 BST 2009 endTime: Sun Jun 07 19:21:37 BST 2009 elapsed: 26 seconds
    aInstance.primInt: 1000000

    For our last test we move the synchronization statement outside of the loop, meaning that only one lock is required instead of a million (one per iteration):

    package tcLockingExample;

    import java.util.Date;

    public class TCLockingExampleMain {
    static A aInstance = new A();

    /**
    * @param args
    */
    public static void main(String[] args) {
    Date startTime = new Date();
    synchronized (aInstance) {
    for (int x = 0; x < 1000000; x++) {
    aInstance.primIntInc();
    }
    }
    Date endTime = new Date();
    System.out.println(“startTime: ” + startTime + ” endTime: ” + endTime
    + ” elapsed: ”
    + ((endTime.getTime() – startTime.getTime()) / 1000)
    + ” seconds”);
    System.out.println(“aInstance.primInt: ” + aInstance.primInt);
    }
    }

    Execution time drops from 26 seconds to less than one second:

    2009-06-07 19:23:52,409 INFO – Terracotta 3.0.0, as of 20090410-200435 (Revision 12431 by cruise@su10mo5 from 3.0)
    2009-06-07 19:23:52,727 INFO – Configuration loaded from the file at ‘/home/dan/workspace/TCLockingExample/tc-config.xml’.
    2009-06-07 19:23:52,861 INFO – Log file: ‘/home/dan/workspace/TCLockingExample/terracotta/client-logs/terracotta-client.log’.
    2009-06-07 19:23:54,335 INFO – Connection successfully established to server at 192.168.1.20:9510
    startTime: Sun Jun 07 19:23:54 BST 2009 endTime: Sun Jun 07 19:23:55 BST 2009 elapsed: 0 seconds
    aInstance.primInt: 1000000

    As these very simple examples show, there are often several choices for how to implement locking in a Terracotta application, and the selection of a strategy can have profound implications for application performance.  For a developer who is new to Terracotta, selecting appropriate locking strategies may involve significant amounts of trial and error.  As the developer’s understanding of Terracotta grows with experience, locking strategy selection becomes easier and less experimentation is required.


    An Even Briefer Look at Distributed Transactions in GigaSpaces

    May 25th, 2009

    A couple of weeks ago I posted a quick example and explanation of a GigaSpaces local transaction.  You can find the post here and get the code here.

    In today’s short post I will extend that example to use a distributed transaction.  We’ll do this in two steps: first we’ll break the example; then we’ll fix it.

    As a reminder, a GigaSpaces distributed transaction is any transaction that operates on more than one primary space.  In the example code, our client program executed a local transaction when it wrote two instances of TestClass to a single instance remote space.

    In that example routing was not a concern because we created the space as unpartitioned, and we did not declare a space routing field. Behind the scenes, however, GigaSpaces selected one (the id field) for us.  You can check this on the Space Browser tab by expanding the GSSimpleTranExample space node, clicking on “Classes”, then clicking on “TestClass”.  The name of Routing Filed will appear on the Classes Info tab just above the table showing the fields (only one in our case) in the class.

    Now drop the space using Undeploy Application on the Cluster Runtime tab.  Then recreate it as a partitioned space with two partitions and no backups.  Rerun the client application, and it will fail with this error message:

    Exception in thread “main” org.openspaces.core.TransactionDataAccessException: Invalid operation – local transaction spans over multiple spaces – [GSSimpleTranExample_container2:GSSimpleTranExample, GSSimpleTranExample_container1:GSSimpleTranExample] !
    You might be using hash based load balancing (partitioned schema) while writing data into multiple spaces and not into a single node.
    Please Use Jini Transaction manager with your operations.
    ; nested exception is net.jini.core.transaction.TransactionException: Invalid operation – local transaction spans over multiple spaces – [GSSimpleTranExample_container2:GSSimpleTranExample, GSSimpleTranExample_container1:GSSimpleTranExample] !
    You might be using hash based load balancing (partitioned schema) while writing data into multiple spaces and not into a single node.
    Please Use Jini Transaction manager with your operations.

    The reason is that GigaSpaces attempted to route each of the two writes to  different partitions, which turned our local transaction into a distributed transaction.  Because we configured the application with a local transaction manager, the transaction fails.

    To fix the application we need to specify a distributed transaction manager instead of a local one.  Here’s how:

    Find the line in the Spring application context file, GSSimpleTranExample.xml, in which we specify a transaction manager:

    <!– @page { margin: 2cm } P { margin-bottom: 0.21cm } –><os-core:local-tx-manager id=transactionManager” space=gSSimpleTranExample”/>

    and replace it with a line that looks like this:

    <!– @page { margin: 2cm } P { margin-bottom: 0.21cm } –>

    <os-core:distributed-tx-manager id=“transactionManager” />

    Note that the distributed transaction manager, unlike a local transaction manager, is not associated with a particular space.

    Now run the client application.  This time it should work.  You can confirm the transactional behaviour using the techniques described in the earlier post.


    A Brief Look at Local Transactions in GigaSpaces

    May 12th, 2009

    Introduction

    One way to think about GigaSpaces1 is as a sort of database management system for maintaining and accessing data spread across a set of caches. Of course this view ignores many important capabilities of the GigaSpaces framework, but it is a useful perspective for considering GigaSpaces’ transactional features.

    GigaSpaces offers transactional control over access to data in its spaces. When the right combinations of API features are employed, data operations assume ACID characteristics.

    As a reminder, ACID is an acronym standing for “atomic, consistent, isolated and durable”. The term refers to the behaviour that is generally expected from transactional systems. Quoting Wikipedia:

    • Atomicity: Either all the tasks in a transaction must be done, or none of them. The transaction must be completed, or else it must be undone (rolled back).

    • Consistency: Every transaction must preserve the integrity constraints — the declared consistency rules — of the database. It cannot place the data in a contradictory state.

    • Isolation: Two simultaneous transactions cannot interfere with one another. Intermediate results within a transaction are not visible to other transactions.

    • Durability: Completed transactions cannot be aborted later or their results discarded. They must persist through (for instance) restarts of the DBMS after crashes

    It is important to understand that GigaSpaces transactions govern only the state of data in the spaces managed by GigaSpaces. Unlike the transactional support provided by heap-oriented products such as Terracotta and Kabira, GigaSpaces transactions do not provide guarantees concerning access to or the state of heap memory in a GigaSpaces application.

    Lineage

    GigaSpaces’ transaction support derives from three sources:

    1. Jini – At its core, GigaSpaces is an implementation of the JavaSpaces specification, which is a component of the Jini specification. Jini was designed to allow heterogeneous software and hardware devices to interact. The Jini specification (and reference implementation) include a facility for Jini-compliant devices to participate in distributed transactions. GigaSpaces has inherited and extended this capability.

    2. JTA (Java Transaction API) – Jini provides the ability to orchestrate transactions among Jini-compliant participants such as JavaSpaces. Sometimes, however, it may be necessary to engage in a transaction both participants that are Jini-compliant and participants that do are not Jini-compliant. For example, a GigaSpaces application might need to remove a data entry from a space and insert a corresponding row into a table in an RDBMS. Most RDBMSs support a distributed transaction protocol called XA that allows them to participate in transactions with otherwise independent participants. Using JTA, GigaSpaces can participate in XA distributed transactions.

    3. Spring – The Spring framework provides an abstraction of transaction management services and constructs. GigaSpaces has embraced this abstraction and uses it as the façade for its own transaction support.

    To this mix GigaSpaces adds support for local transactions, meaning transactions that involve only one instance of one space.

    Transaction Control

    Mirroring Spring’s capabilities, GigaSpaces offers two modes of transaction control, programmatic and declarative. With programmatic transaction control, the programmer uses API calls to configure, start, commit and abort transactions. With declarative transaction control, the programmer includes directives about where and how transactional behaviour should be applied. These directives are interpreted by Spring and translated into transactional control statements that are woven into the application at runtime.

    Spring declarative transaction control itself takes two forms, both of which are supported by GigaSpaces. First, it can be configured using a pointcut specification typical of aspect-oriented implementations. Second, methods can be annotated to indicate that they should (or should not) be executed within transactions.

    A Simple Example

    Get the source code for this example here.

    Here is a simple application that illustrates how to implement a local transaction with GigaSpaces using annotation-driven declarative transaction control. The example creates two instances of a class, then writes both instances to a space within a transaction.

    There are five files:

    1. A spring application context file – GSSimpleTranExample.xml – that sets up the proxy by which the application will access the space. The transaction manager is defined here.

    2. A simple POJO class – TestClass.java – two instances of which will be written to the space under a transaction.

    3. A Java interface – ConnBeanInterface.java – that declares the methods that will be used to access the space. The interface – implementation pattern is used because Spring works better with instances of interfaces than with instances of concrete classes.

    4. A Java bean – ConnBean.java – that performs the space operations.

    5. A Java main program – GSSimpleTranExample.java – that instantiates the objects to be written to the space and invokes the method to write them.

    Let’s start by looking at the Spring application context file. The first item of interest is an Spring namespace element that instructs Spring to apply transactional controls to methods that are annotated with @Transactional in beans that it is managing:

    <tx:annotation-driven />

    The name of a transaction manager bean can be specified as an attribute to this element. This is the bean containing the transaction manager that will be used to manage the transactional behaviour of the annotated methods that Spring finds in the beans that it manages. If none is specified, as in our example, a default value of “transactionManager” is assumed.

    Next is an OpenSpaces namespace element that instruct Spring to instantiate a transaction manager:

    <os-core:local-tx-manager id=“transactionManager” space=“gSSimpleTranExample”/>

    In our case we are using GigaSpaces’ local transaction manager. This is the best choice when each transaction will involve only a single partition of a single space.

    Notice that the transaction manager declaration contains a reference to a space. As we will see shortly, this construct is one of two that constitute an apparent redundancy in the OpenSpaces namespace support for transactions.

    Next we see a typical OpenSpaces namespace space declaration that tells Spring to create a an IJSpace instance:

    <os-core:space id=“gSSimpleTranExample” url=“jini://*/*/GSSimpleTranExample” />

    There is nothing specifically transactional about it; it is included in this discussion because the next element, which has a transactional dimension, refers to it.

    Next is an OpenSpaces declaration of a GigaSpace:

    <os-core:giga-space id=“gigaSpace” space=“gSSimpleTranExample”

    tx-manager=“transactionManager” />

    Note that this element includes both an explicit reference to the transaction manager declared earlier, and refers to the space that was defined earlier and that also refers to the transaction manager.

    <os-core:giga-space id=“gigaSpace” space=“gSSimpleTranExample”

    tx-manager=“transactionManager” />

    Next we specify our application bean that will perform the space operations:

    <bean id=“connBean” class=“ConnBean” />

    Spring will instantiate this bean and manage its lifecycle.

    We also include the OpenSpaces GigaSpace context element:

    <os-core:giga-space-context />

    so that Spring will assign the GigaSpace bean declared in the context file to a variable of type GigaSpace that is annotated with the @GigaSpaceContext annotation in our ConnBean instance.

    The TestClass is not transaction-aware, and is not described further.

    The ConnBeanInterface declares the method that will be implemented in ConnBean:

    public interface ConnBeanInterface {

    public void writeTwoObjects(TestClass tCI1, TestClass tCI2);

    }

    Note that, although it is not transaction-aware, it could have been, as we have the option of annotating the interface class or its methods to be transactional.

    The implementation of:

    public void writeTwoObjects(TestClass tCI1, TestClass tCI2) {

    in our ConnBean class is transactional:

    @Transactional

    public void writeTwoObjects(TestClass tCI1, TestClass tCI2) {

    gigaSpace.write(tCI1);

    gigaSpace.write(tCI2);

    }

    The @Transactional annotation tells Spring to wrap transactional controls around this method.

    From our main application class, here is the code that creates two instances of TestClass, then invokes the ConnBean method that will write them to the space under a transaction:

    TestClass tCI1 = new TestClass(0);

    TestClass tCI2 = new TestClass(1);

    connBean.writeTwoObjects(tCI1, tCI2);

    Running the Example

    Before running this example, create an unpartitioned space called GSSimpleTranExample.

    When you run the example pass the path and name of the spring application context file as a command line argument.

    It it runs successfully the program will produce the following output:

    Done with my work. About to exit.

    Proving the Transactional Behaviour

    Here are a few techniques that can be used to explore the transactional behaviour in this example:

    1. Extend the duration of the transaction and inspect it in the GigaSpaces GUI Space Browser while it is in progress. You can extend the duration of the transaction by modifying the ConnBean class as follows:



      int sleepLength = 10000;

    @Transactional

    public void writeTwoObjects(TestClass tCI1, TestClass tCI2) {

    gigaSpace.write(tCI1);

    gigaSpace.write(tCI2);

    try {

    Thread.sleep(sleepLength);

    } catch (Exception e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    sleepLength is specified in milliseconds. Set it to whatever value is convenient for you. Then run the application, and look at the list of transactions. Note that the transaction type is “Local” because we declared a local transaction manager in the application context file. Also notice that two objects are locked by this transaction. These are the two objects that are being inserted.

    2. Try to query the locked objects using the Space Browser and observe that they cannot be read while the transaction is in progress. To do this, start by removing any instances of TestClass from the space. Set the value of sleepLength to a long enough duration (perhaps 30 seconds) that you will have time to execute a query against the space while the transaction is in progress. Run the program. Then select the TestClass class in the space browser and execute a query. The result set will be empty.

    As you are preparing to run the query you will notice that the instance count for the TestClass class is two, not zero. This is because the method that is used by the GUI to inspect the space has access to locked objects and includes them in the count value it returns. The objects themselves, however, are not visible until the transaction commits.

    3. Force the transaction to roll back, and observe that the space is left empty. To force a roll-back, raise an exception in the WriteTwoObjects() method as follows:

      int sleepLength = 10000;

    @Transactional

    public void writeTwoObjects(TestClass tCI1, TestClass tCI2) {

    gigaSpace.write(tCI1);

    gigaSpace.write(tCI2);

    try {

    Thread.sleep(sleepLength);

    } catch (Exception e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    throw (new RuntimeException());

    }

    Again, start by removing any instances of TestClass from the space. Now when you run the application you see the transaction in progress and the TestClass instance count will go to two. At the end of the period specified by sleepLength, the transaction will abort and the instance count will revert to zero.

    1GigaSpaces technology is spread across two code bases, GigaSpaces and OpenSpaces. This paper often refers to all of the technology indiscriminately as “GigaSpaces”.


    GigaSpaces Distributed Transaction Performance

    May 4th, 2009

    I’ve done some quick performance testing of GigaSpaces’ distributed transactions using their mahalo implementation.  These are GigaSpaces-only transactions as opposed to transactions involving GigaSpaces and some other persistent store, in which case JTA/XA would be required.

    As a reminder, GigaSpaces considers a transaction to be distributed if it involves more than one primary space partition.  So a transaction that operates on two or more partitions of a partitioned space would be distributed, as would a transaction that operates on two or more different spaces.  A transaction that operates on only one partition of one space is not distributed even if that space is replicated.

    I set my test up as follows:

    • A non-pu client acquires a proxy to a (remote of course) clustered space and writes pojos to that clustered space.
    • The writes are single-threaded, one-at-a-time, and synchronous. (GigaSpaces offers other choices that would undoubtedly be faster).
    • The pojos are routed, so the writes end up going to more than one partition.

    I ran two GSCs on two virtual hosts.  When I ran without backups each GSC managed one partition.  When I ran with backups each GSC managed two partitions.  The primary for each partition ran on adifferent host than the backup when backups were used.

    The client ran on the physical host.

    Ping times on my network run at about .19 ms.

    Each test consists of 10,000 operations of two writes (to primaries) each.  When I ran without backups and without transactions, I got 2,000 operations per second.  Using transactions that dropped to 322 operations per second.

    Working with backups and without transactions,  I got 714 operations per second.  Using transactions that dropped to 208 operations per second.

    These performance figures have little to do with fully optimized GigaSpaces performance.  As I mentioned above, there are faster ways to do these writes than the simple approach I used for these tests.  What the results do indicate, however, is that you can expect to pay a 3x – 6x performance cost for using distributed transactions over independent writes.

    Of course transactions have different characteristics than do independent writes, and those characteristics may justify the performance cost.  As a rule, though, it is clear that distributed transactions should be avoided because of their performance implications unless you have a compelling need for transactional behaviour.