JavaServer Faces (JSF) has a reputation for having poor performance. Some claim that this "runtime tax" is simply the cost of using a component-based abstraction layer. After focused research, I have determined that by following a handful of best practices, you can get your JSF data tables to perform almost as well as hand-crafted HTML while still being able to retain the benefits of developing with an event-driven programming model. I begin by identifying some performance problems that occur when using JSF UI components, Seam components, and the EL carelessly, and then present, through a series of four lessons, ways to eliminate these problems one-by-one until you have observed a remarkable, two orders of magnitude improvement.
All the test results reported in this article were gathered on a Lenovo R60 with 2.5GB RAM, Dual Core T2300 @ 1.66Ghz processor running Ubuntu Linux 7.10. The application was built using Seam 2.0.3.CR1, Sun JSF 1.2_04-b16-p02, and RichFaces 3.2.2.GA. It was deployed to JBoss 4.2.2.GA running on Sun JVM 1.6.0_03. The timing results are shown in six progressive phases. Each result shows the total request time and the time to render a data table with 50 records. All metrics were captured using the FireBug extension for Firefox.
Developing a data management application is just a matter of getting data up on the screen in tabular format, correct? Oh, right, and being able to filter the data. Ah, and also allowing the data to be changed. Unfortunately, once those challenges are behind us, we tend to wash our hands of the application and move on. But the principle goal of most web applications is to enable users to perform their work more efficiently than they did before we introduced our "solution." In fact, none of those fancy features you add have any value at all if you can't improve the user's productivity. That's why, before you step away, you have to make sure that you have addressed the issue of performance.
My colleagues and I recently completed the first stage of an open source data management application based on JSF, Seam, and RichFaces in which we addressed this very concern. The application, named EDAS2, was developed for a group of scientists for managing water quality data (stored in the WQX database schema). Now, you have to understand that these scientists, they like their data. Hordes of data. And they like to view it all at once. So much, in fact, that it tends to cause the browser to crash. Naturally, we needed to condition the scientists to some degree that browsers have limits. But regardless, we were going to be dealing with large data sets. Our goal was to make sure that working with those data sets was not painful.
This article documents the bottlenecks that we discovered and a set of best practices for eliminating them. But we went beyond merely removing obstacles in performance. We tuned the application to the point where paginating, sorting, and filtering the data is actually faster than any desktop application our scientists had ever used. Find that hard to believe? Read on.
About the EDAS2 application
The intent of the EDAS2 application is to house and analyze water-quality measurement results. The results are taken from a location, known as a monitoring location, during a given visit, known as an activity. There are various types of results, depending on what is being measured. In this article, we will be focusing on the benthic measurement result, which in layman's terms is a sampling of mud with bugs in it. That data is recorded on site and later entered into the database and analyzed using the EDAS2 interface.
There isn't anything revolutionary about the interface of the EDAS2 application. Rather, the emphasis is on efficiency. We want to provide the experience of the MS Access database-which our scientists are currently using to manage this data-in a web application.
The application has two types of views. The first is a list view, which displays a paginated table of records for the currently selected parent entity, such as monitoring location, activity, or result. You will learn shortly that what makes this interface efficient is that it offers in-place editing of each row (it also has a floating popup dialog for detailed editing of the row).
The editable data table
The key feature of this application is that the data rendered in each table can be modified in place. To implement this functionality, we decided against using an off-the-shelf grid editor from a JSF component library. Instead, we took the RichFaces step-wise approach by building a composite, Ajax-enabled component using the partial page rendering technology that the Ajax4jsf core provides.
Ajax4jsf provides a set of tag libraries that can tie a JSF generated event to the rerendering of one or more regions of the user-interface. Those regions are identified by their JSF client IDs. When the JSF event is sent to the server, instead of the JSF servlet returning an entire page response, it returns only fragments of the HTML. Ajax4jsf then cuts out the old branches from the live view and stitches in the replacements returned from the server. The result is that the user observes the page updating without any noticeable refresh, in real-time, so to speak. And Ajax4jsf's declarative approach let's us fine-tune this behavior.
Figure 1 provides a view of the editable data table with one of the rows in edit mode.
Figure 1. The data table demonstrating its single-line editing capabilities.
When the page is first rendered, all rows have an edit and delete button. Clicking on an edit button puts the corresponding row in edit mode, at which point the outputs in the selected row become inputs. From edit mode, the user can make changes to the visible data and either save or cancel the update, which returns the table to read-only mode.
The strategy we use to deliver this row-level editing functionality is to have two UI components in each column of a standard JSF data table: one output component (e.g., <h:outputText>) and one input component (e.g., <h:inputText>). We then use the JSF rendered attribute on each component to control which one is displayed.
A backend supported by Seam
To support our editable data table, we put together a small hierarchy of classes that are instantiated as Seam components to manage the query (filtering, pagination) and the editing process (select row for editing, update, delete, cancel). We chose to put the components in Seam's conversation scope. The conversation scope is a slice of the HTTP session that is managed by Seam and associated with a sequence of pages through the use of a special request token (known as the conversation id). The conversation provides the developer the convenience of using the HTTP session without the memory leakage concerns, since the conversation has a much shorter lifetime and is kept isolated from other parallel conversations in the same session.
We chose to leverage the conversation for three reasons:
- to reduce the number of times the database needs to be queried
- to ensure the record remains managed by the persistence context while being edited
- to maintain the search criteria and pagination offset
Using the conversation has the added bonus of making previously viewed result pages load faster since the records are already sitting in the persistence context (i.e., the first-level cache).
Given all the benefits the conversations provide, you may be wondering where the performance problem is. Let's take at look at where things began to go wrong and what we did about it.