Sonntag, 14. Februar 2010

Performance of complex web pages with Seam, RichFaces and Facelets

I worked on a web app that basically consists of a fairly large page composed by sophisticated facelet composition components. The page is implemented with Seam, RichFaces and Facelets. The Seam components are mostly in page scope. After an initial GET request the user stays on the page and sends AJAX postbacks.

This page has a lot of cool features but users complained about slow response times, either for initial page loading or subsequent AJAX requests. Performance logging with a JSF phase listener revealed that more than 80% of the server side response time elapsed during the render response phase of the JSF lifecycle. I additionally checked (by profiling with JProfiler) that the performance bottleneck is not in the application code. I took the performance hints given in Dan Allens articles on JSF/Seam performance (part1, part2) into consideration but the page remained slow.

Then I activated facelet logging and found some surprising results:
  1. About 90% of the time in the render response phase were consumed by facelets.
  2. During AJAX requests facelets build the whole component tree from scratch even if only a (small) part would be sufficient due to rerendering.
Facelets was the performance killer! Considering this fact I succeded to improve the response time by relying on the following principles:
  1. Divide the page into different pages. The conversation context could be used as scope of the Seam conponents instead of the page context.
  2. Reduce the number of facelet compositions.
  3. Use real JSF components instead of facelet composition components.
  4. Only load the part of the page front up that is really needed. The other parts should only loaded if necessary. This can be implemented by conditionally including parts of the page by the use of EL evaluation: <ui:include src="#{clicked ? 'full.content.xhtml' : 'empty.content.xhtml'}">
    </ui:include>

    I double checked that facelets dynamically (lazily) loads the corresponding content.
  5. Embed AJAX action components like <a4j:commandLink> in <a4j:region selfrendered="true">. If selfrendered="true" is set then facelets is not at all called during the AJAX request because all information is obtained from the JSF component tree. Yet, there are some potential pitfalls with EL evaluation and with the <rich:columns> tag. Note that transient components are not rendered.

To sum up, there could be some non trival performance pitfalls while developing complex JSF apps. Mayby developers should give Apache Wicket a try (see JSF/Wicket performance comparison by Peter Thomas). I liked this web framework in another project...