Hibernate Envers performance overhead


#1

I executed @OneToOne, @OneToMany, @ManyToOne & @ManyToMany mappings for small performance test without & with Envers (ValidityAuditStrategy). Without Envers to with Envers I got seconds ratios 35:54, 3:7,16:27,4:7 for specified mappings in that order. This extra time is the major problem for us. How can I reduce this extra time ?


#2

First, you need to have a GitHub repository that contains the performance tests. The tests should use JMH too.

After you created the GitHub repository, we will take a look and see whether we can also reproduce the issue, and see what’s causing it.


#3

Thank you Vlad.
I’m in a restricted work environment. I’ll be back after a while after doing tests you mentioned from my personal device.


#4

It’s very important to have performance tests that can be run independently, and using JMH ensures that warming is done properly and statistics histograms are collected as well, not just averages.


#5

I also believe its important to understand what you’re trying to compare.

Lets take a very simple use case here, a @OneToOne mapping between two distinct entities.
For argument sake, lets use these mappings:

@Entity
public class Person {
  @Id
  private Integer personId;
  @OneToOne(optional = false)
  private SocialSecurityNumber ssn;
}

@Entity
public class SocialSecurityNumber {
  @Id
  private Integer id;
  private String value;
}

Lets assume your test is very basic

final Person person = new Person( 1L );
final SocialSecurityNumber ssn = SocialSecurityNumberHelper.generate( 1L );
person.setSocialSecurityNumber( ssn );

entityManager.persist( ssn );
entityManager.persist( person );

Hibernate ORM generates precisely 2 insert statements

INSERT INTO SocialSecurityNumber (id, value) VALUES (?, ?)
INSERT INTO Person (id, ssn) VALUES (?, ?)

I have purposely elected to specify the identifiers as non-generated values here to illustrate a subtle point that we’ll come to in a few moments. But suffice to say, by using a generated identifier strategy, there is a certain amount of overhead you must consider.

In the case of Hibernate Envers, we actually generate several statements.

SELECT next_hibernate_envers_revision_sequence ...

INSERT INTO REVINFO (REV, REV_TSTMP) VALUES(?, ?)
INSERT INTO SocialSecurityNumber_AUD (id, value, REV, REV_TYPE, REV_END) VALUES (?,?,?,?,?)
INSERT INTO Person_AUD (id, ssn, REV, REV_TYPE, REV_END) VALUES (?, ?, ?, ?, ?)

// This is due to using ValidityAuditStrategy
UPDATE SocialSecurityNumber_AUD SET REV_END=? WHERE id=? AND REV<? AND REV_END IS NULL
UPDATE Person_AUD SET REV_END=? WHERE id=? AND REV<? AND REV_END IS NULL

So by just comparing the actual executed SQL calls, there will be 4 times as many (or 8 total) SQL statements that are executed for this transaction when Envers is enabled where-as there will only be 2 SQL statements executed when Envers is not enabled.

The other aspect to consider is the fact that when Envers is disabled, its event listeners won’t be added to the Hibernate event subsystem. This means when making changes to the entities, there is a bit less overhead spent in building the dynamic-map change data capture (CDC) snapshot.

While JMH can help pin point bottlenecks,particularly in the latter area I mentioned; I also believe you are trying to compare apples to oranges on the grand scale here. By its very nature, I would expect the timings involved to be longer when Envers is enabled than when its not.

What we need to understand is if those timings are too far fetched and if so, why. There are variety of reasons I could ellaborate on but your JMH tests should help us understand a number of those such as the usage pattern in the transaction, volume of data, obscure mappings, etc.