Hi, We recently migrated our application to Grails...
# questions
s
Hi, We recently migrated our application to Grails 6.2.3 and switched from Tomcat to Spring Boot (using bootWar instead of war when building the release). Since the migration, we’ve encountered issues with the Hibernate session cache not expiring, which eventually leads to the application running out of memory. Under Tomcat, the session was automatically cleaned up after about 30 minutes, which prevented the issue, but it now seems that we may also be using Hibernate incorrectly in some parts of the application. Specifically, we often pass domain class instances (retrieved via MyDomainClass.findBy... or through service methods) directly to GSP views. Within the GSP, we then access nested fields that belong to other domain classes, which triggers additional lazy-loading and keeps the Hibernate session open. This behavior appears to contribute to the memory leak. Additionally, some of our command objects (Validatable) contain GORM domain objects as fields, which likely adds to the problem. We’re looking for the correct approach to handle retrieved data. Ideally without a full refactor to normalize everything into DTOs, since in many cases the specific nested data required is only known dynamically in the GSP. Perhaps will grails 7 handle it better? Thank you in advance for any hints!
j
The OSIV is considered bad practice but the session is supposed to only live for the transaction length. I haven’t seen the memory leaks you mention - it sounds like you may have second layer caching on and it’s unbounded?
s
Thanks for the clarification @jdaugherty! We are not using second layer caching (hibernate.queries, use_second_level_cache and use_query_cache are explicitly set to false). Just to make sure I understand correctly. Is it also considered bad practice to have a domain class as a field in another class (for example, in a command object)? Would it help if I annotated the field with @ReadOnly, or if I used @Transactional(readOnly = true) when retrieving the domain instance? In our case, the field is bound using @BindUsing, and the binding logic calls a service method that is annotated with @Transactional, but without readOnly = true. I’m wondering if that could be contributing to the issue.
I’ve analyzed the memory dump in more detail, and it turns out the issue is caused by the Grails Form Fields plugin caching data without any limits. In particular: •
BeanPropertyAccessorImpl
https://github.com/apache/grails-core/blob/939b61ad892c98d1ee5fc88f9e92f16bc38eea79/grails-fields/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy uses both
@Canonical
and
@Memoized
. •
FormFieldsTemplateService
https://github.com/apache/grails-core/blob/939b61ad892c98d1ee5fc88f9e92f16bc38eea79/grails-fields/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy also contains multiple
@Memoized
methods. What I’m observing is that every time a page is rendered, new entries are added to the
org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache
, and nothing ever evicts or caps this cache. Because each request creates a new command object instance, the Form Fields plugin generates new memoization keys on every request. It causes unbounded cache growth. It seems no component is cleaning up or limiting these memoized caches, which eventually leads to memory leaks. Does this analysis make sense? Am I missing something in how the plugin should be used, or is this a bug in the plugin itself? Before migrating to Spring Boot, we were running on Tomcat, and it seems Tomcat may have been cleaning up or isolating the caches in a way that prevented this buildup.
note that the links are of the plugin 7.x but they are essentially the same for grails 6.x
s
The memoized usage should be capped here. The annotation does take in a max and protected sizings as parameters so should probably be added. FOr your particular use case, it sounds like the cache is pretty useless if you are creating new values every time, so you should probably disable the cache lookup.
grails.plugin.fields.disableLookupCache
As mentioned here: https://github.com/apache/grails-core/blob/939b61ad892c98d1ee5fc88f9e92f16bc38eea7[…]roovy/grails/plugin/formfields/FormFieldsTemplateService.groovy And see if that helps.
s
Disabling the cache actually solved the memory issue. Additionally, I do not really see any slowing down in terms of performance while having disabled the cache. Thanks!! It was clearly not capped based on our studies and I wonder why there is the cache enabled by default if with new instances of command the cache does not really do its job. I cannot see any use cases in which the command has a singleton then cache would work as expected. Does it make sense?
s
Yes, I understand what you are saying. I think, though, that the cache was never intended to be for singletons but for scenarios where things can be considered equal. Using the hashCode and compareTo methods. And you won't notice any performance issues if you were never utilising the cache in the first place. I've not had anything to do with that module personally, and we don't use it, but there should be a limit added to that cache I think. You should file a bug.
Or if as you say there's no scenario where the keys to the memoized would ever be considered equal then the cache removed all together.
s
Looking into the dump the memory continued to grow, so there may be some mismatch between the keys generated. I need to have a better look to identify which the effective keys were (better how the key was really computed), as there may be some downside here. Tomcat probably took care of blanking the growing hashmap constantly, so I never noticed it till today after using spring boot instead.