BRIDGING
JAVA OBJECTS AND RELATIONAL DATABASES
Important note: This document covers very early releases of OJB (0.1.30 - 0.1.48). Some technical details described here do not apply to the current releases of OJB. The test results presented here should be carefully checked against current releases.
© Thomas Mahler, December 2000
As a part of a J2EE architecture project my team performed a performance analysis for IBM WebSphere.
Our aim was to study different persistence mechanisms under a variety of application scenarios in order to find a solution that provides the best compromise of performance, scalability and architectural “beauty”.
This article presents a rough overview of our approach and our test results.
We decided to implement real-world test-scenarios against an existing database. We used a database with the german postalcodes (270.000 rows) as a base for the following scenarios:
1. Scenario "find all postalcodes of the city 'Baden-Baden' and fully read each entry"
2. Scenario "find all postalcodes of the city ‘Berlin’ with streets named Hauptstrasse and fully read each entry"
3. Scenario "update all postalcode entries for city 'Baden-Baden'"
4. Scenario "Iterate through 1000 entries and update 32 entries"
5. Scenario "Iterate through 500 entries and update randomly selected entries"
These scenarios represent activities (and their distributions) that are typical for applications that we build for our customers. We did not test INSERT operations. Insert operations modify the index structure and may lead to performance degradations for later tests. Thus we ommited inserts to keeps our tests reproducable.
The study of relationships between entities was beyond the scope of the present analysis.
It is important to note that the strucure of the scenarios has a deep impact on the test results. Our scenarios may be not suited to represent the call structure of other applications.
We used the JUnit framework to implement our scenarios as TestCases. We used the JUnitEE Servlet based html Testclient. We chose the servlet based client to eliminate network overhead between client and EJB server.
We identified the following operations as the “atoms” of our scenarios:
Looking up an object from the db. I.e. perform a “SELECT FROM …” and materialize the object from the returned ResultSet.
Reading object attributes i.e. calling getters
Setting object attributes and Updating the underlying database row.
We used a simple “Profiler” that allows to count method calls and to accumulate the time spend in these method calls.
Container managed Entity Beans (CMP) with VAP (IBM VisualAge Persistence) generated JDBC code. This is the pure IBM way of implementing CMP with VisualAge for Java and WebSphere.
BeanManaged Entity beans (BMP) that use the ObJectBridge PersistenceBroker for performing DB read/write operations and object caching.
A pure JDBC reference implementation that does not run within an EJB container. It represent a “normal” JDBC application using prepared statements.
The Application server:
IBM Netfinity M10
- 4x Pentium XEON 400 MHz
- 2 GB RAM
- Windows NT 4.0 Server, SP 6
- WebSphere 3.5 Advanced Edition
- CMP Code generated by IBM VisualAge 3.5 with IBM VAP
- BMP Code uses the ObJectBridge 0.1.30 release
- DB UDB Client 6.1 and respective JDBC App-Driver
Database server
IBM Intellistation Mpro
- 2x Pentium III 500 MHz
- 512 MB Ram
- NT Workstation 4.0 SP 6
- IBM DB2 UDB 6.1
Testclient
IBM PC
- 1x Pentium III 500 MHz
- 256 MB RAM
- NT Workstation 4.0 SP 6
- MS InternetExplorer 5.0 SP1
The following figure presents the results of the performance test. For each scenario we tested all tree persistence mechanisms and recorded values for the three observed persistence operations.
Measurements for 5 scenarios against 3 different persistence mechanisms |
|||||||||
|
|
|
|
|
|
|
|
|
|
1. Scenario "find all postalcodes of city named 'Baden-Baden'" |
|
|
|
|
|||||
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
Lookup incl. Finder |
690 |
1265 |
1,83 |
690 |
1452 |
2,10 |
690 |
292 |
0,42 |
call all getters |
690 |
41.313 |
59,87 |
690 |
1.720 |
2,49 |
690 |
0 |
0,00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2. Scenario "find all postalcodes of city Berlin and streetname Hauptstrasse" |
|
|
|||||||
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
Lookup incl. Finder |
25 |
376 |
15,04 |
25 |
391 |
15,64 |
25 |
47 |
1,88 |
call all getters |
25 |
1.546 |
61,84 |
25 |
78 |
3,12 |
25 |
0 |
0,00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3. Scenario "update all postalcode entries for city 'Baden-Baden'" |
|
|
|
||||||
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
call all getters |
690 |
42.729 |
61,93 |
690 |
1.934 |
2,80 |
690 |
0 |
0,00 |
update one field |
690 |
10.473 |
15,18 |
690 |
329 |
0,48 |
690 |
2.517 |
3,65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. Scenario "Iterate through 1000 entries and update 32 entries" |
|
|
|
||||||
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
Lookup incl. Finder |
1.000 |
10.855 |
10,86 |
1000 |
2.264 |
2,26 |
1.000 |
3.984 |
3,98 |
call all getters |
1.000 |
92.767 |
92,77 |
1000 |
3.314 |
3,31 |
1.000 |
0 |
0,00 |
update one field |
32 |
471 |
14,72 |
32 |
16 |
0,50 |
32 |
125 |
3,91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5. Scenario "Iterate through 500 entries and update randomly selected entries" |
|
|
|||||||
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
Lookup incl. Finder |
500 |
6.430 |
12,86 |
500 |
16.274 |
32,55 |
500 |
3.292 |
6,58 |
call all getters |
500 |
41.381 |
82,76 |
500 |
2.008 |
4,02 |
500 |
0 |
0,00 |
update one field |
100 |
1.435 |
14,35 |
100 |
57,41 |
0,57 |
100 |
488 |
4,88 |
The following figure presents results for each persistence operation. It also shows totals and average values.
Lookup incl. Finder |
|
|
|
|
|
|
|
|
|
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
scenario |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
1 |
690 |
1265 |
1,83 |
690 |
1452 |
2,10 |
690 |
292 |
0,42 |
2 |
25 |
376 |
15,04 |
25 |
391 |
15,64 |
25 |
47 |
1,88 |
4 |
1.000 |
10.855 |
10,86 |
1000 |
2.264 |
2,26 |
1.000 |
3.984 |
3,98 |
5 |
500 |
6.430 |
12,86 |
500 |
16.274 |
32,55 |
500 |
3.292 |
6,58 |
|
|
|
|
|
|
|
|
|
|
total/avg |
2215 |
18926 |
8,54 |
2215 |
20381 |
9,20 |
2215 |
7615,30313 |
3,44 |
remarks: |
OJB in it's current form does not improve performance for finders! Two reasons: 1.) no use of prepared statements yet, 2.) reflection is slow. |
||||||||
|
OJB will use prepared statements in future versions. I expect that this will have a positive impact on finder performance. |
||||||||
|
|
|
|
|
|
|
|
|
|
call all getters |
|
|
|
|
|
|
|
|
|
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
scenario |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
1 |
690 |
41.313 |
59,87 |
690 |
1.720 |
2,49 |
690 |
0 |
0,00 |
2 |
25 |
1.546 |
61,84 |
25 |
78 |
3,12 |
25 |
0 |
0,00 |
3 |
690 |
42.729 |
61,93 |
690 |
1.934 |
2,80 |
690 |
0 |
0,00 |
4 |
1.000 |
92.767 |
92,77 |
1000 |
3.314 |
3,31 |
1.000 |
0 |
0,00 |
5 |
500 |
41.381 |
82,76 |
500 |
2.008 |
4,02 |
500 |
0 |
0,00 |
|
|
|
|
|
|
|
|
|
|
total/avg |
2905 |
219736 |
75,64 |
2905 |
9054 |
3,12 |
2905 |
0 |
0,00 |
remarks: |
In the pure JDBC Version there is no overhead for Container activities. Thus accessing attributes takes virtually no time. |
||||||||
|
CMP/VAP needs exorbitantly more time for calling getters, as Entities are stored, passivated, loaded and activated |
||||||||
|
between each method-call by the EJB Container . The VAP implementation implies a DB read and write for each ejbStore() and ejbLoad(). |
||||||||
|
In BMP/OJB the EJB Container still does all the activation/passivation overhead, but through OJB's ObjectCache DB-lookups are minimized |
||||||||
|
|
|
|
|
|
|
|
|
|
update one field |
|
|
|
|
|
|
|
|
|
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
||||||
|
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
calls |
total [ms] |
avg [ms] |
3 |
690 |
10.473 |
15,18 |
690 |
329 |
0,48 |
690 |
2.517 |
3,65 |
4 |
32 |
471 |
14,72 |
32 |
16 |
0,50 |
32 |
125 |
3,91 |
5 |
100 |
1.435 |
14,35 |
100 |
57,41 |
0,57 |
100 |
488 |
4,88 |
|
|
|
|
|
|
|
|
|
|
total/avg |
822 |
12378,8624 |
15,06 |
822 |
402,407407 |
0,49 |
822 |
3129,26324 |
3,81 |
remarks: |
How can OJB be 7 times faster than a pure JDBC solution for updates? As for updates we have no performance boost through |
||||||||
|
an ObjectCache, but have to perform "real" DB UPDATES, there must be another reson for this. |
|
|
||||||
|
1. There could be an error in our TestCase (I will check this soon) |
|
|
|
|
||||
|
2. The JDBC prepared statement updates ALL columns. OJB uses smart Updates to detect modified attributes and updates only the modified column. |
The following figure is a condensed view that compares performance per scenario and per operation. The times for the JDBC variant are taken as reference and the factors of CMP/VAP resp. BMP/OJB are given behind the totals.
per method |
|
|
|
|
|
|
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
|||
|
total [ms] |
factor to JDBC |
total [ms] |
factor to JDBC |
total [ms] |
factor to JDBC |
Lookup incl. Finder |
18.926,00 |
2,49 |
20.381,00 |
2,68 |
7.615,30 |
1,00 |
call all getters |
219.736,00 |
-- |
9.054,00 |
-- |
- |
1,00 |
update one field |
12.378,86 |
3,96 |
402,41 |
0,13 |
3.129,26 |
1,00 |
|
|
|
|
|
|
|
All methods |
251.040,86 |
23,36 |
29.837,41 |
2,78 |
10.744,57 |
1,00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
per scenario |
|
|
|
|
|
|
|
CMP / IBM VAP |
BMP / ObJectBridge |
JDBC prepared Statements |
|||
|
total [ms] |
factor to JDBC |
total [ms] |
factor to JDBC |
total [ms] |
factor to JDBC |
1 |
42.578,00 |
145,79 |
3.172,00 |
10,86 |
292,05 |
1,00 |
2 |
1.922,00 |
40,89 |
469,00 |
9,98 |
47,00 |
1,00 |
3 |
53.202,00 |
21,14 |
2.263,00 |
0,90 |
2.516,71 |
1,00 |
4 |
104.093,00 |
25,33 |
5.594,00 |
1,36 |
4.109,00 |
1,00 |
5 |
49.245,86 |
13,03 |
18.339,41 |
4,85 |
3.779,80 |
1,00 |
|
|
|
|
|
|
|
All scenarios |
251.040,86 |
23,36 |
29.837,41 |
2,78 |
10.744,57 |
1,00 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
factor to BMP/ObJectBridge |
8,41 |
|
1,00 |
|
0,36 |
From an OJB point of view the results look quite promising. The overall performance of typical EJB applications can be improved by a factor of 8,41 (i.e. 841%).
The performance for the getters is improved by a factor of 24 and the performance for updates is even improved by a factor of 30. Only lookup of objects could not be improved. In a future release of OJB prepared statements will be used for primary key lookups. I assume this will enhance the lookup performance by a remarkable factor.
We anticipated the performance gain for getters, as we observed the behaviour of the WebSphere EJB container. After calling any method on an Entity it is stored and passivated immediately. For any subsequent method-call it has first to be found, loaded and activated (which implies three ejbLoad calls !) . This Activation/Passivation strategy results in an enormous performance degradation if an ejbLoad() call has to access the RDBMS.
If the ejbStore() uses and UPDATE for each call this results in completely unneccesary write operations if the entity has not been modified during the method call.
The CMP/VAP code works in this way,so we were not surprised about the bad results for the getters.
OJB uses an ObjectCache. Thus the ejbLoad() just reads an object once from the rdbms all subsequent calls retrieve the objects from the cache.
In addition, OJB uses smart updates, i.e. if the broker detects that an object has not been modified it will NOT perform an update against the RDBMS. If attributes have been modified it will only update modified columns. Thus the ejbStore() calls are not as expensive as for the CMP/VAP scheme.
Using a caching O/R layer in the context of EJB containers seems to be a good strategy to balance the containers benefits against its performance-costs.
If the Lookup speed can be further improved, OJB will help EJB developers to increase the performance of their applications by more than an order of magnitude.
I think the most important result is: The overhead for using an EJB container instead of writing a pure JDBC application where you have to build all the needed infrastructure and services on your own is only 2,78.
This is really not much compared to the benefits you gain from using an EJB container: O/R mapping, transaction handling, scalability, remote object infrastructure, security, etc.
My biggest surprise are the very good results for the Updates. It’s hard to believe that OJB (with its dynamic SQL generation using the slow Java-Reflection) can outperform a native JDBC application using a fixed set of prepared statements (“SELECT * FROM … WHERE ID=?”, “UPDATE … WHERE ID=?”).
I have two possible explanation for our results:
There is a bug in the BMP/OJB Code and updates are not executed at all.
OJB is really faster because it uses smart updates (only
modified columns are updated). The JDBC prepared statement updates
all columns.
To use a fixed set of prepared statements for smart
updates is not an option due to a combinatorial explosion: for a
table with n columns you would need a set of 2n-1
statements.
I will soon check wich explanation holds true.
We plan to port our test-cases to other EJB containers (JBOSS and
Bea Weblogic). We want to compare the overall performance of IBM
WebSphere with that of different containers. Other containers might
implement different activation/passivation startegies that reduce the
container overhead. They might also use different persistence layers
for CMP that do a better job than VAP.
I’m especially interested
how ObJectBridge compares to ‘industrial strength’ persistence
layers.
release: 0.7.235, date: 2001-11-24