Double the speed of (some of) your JSF pages (and dodge bugs too)
There was a thread on the JSF LinkedIn group about JSF performance and a number of people complained about the fact that as part of the restore view phase, JSF reconstructs the component tree including binding data to data tables causing unnecessary fetches of data from the database. This article looks at one way of avoiding the additional work to fetch the data.
When you first do a postback, the restore view phase rebuilds the tree, including fetching the data to bind it to the data table. Why you ask, well a good question. Typically this wouldn’t be an issue since we want the server state to match the client view state. However, as the developer, we know that for some cases these results are useless since we don’t anticipate using them for anything so fetching them is really a waste of time.
Let’s take a look at this problem in action. You can use the maven archetype to generate the project and code along or download the source from here (fastapp.zip).
- Create a new maven application using the jee6-servlet-basic knappsack archetype. This gives you an empty Java EE 6 application that can be run using Jetty or Tomcat from the command line.
- Add a new class that will be our backing bean that returns a list of paginated numbers but takes a long time (2 seconds) to do it.
@Named @RequestScoped public class DataBean { //indicates the page we are on private int pageNumber = 0; //lazy loaded reference to the results private List<Integer> data; //returns the list of data by lazy loading it public List<Integer> getData() { if (data == null) { data = generateData(); } return data; } //generates the list of data, think of this as an expensive DB operation private List<Integer> generateData() { System.out.println("Generating Data starting from "+pageNumber); //Sleep for 2 seconds while I access the database and do stuff try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //now we actually generate the data List<Integer> results = new ArrayList<Integer>(); for (int i = 0;i < 10;i++) { results.add(pageNumber+i); } return results; } //method to move to the next page in the list public void nextPage() { pageNumber = pageNumber+10; //invalidate the lazy loaded data data = null; } //method to move to the previous page in the list public void previousPage() { pageNumber = pageNumber-10; //invalidate the lazy loaded data data = null; } //getter/setter for page number public int getPageNumber() { return pageNumber; } public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; } }
We lazy load the results so for each request, we start with nothing and then load the results when the restore view phase looks for them. In the render response phase, the same results are used unless the next/previous page methods are called and the data value is set to null and invalidated. In this case when the data is requested in the render response phase, we see it is null and so it will be loaded fresh for the new page number.
- Now we modify the existing home.xhtml page to show a table and add two buttons to move to the previous and next pages.
<ui:define name="content"> <h:dataTable value="#{dataBean.data}" var="v_value"> <h:column> <f:facet name="header">Value</f:facet> #{v_value} </h:column> </h:dataTable> <h:form> <h:commandButton action="#{dataBean.previousPage}" value="Previous" /> Page #{dataBean.pageNumber} <h:commandButton action="#{dataBean.nextPage}" value="Next" /> <h:inputHidden value="#{dataBean.pageNumber}" /> </h:form> </ui:define>
Fairly straightforward, we just show the list of numbers and have buttons for navigating. We store the page number in a hidden field and post it back to our backing bean so we have a stateless setup. I also added a phase listener that I’ve used before to log the timing of the JSF requests that is documented in this post (Timing JSF Requests using a Phase Listener). Since this was for Seam originally I modified it by removing the logger code and just pushing the log text to
System.out
. You can turn it on and off by removing it from thefaces-config.xml
file in thelifecycle
section. - In a command console enter
mvn clean jetty:run
to start the server and go to the home page at http://localhost:8080/fastapp/home.jsf.
When you first go to the page, you trigger a GET request which will cause the data to be loaded one time and displayed. In our output log we see :
Generating Data starting from 0
Fantastic. At this point, we know the page took 2 seconds to render. If we click the next or previous buttons, we cause a postback which will restore the view (including rebuilding the data) and then change the page number which will invalidate the data before again rebuilding the data for the new page. We see this in the log as :
Generating Data starting from 0 Generating Data starting from 10
If you are using the phase listener to time it, you will see the two requests as :
//first request initiated Executed Phase RESTORE_VIEW 1 Generating Data starting from 0 Execution Time = 2228ms Executed Phase RENDER_RESPONSE 6 //click next page to trigger the postback and response Generating Data starting from 0 Executed Phase RESTORE_VIEW 1 Executed Phase APPLY_REQUEST_VALUES 2 Executed Phase PROCESS_VALIDATIONS 3 Executed Phase UPDATE_MODEL_VALUES 4 Executed Phase INVOKE_APPLICATION 5 Generating Data starting from 10 Execution Time = 4116ms Executed Phase RENDER_RESPONSE 6
You can see the first request took 2 seconds while the second request took over 4 seconds. The problem here is that the data is being fetched twice, once for the restore view and again for the rendering of the response with the new data as you can see in the phase listener log entries.
Solution
The solution to the problem is to skip fetching the data the first time altogether. It isn’t needed, it isn’t even used and it isn’t even the correct data (don’t worry, I’ll explain that later). In the FacesContext
object there is a method called getRenderResponse()
which returns true if the render response method has been called and we are in the rendering phase. Until we are rendering the page, we don’t really need our data so we only load it if this method returns true.
//generates the list of data, think of this as an expensive DB operation private List<Integer> generateData() { if (!FacesContext.getCurrentInstance().getRenderResponse()) { return null; } System.out.println("Generating Data starting from "+pageNumber); ... ... ... }
It’s that simple, now redeploy the application load the page and click next, this time, we only get one data fetch per page request even on postbacks because we don’t bother loading the data until we really need it in the render response and if you look at the timing, our pages are loading twice as fast since we only fetch half the data.
//initial request Executed Phase RESTORE_VIEW 1 Generating Data starting from 0 Execution Time = 2278ms Executed Phase RENDER_RESPONSE 6 //click the next button to trigger a postback - look, no data loaded until the response is rendered Executed Phase RESTORE_VIEW 1 Executed Phase APPLY_REQUEST_VALUES 2 Executed Phase PROCESS_VALIDATIONS 3 Executed Phase UPDATE_MODEL_VALUES 4 Executed Phase INVOKE_APPLICATION 5 Generating Data starting from 10 Execution Time = 2302ms Executed Phase RENDER_RESPONSE 6
So there you go, you can increase the speed of your JSF pages by only loading data when you really need it, I guess kind of lazy lazy loading data.
What about that bug we are dodging?
Ah yes, that thing, not sure if it can be called a bug but to see the problem, remove the code to check for the render response so we do the double data request on postbacks. Now load up the application and click the next button twice, and you should see the following log :
//initial GET request, we only load the data once Generating Data starting from 0 //Click next, trigger a postback with two data fetches Generating Data starting from 0 Generating Data starting from 10 //Click next again, trigger a postback with two more fetches. Generating Data starting from 0 --err, this isn't right it should be 10 Generating Data starting from 20
When we do the second postback, the first set of data fetched is starting from position 0. We are loading our data using the default pageNumber
value of 0. So not only are we loading data we don’t need, we are loading the wrong data. This could have consequences if say you are in a search page and the postback triggers an unconstrained search which may take longer than one with search parameters set.
The reason for this is that the restore view process takes place before the request values are applied so the pageNumber
is set to the default value of zero which makes the fetching of the data even more useless. I’m not sure it can be called a bug since the value hasn’t been set at the restore view phase The question is whether it is correct for the restore view phase to fetch data based on values that haven’t yet been initialized and if so, what’s the point?
A Feature?
You know, thinking about it, this might really be a feature, not a bug. It’s feasible to utilize the state of the pageNumber
value to determine whether we can return the results if we change its type to Integer
. We know that we don’t need to fetch the data if the page number is null
because we haven’t hit the read request values stage so we must still be in the restore view stage and therefore don’t need any data.
The only problem comes with the initial setting of the value when you first enter the page, what sets it from null to zero initially. You’d have to fudge around with the getters and settings, plus it would be hard to display that initial page of results since it would always be empty.
The source code for this project is available from here (fastapp.zip). To run, simply unzip, and in the console, type mvn clean jetty:run
or mvn clean tomcat:run
and go to http://localhost:8080/fastapp/home.jsf.
8 thoughts on “Double the speed of (some of) your JSF pages (and dodge bugs too)”
Comments are closed.
Hello, thank you for article, I working with JSF many years, and some times, I have performance problem if I have big data in the page.
The biggest problem memory use in the server side for each user, for it I use in general Request Beans, Sessions to less, but I think in JSF 2.0 problem solved
Very nice article, Andy!
I have encountered this problem some time ago, but I have solved it using the @PostConstruct annotation. The container will call a method annotated with @PostConstruct as soon as it has finished creating the bean. So the database query should be happening in this method, thus avoiding the if statement from your getter.
Hey Bogdan,
That wouldn’t solve the problem for cases where the data to be rendered is different from the data read in the restore view phase or in the post construct event. In the example, when you invoke the application you are changing the page number so you want to invalidate the data, and render a different set of data in the page. In the post construct event, you will read data from the database prior to the invoke application phase so you are using (at best) the old page number and at worst (and I suspect this is the case) the default value (0) for the page number.
Cheers,
Andy Gibson
Hi again,
I have rich datatable who generated data from DB
in managed bean I have method for datatable named
public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {
…….
I added
if(!FacesContext.getCurrentInstance().getRenderResponse()){
System.out.println(“return”);
return;
}
following in the start of this method,
but executed time was grow from 2499,2670 to 3304 – 5595 ms.
:)Now working twice slow..
What is the problem??????
I did new tests, executed time less, but no twice,only less 100-200 ms, if added check
if(!FacesContext.getCurrentInstance().getRenderResponse()){
// System.out.println(“return”);
// return;
// }
Armen, I don’t know without seeing the code, but in a nutshell the code to fetch the data should only execute once. How many times is it being executed and are you storing the result once it is loaded so it doesn’t need to run more than once. Also, which JSF implementation and version are you using?
Hi, rich faces data table always make a lot of calls to db, for example for one search in db, table can call count 6 times.. I used Apache Myfaces for JSF 1.2 in our application, where is one problem only , in the server side for each logged user used memory 50 mb… we added second frontend layer(dublicated)for solving this… Our tables are big but we use paging and lazy loading, in some pages I added your suggestion… Thanks
Also one important moment!
If with table in the page you have ajax actions u can not use
if (!FacesContext.getCurrentInstance().getRenderResponse()) {
return null;
}
optimization, because JSF can not find component from his tree…..