In the last blog post I demonstrated the impact of the correct type of annotations on performance of Sling Models. But there is another aspect of Sling Models, which should not be underestimated. And that’s the impact of the method which is annotated with @PostConstruct.
If you are not interested in the details, just skip to the conclusion at the bottom of this article.
To illustrate this aspect, let me give you an example. Assume that you have a navigation (or list component) in which you want to display only pages of the type “product pages” which are specifically marked to be displayed. Because you are developer which is favoring clean code, you already have a “ProductPageModel” Sling Model which also offers a “showInNav()” method. So your code will look like this:
List<Page> pagesToDisplay = new ArrayList<>(); for (Page child : page.listChildren()) { ProductPageModel ppm = child.adaptTo(ProductPageModel.class); if (ppm != null && ppm.showInNav()) { pagesToDisplay.add(child); } }
This works perfectly fine; but I have seen this approach to be the root cause for severe performance problems. Mostly because the ProductPageModel is designed the one and only Sling Model backing a Product Page; the @PostConstruct
method of the ProductPageModel contains all the logic to calculate all retrieve and calculate all required information, for example Product Information, datalayer information, etc.
But in this case only a simple property is required, all other properties are not used at all. That means that the majority of the operations in the @PostConstruct
method are pure overhead in this situation and consuming time. It would not be necessary to execute them at all in this case.
Many Sling Models are designed for a single purpose, for example rendering a page, where such a sling model is used extensively by an HTL scriptlet. But there are cases where the very same SlingModel class is used for different purposes, when only a subset of this information is required. But also in this case the whole set of properties is resolved, as it you would need for the rendering of the complete page.
I prepared a small test-case on my github account to illustrate the performance impact of such code on the performance of the adaption:
- ModelWithPostConstruct contains a method annotated with
@PostConstruct
, which resolves a another property via an InheritanceValueMap. - ModelWithoutPostConstruct provides the same semantic, but executes the calculations lazy, only when the information is required.
The benchmark is implement in a simple servlet (SlingModelPostConstructServlet), which you can invoke on the path “/bin/slingmodelpostconstruct”
$ curl -u admin:admin http://localhost:4502/bin/slingmodelpostconstruct test data created below /content/cqdump/performance de.joerghoh.cqdump.performance.core.models.ModelWithPostconstruct: single adaption took 50 microseconds de.joerghoh.cqdump.performance.core.models.ModelWithoutPostconstruct: single adaption took 11 microseconds
The overhead is quite obvious, almost 40 microseconds per adaption; of course it’s dependent on the amount of logic within this @PostConstruct
method. And this postconstruct method is quite small, compared to other SlingModels I have seen. And in the cases where only a minimal subset of the information is required, this is pure overhead. Of course the overhead is often minimal if you just consider a single adaption, but given the large number of Sling Models in typical AEM projects, the chance is quite high that this turns into a problem sooner or later.
So you should pay attention on the different situations when you use your Sling Models. Especially if you have such vastly different cases (rendering the full page vs just getting one property) you should invest a bit of time and optimize them for these usecases. Which leads me to the following:
Conclusion
When you build your Sling Models, try to resolve all data lazily, when it is requested the first time. Keep the @PostConstruct
method as small as possible.
One thought on “Sling Models performance, part 2”
Comments are closed.