Browse DevX
Sign up for e-mail newsletters from DevX


18 Common Programming Traps in MTS

This article contains 18 great tips for improving the performance of MTS applications as well as making their debugging easier. Whenever you start a new MTS application using VB5 or VB6, check out this list of the usual mistakes many developers make, regardless of their expertise level.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

icrosoft Transaction Server (MTS) is certainly not new, but there are still a lot of common design and programming errors being made. In this article I will show you 18 examples of such errors (together with proposed solutions). A few of the problems will lead to erroneous results and others will give performance problems.

I did a Gallup investigation asking one million experienced MTS programmers what they thought were the most common mistakes when designing and programming for MTS. Hmmm How do you know that I am lying here? Correct, there are not that many experienced MTS programmers in the world. In Sweden we say something like "A lot of talk and little action" and that applies perfectly in this area. "Everybody" is talking about MTS, but only a few are building applications for that environment so far. I am very sure this is changing rapidly, but it means that a lot of programmers are running into a whole new world of new problems. It's boring seeing the same old problems, isnt it?

In this article (split into two), I will show you some common problems. I expect the reader to have at least some experience of MTS and a fair bit of knowledge about database systems, VB and COM. If you have a lot of experience, you will find many of the tips painfully "easy", but I hope that even the "real MTS programmers" will find a tip or two, so please bear with me. If on the other hand you don't have the experience I expect, you will find my purposely short explanation of certain techniques inappropriate.

The problems/tips are totally unordered and what is a simple one for some guys, could be totally new and important for some others. I will focus on design and programming here and not so much on deployment. There is a lot to do wrong in deployment too, but that is another story.

Did I get your attention now when I mentioned COM+? Good. Even if you will only be building for COM+ in the near future (which I believe is uncommon), I think this article will be interesting anyway. Most of the tips are usable in COM+ too. COM + MTS = COM+, at least in a way.

As you probably know, it is hard to collect metrics about performance. A lot of factors are involved and the problem is no easier when you are trying to collect metrics for distributed systems. So please be aware that the comparisons I will show you are from my system and could be quite different on yours.

I would also like to state that I haven't spent any time at all on optimization here. I have only written the code from top to bottom. But there is probably as much to win by optimizing both the good and the bad case, so To make the difference apparent I will do some looping. That may sometimes look stupid, but the difference will at least be obvious.

When I taught students at the university, I sometimes had them do performance tests and tuning on database systems. Very often the students got disappointed because their tuning efforts only gave 20% better performance, for example. You and I know that this is often a great deal. When a salesman tells you that you will get 5% off if you buy a new Saab you will jump up and down with joy and buy it at once!

In some of my tests, the real performance difference will drown in overhead such as calls over the network. So the "real" difference could be even larger in many cases than what I say below! (See for example problem 3, 5, 7, 8, 14 and 15.) Remember also that the difference will be larger for many cases when more users are banging transactions. All the tests have been done with only one user.

Please also be aware that I am not giving production code, but simply examples to illustrate certain behaviour. Don't cut the code and use it in your apps! (I hope this is an unnecessary warning!)

The Test Environment
I used one Pentium II 266, 128 MB RAM as the client running the test driver, and one Pentium II 266, 128 MB RAM as the server running MTS 2 and SQL Server 7. The database was almost empty and the network was "free" except for my tests.

Enough said. Let's move on to the problems.

Problem 1: Creating Objects Too Late in the Client
Let's say the client needs an MTS object in a form. One typical solution before MTS was used would be to declare the object at the module level and create the object in the load-event. But now the programmer knows that he will use an MTS object and he also knows that it is good to think JIT activation and ASAP deactivation. So he creates the object at the beginning of the event that will need the object.

'The event, for example a click on a button
Dim obj As TestApp.IPerson
Set obj = CreateObject("TestApp.Person")
Set obj = Nothing

This is no more effective than:

Private m_obj As TestApp.IPerson
'Form load
Set m_obj = CreateObject("TestApp.Person")
'The event, for example a click on a button
'Form unload
Set m_obj = Nothing

But won't the first example be less resource consuming? Well, the difference is not that big. At first, the object is not activated until DoSomething is called. The client will only get a reference to a context wrapper. No locks in the database or such will be held. In my test I fired the event 1000 times and the second code snippet was 1600% faster.

Problem 2: Haven't Realized the Transaction Isolation Level
The default transaction isolation level for ADO against SQL Server is Read Committed, but when MTS is involved it will be a higher level, namely Serializable. Not thinking about that at the design stage could really hurt you! (If you are unfamiliar with transaction isolation levels, see a general textbook about database systems or the SQL Server manual.)

If for example you do something like:

SELECT SUM(numericcolumn) FROM table

in an MTS object that takes part of the first transaction, no other transaction could do anything that will change the result of the operation above. And this applies as long as the first transaction lives. Let's say that the first transaction is long and takes one second to finish, then no other transaction could update the numeric column in any row in the table. Of course, you should not insert new rows either, as that will change the result. In certain e-commerce applications that could be a huge bottleneck.

Something else to think about is the amount of locks that will be held. When you are trying to build scalable applications it is extremely important not to waste resources. When the SQL-statement from above is used in an MTS object running in a transaction you will get a lot of locks. In my test I did this on a table with 1000 rows (which is of course almost nothing) and when I counted the rows in the syslockinfo table I got 352 locks instead of 0, which I got when the same SQL statement was used from an MTS object not running in a transaction.

I made a similar test from the base-client by running an operation called GetById(vlngID) 100 times with different values for the id to use when reading from a table in the database (and without doing SetComplete after each). In a transaction this resulted in 70 locks before I was finished. Without the transaction there were no locks.

Exercise for the reader: Does it matter if you just for convenience use "Require Transaction" everywhere?

You can downgrade the isolation level by using an optimizer hint (like NOLOCK for example) but take care because youre on your own then.

Problem 3: Not Using CreateInstance
The way to create objects in VB is to use New or CreateObject. The recommendation for MTS is to use CreateObject from the base-client (see next problem for more information about the problem with New), BUT when inside an MTS object you should not use CreateObject to create another MTS object. Nor should you use New. Instead you should use CreateInstance on the ObjectContext interface. Otherwise the new object will run in another activity, separate from the first one. It will probably also run in another STA (Single Threaded Apartment). This leads to two problems. First, if the objects are transactional, you will get two transactions. Not good. Secondly, you will get a proxy/stub-pair between the objects leading to worse performance. Let's take some code that is the same in both cases first:

'In an MTS object:
Implements ObjectControl
Private m_objCtx as ObjectContext
'One of the operations of the
'ObjectControl-interface that I implement:
Private Sub ObjectControl_Activate()
Set m_objCtx = GetObjectContext
End Sub

And now the bad example:

Public Sub DoSome()
Dim obj As TestApp.IAnotherMTSobject
Set obj = CreateObject("TestApp.AnotherMTSobject")
m_objCtx.SetAbort 'Since something failed...
End Sub

If in DoSomeUpdate you do some update against the database and a SetComplete, then the update is committed even though you didnt like it! The code should look like this instead:

Public Sub DoSome()
Dim obj As TestApp.IAnotherMTSobject
Set obj = _
m_objCtx.SetAbort 'Since something failed...
End Sub

Now, both DoSome and DoSomeUpdate run in the same transaction and nothing will be saved to the database in this example. When running both the operations 100 times, the second is 110% faster since there will not be a proxy/stub-pair between the objects.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date