Sunday, June 6, 2010

Transactions and volatile state

So far we've looked at some of the basic of writing transactional applications using just JBossTS and TXOJ, as well as the importance of nested transactions. In this entry we'll look at another way of relaxing some of the traditional ACID properties, namely that of durability (persistence).

Hopefully you'll realize that normally when using transactions the Durability aspect kicks in with the transaction log, which maintains a record of the participants and the state of the transaction, as well as transactional participants (e.g., business objects), which have to record any state changes that are made in the context of the transaction. Both of these are needed to ensure atomicity in the presence of failures. But although disk speeds have improved significantly over the years, compared to improvements in processor speeds the disk is still the bottleneck. But it needn't be if all you want is ACI for your objects and applications. Such objects are often referred to as volatile or recoverable in that they have state that cannot survive machine crashes but which is still subject to atomic updates, i.e., previous states can be recovered if the transaction rolls back as long as there are no crashes.

But of course it's no good to simply stop your business objects from writing state, assuming you are even aware that they are doing so within a transactional application; you must have similar recognition by the transaction manager so that it doesn't write the log on behalf of volatile objects. Obviously if there are only volatile objects in the transaction then there's no need for a lot at all and performance can improve significantly. But of course you need to understand the trade-offs that using volatile objects give, which can essentially be summarized as performance vs crash recovery.

Fortunately JBossTS and TXOJ support volatile objects, including recognition by the coordinator. You don't need to do anything special when creating or starting your transactions: the coordinator will only create a log during commit if it detects more than one non-volatile object within its scope. And for your TXOJ objects you have to do very little, as illustrated by the modified AtomicObject code below from one of the previous articles:


public class AtomicObject extends LockManager
{
public AtomicObject ()
{
state = 0;
}

public void incr (int value) throws TestException
{
AtomicAction A = new AtomicAction();

A.begin();

if (setlock(new Lock(LockMode.WRITE), retry) == LockResult.GRANTED)
{
state += value;

if (A.commit() != ActionStatus.COMMITTED)
throw new TestException("Action commit error.");
else
return;
}

A.abort();

throw new TestException("Write lock error.");
}

public void set (int value) throws TestException
{
AtomicAction A = new AtomicAction();

A.begin();

if (setlock(new Lock(LockMode.WRITE), retry) == LockResult.GRANTED)
{
state = value;

if (A.commit() != ActionStatus.COMMITTED)
throw new TestException("Action commit error.");
else
return;
}

A.abort();

throw new TestException("Write lock error.");
}

public int get () throws TestException
{
AtomicAction A = new AtomicAction();
int value = -1;

A.begin();

if (setlock(new Lock(LockMode.READ), retry) == LockResult.GRANTED)
{
value = state;

if (A.commit() == ActionStatus.COMMITTED)
return value;
else
throw new TestException("Action commit error.");
}

A.abort();

throw new TestException("Read lock error.");
}

public boolean save_state (OutputObjectState os, int ot)
{
boolean result = super.save_state(os, ot);

if (!result)
return false;

try
{
os.packInt(state);
}
catch (IOException e)
{
result = false;
}

return result;
}

public boolean restore_state (InputObjectState os, int ot)
{
boolean result = super.restore_state(os, ot);

if (!result)
return false;

try
{
state = os.unpackInt();
}
catch (IOException e)
{
result = false;
}

return result;
}

public String type ()
{
return "/StateManager/LockManager/AtomicObject";
}

private int state;
}


The only real differences here are that we don't need the constructor call to LockManager with the ANDPERSISTENT flag (the default for all TXOJ objects is that they are recoverable) and it doesn't make sense to have a Uid constructor because there will never be any persistent state for this object, so it can never be recreated later. If you know that the object is only ever going to be recoverable then you may also be able to play tricks with state manipulation during save_state and restore_state calls, which is why there's a second parameter to them, which is either ANDPERSISTENT or RECOVERABLE.

But that's it. With these very minor changes to the code you can create atomic and recoverable objects that have a lot of the benefit of being used within a transaction, including transactional locking, but lacking the overhead of logging. And of course it's possible to mix recoverable and persistent objects in the same transaction seamlessly. But again, there's a price to be paid for turning off persistence. However, for some types of object and application this is a price worth paying, and one of those areas is STM. But it's not alone and you may find the idea of volatile transactional objects appealing for your own needs. In which case give it a go.