Thursday, June 7, 2007

Comparing Hibernate and JDBC (time issues)

I used JFluid(Performance Analysis tool) for my web application
Contact management.
The product uses Hibernate to interact with the database.
As an example I will take Hibernate methods and some statistics
to compare it with simple JDBC.

On the console when I started the server the output was this.




It has all the methods that the tomcat needs to start up. The profiling is perfect. It has four columns. The first column tells about the method name. The second one tells about the % of total time. The third one tells about the time in absolute terms and fourth one tells about the invocation time in absolute terms.

Now coming on the actual implementation.





In this picture one thing that is worth noting that the hibernate internally does not take as much of the time as the apache Catalina loader and other apache functionalities. Even the so called expensive process of creation of sessionFactory (87 ms this takes into account the time taken to read the configuration file and the time taken to build the factory.) taking no time in comparison to the server methods. But it takes comparatively higher times in comparison to other methods like getNamedQuery ( double the time than this 54ms ) and other methods like getterMehods (34 ms) still lesser time.

Now coming on to Hibernate specific methods.

We will divide the work of hibernate in three different categories.

· Loading of Configuration . It will include reading from the XML file, loading the mapping and getting the methods to deliver the result.

· Interaction with the database. It will include the creation of connection with the sql and the time taken to query and creation of tables.

· Displaying the result.

Before we take a dip into the categories I would like to say that the first category took around 0.75 times the time taken by second category. This can change drastically if we increase the number of users. Though the schema update time won’t change. The third category is not even in contention.

Among the categories that are listed above the time taken by the different category is

Coming onto the first category.

In the first category when we take out the methods those took the maximum amount of time are listed in the next paragraph.

The init() method of org.hibernate.cfg.Configuration() which is used to upload the configuration given in the hibernate.cfg.xml takes 2.9 ms. The add method of the same class takes 13.7 ms. The building of factory takes 5.62 ms and creationMapping() took 1.32 second. Rest all the processes took less than 1 ms. Creation of session(0.012ms) out of sessionFactory did not take much time ( as per the expectation., session is lightweight object) . So putting the creation of sessionFactory in the Plugin is the step in the right direction.

Moving onto the second category.

The second category mainly consists of one time process of dialect upload , creation of Connection and multiple time processes of query translation, sqlgenerator from HQL and fetching of data from the tables.

One time process of connection creation takes in total of 23 ms. The amount of time is spent in dialect upload ( that is equivalent of loading the driver in JDBC) is 43ms. Which compares badly with the direct JDBC Driver loading. Even the creation of connection takes more time than the usual JDBC connection creation.

This statistics can put Hibernate lover to think once again for the application where the time is important parameter. The other big demerit of Hibernate is the translation of HQL to sql that also takes 12 ms which can be saved in case of JDBC. Fetching data once the sql has been generated would take the same amount of time.

Grinder

The Grinder is a Java load-testing framework. It is freely available under the BSD-style open source license.

The Grinder makes it easy to create the activities of a test script in many processes across many machines, using a graphical console application. Test scripts make use of client code embodied in Java plug-ins. Most users of The Grinder do not write plug-ins themselves, instead they use one of the supplied plug-ins. The Grinder comes with a mature plug-in for testing HTTP services, as well as a tool which allows HTTP scripts to be automatically recorded. The Grinder 3.0 which I used is still in the beta version. It uses a scripting language called Jython.

How Do One Start Grinder.

  1. Create a grinder.properties file. This file specifies general control information (how the worker processes should contact the console, how many worker processes to use, ..), as well as the name of the Jython script that will be used to run the tests.
  2. Set your CLASSPATH to include the grinder.jar file which can be found in the lib directory.
  3. Start the console on one of the test machines:
   java net.grinder.Console
  1. For each test machine, do steps 1. and 2. and start an agent process:
    java net.grinder.Grinder

You can also specify an explicit properties file as the first argument. For example:

java net.grinder.Grinder myproperties

Working of Grinder

Now coming on to the working of Grinder.

The framework is comprised of three types of process (or program). Worker processes, agent processes, and the console. The responsibilities of each of the process types are:

  • Worker processes
    • Interpret Jython test scripts and performs tests using a number of worker threads
  • Agent processes
    • Manage worker processes
  • The console
    • Coordinates the other processes
    • Collates and displays statistics

As The Grinder is written in Java, each of these processes is a Java Virtual Machine (JVM).

For heavy duty testing, you start an agent process on each of several client machines. The worker processes they launch can be controlled and monitored using the console. One can run more than one agents from a single machine too.

Going on to working an example.

Jython is java adapted version of Python.

Writing Script:

Scripts must conform to a few conventions in order to work with The Grinder framework.

Scripts must define a class called TestRunner

When a worker process starts up it runs the test script once. The test script must define a class called TestRunner. The Grinder engine then creates an instance of TestRunner for each worker thread. A thread's TestRunner instance can be used to store information specific to that thread.

The TestRunner instance must be callable

A Jython object is callable if it defines a __call__ method. Each worker thread performs a number of runs of the test script, as configured by the property grinder.runs.

The test script can access services through the grinder object

The engine makes an object called grinder available for the script to import. It can also be imported by any modules that the script calls. This is an instance of the GrinderScriptContext class and provides access to context information (such as the worker thread ID) and services (such as logging and statistics).

An Example

This is an example of a script that conforms to the rules above. It doesn't do very much - every run will log Hello World to the output log.

from net.grinder.script.Grinder import grinder
 
# An instance of this class is created for every thread.
class TestRunner:
    # This method is called for every run.
    def __call__(self):
        # Per thread scripting goes here.
        grinder.logger.output("Hello World")

This is a simple script that says to write “Hello World” in the log file, everytime the suite is called.

Each worker process writes logging information to a file called out-host-n.log, where host is the machine host name and n is the worker process number. Errors are written to error-host-n.log. If no errors occur, an error file will not be created.

Although our simple test script can be used with The Grinder framework and can easily be started in many times in many worker processes on many machines, it doesn't report any statistics. For this we need to create some tests.

A Test has a unique test number and description.

Let's add a Test to our script.

from net.grinder.script import Test
from net.grinder.script.Grinder import grinder
 
# Create a Test with a test number and a description.
test1 = Test(1, "Log method")
 
class TestRunner:
    def __call__(self):
        log("Hello World")
 
Here we have created a single Test with the
test number 1 and the description Log method.
Note how must import the Test class in a similar manner to Java.