Thursday, September 10, 2015

Updating multiple one-phase resources with Narayana

I was recently forwarded a link to an article regarding the use of Springs chained-transaction manager facility wherein the author had utilised this facility to coordinate updates to multiple one-phase resources. This gave me the opportunity to show-case a Narayana facility which has existed for many years that allows you to build something with similar a similar purpose and possibly richer properties.
What we will create is an application that uses multiple one-phase resources (for example, some hypothetical none-XA database and message queue). We will use Narayanas AbstractRecord extension mechanism to order the commit messages to the resource managers in any way that would be appropriate for the application. We will then take a look at some options for failure recovery options.

Notes:

  • Applications of this style (i.e. multiple 1PC) are only suited for certain classes of applications. Where possible it is almost always preferable to use 2PC resources to provide spec-compliant transactional outcomes.
  • The code I am going to use to demonstrate some of this is derived from a unit test in our repository but I will extract some of the code to below. I won't actually use resource managers in this example to try to illustrate the pattern as clearly as possible.
  • If the test ever moves around, you can probably track it via its SHA 8e9f712d5b89762c7b841cf370eb5bdb341fff4d.

Transactional business logic

The general layout of the application follows the same pattern of any other transactional application:
        // Get references to resource managers
        ResourceManager1 rm1 = ...;
        ResourceManager2 rm2 = ...;

        // Create a transaction
        AtomicAction A = new AtomicAction();
        A.begin();

        // Enlist resource manager in transaction
        A.add(new OrderedAbstractRecord(rm1));
        A.add(new OrderedAbstractRecord(rm2));

        // Do business logic
        // rm1.sendMessage()
        // rm2.doSQL()

        // Commit the transaction, the order will be defined in OrderedAbstractRecord rather than
        // the business logic or AtomicAction::add() order
        A.commit();

Guaranteeing the order of commit messages

The ordering of the list for transaction completion events is dictated by the RecordList class. At the most fundamental level, for AbstractRecords of the same type it is determined by the Uid returned in the AbstractRecords order method. As Uids are sequentially numbered at some level, this basically means that if you return a Uid lower to a peer, your record instance will be committed before that one.
So for example, the order of Uid you allocate to the following class will determine the order AbstractRecord::topLevelCommit() is called:

public class OrderedOnePhaseAbstractRecord extends AbstractRecord {
    public OrderedOnePhaseAbstractRecord(Uid uid)
    {
        super(uid);
        order = uid;
    }
    public Uid order()
    {
        return order;
    }
    //...
}

Failure tolerance properties

A final observation to make is that by using the Narayana AbstractRecord facility, it allows you to know that in the presence of a failure, during crash recovery you will receive callback where it may even be possible to re-do some of the work in the later resources.
For example, in the AbstractRecords save_state you could save some of the content of the JMS message which can then be used in a possible recovery scenario to resend a similar message.

No comments: