JSF Central - Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude - Part 1
JSF Central

 
 Home 
 
 Products 
 
 Articles & Books 
 
 Resources 
Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude
 
Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude - Part 1
(continued)
by Dan Allen
05 Feb 2009 03:00 EST

In the first of this two-part article, Dan Allen discusses some common performance problems you may encounter when using JSF components, Seam components, and the EL. You'll learn about the set of best practices for eliminating them that led to an improvement of two orders of magnitude in the performance of his application.


As the EL resolver chain seeks out a variable, it becomes increasingly more aggressive. The standard EL resolver looks in the familiar places: the request, session, and application scope. It then turns the task over to the Seam resolver, which is where things start to slow down. Seam has lots of different places to look to resolve a variable: a component, a factory, a Seam namespace, a Seam context, and the list goes on. Thus, not finding a variable comes at a high cost.

So the solution is simply to avoid referencing a missing variable, right? Well, what happens when a null value for a variable is meaningful in your application, as is the case of our editable data grid. A null value for the itemInEditMode variable means the row is not in edit mode. Unfortunately, the EL resolver chain doesn't know that a null value means something, and will keep working through its crib sheet until it has tried all possible combinations. Thus, we need to find some way to tell Seam exactly where to look rather than allowing Seam to send out its search party, so to speak.

Again, taking advantage of the flexibility afforded to us by the JBoss EL, we can reach directly into the conversation context to look for the row in edit mode:

rendered="#{_item == conversationContext.get('itemInEditMode')}"

Here's the reward we get for telling Seam exactly where to look:

Stage 3 timing results (50 rows)
Request Elapsed time of request (ms) Time to render table (ms)
1 491 207
2 495 224
3 493 201
4 444 211
avg 480.8 210.8

We roughly doubled our performance and now have 100 rows coming in under a second, with an order of magnitude improvement over the first run. But there is still a slight bottleneck. The variable _item is stored in request scope by the data table and is resolved quickly, but the variable conversationContext is a pseudo-variable that Seam interprets after looking in all the usual places for a real variable named conversationContext. Not only that, conversationContext is an imported context variable, the qualified name being org.jboss.seam.context.conversationContext. It turns out that referencing a context variable in an imported namespace has a measurable cost associated with it. A better choice would be to pull the result of this lookup somewhere closer so that Seam doesn't have to keep searching for it. We can set that up using an alias (an event-scoped factory) in the Seam component descriptor named conversationScope (to match requestScope, sessionScope, and applicationScope provided by the standard EL resolver):

<factory name="conversationScope" value="#{conversationContext}"/>

We now reference this name in our rendered logic:

rendered="#{_item == conversationScope.get('itemInEditMode')}"

Here's how the timing results improve:

Stage 4 timing results (50 rows)
Request Elapsed time of request (ms) Time to render table (ms)
1 399 161
2 373 150
3 458 207
4 560 163
avg 447.5 170.25

Those are the kinds of numbers we want to see! Just as I mentioned at the start of this article, there was likely a single culprit that was squandering a majority of the rendering time. It turns out to have been the logic in the rendered attribute of components within a data table. But really any logic inside of a data table has to be optimized because it's going to be compounded by the number of rows being rendered. For instance, you might be conditionally rendering columns based on the user's preferences. That brings us to the third lesson.

Lesson #3: Be extremely frugal with the logic you use within a data table

Incidentally, I thought about using an action listener to toggle the rendered state on components in a row when the user clicks on the edit button, since that's the "object-oriented" way of doing things. Unfortunately, the design of the data table is extremely naive and does not support this usage pattern. A data table doesn't have any concept of rows, only columns. The rows are introduced dynamically based on the data fed to the table (they are not represented in the state of the component tree). Thus, if you change the rendered attribute on a component in one of the rows, you end up affecting every row in the table. The dynamic nature of the data table leads to many other problems in JSF, including the "ghost click" which I discuss in my book, Seam in Action

If you are committed to squeezing as much performance as possible out of your page, then there is one more way you can optimize the speed of the rendered logic: don't use it. I'm not suggesting that we throw out the editable grid functionality. If you think about it, that logic only needs to be performed once the user has selected a row. Before that time, you know that you only need to display the table in read-only mode (and you know which controls to provide in that case). Thus, the best thing to do is split the table into two, one that has the rendered logic in the columns and one that does not, then toggle the rendering of the entire table. That way, the person just browsing the data does not have to pay the tax of checking for the row selected for editing. While this does increase the amount of code to maintain, it introduces the possibility of having different columns displayed when the user is editing than when they are just viewing (or even having the table look different in some way). You can move common code into templates to prevent duplication. Of course, the performance is now going to increase noticeably.

Stage 5 timing results (50 rows)
Request Elapsed time of request (ms) Time to render table (ms)
1 537 174
2 355 127
3 372 127
4 374 127
avg 409.5 138.8

You are probably feeling pretty happy with the progress so far. Where to next? In each of the performance results, I have provided two columns of data for a reason: to emphasize that we are paying yet another tax to render the remainder of the page. That is the focus of the next round of optimizations. Obviously, the size of this tax is going to depend on what else you have on your screen and won't necessarily amount to the ~270ms appearing in these test results. Regardless, the amount of this tax now exceeds the cost of the product and we need to do something about it. That's where Ajax comes in.

Cutting costs with Ajax

The bottleneck in any decently performing web application is the process of the browser requesting and ultimately rendering a new page. What makes the process worse is that it happens synchronously, forcing the user to wait until it finishes. It's extremely disruptive and it makes the application feel slow. A far better approach is to have the browser only replace the portions of the page that need to be changed and to insert those changes into the page when they arrive (i.e., partial page rendering), which doesn't interrupt what the user is currently doing (or at least keeps the disruption localized).

The exchange just described is achieved using Ajax. Fortunately, the RichFaces component library for JSF makes adding Ajax interactions to a page extremely straightforward. In the next part of this article, you'll learn to use RichFaces' partial-page rendering to only update the data table when the user change its state, such as to select a row for editing or paginating the table, thus eliminating the tax that comes with rerendering the entire page. Once this change is made, the two orders of magnitude performance boost will be realized.

Resources



RSS feed(all feeds)

The Editor's Desk
Podcasts
Inside Facelets
In the Trenches

Site version 1.83  Report web site problems

Copyright (C) 2003-2015 Virtua, Inc. All Rights Reserved. Java, JavaServer Faces, and all Java-based marks are trademarks or registered trademarks of Oracle Corporation. in the United States and other countries. Virtua, Inc. is independent of Oracle Corporation. All other trademarks are the sole property of their respective owners.