lunes, 25 de abril de 2011

Speeding up a seam application from the server side

Usually when developing a seam's based web application, one of the main complaints from the developers and users is how slow seems to be the webapp. This makes sense, since seam is a heavy stateful framework quite big with hundred of different files and option to optimize.

I always tought that the first thing before start coding a single line is try to understand what do you want and how you want to have it done with the tools at your disposal. Just to try to make it as easy as possible i'll write down some "tricks" or practices that could be applied so the application is properly tunned:

1. Conditional Rendering
The conditional rendering in JSF is extremely heavy in its use, because the expression is evaluated four or five times every time is called. A good practice in order to have the best performance possible is group the rendered attributes using a h:panelgroup tag so the attribute is only applied once per row or Item. Anyway if we need the rendered attribute in an item one option would be to use the facelets expression c:if, but since it is not a true JSF component it wont be rerender conditionally, because the tree is already resolved when we go to JSF handler. Also when a rendered expression is resolved it goes trough a huge amount of interceptors so we can avoid them using:

  • @BypassInterceptors: This annotation is intended to be used on methods that read the state of a component, as opposed to a method with behavior. After all, Seam is a stateful framework and espouses using objects as they were intended, to have behavior and state. The only problem is that Seam relies on interceptors to restore the state of conversation-scoped components, at least in Seam 2.0. In Seam 2.1, this behavior is disabled by default, so you could just add @BypassInterceptors to the method. Do not use in conversation-scoped components.
  • @Out: This annotation outjects a seam component, so it should be put in the object you want to outject. Outjection is a mechanism in Seam that takes the value of a JavaBean property on a component and assigns it directly to the name of a variable in the specified scope (such as the conversion scope). This should be used as much as possible when evaluating a property of a Seam Component.
  • The cost of use of rendered="#{benthicMsmntEditor.editing(_item)}" and rendered="#{_item == benthicMsmntEditor.itemInEditMode}" is pretty much the same, since the EL Resolver is quite fast and the Java Reflections works quite ok. Anyway for this issue we will explain a better approach later in the section 2.4.
Anyway if the forms are rendered conditionally, and some via Ajax, the reference data can be retrieved at the same time the forms are activated. So we avoid the huge loading at the beginning.

2. Not conditional Rendering
If you must display a form unconditionally, think about the most efficient way to prepare the data (it’s about time to use a cache).

3. Interceptors
An interceptor transfers the values of all fields holding references to entity instances directly into the conversation context, and then nullifies the values of those fields. It reverses the process at the beginning of the next call. The consequence is that without interceptors, your conversation-scoped object is missing state. Because of that, when working with large datasets inside of data tables or similar the performance can be seriously affected by these interceptors.

Rather than worrying about whether to use this interceptor, or locking the design of your application into its absence, it's best just to avoid the need to disable interceptors. Besides, there are still other interceptors that may need to be enabled on a component's methods (its all or nothing when you disable them) and working with outjected data is the fastest approach anyway. You can feel comfortable calling methods on Seam components in other areas of your page, but you should avoid doing so inside of a data table. So, don't call intercepted methods inside a data table (or in excess).

4. ConversationContext
When trying to resolve the value of a variable from the EL resolver chain its important to understand that jsf first and seam after will try to go through a chain of places to look for the value of the variable, this means that if the value is null, the time to resolve the variable is going to be significantly bigger than with a variable with value. In fact the standard EL resolver looks in the request, session and application scope; the seam EL resolver will check also the components, factories, seam namespace, seam context, and so on.
The solution to avoid this and at the same time be able to use null as value for our variables is look for the variable into the conversation context straight, so it goes much faster. But the conversation context is registered as an imported variable that seams takes some time to find, so what we can do is register a seam component called conversationScope which is associated with the conversation Context. So the steps are:

  • In components.xml add factory name="conversationScope" value="#{conversationContext}"
  • Call to the property as follows rendered="#{_item == conversationScope.get('itemInEditMode')}"

5. Consider the cost of your logic
Sometimes the cost of using logic within a table or page is significant enough to consider not using the logic at all. But the requirements needs it, how to do it then? One option is make 2 different pages, this is quite useful in the case of editing items, for instance, if we want to show different information when a table is in “edit mode” than if it is in “view mode” it’s a much better approach to use 2 tables and render one or another than using a single table with multiple rendered fields.

6. Use Ajax to load of portions of the page
Since the response speed from the server side is already tuned we can continue with the improvement trying to add ajax for the parts of the pages which are most likely that could be used. Ajax allows us to perform a load of some content of a page independently of the rest of the page. Said in other words, allows us to change the content of some part of a page without need of re-load the whole page.

The cost attached to it, as always there is some side effect, is the size of the page which is increased since the inclusion of embedded and external javascript code. So when using ajax we must focus in optimizing the size of our response.

It's preferable to use Ajax-based autocomplete rather than select menus with a large list of options, since making this switch can drastically reduce the speed of the initial rendering of the form. The user will likely be more patient when working on the field with autocomplete, and you can even keep the number of options delivered to a minimum as the user types.

1 comentario:

  1. Thanks to Dan Allen for this excellent article from where i got a lot of information http://www.jsfcentral.com/articles/speed_up_your_jsf_app_1.html

    ResponderEliminar