OpenEdge performance issues with Windows 2003 Server SP3 with Broadcom NICs

June 6, 2008 · Filed Under Performance Tuning, Reliability · Comment 

Progress KBase entry P128141 may be of note to Progress users on Windows 2003 Server if you are running into intermittent performance problems.

Windows 2003 SP3 deploys with the incorrect version of a Broadcom NIC driver causing potentially severe performance problems. As many IBM blade centers ship with Broadcom NICs, and newer ones particularly with the NetExtreme II this kbase entry is worthy of review especially if your planning on going to SP3 of Windows 2003 Server or have and are noticing any performance degradation post patch.

Additionally the SP3 release of Windows Server 2003 releases an entirely new network protocol architecture which attempts to off load the TCP protocol stack (among others) onto the NIC. We are reviewing the effect of this on performance, particularly in regards to short lived TPC connections (WebSpeed, AppServer, HTTP, etc).

OpenEdge Record Lock Anti-pattern

November 30, 2007 · Filed Under Development, Performance Tuning · Comment 

Everyone knows that if you need an exclusive lock on a database record in the 4gl you do this:

find customer exclusive-lock where customer.id eq 1 no-error.

However, this will not wait forever. It waits for lkwtmo, which defaults to 30 minutes, then raises the STOP condition. Well, what if you really want to wait longer than 30 minutes (or whatever lkwtmo is set to)? We have occasionally seen developers do this (gasp!):

find customer where customer.id eq 1 exclusive-lock no-wait no-error.
do while locked customer: 

  find customer where customer.id eq 1 exclusive-lock no-wait no-error.

end.

This will certainly work. However, if there is actually a lock on that row, this code will SLOW the database AND saturate the CPU of the client machine on which this code is running. That is an unbridled loop that will iterate thousands of times a second. Each iteration will cause the database to do work as well slowing all access (each lock attempt will show in promon as 1 Commit and as 3 DBRequests).
For example, on a 2.1ghz CPU that loop will iterate 40,000 times per second.

There are a number of good alternatives to the above design. One would be the following:

getLock: do on stop undo getLock, retry getLock:
  if retry then do:/* you may want to log something here 
                      or possibly put an upper limit on the wait time
                      by counting the number of times it hits this code
                      The below will cause an error to be returned after
                      60 minutes assuming the default lkwtmo */

     cnt = cnt + 1.
     if cnt eq 2 then return error “Unable to get a lock on customer”.
  end.

  find customer exclusive-lock where customer.id eq 1 no-error.

end.

As you can see, it is much, much better to let the Progress VM deal with getting the lock in this scenario.

OpenEdge Memory Management Anti-pattern

November 30, 2007 · Filed Under Development, Performance Tuning, Reliability · Comment 

Hello all,
I’ve recently been reviewing some Progress 4GL and have found an all too common anti-pattern related to memory management.

When a variable is defined, the Progress runtime client (Virtual Machine) allocates memory at runtime for that variable. Once the variable is out of scope, the memory is released and everyone is happy. Progress programmers have grown comfortable with this design and obliviously define variables whenever they are needed knowing that they will be de-allocated automagically by the Progress VM.

Then came dynamic objects.

Progress programmers were overjoyed! They could now create temp-tables, buttons, queries all on the fly at runtime. No more convoluted if-then statements or .i’s or having to code a different “for each” for every combination of where clause.

However, as with any power bestowing feature, there is a dark side to this wonderful new world of dynamic 4gl: memory management. Most programmers never really stopped to consider the fact that if something is created dynamically at runtime, the VM has no way of knowing the scope. It cannot tell when to release the memory required for the dynamic object. REMEMBER: the scope of the variable you happen to assign the object to HAS NO BEARING on the scope of the OBJECT since it can be passed around. In other words, the scope of the variable holding the HANDLE to the object is NOT bound to the OBJECT itself. The scope of ALL OBJECTS are always at the SESSION. This applies to GUI widgets, dynamic queries, temp-tables, etc.

Java (and other VM’s) solve this through the use of a separate execution thread running concurrently called a Garbage Collector. Its job is to scan memory and find dynamic objects that are no longer “reachable” and release their memory. Unfortunately, the Progress VM has no such thread/concept.

To add insult to injury, not only does this leak memory but it also causes progressively worse performance: The more widgets in memory, the more time it takes to create another widget. Here are three examples (run on a 2.1ghz processor):

Button Handles

Create 1000 Button Handles

Minimum memory required/lost per Button Handle: 512 bytes

Query Handles

Create 1000 Query Handles

Minimum memory required/lost per Query Handle: 1024 bytes

Temp-Table Handles

Create 1000 Temp-Table Handles

Min. memory required/lost per Temp-Table Handle: 512 bytes

So, as you can see, from both a memory and CPU footprint standpoints, it is very important to be sure to clean up your objects.

This may seem like a large number of handles, but remember two important points:
1. This is at the session level. This means that if a.p calls b.p which creates objects then those will exist for the life of the session: THERE IS NO SCOPE other than SESSION FOR DYNAMIC OBJECTS and they are NEVER automatically reclaimed!
2. If the programs are running as part of a long-running session such as AppServer, EagleIQ server or Webspeed, then you have to consider the cumulative affect over days, weeks or months.
Also note that if it is a temp-table, it could potentially have a much larger memory footprint.

So, what must be done?
It is up to the Progress programmer to clean up each and every dynamic object created using the “delete object” command.
It may be appropriate to create a widget-pool in which to assign your objects so you can just delete the pool and all the objects within will be released as well. In fact, if you create a non-persistent widget-pool, it will be automatically deleted when it goes out of scope. Creating the object into a non-persistent pool will make it behave as if it were scoped at the level that the widget-pool is created: in effect, making it behave as if it were statically defined.

If you don’t use a non-persistent widget-pool, then It is also important to be sure the “clean up” code is executed even when there is an error. For example, the following will bleed memory if an error condition is raised within the blah blah:

      procedure doQuery:
          def var qh as handle.
          create query qh.
          /* so some business logic here */
          do while true:
               blah blah
          end.
          delete object qh.
     end.

However, if you create a non-persistent widget pool, then it is automatically deleted when it goes out of scope. So the following will not leak memory even if an error condition happens:

procedure doQuery:
    def var qh as handle.
    create widget-pool “wp”.
    create query qh in widget-pool “wp”.
    /* so some business logic here */
    do while true:
         blah blah
    end.
    delete object qh.
end.

The widget-pool may be defined at the .p level as well. In this case the pool is deleted when the .p is exited.

Oh, by the way, persistent procedures and memptr’s are two other constructs that have a session level scope. However, they cannot be part of a widget-pool and therefore must be handled individually.

OpenEdge ASSIGN Statement - More than just performance.

November 22, 2007 · Filed Under Development, Performance Tuning, Reliability, Trivia · Comment 

Back in the early ’90s when I broke the news of the ASSIGN statement in a Profiles in Progress article, I had no idea what silliness would follow. I also had no idea that twenty years later we would be seeing newly written code that still gets it wrong.

For the few who are as yet unaware, wrapping consecutive assignments with an ASSIGN has multiple benefits.

a = 4.
b = 3.
c = 7.

isn’t as good as

ASSIGN a = 4
       b = 3
       c = 7. 

Shortly after the Profiles in Progress article was published there were signs that some in the Progress community were getting it wrong. Even though the article clearly graphed variations in execution time, some performance pundits started running around saying the ASSIGN statement was 2.7163639281 times faster than not using the ASSIGN. I’m exaggerating the precision of the number, of course, to make a point. None of those digits (including the initial “2″) are significant. None.

The ASSIGN statement varies for many reasons, not the least of which are whether what’s being assigned are components of an index in common, or part of a key that fully qualify a unique index. Let’s demonstrate one of these two factors using the example above.

Let’s suppose that a, b, and c are components of an index a-b-c. And let’s start, for simplicity’s sake, with a, b and c all having the value of “1″. In the ASSIGN-less code snippet, the index a-b-c will move through two transitional values, 4-1-1 and 4-3-1, before it gets to 4-3-7. Using ASSIGN, index a-b-c becomes 4-3-7 directly. Not using ASSIGN causes three-fold the activity:

ASSIGN DB Activity

Further, if index a-b-c is unique and the transitional values 4-1-1 or 4-3-1 already exist for another row, then the code without the ASSIGN statement will fail. Yes, fail.

Here’s a diagram that illustrates this index key collision for the above example:

ASSIGN Index Collision

The ASSIGN statement is not just an optional performance improvement as some believe. In some contexts, likely those least considered, lack of ASSIGN will affect reliability. Reliability is more important than Performance. Performance is also impacted as, in the example above, the application database must now perform triple the number of index lookups, inserts, and removes. Not good.

So everyone is using the ASSIGN statement, right? Sadly, no. One would think that we should find proper use of the ASSIGN statement it in all code written since the 80’s. Sadly, this isn’t the case –even in newly written 2007 code. We’ve seen it with our own eyes. As a community of Progress users, I know we can do better.

While this article isn’t an exhaustive presentation of the reasons why using ASSIGN is better, I’m hoping that the reliability and performance example above is compelling enough. It should be.

Hope this helps.