From 6a9ab49ef046915c710d9e887702999265c5af86 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Sat, 16 Jun 2018 00:09:03 +1200 Subject: [PATCH] #1427 - QueryCache should be cleared, if one of a dependent bean is updated Propagation across cluster. --- .../io/ebean/cache/ServerCacheFactory.java | 9 ++++++ .../ebean/cache/ServerCacheNotification.java | 29 +++++++++++++++++++ .../io/ebean/cache/ServerCacheNotify.java | 12 ++++++++ .../ebean/cache/ServerCacheNotifyPlugin.java | 17 +++++++++++ .../cache/DefaultServerCacheFactory.java | 18 ++++++++++-- .../server/core/InternalConfiguration.java | 17 +++++++++-- .../transaction/PostCommitProcessing.java | 6 ++-- .../server/transaction/TableModState.java | 27 ++++++++++++++--- .../transaction/TransactionManager.java | 5 ++++ .../TransactionManagerOptions.java | 5 +++- 10 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/ebean/cache/ServerCacheNotification.java create mode 100644 src/main/java/io/ebean/cache/ServerCacheNotify.java create mode 100644 src/main/java/io/ebean/cache/ServerCacheNotifyPlugin.java diff --git a/src/main/java/io/ebean/cache/ServerCacheFactory.java b/src/main/java/io/ebean/cache/ServerCacheFactory.java index d59bfd3c4..54c71e7d1 100644 --- a/src/main/java/io/ebean/cache/ServerCacheFactory.java +++ b/src/main/java/io/ebean/cache/ServerCacheFactory.java @@ -10,4 +10,13 @@ public interface ServerCacheFactory { */ ServerCache createCache(ServerCacheConfig config); + /** + * Return a ServerCacheNotify that we will send ServerCacheNotification events to. + *

+ * This is used if a ServerCacheNotifyPlugin is not supplied. + *

+ * + * @param listener The listener that should be used to process the notification events. + */ + ServerCacheNotify createCacheNotify(ServerCacheNotify listener); } diff --git a/src/main/java/io/ebean/cache/ServerCacheNotification.java b/src/main/java/io/ebean/cache/ServerCacheNotification.java new file mode 100644 index 000000000..4b5b08a6f --- /dev/null +++ b/src/main/java/io/ebean/cache/ServerCacheNotification.java @@ -0,0 +1,29 @@ +package io.ebean.cache; + +import java.util.Set; + +/** + * Notification event that dependent tables have been modified. + *

+ * This is sent to other interested servers (in the cluster). + *

+ */ +public class ServerCacheNotification { + + private final long modifyTimestamp; + + private final Set dependentTables; + + public ServerCacheNotification(long modifyTimestamp, Set dependentTables) { + this.modifyTimestamp = modifyTimestamp; + this.dependentTables = dependentTables; + } + + public long getModifyTimestamp() { + return modifyTimestamp; + } + + public Set getDependentTables() { + return dependentTables; + } +} diff --git a/src/main/java/io/ebean/cache/ServerCacheNotify.java b/src/main/java/io/ebean/cache/ServerCacheNotify.java new file mode 100644 index 000000000..3e9f30a56 --- /dev/null +++ b/src/main/java/io/ebean/cache/ServerCacheNotify.java @@ -0,0 +1,12 @@ +package io.ebean.cache; + +/** + * Interface for both listening to notification changes and sending them to other members of the cluster. + */ +public interface ServerCacheNotify { + + /** + * Notify other server cache members of the table modifications or process the notifications. + */ + void notify(ServerCacheNotification notification); +} diff --git a/src/main/java/io/ebean/cache/ServerCacheNotifyPlugin.java b/src/main/java/io/ebean/cache/ServerCacheNotifyPlugin.java new file mode 100644 index 000000000..2e87a067c --- /dev/null +++ b/src/main/java/io/ebean/cache/ServerCacheNotifyPlugin.java @@ -0,0 +1,17 @@ +package io.ebean.cache; + +import io.ebean.config.ServerConfig; + +/** + * Plugin that provides a ServerCacheNotify implementation. + *

+ * Is supplied this will be used to send the ServerCacheNotification event to other cluster members. + *

+ */ +public interface ServerCacheNotifyPlugin { + + /** + * Create a ServerCacheNotify implementation given the server configuration. + */ + ServerCacheNotify create(ServerConfig serverConfig); +} diff --git a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheFactory.java b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheFactory.java index a519ea469..1b0476f3c 100644 --- a/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheFactory.java +++ b/src/main/java/io/ebeaninternal/server/cache/DefaultServerCacheFactory.java @@ -4,6 +4,8 @@ import io.ebean.BackgroundExecutor; import io.ebean.cache.ServerCache; import io.ebean.cache.ServerCacheConfig; import io.ebean.cache.ServerCacheFactory; +import io.ebean.cache.ServerCacheNotification; +import io.ebean.cache.ServerCacheNotify; /** @@ -16,14 +18,14 @@ class DefaultServerCacheFactory implements ServerCacheFactory { /** * Construct when l2 cache is disabled. */ - public DefaultServerCacheFactory() { + DefaultServerCacheFactory() { this.executor = null; } /** * Construct with executor service. */ - public DefaultServerCacheFactory(BackgroundExecutor executor) { + DefaultServerCacheFactory(BackgroundExecutor executor) { this.executor = executor; } @@ -43,4 +45,16 @@ class DefaultServerCacheFactory implements ServerCacheFactory { return cache; } + @Override + public ServerCacheNotify createCacheNotify(ServerCacheNotify listener) { + return new NoopServerCacheNotify(); + } + + private static class NoopServerCacheNotify implements ServerCacheNotify { + + @Override + public void notify(ServerCacheNotification notification) { + // do nothing + } + } } diff --git a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index 8d201d9c9..4beb65c58 100644 --- a/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -5,6 +5,8 @@ import io.ebean.ExpressionFactory; import io.ebean.annotation.Platform; import io.ebean.cache.ServerCacheFactory; import io.ebean.cache.ServerCacheManager; +import io.ebean.cache.ServerCacheNotify; +import io.ebean.cache.ServerCacheNotifyPlugin; import io.ebean.cache.ServerCacheOptions; import io.ebean.cache.ServerCachePlugin; import io.ebean.config.ExternalTransactionManager; @@ -138,6 +140,8 @@ public class InternalConfiguration { private final ServerCachePlugin serverCachePlugin; + private ServerCacheNotify cacheNotify; + private boolean localL2Caching; private final ExpressionFactory expressionFactory; @@ -418,7 +422,8 @@ public class InternalConfiguration { TransactionManagerOptions options = new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor, - indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager, tableModState); + indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager, + tableModState, cacheNotify); if (serverConfig.isExplicitTransactionBeginMode()) { return new ExplicitTransactionManager(options); @@ -565,7 +570,7 @@ public class InternalConfiguration { if (iterator.hasNext()) { // use the cacheFactory (via classpath service loader) plugin = iterator.next(); - logger.debug("using ServerCacheFactory {}", serverCachePlugin.getClass()); + logger.debug("using ServerCacheFactory {}", plugin.getClass()); } else { // use the built in default l2 caching which is local cache based localL2Caching = true; @@ -587,6 +592,14 @@ public class InternalConfiguration { ServerCacheFactory factory = serverCachePlugin.create(serverConfig, backgroundExecutor); + ServerCacheNotifyPlugin notifyPlugin = serverConfig.service(ServerCacheNotifyPlugin.class); + if (notifyPlugin != null) { + // plugin supplied so use that to send notifications + cacheNotify = notifyPlugin.create(serverConfig); + } else { + cacheNotify = factory.createCacheNotify(tableModState); + } + // reasonable default settings are for a cache per bean type ServerCacheOptions beanOptions = new ServerCacheOptions(); beanOptions.setMaxSize(serverConfig.getCacheMaxSize()); diff --git a/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java index b715d2ee3..08a85fd7d 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java +++ b/src/main/java/io/ebeaninternal/server/transaction/PostCommitProcessing.java @@ -22,7 +22,7 @@ import java.util.Set; * This includes Cluster notification, and BeanPersistListeners. *

*/ -public final class PostCommitProcessing { +final class PostCommitProcessing { private static final Logger logger = LoggerFactory.getLogger(PostCommitProcessing.class); @@ -51,7 +51,7 @@ public final class PostCommitProcessing { /** * Create for an external modification. */ - public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) { + PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) { this.clusterManager = clusterManager; this.manager = manager; @@ -68,7 +68,7 @@ public final class PostCommitProcessing { /** * Create for a transaction. */ - public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) { + PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) { this.clusterManager = clusterManager; this.manager = manager; diff --git a/src/main/java/io/ebeaninternal/server/transaction/TableModState.java b/src/main/java/io/ebeaninternal/server/transaction/TableModState.java index d2aa4143a..b876a4ed0 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TableModState.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TableModState.java @@ -2,6 +2,10 @@ package io.ebeaninternal.server.transaction; import io.ebean.cache.QueryCacheEntry; import io.ebean.cache.QueryCacheEntryValidate; +import io.ebean.cache.ServerCacheNotification; +import io.ebean.cache.ServerCacheNotify; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Set; @@ -13,24 +17,28 @@ import java.util.concurrent.ConcurrentHashMap; * This information is used to validate entries in the L2 query caches. *

*/ -public class TableModState implements QueryCacheEntryValidate { +public class TableModState implements QueryCacheEntryValidate, ServerCacheNotify { + + private static final Logger log = LoggerFactory.getLogger("io.ebean.cache.TABLEMOD"); private Map tableModStamp = new ConcurrentHashMap<>(); /** * Set the modified timestamp on the tables that have been touched. */ - public void touch(Set touchedTables, long modTimestamp) { - + void touch(Set touchedTables, long modTimestamp) { for (String tableName : touchedTables) { tableModStamp.put(tableName, modTimestamp); } + if (log.isDebugEnabled()) { + log.debug("TableModState updated - " + tableModStamp); + } } /** * Return true if all the tables are valid based on timestamp comparison. */ - public boolean isValid(Set tables, long sinceTimestamp) { + boolean isValid(Set tables, long sinceTimestamp) { for (String tableName : tables) { Long modTime = tableModStamp.get(tableName); if (modTime != null && modTime > sinceTimestamp ) { @@ -48,4 +56,15 @@ public class TableModState implements QueryCacheEntryValidate { } return true; } + + /** + * Update the table modification timestamps based on remote table modification events. + */ + @Override + public void notify(ServerCacheNotification notification) { + + // TODO: Change to use ClockService ... + long modifyTimestamp = notification.getModifyTimestamp(); + touch(notification.getDependentTables(), modifyTimestamp); + } } diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 0ff2894d6..b462b970b 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -5,6 +5,8 @@ import io.ebean.ProfileLocation; import io.ebean.TxScope; import io.ebean.annotation.PersistBatch; import io.ebean.annotation.TxType; +import io.ebean.cache.ServerCacheNotification; +import io.ebean.cache.ServerCacheNotify; import io.ebean.config.CurrentTenantProvider; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly; @@ -137,6 +139,7 @@ public class TransactionManager implements SpiTransactionManager { private final TransactionScopeManager scopeManager; private final TableModState tableModState; + private final ServerCacheNotify cacheNotify; /** * Create the TransactionManager @@ -160,6 +163,7 @@ public class TransactionManager implements SpiTransactionManager { this.serverName = options.config.getName(); this.scopeManager = options.scopeManager; this.tableModState = options.tableModState; + this.cacheNotify = options.cacheNotify; this.backgroundExecutor = options.backgroundExecutor; this.dataSourceSupplier = options.dataSourceSupplier; this.docStoreActive = options.config.getDocStoreConfig().isActive(); @@ -510,6 +514,7 @@ public class TransactionManager implements SpiTransactionManager { if (viewInvalidation) { beanDescriptorManager.processViewInvalidation(touchedTables); } + cacheNotify.notify(new ServerCacheNotification(modTimestamp, touchedTables)); } /** diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java index 16f5dafe9..1b8ce39a8 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.transaction; import io.ebean.BackgroundExecutor; +import io.ebean.cache.ServerCacheNotify; import io.ebean.config.ServerConfig; import io.ebeaninternal.api.SpiLogManager; import io.ebeaninternal.api.SpiProfileHandler; @@ -25,12 +26,13 @@ public class TransactionManagerOptions { final TransactionScopeManager scopeManager; final SpiLogManager logManager; final TableModState tableModState; + final ServerCacheNotify cacheNotify; public TransactionManagerOptions(boolean notifyL2CacheInForeground, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler, - SpiLogManager logManager, TableModState tableModState) { + SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify) { this.notifyL2CacheInForeground = notifyL2CacheInForeground; this.config = config; @@ -43,6 +45,7 @@ public class TransactionManagerOptions { this.profileHandler = profileHandler; this.logManager = logManager; this.tableModState = tableModState; + this.cacheNotify = cacheNotify; } }