I spent some time measuring the performance of the new System.Transactions APIs in .NET v2.0, comparing it to the performance of transaction using the Services-without-components (SwC) capabilities in .NET v1.1, specifically when using IBM MQSeries as a resource manager. Some surprising results. System.Transactions shows some good performance gains for minimal changes in code.
I used 2 simple transactional apps to benchmark. The first app simply puts or gets a single message to or from a MQ queue, transactionally. It does this via IBM's supplied .NET Class library for MQ (namespace IBM.WMQ). The app randomly selects whether to put or get, and does this in a loop, with multiple threads. In every transaction, there is always just one resource manager involved (the MQ queue itself), and one queue operation (a put or get). The lpayload size is ridiculously small: under 20 bytes.
The second app involves a mix of different set of 2 flavors of transaction. The first flavor is an enqueue operation, to a single queue, much like the put operation in app #1. But, the second flavor of transaction involves a distributed commit: the app dequeues from the queue, and then inserts a row derived from the queue data into SQL Server. Again this is done via IBM's MQ classes for .NET, in a loop, with multiple worker threads. And as with app #1, the app randomizes its operations so that 50% of the transactions are one flavor (enqueues), and 50% are the other (2PC transactions).
In all cases, I used MQSeries v6.0, the latest version from IBM. Also, in all cases I performed these tests on Windows XP SP2. For the 2PC transactions, I tested against both SQL 2000 SP4, and SQL2005.
I tested with both .NET v1.1 and .NET v2.0, using the System.EnterpriseServices namespace and Services without components (SwC). For .NET 2.0, I additionally test with the System.Transactions APIs. So the complete matrix looks like this:
using( TransactionScope scope = new TransactionScope()) {
try {
TransactionalWork(...);
scope.Complete();
}
catch (Exception ex1) {
...
It creates a transaction, then within the transaction, does some work, and tries to commit it. The transactional scope class is either the built-in class if the test uses .NET 2.0 (System.Transactions.TransactionScope) or a utility class built atop "Services without Components", which behaves in much the same way.
The TransactionalWork() varies depending on the app type, and then within the TransactionalWork(), there is some randomization.
This work is run iteratively, by multiple threads, over a set period of time, and performance data is collected during the run. This is not a web application, so I cannot use a Mercury load generation tool or the Visual Studio web Load Tester. Instead I use a custom performance test harness that starts and manages a configurable number of threads. The test harness is the same for all variations of tests.
Summary of my findings:
What can we conclude from all of this?
Disclaimer: I ran these tests on several machines running Windows XP SP2. The results were consistent across trials, and machines.
I packed up the test code and made it available for you. If you follow the readme you should be able to set up a database, MQSeries, and your .NET apps to all run on a single node.
Get the code.
I'm going to try to obtain some more appropriate server-grade hardware, running Windows Server 2003, to re-run these tests. I'll get back to you with those results, I hope soon.