I am designing a back-end where Database contention is the number-one issue for scalability.
We use those darn distributed transactions and therefore I do not know how long the transaction my component is participating into will last. If a transaction remains open for a few seconds, many of those would create contention in the Database my component is using. This will result in dead-locks, time-outs and scalability would go out of the window.
I didn’t want to go the all no-sql way and forgo any transaction management. The crux of the deal is that during one of those distributed transaction, we are inserting one record while a recurrent task performs a large and complicated select on the entire table. I though if I could wait long enough to insert, until the end of the transaction, I would reduce the contention. Here is how to do it.
First, what you do not want to do is to hook on the TransactionCompleted event of the Transaction.Current object. It is tempting, it is easy, it is right at hand, but it doesn’t work. Well… it works but whatever code you run in the event handler will exist outside the transaction since as the name of the event suggests, the transaction has already completed by then. The point of using transaction is to have many actions being atomically bundled so I didn’t want to have a key operation happening outside the transaction.
The real solution isn’t that much more complicated actually. Check out Transaction.Current.EnlistVolatile. This method allow you to hook yourself in the transaction process. You basically become a transaction participant. We use the volatile version of the method since we do not want to appear as a durable participant since that would involve us being able, like the antic Pheonix, to be born again from our ashes if the process fail. No need for that, I was happy for this to work only if the original process stayed online for the duration of the transaction.
public Enlistment EnlistVolatile( IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions )
We need to implement the IEnlistmentNotification interface:
public interface IEnlistmentNotification
void Commit(Enlistment enlistment);
void InDoubt(Enlistment enlistment);
void Prepare(PreparingEnlistment preparingEnlistment);
void Rollback(Enlistment enlistment);
In my case I simply needed to hold a few variable within the object implementing that interface. Basically the data I wanted to insert in the DB and the Transaction instance itself. Then I had the simple following implementation:
- In Prepare, do the insertion and call PreparingEnlistment.Prepared. This last step is essential since we simulate a transaction resource, we need to confirmed that we prepared in the two-phase commit.
- In Commit, simply call Enlistment.Done.
That’s it! You’re now in the Transaction pipeline and can do late operations.