From d8e72e5469690e1b1165afbe50dbecfedf33541d Mon Sep 17 00:00:00 2001 From: lbwexler Date: Tue, 16 Jul 2024 13:54:30 -0400 Subject: [PATCH] + Improvements from ATM CR. + Show Expiry info for caches in admin panel --- CHANGELOG.md | 5 ++-- .../io/xh/hoist/role/provided/Role.groovy | 6 ----- .../init/io/xh/hoist/ClusterConfig.groovy | 25 +++++++++++++------ .../hoist/cluster/ClusterAdminService.groovy | 23 ++++++++++++++++- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8752f823..04aaf3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ ## 21.0-SNAPSHOT - unreleased ### ⚙️ Technical - -* Improvements to the ability to configure Hibernate Caches for 2nd-level and connection caches. +* +* Improvements to the ability to configure Hibernate 2nd-level caches. See `ClusterConfig` for more + information. ## 20.2.1 - 2024-07-09 diff --git a/grails-app/domain/io/xh/hoist/role/provided/Role.groovy b/grails-app/domain/io/xh/hoist/role/provided/Role.groovy index 2da6e011..3cd1a424 100644 --- a/grails-app/domain/io/xh/hoist/role/provided/Role.groovy +++ b/grails-app/domain/io/xh/hoist/role/provided/Role.groovy @@ -33,12 +33,6 @@ class Role implements JSONFormat { lastUpdatedBy maxSize: 50 } - // We don't expect a huge number of roles. - // Tighten as example of a custom cache config being applied to a collection cache (Role.members) - static cache = { - evictionConfig.size = 1000 - } - static beforeInsert = { if (Role.findByNameIlike(name)) { throw new RoutineRuntimeException('Role Name must be case-insensitive unique.') diff --git a/grails-app/init/io/xh/hoist/ClusterConfig.groovy b/grails-app/init/io/xh/hoist/ClusterConfig.groovy index a7328a88..1ec8f195 100755 --- a/grails-app/init/io/xh/hoist/ClusterConfig.groovy +++ b/grails-app/init/io/xh/hoist/ClusterConfig.groovy @@ -119,7 +119,17 @@ class ClusterConfig { } /** - * Override this to create additional default configs in the application. + * Override this to create additional default Hazelcast configs in the application. + * + * Note that Hoist also introduces two properties for declarative configuration: + * + * - a static 'cache' property on Grails domain objects to customize associated + * Hibernate caches. + * - a static 'clusterConfigs' property on Grails services to customize any Hazelcast + * Distributed Objects associated with the service e.g. Hoist caches + * + * See toolbox's `Phase` object and Hoist Core's `ClientErrorService` for examples of these + * customizations. */ protected void createDefaultConfigs(Config config) { config.getMapConfig('default').with { @@ -162,21 +172,21 @@ class ClusterConfig { //------------------------ private void createHibernateConfigs(Config config) { grailsApplication.domainClasses.each { GrailsClass gc -> - // Pre-access cache for all domain classes to ensure we capture the common 'default' - // (Not clear why this is needed -- but hibernate would otherwise create these differently) + // Pre-access cache config for all domain classes to ensure we capture the common 'default' + // (not clear why this is needed -- but hibernate would otherwise create these differently) def configs = [ // 1) Main 2nd-level entity cache config.getCacheConfig(gc.fullName), - // 2) Collection caches + // 2) any related collection caches config.getCacheConfig(gc.fullName + '.*') ] - // apply any app customization + // Apply any app customization specified by new static prop introduced by Hoist + // note we apply the same for both the entity cache [1] and any collection caches [2]. Closure customizer = gc.getPropertyValue('cache') as Closure if (customizer) { configs.each { cfg -> - // workaround - hz does not clone evictionConfig - cfg.evictionConfig = new EvictionConfig(cfg.evictionConfig) + cfg.evictionConfig = new EvictionConfig(cfg.evictionConfig) // workaround - hz does not clone customizer.delegate = cfg customizer.resolveStrategy = Closure.DELEGATE_FIRST customizer(cfg) @@ -187,6 +197,7 @@ class ClusterConfig { private void createServiceConfigs(Config config) { grailsApplication.serviceClasses.each { GrailsClass gc -> + // Apply any app customization specified by new static prop introduced by Hoist Map objs = gc.getPropertyValue('clusterConfigs') if (!objs) return objs.forEach {String key, List value -> diff --git a/grails-app/services/io/xh/hoist/cluster/ClusterAdminService.groovy b/grails-app/services/io/xh/hoist/cluster/ClusterAdminService.groovy index 1a4f1e4a..21ca43e5 100644 --- a/grails-app/services/io/xh/hoist/cluster/ClusterAdminService.groovy +++ b/grails-app/services/io/xh/hoist/cluster/ClusterAdminService.groovy @@ -11,6 +11,9 @@ import com.hazelcast.topic.ITopic import io.xh.hoist.BaseService import io.xh.hoist.util.Utils +import javax.cache.expiry.ExpiryPolicy +import javax.cache.expiry.Duration + import static io.xh.hoist.util.Utils.appContext import static io.xh.hoist.util.Utils.getExceptionHandler @@ -142,6 +145,7 @@ class ClusterAdminService extends BaseService { ] case CacheProxy: def evictionConfig = obj.cacheConfig.evictionConfig, + expiryPolicy = obj.cacheConfig.expiryPolicyFactory.create(), stats = obj.localCacheStatistics return [ name : obj.getName(), @@ -156,7 +160,8 @@ class ClusterAdminService extends BaseService { config : [ size : evictionConfig.size, maxSizePolicy : evictionConfig.maxSizePolicy, - evictionPolicy: evictionConfig.evictionPolicy + evictionPolicy: evictionConfig.evictionPolicy, + expiryPolicy : formatExpiryPolicy(expiryPolicy) ] ] default: @@ -180,4 +185,20 @@ class ClusterAdminService extends BaseService { ratio : stats.ratio.round(2) ] } + + private Map formatExpiryPolicy(ExpiryPolicy policy) { + def ret = [:] + if (policy.expiryForCreation) ret.creation = formatDuration(policy.expiryForCreation) + if (policy.expiryForAccess) ret.access = formatDuration(policy.expiryForAccess) + if (policy.expiryForUpdate) ret.update = formatDuration(policy.expiryForUpdate) + return ret + } + + + private String formatDuration(Duration duration) { + if (duration.isZero()) return 0 + if (duration.isEternal()) return 'eternal' + return duration.timeUnit.toSeconds(duration.durationAmount) + 's' + } + }