1

I've run into an issue where hibernate flushing strategy experiences slowdowns over time as it processes a set of line items on an order. The slowdowns increase in time when subsequent updates are called to the JBoss Seam update.

The business process of the code takes an order with 'n' line items, modifies each line item for a business purpose, calls a seam/hibernate update() on that line item, and then moves onto the next line item. At the Hibernate flushing strategy responds instantly when the call to update() is made. But as the code makes its way down the list of line items, Hibernate slows down (almost exponentially). With logging enabled, I can see the slowdowns start as seconds, then a minute or two, then 5+ minutes, then 20+ minutes. All processing the same order line items in the collection. It does not change the size of the collection, no items are added or deleted.

I have enabled tracing of hibernate transactions and find in the output that the org.hibernate.event.def.AbstractFlushingEventListener runs into a problem somewhere between a call to:

prepareCollectionFlushes(session);

and

flushEntities(event);

I have traced it to an issue between these two method calls based on the log entries created by Hibernate:

2014-12-02 17:50:30,808 DEBUG [org.hibernate.engine.CollectionEntry] (http-0.0.0.0-8443-2) Collection dirty: [com.companioncabinet.ccs.domain.data.Job.jobPhases#10324859]

2014-12-02 18:19:09,015 TRACE [org.hibernate.event.def.AbstractFlushingEventListener] (http-0.0.0.0-8443-2) Flushing entities and processing referenced collections

Notice the group of messages at 17:50 and then the time gap to 18:19. The code is all in Hibernate's AbstractFlushingEventListner. I have checked the Hibernate sources and found their code looks like this which result in the messages above:

prepareCollectionFlushes(session);   
// now, any collections that are initialized   
// inside this block do not get updated - they   
// are ignored until the next flush   

persistenceContext.setFlushing(true);   
try {   
   flushEntities(event);   
   flushCollections(session);   
}   
   finally {   
   persistenceContext.setFlushing(false);   
}

The messages that appear are coming from:

prepareCollectionFlushes(session);

and

flushingEntities(event);

The method prepareCollectionFlushes makes a call to CollectionEntry.preFlush which produces the "Collection dirty" message.

Here are all the methods involved:

prepareCollectionFlushes

/**  
* Initialize the flags of the CollectionEntry, including the  
* dirty check.  
*/   
private void prepareCollectionFlushes(SessionImplementor session) throws HibernateException {   
   // Initialize dirty flags for arrays + collections with composite elements   
   // and reset reached, doupdate, etc.   
   log.debug("dirty checking collections");   
   final List list = IdentityMap.entries( session.getPersistenceContext().getCollectionEntries() );   
   final int size = list.size();   
   for ( int i = 0; i < size; i++ ) {   
   Map.Entry e = ( Map.Entry ) list.get( i );   
      ( (CollectionEntry) e.getValue() ).preFlush( (PersistentCollection) e.getKey() );   
   }   
}

preFlush

public void preFlush(PersistentCollection collection) throws HibernateException {

    boolean nonMutableChange = collection.isDirty() && 
            getLoadedPersister()!=null && 
            !getLoadedPersister().isMutable();
    if (nonMutableChange) {
        throw new HibernateException(
                "changed an immutable collection instance: " + 
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    dirty(collection);

    if ( log.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
        log.debug(
                "Collection dirty: " +
                MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
            );
    }

    setDoupdate(false);
    setDoremove(false);
    setDorecreate(false);
    setReached(false);
    setProcessed(false);
}

dirty

/**
 * Determine if the collection is "really" dirty, by checking dirtiness
 * of the collection elements, if necessary
 */
private void dirty(PersistentCollection collection) throws HibernateException {

    boolean forceDirty = collection.wasInitialized() &&
            !collection.isDirty() && //optimization
            getLoadedPersister() != null &&
            getLoadedPersister().isMutable() && //optimization
            ( collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization
            !collection.equalsSnapshot( getLoadedPersister() );

    if ( forceDirty ) {
        collection.dirty();
    }

}

The collection.dirty() in the above block sets the PersistenceCollection dirty to true.

flushEntities

/**  
* 1. detect any dirty entities  
* 2. schedule any entity updates  
* 3. search out any reachable collections  
*/   
private void flushEntities(FlushEvent event) throws HibernateException {   
   log.trace("Flushing entities and processing referenced collections");   
   ...remainder of code omitted since the delay happens before the above log entry appears

With all the code in hand, there is nothing clear happening. There isn't anything in the code happening during the delay. Everything just appears to stop.

The database is PostgreSQL. I have enabled logging of all queries. When I align the last query to immediately before the delay, it appears to be a reload of all entities that were dirty. When I run the logged select it takes less than 1 mili-second.

I know these are older versions and "upgrade" is something to be done. But still, it would be good to point at a specific flaw, bug, or generally known performance issue as the catalyst to trigger the upgrade.

Thanks for looking!

orm.xml

<persistence-unit-metadata>
    <persistence-unit-defaults>
        <entity-listeners>
            <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>
        </entity-listeners>
    </persistence-unit-defaults>
</persistence-unit-metadata>

persistence.xml

<persistence-unit name="DS1" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/DS1</jta-data-source>
    <properties>
        <property name="hibernate.archive.autodetection" value="class" />
        <property name="hibernate.search.default.indexBase" value="/home/lucene/DS1" />
         <!-- Binds the EntityManagerFactory to JNDI where Seam can look it up.
        This is only relevant when the container automatically loads the persistence unit, as is the case in JBoss AS 5. -->
        <property name="jboss.entity.manager.factory.jndi.name" value="java:/DS1EntityManagerFactory"/>
    </properties>

</persistence-unit>

hibernate.properties is empty

ds.xml

<local-tx-datasource>
    <jndi-name>DS1</jndi-name>
    <connection-url>jdbc:postgresql://127.0.0.1:5432/data</connection-url>
    <driver-class>org.postgresql.Driver</driver-class>
    <user-name>a_user</user-name>
    <password>a_pw</password>
    <idle-timeout-minutes>90</idle-timeout-minutes>
    <new-connection-sql>select 1</new-connection-sql>
    <check-valid-connection-sql>select 1</check-valid-connection-sql>
</local-tx-datasource>

DB Log excerpt

2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:43:19 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: update PurchaseOrder set active=$1, createdby=$2, createdStamp=$3, ...
2014-12-08 19:49:29 EST LOG:  duration: 0.000 ms  execute <unnamed>: select orderlines0_.purchaseorderid as purchas66_18_5_, ...
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Gary
  • 37
  • 1
  • 9

1 Answers1

1

The larger the Persistence Context the more time it will take to verify all dirty properties.

The default automatic dirty checking mechanism must traverse all properties for all entities currently attached to the current running Session.

If you want to speed it up you need to:

  1. switch to a custom dirty checking mechanism.
  2. use bytecode enhancing(which didn't seem to work on latest Hibernate versions)

Update

One statement of yours caught my attention:

The business process of the code takes an order with 'n' line items, modifies each line item for a business purpose, calls a seam/hibernate update() on that line item, and then moves onto the next line item. At the Hibernate flushing strategy responds instantly when the call to update() is made.

If you load N line items you don;t need to run an update as long as the session is still active. All changes will be automatically detected by the dirty checking mechanism.

If you are mixing managed entity changes and issue selects then the AUTO flush mode will trigger a flush prior to any HQL query. So it's better to arrange your code to do the selects first and leave the modifications for the last part of your transaction.

You can also set hibernate flush mode to COMMIT:

session.setFlushMode(FlushMode.COMMIT);
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • I'm not sure your posts apply here. While I understand the concepts, the issue is the size of the collection is not changing. I've updated my question with more detail about the process. In short, at the start of the business logic Hibernate responds instantly and the logs show this. But towards the end, the slowdowns begin to appear and the gap between the two actions grows in time from miliseconds to 20+ minutes. The collection is not changing. The first dirty check takes miliseconds. Something else seems amiss here. – Gary Dec 03 '14 at 17:05
  • Please post your Hibernate configs as well the DataSource – Vlad Mihalcea Dec 03 '14 at 18:45
  • I posted the configs. Thanks. – Gary Dec 05 '14 at 20:26
  • The session is flushing at each transaction actually. I've enabled database logging of all sql. I've posted the database logs. The db reports it is completing the execute in less than 1ms. So it is not the db. It appears after the results of each select are sent back to the hibernate, the app slows down more. The last time I saw this increased slowdown behavior was when an arraylist was used for a very large set of data (the copy of the arraylist to a new list caused the slowdown as the arraylist grew). – Gary Dec 09 '14 at 02:15
  • session.setFlushMode(FlushMode.COMMIT); Worked for me! Thank you! – Rahul Dudhane May 22 '23 at 16:07