Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


18 Common Programming Traps in MTS : Page 3

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.


Problem 11: Getobjectcontext In Initialize
A common way of programming for MTS is to check if you have got an ObjectContext-instance or not. If you use this style (I'm not very fond of it actually) you can use the same code inside and outside MTS without any recompilation. An example:

If m_objCtx Is Nothing Then
Set obj = CreateObject("TestApp.Person")
Set obj = m_objCtx.CreateInstance("TestApp.Person")
End If

The above, in combination with getting the context object in Initialize, will lead to undesired results. The Initialize procedure is trying to get the context object too early. So you will not get any. Therefore the m_objCtx variable will be Nothing and you will always be using CreateObject. If you read problem number 3, then you know what will happen.

Private Sub Class_Initialize()
'Not good:
Set m_objCtx = GetObjectContext
End Sub

Instead you should implement ObjectControl interface and use the Activate procedure to set the m_objCtx as shown below:

Implements ObjectControl
Private Sub ObjectControl_Activate()
Set m_objCtx = GetObjectContext
End Sub

Problem 12: Too Many COM Exceptions
Perhaps you have heard that Err.Raise is slow? What happens is that it is converted to a COM Exception, and a COM Exception will give you several roundtrips. Often programmers say that when an error occurs, it is not important to give the user incredible performance. I agree.

The reason I write about this problem is that it is a common programming style to use COM Exceptions instead of return values, to inform about success/failure of an operation. (Failure in this case is not a real error.) If you use this style then you can call a Sub, stating something. In, for example 70% of situations, everything is fine. In the remaining 30% of situations, you were wrong and you get an exception. In my test calling an MTS object 1000 times from a base-client, the "old" approach of using a function returning True or False gave 150% better performance than the "new" approach when the operation generates an exception.

"New" exception style:

On Error Goto FalseStuff
'Do the "True"-stuff...
Exit Sub
If Err = jnskErrFalseStuff Then
'Do the "False"-stuff...
End If

"Old" (but faster) C style:

Dim bolOK As Boolean
bolOK = obj.DoSomething()
If bolOK Then
'Do the "True"-stuff...
'Do the "False"-stuff...
End If

Problem 13: Msgbox
You probably know that you should use Unattended Execution for your COM servers, especially if they are to be used in MTS. But when you would like to do some quick and dirty debugging of an MTS object, then you perhaps deselect Unattended Execution and use som MsgBox-statements in the MTS object. That works fine if you also run the package as Interactive User. Everybody is happy.

Let's assume you find and correct the bug (you always do, right?), but you forget one of the MsgBox-statements. When the code is executed in production again, the message box will perhaps be apparent on the server (depending on which user is running the package), but definitely not on the client. If the object is transactional, there will be a wait-state for the transaction until it gets the timeout. There may be a lot of locks, making the rest of your users unhappy to say the least.

Of course, the client will hang, as will the operation in the object, until somebody acknowledges the message box. If you don't run the package as Interactive user, then there will be no visible message box to acknowledge. The message box will be put on a virtual screen. If you use Unattended Execution for your COM server (which you definitely should!), the problem will not appear since the MsgBox will not be "used." Instead there will be an event that you can inspect in the Event Viewer.

In the scenario above, there were a lot of "ifs." Perhaps you think the case is only theoretically possible? I know from painful experience that this happens. Why not make it theoretically by wrapping your usage of the MsgBox-statement into a Sub of your own where you inspect the environment to see if it is MTS, to escape this possible problem in your production application!

Problem 14: "Old" Handling of Surrogate Keys
In 1989 I was working on a large project where we had a problem with our surrogate keys. (As a matter of fact we had several problems. Most projects do.) We used a table that had information about which should be the next value for each surrogate key. The problem we had was that this table became an incredible bottleneck.

Nowadays you don't have to take care of this yourself. In SQL Server for example you can use Identity if you think that functionality is good enough for you. Despite this, you will often see the old way of doing things if you read books and such. If you do it yourself you will of course get flexibility, but be aware that you can suffer when it comes to performance. And if performance goes down here, the throughput will too.

A common reason for using the old approach is that you would like the client to know what the new key value was. When you use Identity you will not get that information automatically through ADO to your disconnected recordset. You can for example use a stored procedure to do the insert and return @@identity to tell the client which was the new value. (Whether or not the client needs to know the new value is not the only factor to consider. The choice is not trivial.)

In a test I compared the old and new solution, and when I let a base-client make 100 calls to an MTS object that created a new row in a table with a surrogate-key, I got 200% better performance when I used a stored procedure that returned the @@identity-value than when I wrote the code myself in the MTS object with a table that remembered which was the next value for the id column.

If you use a SPM-based solution you could make the difference less, but then you get new problems, as you would if you had more than one MTS server. You can of course also move the code to a stored procedure, but there will probably still be a time difference.

Observe also that the client doesn't always have to know the new id value. Then you have one less reason for not using the built-in functionality in the database product.

Problem 15: Sloppy SQL
You have heard that the stuff you put in MTS is called the Business Rules Tier. The database is degraded to a place where you just spool out the state. Don't listen to this any more. You have to be as careful with how you handle the database as usual. If you write bad SQL, you will get bad performance. It is still as simple as that.

In my test, my base-client made 100 calls for the name of a dog for a certain persons name. In the first case the MTS object called the database to find the id for a persons name first. Then it went to the database again to fetch the dogname, using the persons id as a foreign key. In the second case the MTS object did a join between the person and dog table and used the persons name as the criteria. The second case went 40% faster.

First case:

strSQL = "SELECT id FROM person WHERE fname = 'Jimmy'"
With rstPerson
.Open strSQL
If .RecordCount > 0 Then
strSQL = _
"SELECT dogname FROM dog WHERE person_id = " & _
rstDog.Open strSQL
NameOfDog = rstDog!dogname
End If
End With

Second case:

strSQL = "SELECT dogname FROM person INNER JOIN " & _
"dog ON person.id = dog.person_id WHERE fname = 'Jimmy'"
With rst
.Open strSQL
If .RecordCount > 0 Then
NameOfDog = !dogname
End If
End With

In my test I ran the database and MTS on the same machine (see part 1 of this article for further information about the environment for the tests). The difference will of course be larger if I move the database to another machine.

Problem 16: A Lot of Small Property Calls
In the "old" days of object orientation we were told that objects had behaviour and state. A common implementation would be to do some stuff and then check what the new state would be. Something like this could be typical:

With objPerson
.GetById intI
strFname = .FName
strLname = .LName
strPhone = .Phone
End With

Unfortunately this style is not very effective in the distributed scenario since you get a lot of roundtrips. In my test (when I looped 100 times) the version below was 33% faster, and please observe that the number of properties is not very large. I guess you sometimes have objects with more than four properties? You sometimes have to weigh pureness in object orientation against effectiveness.

objPerson.GetById intI, strFname, strLname, strPhone

OK, the second version is not very good-looking, especially not if you will send a lot of properties, but then you can use other techniques to send all the values in one call.

The first style is also an example of stateful programming and the second is an example of stateless programming. MTS really favours the second and when it comes to transactions, there is no discussion about it. Get in fast and out even faster!

Problem 17: Thinking That Byref Is Faster as Usual
For several years you have heard that calling functions with parameters will be more effective if you transfer the parameters by reference than by value (at least when the datatypes are large). That is obvious since you will not have to do any time consuming copying of data, just transfer a reference. Don't think this is still valid in all situations.

In my test my base-client called an operation in an MTS object 10 times. Each time transferring a 200 K string. In the first case the string was transferred ByRef and in the second case ByVal was used. The second case was 150% faster. The reason is simple. Marshalling will only have to take place in one direction, namely at the call and not on the return also.

My recommendation is (and as a matter of fact was before MTS too) to use the semantically "correct" way of transferring data. If the receiver needs to update the data, use ByRef, if not use ByVal. Simple, secure and actually faster in some situations nowadays.

(As a little comment to the above I would like to state that I am sorry that VB doesn't support [out] as in COMs IDL. Therefore you can't just return data, except as [retval] of course. Remember this, for example, when you want to create a large array on the client, but the array has to get its data on the server. Instead create a small array on the client and ReDim it on the server or return it as the return value of the function.)

Problem 18: Too Little Testing
Last but not least, distributed applications require more testing. Period. Don't make the mistake of only testing outside MTS or with everything (client code, MTS objects and database) on a local machine. You have to test in a configuration like the target one. Otherwise you will get surprises at an unpleasant time, namely when your customer is running your application.

In my test I got 1200% better performance when I tested a lot. OK, I'm only joking, but this could be true. Worse yet, if you do not test enough, you can be sure the application will have nasty bugs!

My main goal with this article (part one and two) was to give you some tips about how to avoid some nasty problems with MTS and how to raise performance. If you said something like "hmmm, perhaps I should try that" at least once while reading the article, then I'm satisfied.

Comment and Contribute








