#1427 - QueryCache should be cleared, if one of a dependent bean is updated

Initial work, does not include propagation across cluster.
This commit is contained in:
rob bygrave
2018-06-15 17:36:23 +12:00
parent 59537227b6
commit 96cc8d7605
51 changed files with 952 additions and 283 deletions
@@ -67,6 +67,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private Map<String, Object> userObjects;
private long startNanos;
private long startMillis;
/**
* Create without a tenantId.
@@ -79,6 +80,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
this.startMillis = manager.clockNowEpoch();
}
/**
@@ -89,6 +91,12 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
this.tenantId = tenantId;
}
@Override
public long getStartMillis() {
// not used on read only transaction
return startMillis;
}
@Override
public void setLabel(String label) {
// do nothing
@@ -187,6 +187,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected ProfileLocation profileLocation;
protected final long startNanos;
private final long startMillis;
/**
* Create a new JdbcTransaction.
@@ -203,6 +204,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.startNanos = System.nanoTime();
if (manager == null) {
this.startMillis = System.currentTimeMillis();
this.logSql = false;
this.logSummary = false;
this.skipCacheAfterWrite = true;
@@ -210,6 +212,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.batchOnCascadeMode = PersistBatch.NONE;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.startMillis = manager.clockNowEpoch();
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
@@ -235,6 +238,11 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return label;
}
@Override
public long getStartMillis() {
return startMillis;
}
@Override
public long profileOffset() {
return (profileStream == null) ? 0 : profileStream.offset();
@@ -853,7 +861,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
public TransactionEvent getEvent() {
queryOnly = false;
if (event == null) {
event = new TransactionEvent();
event = new TransactionEvent(startMillis);
}
return event;
}
@@ -1050,7 +1058,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
// the event has been sent to the transaction manager
// for postCommit processing (l2 cache updates etc)
// start a new transaction event
event = new TransactionEvent();
event = new TransactionEvent(startMillis);
} catch (Exception e) {
doRollback(e);
@@ -47,6 +47,12 @@ class NoTransaction implements SpiTransaction {
}
@Override
public long getStartMillis() {
// not used
return System.currentTimeMillis();
}
@Override
public boolean isActive() {
// always false
@@ -14,6 +14,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
/**
* Performs post commit processing using a background thread.
@@ -88,10 +89,10 @@ public final class PostCommitProcessing {
processTableEvents(event.getEventTables());
if (manager.notifyL2CacheInForeground) {
// process l2 cache changes in foreground
processCacheChanges(event.buildCacheChanges(manager.viewInvalidation));
processCacheChanges(event.buildCacheChanges());
} else {
// collect l2 cache changes for delayed background processing
cacheChanges = event.buildCacheChanges(manager.viewInvalidation);
cacheChanges = event.buildCacheChanges();
}
}
@@ -165,7 +166,12 @@ public final class PostCommitProcessing {
*/
private void processCacheChanges(CacheChangeSet cacheChanges) {
if (cacheChanges != null) {
manager.processViewInvalidation(cacheChanges.apply());
Set<String> touched = cacheChanges.touchedTables();
if (touched != null && !touched.isEmpty()) {
manager.processTouchedTables(touched, cacheChanges.modificationTimestamp());
// TODO: Propagate touched tables to other cluster members
}
cacheChanges.apply();
}
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.transaction;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.QueryCacheEntryValidate;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Holds timestamp of last modification per table.
* <p>
* This information is used to validate entries in the L2 query caches.
* </p>
*/
public class TableModState implements QueryCacheEntryValidate {
private Map<String,Long> tableModStamp = new ConcurrentHashMap<>();
/**
* Set the modified timestamp on the tables that have been touched.
*/
public void touch(Set<String> touchedTables, long modTimestamp) {
for (String tableName : touchedTables) {
tableModStamp.put(tableName, modTimestamp);
}
}
/**
* Return true if all the tables are valid based on timestamp comparison.
*/
public boolean isValid(Set<String> tables, long sinceTimestamp) {
for (String tableName : tables) {
Long modTime = tableModStamp.get(tableName);
if (modTime != null && modTime > sinceTimestamp ) {
return false;
}
}
return true;
}
@Override
public boolean isValid(QueryCacheEntry entry) {
Set<String> dependentTables = entry.getDependentTables();
if (dependentTables != null && !dependentTables.isEmpty()) {
return isValid(dependentTables, entry.getTimestamp());
}
return true;
}
}
@@ -46,7 +46,7 @@ import java.util.Set;
/**
* Manages transactions.
* <p>
* Keeps the Cache and Cluster in synch when transactions are committed.
* Keeps the Cache and Cluster in sync when transactions are committed.
* </p>
*/
public class TransactionManager implements SpiTransactionManager {
@@ -136,6 +136,8 @@ public class TransactionManager implements SpiTransactionManager {
private final TimedMetricMap txnNamed;
private final TransactionScopeManager scopeManager;
private final TableModState tableModState;
/**
* Create the TransactionManager
*/
@@ -157,6 +159,7 @@ public class TransactionManager implements SpiTransactionManager {
this.clusterManager = options.clusterManager;
this.serverName = options.config.getName();
this.scopeManager = options.scopeManager;
this.tableModState = options.tableModState;
this.backgroundExecutor = options.backgroundExecutor;
this.dataSourceSupplier = options.dataSourceSupplier;
this.docStoreActive = options.config.getDocStoreConfig().isActive();
@@ -178,6 +181,13 @@ public class TransactionManager implements SpiTransactionManager {
scopeManager.register(this);
}
/**
* Return the NOW timestamp in epoch millis.
*/
public long clockNowEpoch() {
return System.currentTimeMillis(); // TODO: Review when we supply a Clock via ServerConfig.
}
/**
* Create a new scoped transaction.
*/
@@ -434,7 +444,7 @@ public class TransactionManager implements SpiTransactionManager {
private void externalModificationEvent(TransactionEventTable tableEvents) {
TransactionEvent event = new TransactionEvent();
TransactionEvent event = new TransactionEvent(clockNowEpoch());
event.add(tableEvents);
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
@@ -495,9 +505,10 @@ public class TransactionManager implements SpiTransactionManager {
/**
* Invalidate the query caches for entities based on views.
*/
public void processViewInvalidation(Set<String> viewInvalidation) {
if (!viewInvalidation.isEmpty()) {
beanDescriptorManager.processViewInvalidation(viewInvalidation);
public void processTouchedTables(Set<String> touchedTables, long modTimestamp) {
tableModState.touch(touchedTables, modTimestamp);
if (viewInvalidation) {
beanDescriptorManager.processViewInvalidation(touchedTables);
}
}
@@ -24,10 +24,13 @@ public class TransactionManagerOptions {
final SpiProfileHandler profileHandler;
final TransactionScopeManager scopeManager;
final SpiLogManager logManager;
final TableModState tableModState;
public TransactionManagerOptions(boolean notifyL2CacheInForeground, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler, SpiLogManager logManager) {
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
@@ -39,6 +42,7 @@ public class TransactionManagerOptions {
this.dataSourceSupplier = dataSourceSupplier;
this.profileHandler = profileHandler;
this.logManager = logManager;
this.tableModState = tableModState;
}
}