Cascades is saturating the stack

So I have this old project running 5.2.11 with a huge domain model. The problem I am looking at, as the application has grown a certain age and accumulated a good set of data with history build into the model.

I am looking at StackOverflowExceptions… I know what you are gonna say, recursion, and yup thats it, but its not recursion in the application pr. se and if i increase the stack size from an already healthy 2M to say, 4M, the app runs again, albeit slow.
Investigating it I find that the recursion is around Cascade (org.hibernate.engine.internal.Cascade), and I get stack levels approaching 10.000 frames. Thats a lot. So I monkey patched Cascade and put in some logging for statistics and its pretty clear it is all the @ManyToOne OneToMany etc that is at the root of it, all of them with CascadeType.PERSIST and MERGE.
As far as I can tell mappedBy is also placed correctly.
Cascade gets hit upwards of a billion times.(ill get back with an true number later, gonna run the test again).

As far as I can tell this is what happens, for entity A,B,C,N’s
A->B->C is OneToMany and ManyToOne the other way around.
A->N’s OneToMany and in reverse, N’s represents the rest of the data model, everything is in some way tied up to master table A.

Now a relatively simple Criteria is being made on C, “select from C where…” and what happens is that cascades to B and B cascades to A and A … cascades to all the rest. Now this doesnt translate into database ops, its in memory only, but there is SO many of them and the recursive nature of it just piles on the stack until its game over. Increasing the stack will fix the problem for a year maybe… then its gonna turn sour again.

Can someone explain to me what we are doing wrong? My gut is telling me that doing history like that is a bad idea (columns with cancelled_date or end_date etc) as hibernate cant tell current from history data.

I’m not sure I understand your issue exactly. Can you share some code and/or stack traces?

Sure, this is what we are looking at … 10.000 frames deep

… java.lang.StackOverflowError
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:239)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:172)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:171)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:171)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:172)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:171)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:172)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:171)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:172)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:171)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:491)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:423)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:386)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
at org.hibernate.event.internal.DefaultPersistEventListener.justCascade(DefaultPersistEventListener.java:172)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsPersistent(DefaultPersistEventListener.java:164)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
at org.hibernate.internal.SessionImpl.firePersistOnFlush(SessionImpl.java:842)
at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:835)
at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:458)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:383)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)

Ok so the issue is, that your object graph is so deep that the stack size becomes a problem.
How deep is the graph we are talking about here? As far as I can see, we “consume” 14 frames per cascade depth. According to some online resources the usual maximum method call depth is at around 7000. Let’s assume your frameworks require a method call depth of 1000 which leads to a method call depth of around 6000 that is available for Hibernate. This would allow for around 400 cascades. I’d be really surprised if your model is that big. I can imagine that we could try to improve this in Hibernate 6.0 by doing cascading iteratively but changing this design in 5.x is probably not an option.

If increasing the stack size is not an option because you think that the depth will increase further you should maybe rethink your design as I think such a deep object structure is pretty rare and might lead to problems in other areas as well. Either way, IMO it’s fine to increase the stack size for certain use cases like this.

Thanks for the reply. I am afraid it “is that deep”, history, self-references, a lot of recursive constructs. We will take your advice about the stack size to heart. Thanks man.