Skip to Content
Author's profile photo Thomas Jung

Web Dynpro ABAP: How Fast Can You Consume 1 Million Rows?

The other morning I was listening to Episode 17 the RIA Weekly Podcast. The RIA Weekly Podcast is always an excellent show that produces some of the most interesting content on the topic of Rich Internet Applications. This episode in particular caught my attention during the discussion on Enterprise scale applications.  The guest for this episode was Richard Monson-Haefel of Curl, Inc.  If you haven’t heard of Curl; it is a programming language and RIA development environment that is focused on the Enterprise. 
Now I don’t want to get into some detailed comparison of SAP’s development tools to Curl’s but Richard brought up a particular topic that resonated with me.  He was discussing how Enterprise RIA applications need to be able to handle very large datasets – his example was 100,000 rows – with very good performance. Looking back on my own experience as an SAP customer for 10 years, I can remember writing more than a few reports that had to chunk through 100,000+ records – usually destined to be ran by accountants. Although there are tools specifically designed to help process very large data sets in real time (for example BIA or Polestar), it is still nice to see just how far you can push the UI framework itself.
So without really wanting to draw comparisons to other tools, I simply wanted to see just how far I could push Web Dynpro ABAP and what kind of performance numbers I could get from it. Interestingly enough, just the other day Todor Petrov of IBSolution GmbH posted a Web Dynpro Java Context – Speed test. I had started building the examples for this blog a day before this blog was posted, so the similarities are pure coincidence – although I do wonder if perhaps we both listened to the RIA weekly podcast. 🙂
So I set out to test a large number of records in the Web Dynpro ABAP table UI element.  I wanted to be able to test how long it took to load the records, scroll through the table and sort the table.  I used the performance monitor built into Web Dynpro ABAP (activated by pressing CTRL-ALT-SHIFT-P). The response times are then broken down by roundtrip, server and client rendering.
I recorded videos of all my experiments which are posted below – but not to keep anyone in suspense let’s just say that WDA didn’t even break a sweat with 100,000 rows so I needed to push things harder and decided to add an option to test with 1,000,000 rows.
After you have watched the video, you will see that even 1,000,000 rows resulted in a really nice user experience – sorting in around 3 seconds.  However the only drawback to this approach is that it does result in a large user session state – since the data is sorted in memory. The second option I decided to try out was using an ABAP Shared Memory object.  ABAP has a very nice technique for table sharing and copy on change that makes it easy to access and use
shared memory objects without expanding your session memory footprint.
Now the only drawback to this approach was that once you sort the table, it still has to be copied into the session memory of the user.  My final experiment was to try and use Context Paging. This Web Dynpro technique allows you to send small chunks of data to the Table UI element and have a server event triggered when more data is needed. This way I can go back to the database and read only small sections of data – yet the user can still sort and navigate through the entire dataSet. The response time was a bit longer for sorts since this option relies on SQL ORDER BY commands, but keeps the response time at quite usable levels while maintaining a tiny session memory footprint.

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo David Halitsky
      David Halitsky
      Thomas -

      Thoughtful and careful post, as usual.  Thanks.

      From 1995-1998 I had one hand doing maintainenance in "Model 204" (the conceptual father of Software AG's Adabas) and another hand doing coding in Powerbuilder (remember the "client-server model?) 

      I distinctly remember that Powerbuilder also had an option which let the system determine when new rows had to be fed to the screen.

      It never ceases to amaze me how the same ideas keep on coming around - like SAP's "robust" JAVA with compartmentalized vm's and the MUSAS's that used to run under IBM's MVS (Multi-User Single Address Spaces.)

      Finally, one off-topic request for another blog post from you on the following topic.

      I really really really liked your remark about how you can reduce the size of your session footprint by used Shared Objects, for the following reason.

      In high-volume high-concurrency environments, I assume you'll agree that it is critical to keep session footprints as small as possible in order to minimize session swap-in/swap-out time and avoid the time-honored phenomenon known as "thrashing".

      But as you know, if you try to religiously follow SAP's "no selects within loops" best practice, you have to read all your data up front into itabs, and do itab reads within loops instead.  And when you have a report that has to loop successively on several large itabs, this can increase the size of your swappable session footprint drastically (since the itabs are in the footprint, as you note.)

      To me, one of the main virtues of Shared Obects is that the provide a compromise solution that allow you to avoid selects with loops without increasing session footprint to ludicrous sizes.

      If you more or less agree with that last statement, I'd really love to see a blog from you on the topic, because folks will believe you right off.

      (And when you've finished that blog, I've got another request for a blog by you on how to solve recoverability issues when using Shared Objects in batch programs ...)

      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author
      First I have a few comments on memory optimization in ABAP in general.  It is never an exact science.  I think that good code optimizers should be just as aware of what is going on in a production system as the basis people. They should understand the different memory pool sizes, average utilization, etc. Sometimes increasing the session memory size can be the right choice given that you have studied and understand the cost:performance ratio. Memory is a resource to be used.  Keeping session state low for the sake of keeping large amounts of system memory free all the time doesn't benefit anyone.  In the end this is often a balancing act with some measure of trial and error thrown in. As my videos showed, I had three very different approaches that I could take - each with different cost/benefits.

      >To me, one of the main virtues of Shared Obects is that the provide a compromise solution that allow you to avoid selects with loops without increasing session footprint to ludicrous sizes

      I think regardless of how you use the data in Shared Objects you should keep several guidlines in mind.  Shared objects are most useful for data that is relatively static, will be accessed by more than a handful of users over a reasonably long period of time.  Personally I don't think shared memory is a good use for swapable cache - but instead something along the lines of a semi-persistent cache.

      So in your scenario if I only needed the outter itab temporarily, would I place it in shared memory to reduce the session footprint?  Probably not.  I think I would rather have the single session grown and then shrink during the short time I needed the outter itab to loop and lookup the inner itab values. I wouldn't want to potentially push other objects out of the shared memory area to make space for something I needed temporarily.  Be careful that you don't just push your thrashing problem to the shared memory area. 

      On the other hand if the outter itab values were fairly constant and were used by many users or many different reports; this might be an excellent case for storing them in the shared memory object.

      Once again this is a case-by-case decision. Let me give you an example from my former life as an SAP customer.  I had a program with had just such an outter itab/inner itab loop.  I needed to have as fast as possible performance on this and I was dealing with rather large datasets.  I experimented with different approaches. Ultimately for this application the fastest approach was to read the entire contents of outter and inner itab into memory and then perform the loop with a read binary search on the inner table.  There was a cost of having an excessivly large session state temporarily, but the benefit was the fastest possible calculation of the needed data.  The business made a decision that the cost was worth the benefit for this particular application.

      >I've got another request for a blog by you on how to solve recoverability issues when using Shared Objects in batch programs

      What kinds of problems are your refering to?  In general I try to approach Shared Objects pesimestic - I never assume that the object will already be in cache and on the first request I may always have to pay the penalty of filling the cache.  In the video you might have noticed the coding I had for attaching on read:
                wd_this->shma_handle = zcl_wda_table_shmo=>attach_for_read( ).
              catch cx_shm_no_active_version.
                wait up to 1 seconds.
      This try/retry block ensures that my processing logic doesn't move on until the share object is fully initialized. My root object implements the interface IF_SHM_BUILD_INSTANCE and therefore contains all the logic to auto-initialize itself on first request for an invalidated object.

      Is there a particular situation you have ran into in regards to batch?

      Author's profile photo David Halitsky
      David Halitsky
      Hi Thomas -

      Thanks so much for taking the time to compose such a comprehensive reply.

      Regarding batch recovery, it may be that I just didn't know how to answer a simple question asked by my boss.

      Here was the scenario.  For (SCM-)APO Means of Transport (MoT) and Quota (Q) data, the situation is slightly different.  On the (SCM-)APO side, there is a BAPI for each purpose.  However, in the ERP->APO "CIF" process (which is really just an qRFC between an ERP "CIF_SEND" function and an APO "CIF_RECEIVE" function), there is also a BAdI CIF exit for MoT-data but not for Q-data. (The reason for the lack of a Q-data CIF BAdI was explained to me down in the APO forum - it has to do with the fact that SAP can't possibly anticipate the kinds of quotas a customer might want to set up.)

      So anyway, since there was a CIF BAdI for MoT data, the question arose whether we should use this BAdI to store the keys of the Transportation Lanes being created in a "first-time" CIF or created/changed in a "delta-CIF".  That way, once the CIF was done, the MoT BAPI could read this list of keys to figure out what had been created/changed/deleted, rather than trying to figure this out by reading the valid-to/valid-from dates in the MoT table (which could get nasty.)

      So this seemed like a good idea to everybody, and so I said to them that it would be a great way to try out the Jung/Heilmann Shared Memory Object (SMO) approach rather than use the "EXPORT TO DATABASE" approach that you had showed me how to use successfully last year.  In other words, the CIF BAdI would write the create/change MoT keys to an SMO and after the CIF was done, the APO MoT BAPI would read this SMO to find out what MoT rows had been created or changed or deleted.

      But here's the potential problem my boss saw: regardless of whether a CIF is triggered on-line on the ERP side by "model activation", or run in batch mode, it's a long process.

      And he said to me: "what happens when if the lights go out in the middle of the run, leaving the SMO half-built.  Assuming the CIF can be restarted after the system comes back up, what about the SMO?"

      And I didn't know how to answer him so we didn't adopt the SMO approach, even though every one agreed it was very attractive in a lot of respects.

      What would be your answer?

      And more generally, should SMO's be avoided as "stores" built during batch update processes of all kinds (not just CIF)????

      Thanks for considering these questions.


      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author
      >And more generally, should SMO's be avoided as "stores"
      The short answer to your question is not they shouldn't be used as stores. SMO aren't good communication channels.  They are intended to be used as a cache. Ideally they should be self-creating and encapsulated so that they can reinstaniate themselves completely when needed again after any flush.  As I said, it is safest to take a pesimestic view to SMO and never depend upon them being there. There are many reasons why a SMO might flush (Share area is full/thrashed, Admin clears the shared pool, SMO times out, application server restarts, etc).
      Author's profile photo David Halitsky
      David Halitsky
      Darn - I hope he doesn't read blogs at SDN!!!!!

      Seriously, thanks for considering the question and for the advice.


      Author's profile photo B. Meijs
      B. Meijs

      A useful post for me, currently working on a combined BSP / Webdynpro ABAP project where performance demands are really high. We did a demo to proof that these performance demands are feasible using BSP and Webdynpro, and your blog is another proof for this.

      Ben Meijs

      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author
      Tonight I read the blog The One Million Records Challenge by Richard Monson-Haefel. He picks up my 1 million row challenge and tries to push things further to 10 million. Now we are starting to get into the area of "more records for the fun of it", but I can't resist trying it out just to see what would happen.  Now I have the advantage of running via a moderately sized server, but suprisingly 10 million rows does actual run.

      I was too lazy to whip up a new database table or change the label on the screen.  Instead I just read from the database 10 times appending to my internal table:
            DO 10 TIMES.
              SELECT * FROM zsflight_1m   APPENDING TABLE wd_this->sflight.

      Here is the initial read
      14 seconds isn't bad but a 1.3Gb session size is getting pretty crazy.
      I then did a sort - 26 seconds

      I should note that this sort is on a standard table. I could certain bump up that performance on certain sort columns if I made the table Hash or Sorted.

      Now I'm probably going to get a call from some server admin in the morning wanting to know what I did to his server last night. I'm also not sure what the business use case is for directly interacting with 10 million rows, but is nice to see how the architecture does scale - even when given unreasonable demands.

      Author's profile photo David Halitsky
      David Halitsky
      Not if it helps you avoid "selects within loops"

      Just kidding, just kidding.

      Seriously, though, I'm hoping you'll take a moment either now or perhaps in a separate blog to explain why the situation of an app server handling 10000 perfectly keyed or indexed select singles on behalf of 10000 different on-line "OLTP" users is any different from an app server handkling 10000 perfectly keyed or indexed select singles inside a loop on behalf of one "OLAP" user.

      Since SAP makes such a big deal out of "no selects within loops", there's gotta be a difference.

      But I'll be damned if I can figure out what it is.   The second case shouldn't even trigger any deliberate system slowdown of the user, because he's neither CPU-bound or IO-bound - in effect, he's just emulating 10000 users all by himself and sequentially rather than concurrently.

      I'm sure I'm wrong in this POV, but like I said,  I don't know why.


      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author
      Well no selects within loops is all about having efficient database access.  Calling from the application server to the database server means a network request - which is relativly slow compaired to other operations.  Selects within loops will generally cause many small requests to the database.  The optimal approach is to batch your requests (FOR ALL ENTRIES) or to pull records back in larger packages (SELECT INTO TABLE). 

      Now why is bad to have one user with the same memory spaces as 10000 users - well that's just selfish I guess. 🙂  If you only have enough memory for 10000 users and one user takes it all, that doesn't leave much for the other 9,999.  Maybe I am missing something in your question.  Of course you can always give this one really special user his own application server and controll access via logon groups (if the business decides he/she is worth the cost) and then a 1.3Gb session state won't effect anyone else.

      Author's profile photo David Halitsky
      David Halitsky
      OK - you've actually helped by reminding me of one thing that I wasn't taking into account: in SAP, a db request is a network request whereas in older DB systems with their own presentation layers,  a db request was just an EXCP (exec channel program) to a disk controller.  And I agree, that is an important factor: SAP gains independence from the database layer at the cost of speed of access to it.

      But let's agree to put that issue aside for a moment and decompose selfishness into its two components - space and time.

      The user who selects within a loop rather than invoking a for-all-entries is:

      a) keeping his session footprint smaller
      b) making his own run-time longer

      Vice-versa, the user who invokes a for-all-entries is:

      c) making his session footprint larger
      d) keeping his run-time shorter

      Comparing (a) with (c), it's obious that the for-all-entries guy is using space more selfishly and in certain cases helping to nudge the system closer to the threashing threshhold (because it takes longer for the system to swap his larger footprint in and out.)

      On the other hand, comparing (b) with (d), it's obvious that the select-within-loop guy is consuming time more selfishly, because the system has to worry about swapping him in and out for a longer period of wall time than if his program ran quicker (even though it takes less time per swap since his footprint is smaller.)

      So my point here is that time vs space arguments are complicated, and that indvidual program performance SHOULD NEVER be evaluated in isolattion from overall system performance. I know many basis folks who instinctively hate those big for-all-entries programs because they're like the big trucks on the high-way - they screw up the swapping algorithm the way trucks screw up traffic algorithms.

      OK - turning now to the question of the 10000 requests.

      Say we do a REAL stress-test of the type that is NEVER done anymore - say we get 10000 users to hit enter simultaneously and invoke 10000 select singles.  Well I think you'd agree that at any given point in time, a lot of these requests are going to be queued up at the app server, waiting to be passed to the db server.

      On the other hand, say we get one program to submit 10000 select singles from within a loop.  Then again, at any given point in time, a lot of these requests are going to be queued up at the app server.

      So yes - you're right - the one guy is creating the same queue-up as 10000 users, and he appears to be selfish for doing so.

      But here's the point - I am familiar with at least one DBMS whose scheduler algorithm was robust enough to penalize this guy - to slow him down so that in effect, he'd get each of his 10000 requests processed when the system was good and ready to swap him back in.

      So this kind system was really saying to the user - if you don't like how slow your program is running, run it off-line or buy BW/BI and run the equivalent there.

      This is a much better approach, in my opinion, than SAP saying to folks - "hey - go ahead and do those "18-wheeler Mack truck" for-all-entries OLAP programs so you can get some decent run-times without going off-line or to BW."

      Like you said, it's a complicated world.

      Author's profile photo David Halitsky
      David Halitsky
      PS - in the above response, I should have used the phrase "interval of time" rather than "point in time" when discussing the 10000 vs 1 siutations.  In other words, if you look at what happens in the system over a 10 second interval, you will find that the app server has received a certain number of db requests in that period of time, both in the 10000 case and the 1 case.
      Author's profile photo Former Member
      Former Member
      "I used the performance monitor built into Web Dynpro ABAP (activated by pressing CTRL-ALT-SHIFT-P)."-- I tried to use performance monitor on IE 6, not able to see it, could you please tell me how to get it.
      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author
      It is possible that the keyboard shortcut is only available in newer versions of Web Dynpro (I'm running on 7.0 Enhancement Package 1). It can also be triggered via URL parameter.  That URL parameter is sap-wd-perfmonitor=X.
      Author's profile photo Former Member
      Former Member


      I am not able to view the videos embedded here. can you please share the videos in a different link. Is context paging in ALV or POWL possible now?



      Author's profile photo Thomas Jung
      Thomas Jung
      Blog Post Author

      SCN no longer allows the embedding of videos from  Therefore I changed the embedding to a link.

      I coudn't comment on the support of context paging in ALV or POWL as I haven't worked on the Web Dynpro topic for over a year now.

      Author's profile photo Former Member
      Former Member

      Hi Thomas,

      Thanks alot for the vidoe links.

      In a another sdn post on the similar topic, I noticed that you've mentioned context paging in alv in possible from netweaver 7.2. I was wondering if its possible at all?