So, I wrote a little while back on some great ways to kill performance when using hibernate. Obviously the best (as in most successful, not most recommended) way to come to these conclusions is to experience the pain from making these mistakes.Being the good little refactoring shop that we are, we took it as a challenge to clean this up and see how things improved. First a little background on a particularly problematic area of the system:
- Basic Hibernate DAO, ensures that all needed nodes in an object graph are initialized before sending the graph to business and presentation components.
- The graph in question is made up of 4 object classes:
Average cardinalities for each relationship are noted below the role names in the diagram.
- Relationships ‘j’, ‘k’, and ‘l’ are managed using property-refs/unique keys instead of primary key relationships.
- Relationship ‘m’ is mapped as an ‘any’ relationship.
Because of Hibernate’s inability to batch or proxy objects using these strategies, and because each cardinality compounds on the previous ones this model results in the following actions to load this data:
- run the original query for A’s
- for each A, query for all related B’s
- for each B, query for all related D’s
- for each B, query for all related C’s
- for each C, query for all related A’s
This results in 1(j)+100(m)+100(k)+1000(l)=1201 individual queries for each root object retrieved!!! If I end up with 100 records coming back in my initial search, that’s 120,100 queries even if I only wanted the A’s returned from the initial query!!!Convert these relationships to regular primary relationships (through simple DML updates for the property-refs and a little ORM hierarchy for the “any”), set some reasonable batch fetch sizes (let’s assume 100 for this example) and the flow changes to:
- run the original query for A’s
- for each 100 A’s, query for all related B’s
- for each 100 B’s, query for all related D’s
- for each 100 B’s, query for all related C’s
- for each 100 C’s, query for all related A’s
1(original search for A)+1(j)+1(m)+1(k)+10(l)=14 queries to get the entire object graph, or 1 query if all I care about is the root nodes.ConclusionA combination of proxying and batch fetching changed the load characteristics dramatically:
| Task |
Queries without batching and proxies |
Queries with batching and proxies |
| Load A’s |
120,100 |
1 |
| Load graph of 4 Objects |
120,100 |
14 |
For our system, making this change brought the load time for a single view component down from the 10’s of seconds to hundredths of a second.
Hibernate, Performance - 1 Comment »