JSF Performance Tuning
I had the chance to talk about JSF performance tuning at JAX Con in Mainz, Germany. The slides are available only in German, so here is an English summary of what I was talking about.
Introduction
Performance-tuning and -optimisation for (JSF-) web applications is a huge topic that can be very challenging. Measures can be taken at different technical levels:
- JVM level
At this level we usually deal with stuff like Garbage Collection, Heap size, etc. - JSF level
At the JSF level we take a look at the performance impact of some core concepts like the lifecycle or component tree. Implementation specific behavior is also worth a look … - WEB level
Here we talk about concepts like caching, requests and resources. Though the web level is totally agnostic to the technology used, we need to find Java- and JSF-based solutions.
There is a number of measures that can be taken to improve the performance of JSF applications in general and there are some requirements for those measures that make them particularly interesting from a pragmatic point of view:
- Positive effect on
- JSF Response Time (this is the overall time needed for JSF to create the response to a request)
- Resource Loading Time (the overall time needed for the browser to load all resources for a document eg. stylesheets, scripts, images, …)
- Network Latency
- Minimum impact on
- application architecture
- programming model
- deployment model
- Integration into the existing toolstack
- Usage of existing extension points
That said, we can focus on measures that comply with above requirements:
The component tree and why size matters
The component tree is a fundamental concept in JSF. The general programming model of JSF is based on a declarative definition of views using xhtml markup. JSF components are placed within a view by using their appropriate tags. The xhtml definition is parsed and an instance of the component class is created for every component that was found in the xhtml code. These instances are organized in a tree structure that resembles the logical structure of the view. This component tree is used throughout the entire JSF lifecycle:
- Phase 1 (Restore View):
The component tree is created or restored if it has already been created in an earlier request. - Phase 2 (Apply Request Values):
Components are updated with the values sent with the request e.g. form inputs. - Phase 3 (Conversation/Validation):
Converters and validators are applied on input components. - Phase 4 (Update Model):
If validation succeeded, the validated values from the input components are written to the model. That means that the bound model beans setter methods are invoked. - Phase 5 (Invoke Application):
This is where the application logic is expected to be invoked. That means that action methods and listeners that were attached to components will be executed. - Phase 6 (render Response):
Traverse the component tree and execute each components renderer to create the html markup for the view.
The component tree is involved in every lifecycle phase and thus potentially traversed several times. This suggests that the size of the component tree has impact on the duration of the JSF lifecycle und thus on the performance of the application.
We therefore conducted a measurement of the lifecycle phases in relation to the size of the component tree.
Figure 1: Duration of Lifecycle Phases in relation to component tree sizes.
Each data row belongs to different sized component tree. Trees with 10, 100, 250, 500 and 1000 components per view were used.
The following diagram shows for each phase the proportionate amount of time taken in the lifecycle.
Figure 2: Proportionate amount of time taken in the lifecycle
The diagrams shows that
- A direct correlation exists between the size of the component tree and the amount of time taken for each lifecycle phase to pass.
- Phases 1 and 6 are the most time consuming phases in the lifecycle
The sample application did not contain any business logic, hence the time taken for phase 5 is close to zero. On the other hand, the sample application was designed so that all created components had to to be validated, so the duration of phase 3 is rather long-lasting.
With the information given, we can assume that having small-sized component trees is beneficial for the runtime behavior of a JSF application.
Less than 50 components per view are rather seldom
Looking at the diagrams scales, one might ask the question if it is common for a JSF application to have several hundred components per view. While this is totally dependent on the individual application, we were able to gather some numbers from our customers projects. We found out, that views with less than 50 components are rather seldom. In fact, we encountered some exceptional cases with more than 3000 components on a single page. Why are there so many components in typical JSF views? And is having a lot of components necessarily a bad thing? To answer this questions, we have to recall that the component tree is traversed several times during a lifecycle run. So every unnecessary component may have negative impact on the response time. So where do all these components come from? There are quite a number of reasons and we found these to be the most common:
- Unnecessary usage of components
- Usage of composite components where custom tags, decorators or includes are sufficient
- “Dead code”: Though setting the rendered attribute to false will prevent components from showing up on the client side, they are still part of the component tree
Especially the overuse of composites seems to be a common problem: Composites make it very easy to create reusable units. A lot of developers tend to use them to create own tags or even for templating purposes. While it is convenient to do so, composites are turned into full-fledged components and thus become part of the component tree. It is necessary to emphasize that it is very easy to create custom tags in JSF. Contrary to composites, custom tags do not take part in component-specific actions like conversion and validation and thus do not burden the component tree.
Implementation specific and performance relevant behavior
We conducted a comparative study about the performance impact of large component trees on JSF implementations. One of the key findings was that Mojarra behaves significantly slower than MyFaces.
According to Mojarras issue tracker, this problem has been addressed recently, so coming releases of Mojarra may not suffer from this performance issue. However, for existing applications that use a version of Mojarra up to 2.1.21, switching from Mojarra to MyFaces may be an appropriate action to enhance the JSF response time.
Enhanced Resource Loading
JSF 2.0 introduced an extensible and easy to use resource handling mechanism. Resources like Stylesheets and JavaScript can easily be organized in libraries and referenced in views or component implementations. JSF renders links to the referenced resources and serves resource requests. All this is based on the ResourceHandler API, one of JSF’s extension points.
Fewer requests, faster applications
A common approach to web performance optimisation across different technologies is to reduce the number of HTTP requests issued to the server by combining resources of the same type into a single file. While this can easily be done manually, it is not desirable: Putting all JavaScript code in a single file breaks the concept of modularity and makes it very hard to keep a maintainable codebase.
JSF allows to extend the ResourceHandler facility with a custom implementation that combines all scripts and stylesheets, renders appropriate links pointing at the combined resources and serve requests for them. We may do so in a custom implementation or make use of existing frameworks. ICEFaces and RichFaces both offer a customizable approach to combined resources but the one I like to describe in detail is offered byOmniFaces: The CombinedResourceHandler.
OmniFaces comes with a ready-to-use implementation of ResourceHandler. All we need to do is to configure it in faces-config.xml:
1
2
3
4
5
| < application > < resource-handler > org.omnifaces.resourcehandler.CombinedResourceHandler </ resource-handler > </ application > |
OmniFaces will then combine all JavaScript files used on the same page into a single file and all the stylesheets in another. While this effectively reduces the number of HTTP requests issued to the server it also has the drawback of preventing resources used on multiple pages from being cached by the browser. Scripts that are used on several pages will be part of several combined script resources, so while the total number of scripts is reduced, the total amount of transferred bytes will increase due to the redundant existence of some scripts in combined resources. Luckily OmniFaces provides an easy way to exclude resources from being combined. We can do so by setting the target attribute to “head”. While this is the default for JSF, it also tells OmniFaces to include the resource or to exclude it when the attribute is not set:
1
2
3
4
5
6
|
< h:outputScript library = "js" name = "scriptB.js" target = "head" /> < h:outputScript library = "js" name = "scriptC.js" target = "head" />
< h:outputScript library = "js" name = "jquery-1.9.1.min.js" /> |
Using this approach, resources can be excluded on a per-view basis. If this is not desirable, exclusion can also be configured globally:
1
2
3
4
5
6
7
8
| < context-param > < param-name > org.omnifaces.COMBINED_RESOURCE_HANDLER_EXCLUDED_RESOURCES </ param-name > < param-value > javax.faces:jsf.js </ param-value > </ context-param > |
Finally, OmniFaces CombinedResourceHandler is able to suppress resources. This comes in handy to e.g. block stylesheets or scripts of component libraries:
1
2
3
4
5
6
7
8
| < context-param > < param-name > org.omnifaces.COMBINED_RESOURCE_HANDLER_SUPPRESSED_RESOURCES </ param-name > < param-value > primefaces:primefaces.css </ param-value > </ context-param > |
Adjusting Response Headers
JSF typically sends the expires response header set to seven days in the future for every resource request (in production stage). Sometimes it is desirable to adjust this period but JSF does not offer a way to do so out of the box. We can however extend the ResourceHandler and Resource classes. Instances of Resource are typically created by a ResourceHandler and offer the method java.util.Map Resource#getResponseHeaders(). By creating our own Resources we can define the set of response headers that should be sent when servicing requests for those resources.
Swap out resources
By now we are able to reduce the total number of resource requests in a JSF application by leveraging caching concepts and through resource combination. Time to think about relieving the application server from servicing resource requests at all. Seriously, why should the application server be burdened with the task of servicing static resources when a simple webserver is better suited? By swapping out static resources to a simple web server, preferably in a different network segment or even outside of our companies network, we could improve the performance of a JSF application even further. The problem here is that until now JSF managed the whole process of resource handling. Moving the resources to another machine implies to:
- copy the resources kept so far under WebContents/resources to the new machine during deployment
- setting up a web server
- configure JSF to create links to resources on another machine/network/URL
While 1) is relatively easy to accomplish using standard build tools like Maven or ANT and 2) being also an easy task, 3) is a bit more challenging.
It is desirable to have the static resources on the same machine during development time but to swap them out when entering production stage. Links to the resources will have to point to a location outside of the application and we need JSF to create and render those links. This can again be achieved by customizing the ResourceHandler and related Resource classes. Creating an own javax.faces.application.Resource enables us to let Resource#getURL() return the appropriate URL for every referenced resource during render time.
It is desirable to have the static resources on the same machine during development time but to swap them out when entering production stage. Links to the resources will have to point to a location outside of the application and we need JSF to create and render those links. This can again be achieved by customizing the ResourceHandler and related Resource classes. Creating an own javax.faces.application.Resource enables us to let Resource#getURL() return the appropriate URL for every referenced resource during render time.
OmniFaces also offers a nice solution for this scenario. We can use OmniFaces CDNResourceHandler for this purpose and we can even combine it with the already mentioned CombinedResourceHandler.
Once configured in faces-config.xml the same way that we already did with CombinedResourceHandler, we can define a mapping between resources and their external URL in web.xml:
Once configured in faces-config.xml the same way that we already did with CombinedResourceHandler, we can define a mapping between resources and their external URL in web.xml:
1
2
3
4
5
6
7
8
| < context-param > < param-name > org.omnifaces.CDN_RESOURCE_HANDLER_URLS </ param-name > < param-value > js:jquery.js=http://code.jquery.com/jquery.js </ param-value > </ context-param > |
OmniFaces will now create links to the mapped resources on the webserver. The mapped resources will be requested by the client browser from the webserver, relieving the application server from the load generated by requests for static resources. This way, our JSF application can easily make use of the services offered by CDN providers with only a minimum of development effort.
Use Compression, your browser can handle it since years
Admittedly, this is a an easy suggestion to make but a surprisingly large number of projects that we have seen did not use any compression at all. Using GZip compression on the response is a configurable feature of the container and has nothing to do with JSF. The following lines show how to do it for Tomcat:
1
2
3
4
5
6
| < Connector port = "8080" protocol = "HTTP/1.1" connectionTimeout = "20000" redirectPort = "8443" compression = "on" compressionMinSize = "1024" compressableMimeType = "text/css,application/javascript" /> |
Some final words
In this lengthy post I showed some easy to take measures to improve the overall performance of JSF applications. However, these are suggestions and ignoring them won’t inevitably result in poor performance of a JSF application. Subsequently, following them is no guarantee to solve all of your performance issues. This is a non-comprehensive list of easy winners. The measures described here can help to improve performance with a decent amount of effort and were successfully applied in real-world projects. However, there are a lot of other interesting performance related topics that are worth to take a look. We did not actually talk about EL Resolvers and I have not mentioned memory anywhere in this article …
No comments:
Post a Comment