Hibernate version: 3.2.5 GA
I have some serious reservations about the scalability of Hibernate, here's why:
I am using Hibernate with a local (not distributed, yet) Coherence L2 & query cache, the cache high-units are set large enough so that all entities in question can fit into the cache. The Hibernate/Spring app is running on a 2.66Ghz Core2 Quad and the database is running on a Athlon X2 64 connected via gigabit ethernet.
When loading a collection on my User object with the following code:
Code:
log.warn("GETTING CONTACTS");
start = System.currentTimeMillis();
Set<ContactAssociation> contacts = testUser.getContactAssociations();
contacts.size(); // trigger proxy
total = (System.currentTimeMillis() - start);
log.warn("FOUND {} CONTACTS in {}ms", contacts.size(), total);
it will produce the following output on first run:
Code:
2007-12-22 22:33:20,684 [RMI TCP Connection(6)-192.168.100.3] WARN service.impl.UserServiceImpl - GETTING CONTACTS
2007-12-22 22:33:20,858 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 1 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=0
2007-12-22 22:33:20,859 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,861 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: commit
2007-12-22 22:33:20,861 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,862 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 1 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=1
2007-12-22 22:33:20,862 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,864 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=0
2007-12-22 22:33:20,864 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,865 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 1 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: commit
2007-12-22 22:33:20,865 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,866 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=1
2007-12-22 22:33:20,866 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,867 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=0
2007-12-22 22:33:20,867 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,869 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: commit
2007-12-22 22:33:20,869 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.commit(NewProxyConnection.java:803) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:20,869 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0, message: SET autocommit=1
2007-12-22 22:33:20,870 [Compass Scheduled Optimizer] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyConnection.setAutoCommit(NewProxyConnection.java:881) duration: 0 ms, connection-id: 377, statement-id: 999, resultset-id: 0
2007-12-22 22:33:21,208 [RMI TCP Connection(6)-192.168.100.3] INFO MySQL - Profiler Event: [QUERY] at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76) duration: 183 ms, connection-id: 383, statement-id: 378, resultset-id: 646, message: select contactass0_.USER_ID as USER3_4_, contactass0_.CONTACT_ASSOCIATION_ID as CONTACT1_4_, contactass0_.CONTACT_ASSOCIATION_ID as CONTACT1_2_3_, contactass0_.LAST_MODIFIED as LAST2_2_3_, contactass0_.USER_ID as USER3_2_3_, contactass0_.CONTACT_ID as CONTACT4_2_3_, contactass0_.CREATED as CREATED2_3_, contactass0_.GROUP_ID as GROUP6_2_3_, user1_.USER_ID as USER1_1_0_, user1_.LAST_MODIFIED as LAST2_1_0_, user1_.GLOBAL_USER_ID as GLOBAL3_1_0_, user1_.USERNAME as USERNAME1_0_, user1_.PASSWORD as PASSWORD1_0_, user1_.SCREEN_NAME as SCREEN6_1_0_, user1_.CREATED as CREATED1_0_, user1_.AVAILABLE_INVITE_COUNT as AVAILABLE8_1_0_, user1_.ENABLED as ENABLED1_0_, user1_.PRIORITY_ACKNOWLEDGE_ID as PRIORITY10_1_0_, user1_.PROFILE_ID as PROFILE11_1_0_, profile2_.PROFILE_ID as PROFILE1_47_1_, profile2_.CREATED as CREATED47_1_, profile2_.LAST_MODIFIED as LAST3_47_1_, profile2_.avatar_FILE_STORE_ID as avatar17_47_1_, profile2_.DESCRIPTION as DESCRIPT4_47_1_, profile2_.DOB_DAY as DOB5_47_1_, profile2_.DOB_MONTH as DOB6_47_1_, profile2_.DOB_YEAR as DOB7_47_1_, profile2_.GENDER as GENDER47_1_, profile2_.LOCATION_CITY as LOCATION9_47_1_, profile2_.country_COUNTRY_ID as country18_47_1_, profile2_.LOCATION_STATE as LOCATION10_47_1_, profile2_.FIRST_NAME as FIRST11_47_1_, profile2_.LAST_NAME as LAST12_47_1_, profile2_.MIDDLE_NAME as MIDDLE13_47_1_, profile2_.TITLE as TITLE47_1_, profile2_.OCCUPATION as OCCUPATION47_1_, profile2_.USER_ID as USER16_47_1_, group3_.GROUP_ID as GROUP1_3_2_, group3_.LAST_MODIFIED as LAST2_3_2_, group3_.GLOBAL_GROUP_ID as GLOBAL3_3_2_, group3_.NAME as NAME3_2_, group3_.SHORT_NAME as SHORT5_3_2_, group3_.DESCRIPTION as DESCRIPT6_3_2_, group3_.TYPE as TYPE3_2_, group3_.SECURITY_MODEL as SECURITY8_3_2_, group3_.CREATED as CREATED3_2_, group3_.DELETED as DELETED3_2_, group3_.USER_ID as USER11_3_2_, group3_.FILE_STORE_ID as FILE12_3_2_, group3_.GROUP_CUSTOMIZATION_ID as GROUP13_3_2_ from CONTACT_ASSOCIATION contactass0_ inner join USER user1_ on contactass0_.CONTACT_ID=user1_.USER_ID left outer join PROFILE profile ... (truncated)
2007-12-22 22:33:21,208 [RMI TCP Connection(6)-192.168.100.3] INFO MySQL - Profiler Event: [FETCH] at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76) duration: 340 ms, connection-id: 383, statement-id: 378, resultset-id: 646
2007-12-22 22:33:21,209 [RMI TCP Connection(6)-192.168.100.3] INFO MySQL - Profiler Event: [SLOW QUERY] at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76) duration: 183 ms, connection-id: 383, statement-id: 378, resultset-id: 646, message: The following query was executed with a bad index, use 'EXPLAIN' for more details: select contactass0_.USER_ID as USER3_4_, contactass0_.CONTACT_ASSOCIATION_ID as CONTACT1_4_, contactass0_.CONTACT_ASSOCIATION_ID as CONTACT1_2_3_, contactass0_.LAST_MODIFIED as LAST2_2_3_, contactass0_.USER_ID as USER3_2_3_, contactass0_.CONTACT_ID as CONTACT4_2_3_, contactass0_.CREATED as CREATED2_3_, contactass0_.GROUP_ID as GROUP6_2_3_, user1_.USER_ID as USER1_1_0_, user1_.LAST_MODIFIED as LAST2_1_0_, user1_.GLOBAL_USER_ID as GLOBAL3_1_0_, user1_.USERNAME as USERNAME1_0_, user1_.PASSWORD as PASSWORD1_0_, user1_.SCREEN_NAME as SCREEN6_1_0_, user1_.CREATED as CREATED1_0_, user1_.AVAILABLE_INVITE_COUNT as AVAILABLE8_1_0_, user1_.ENABLED as ENABLED1_0_, user1_.PRIORITY_ACKNOWLEDGE_ID as PRIORITY10_1_0_, user1_.PROFILE_ID as PROFILE11_1_0_, profile2_.PROFILE_ID as PROFILE1_47_1_, profile2_.CREATED as CREATED47_1_, profile2_.LAST_MODIFIED as LAST3_47_1_, profile2_.avatar_FILE_STORE_ID as avatar17_47_1_, profile2_.DESCRIPTION as DESCRIPT4_47_1_, profile2_.DOB_DAY as DOB5_47_1_, profile2_.DOB_MONTH as DOB6_47_1_, profile2_.DOB_YEAR as DOB7_47_1_, profile2_.GENDER as GENDER47_1_, profile2_.LOCATION_CITY as LOCATION9_47_1_, profile2_.country_COUNTRY_ID as country18_47_1_, profile2_.LOCATION_STATE as LOCATION10_47_1_, profile2_.FIRST_NAME as FIRST11_47_1_, profile2_.LAST_NAME as LAST12_47_1_, profile2_.MIDDLE_NAME as MIDDLE13_47_1_, profile2_.TITLE as TITLE47_1_, profile2_.OCCUPATION as OCCUPATION47_1_, profile2_.USER_ID as USER16_47_1_, group3_.GROUP_ID as GROUP1_3_2_, group3_.LAST_MODIFIED as LAST2_3_2_, group3_.GLOBAL_GROUP_ID as GLOBAL3_3_2_, group3_.NAME as NAME3_2_, group3_.SHORT_NAME as SHORT5_3_2_, group3_.DESCRIPTION as DESCRIPT6_3_2_, group3_.TYPE as TYPE3_2_, group3_.SECURITY_MODEL as SECURITY8_3_2_, group3_.CREATED as CREATED3_2_, group3_.DELETED as DELETED3_2_, group3_.USER_ID as USER11_3_2_, group3_.FILE_STORE_ID as FILE12_3_2_, group3_.GROUP_CUSTOMIZATION_ID as GROUP13_3_2_ from CONTACT_ASSOCIATION contactass0_ inner join USER user1_ on contactass0_.CONTACT_ID=user1_.USER_ID left outer join PROFILE profile ... (truncated)
2007-12-22 22:33:23,194 [RMI TCP Connection(6)-192.168.100.3] WARN service.impl.UserServiceImpl - FOUND 4772 CONTACTS in 2510ms
Note that the query to produce the data (which if I rewrote manually I can half the time again) took about 180ms and fetching the data from the database took about 340ms... what happened during the other 1990ms?!
Now running the same code a second time (using a new session)
Code:
2007-12-22 22:36:16,280 [RMI TCP Connection(8)-192.168.100.3] WARN service.impl.UserServiceImpl - GETTING CONTACTS
2007-12-22 22:36:17,018 [RMI TCP Connection(8)-192.168.100.3] WARN service.impl.UserServiceImpl - FOUND 4772 CONTACTS in 738ms
Notice no database queries were executed, the data is coming from the L2 cache (from the User.contactAssociations cache and the User & Profile entities cache). I can retrieve the same set of data from the Coherence cache in about 6ms, so the overhead is not with Coherence. Looking further with a profiler (JProfiler) I confirmed that 90% of this overheard is with Hibernate assembling objects from cache, using some sort of custom "serialization" reassembling entities from disassembled state. Realizing this collection of entities from cache is slower than it was from the database originally!!
Obviously this gets exponentially worse with large collections, with small collections it's relatively benign vs database time.
This is a specific example but our application is littered with them.
This amount of overhead seemingly makes Hibernate unsuitable for large applications needing to deal with 10,000s of concurrent users due to excessive cache assembly times and hence CPU usage.