Is it worth to use POJOs instead of EJB 3 in terms of performance? (with results, source and load scripts) 📎
I hear from time to time funny statements like "EJB 3 are too heavyweight", or "POJOs are lightweight". If I have the chance I ask directly how the term "lightweight" is defined - and get really funny (mostly inconsistent) answers. This remembers me somehow the answers I get for questions like: "What you actually meant by serviceorientation?" or even better "What do you mean by Web 2.0?".
Lastly I was asked "...It would be interesting to see if the same web app would be faster if you only developed a web-project using JSF, Derby and TomCat as Webserver..." (instead of Glassfish with EJB 3, JSF and Derby). This request made me really curious - and I built two independent projects. An EAR "EJBLoadTest" which consists of an Servlet and to cascaded EJB 3.
public class NumberGenerator extends HttpServlet {
@EJB
private NumberGeneratorFacadeLocal numberGeneratorFacadeBean;
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
out.println(numberGeneratorFacadeBean.getNumber());
} finally {
out.close();
}
}
}
With two local Session Beans. The first one fetches the millis, calls the second one and computes the result. The business logic is dumb, but it avoids GC optimizations.
@Local
public interface NumberGeneratorFacadeLocal {
public long getNumber();
}
@Stateless
public class NumberGeneratorFacadeBean implements NumberGeneratorFacadeLocal {
@EJB
private MillisProviderLocal millisProviderBean;
public long getNumber(){
return System.nanoTime() - millisProviderBean.getMillis();
}
}
The second bean is simple as well.
package com.abien.loadtest.ejb;
import javax.ejb.Stateless;
@Local
public interface MillisProviderLocal {
public long getMillis();
}
@Stateless
public class MillisProviderBean implements MillisProviderLocal {
public long getMillis(){
return System.currentTimeMillis();
}
}
I introduced the two beans, to maximize the overhead between the "POJOs" and EJB3. In simple applications there would be only one layer - and so less overhead.
The POJO application is identical. Instead of EJBs I used POJOs, and instead of an EAR, a WAR.
The POJO servlet comes with an additional "init" method. Because dependency injection doesn't work for POJOs (=classes without the @EJB annotation), the POJO creation was factored out in a init method:
public class NumberGenerator extends HttpServlet {
private NumberGeneratorFacadeLocal facade;
public void init(){
this.facade = new NumberGeneratorFacadeBean();
}
The same story in the facade - the DI was replaced with an constructor call:
public class NumberGeneratorFacadeBean implements NumberGeneratorFacadeLocal {
private MillisProviderLocal millisProviderBean;
public NumberGeneratorFacadeBean(){
this.millisProviderBean = new MillisProviderBean();
}
public long getNumber(){
return System.nanoTime() - millisProviderBean.getMillis();
}
}
For the load tests I used JMeter. I repeated the tests several times. The load generator and the Glassfish v2 were on the same machine. This differs a little bit from real world - but is perfectly suitable to show the EJB 3 overhead.
The results:
- EJB3: The throughput of the EJB 3 solution was 2391 transactions/second. The slowest method call took 7 milliseconds. The everage wasn't measurable. Please keep in mind that in every request two session beans were involved - so the overhead is doubled.
- POJO: The throughput of the POJO solution was 2562 requests/second (request - there are no transactions here). The slowest method call took 10 ms.
The difference is 171 requests / seconds, or 6.6% (therefore 3.3% for a single session bean).
Conclusion:
The dfference is less than expected (I expected an overhead over 10%)... However the POJO sample only works for idempotent / transient use cases. It wouldn't work for highly concurrent, database applications - I only created one instance for servlet - not request.... In that case, you will have to synchronize the access to the shared state e.g. with transactions or "instances per request". In that case a plain web container wouldn't be the simplest solution.
However the EJB 3 solution would work even with database and JMS without modification... The funny story here - the POJO solution required some more lines of code. :-).
I checked the projects (POJOLoadTest, EJB3LoadTest), the load script, as well as the results (as screenshots) into http://p4j5.dev.java.net. I used plain Netbeans 6.0 for this purpose - feel free to reexecute the load test again - and share the results :-).