Support expiring from in-memory caches after last access
Use the existing CacheBuilder#expireAfterAccess method for in-memory caches. This configuration is not respected for persistent H2 caches. There is already an "accessed" column, so in theory we could compare the current time to the value in this column. However, this has the problem that the column is not updated on every lookup against the outer memory cache which wraps the H2 cache; it's only updated when the value isn't in the memory cache and needs to be read into the memory cache from storage. As a result, the most frequently accessed elements in the in-memory cache would be the first elements to expire from the persistent cache. Rather than allowing this perverse behavior, just ignore expireAfterAccess in the H2 implementation, and document it properly. Elements may still get pruned from the cache when it exceeds the configured size. (The pruning code actually uses the accessed column and thus may be subject to the perverse behavior we're trying to avoid, but fixing it is beyond the scope of this change.) It would be theoretically possible to update the accessed timestamp in the H2 database on every read from the memory cache, but cache reads are extremely frequent and this would likely put too much write load on the H2 database. Change-Id: I881df14bcabde1376145eed815f69d7493751574
This commit is contained in:
@@ -762,6 +762,16 @@ Default is 1024 for most caches, except:
|
|||||||
If set to 0 the cache is disabled. Entries are removed immediately
|
If set to 0 the cache is disabled. Entries are removed immediately
|
||||||
after being stored by the cache. This is primarily useful for testing.
|
after being stored by the cache. This is primarily useful for testing.
|
||||||
|
|
||||||
|
[[cache.name.expireFromMemoryAfterAccess]]cache.<name>.expireFromMemoryAfterAccess::
|
||||||
|
+
|
||||||
|
Time after last access to automatically expire entries from an in-memory
|
||||||
|
cache. If 0 or not specified, entries are never expired in this manner.
|
||||||
|
Values may use unit suffixes as in link:#cache.name.maxAge[maxAge].
|
||||||
|
+
|
||||||
|
This option only applies to in-memory caches; persistent cache values are
|
||||||
|
not expired in this manner, and are only pruned via
|
||||||
|
link:#cache.name.diskLimit[diskLimit].
|
||||||
|
|
||||||
[[cache.name.diskLimit]]cache.<name>.diskLimit::
|
[[cache.name.diskLimit]]cache.<name>.diskLimit::
|
||||||
+
|
+
|
||||||
Total size in bytes of the keys and values stored on disk. Caches that
|
Total size in bytes of the keys and values stored on disk. Caches that
|
||||||
|
@@ -23,9 +23,12 @@ public interface CacheBinding<K, V> {
|
|||||||
/** Set the total size of the cache. */
|
/** Set the total size of the cache. */
|
||||||
CacheBinding<K, V> maximumWeight(long weight);
|
CacheBinding<K, V> maximumWeight(long weight);
|
||||||
|
|
||||||
/** Set the time an element lives before being expired. */
|
/** Set the time an element lives after last write before being expired. */
|
||||||
CacheBinding<K, V> expireAfterWrite(Duration duration);
|
CacheBinding<K, V> expireAfterWrite(Duration duration);
|
||||||
|
|
||||||
|
/** Set the time an element lives after last access before being expired. */
|
||||||
|
CacheBinding<K, V> expireFromMemoryAfterAccess(Duration duration);
|
||||||
|
|
||||||
/** Populate the cache with items from the CacheLoader. */
|
/** Populate the cache with items from the CacheLoader. */
|
||||||
CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
|
CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> clazz);
|
||||||
|
|
||||||
|
@@ -47,6 +47,9 @@ public interface CacheDef<K, V> {
|
|||||||
@Nullable
|
@Nullable
|
||||||
Duration expireAfterWrite();
|
Duration expireAfterWrite();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Duration expireFromMemoryAfterAccess();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Weigher<K, V> weigher();
|
Weigher<K, V> weigher();
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>,
|
|||||||
private String configKey;
|
private String configKey;
|
||||||
private long maximumWeight;
|
private long maximumWeight;
|
||||||
private Duration expireAfterWrite;
|
private Duration expireAfterWrite;
|
||||||
|
private Duration expireFromMemoryAfterAccess;
|
||||||
private Provider<CacheLoader<K, V>> loader;
|
private Provider<CacheLoader<K, V>> loader;
|
||||||
private Provider<Weigher<K, V>> weigher;
|
private Provider<Weigher<K, V>> weigher;
|
||||||
|
|
||||||
@@ -74,6 +75,13 @@ class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>,
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheBinding<K, V> expireFromMemoryAfterAccess(Duration duration) {
|
||||||
|
checkNotFrozen();
|
||||||
|
expireFromMemoryAfterAccess = duration;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> impl) {
|
public CacheBinding<K, V> loader(Class<? extends CacheLoader<K, V>> impl) {
|
||||||
checkNotFrozen();
|
checkNotFrozen();
|
||||||
@@ -129,6 +137,12 @@ class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>,
|
|||||||
return expireAfterWrite;
|
return expireAfterWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Duration expireFromMemoryAfterAccess() {
|
||||||
|
return expireFromMemoryAfterAccess;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Weigher<K, V> weigher() {
|
public Weigher<K, V> weigher() {
|
||||||
|
@@ -36,6 +36,12 @@ class H2CacheDefProxy<K, V> implements PersistentCacheDef<K, V> {
|
|||||||
return source.expireAfterWrite();
|
return source.expireAfterWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Duration expireFromMemoryAfterAccess() {
|
||||||
|
return source.expireFromMemoryAfterAccess();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public Weigher<K, V> weigher() {
|
public Weigher<K, V> weigher() {
|
||||||
|
@@ -3,6 +3,7 @@ java_library(
|
|||||||
srcs = glob(["*.java"]),
|
srcs = glob(["*.java"]),
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//java/com/google/gerrit/common:annotations",
|
||||||
"//java/com/google/gerrit/extensions:api",
|
"//java/com/google/gerrit/extensions:api",
|
||||||
"//java/com/google/gerrit/server",
|
"//java/com/google/gerrit/server",
|
||||||
"//lib:guava",
|
"//lib:guava",
|
||||||
|
@@ -14,12 +14,16 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.cache.mem;
|
package com.google.gerrit.server.cache.mem;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import com.google.common.cache.Weigher;
|
import com.google.common.cache.Weigher;
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.server.cache.CacheDef;
|
import com.google.gerrit.server.cache.CacheDef;
|
||||||
import com.google.gerrit.server.cache.ForwardingRemovalListener;
|
import com.google.gerrit.server.cache.ForwardingRemovalListener;
|
||||||
import com.google.gerrit.server.cache.MemoryCacheFactory;
|
import com.google.gerrit.server.cache.MemoryCacheFactory;
|
||||||
@@ -27,7 +31,6 @@ import com.google.gerrit.server.config.ConfigUtil;
|
|||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
|
|
||||||
class DefaultMemoryCacheFactory implements MemoryCacheFactory {
|
class DefaultMemoryCacheFactory implements MemoryCacheFactory {
|
||||||
@@ -67,24 +70,38 @@ class DefaultMemoryCacheFactory implements MemoryCacheFactory {
|
|||||||
}
|
}
|
||||||
builder.weigher(weigher);
|
builder.weigher(weigher);
|
||||||
|
|
||||||
Duration age = def.expireAfterWrite();
|
Duration expireAfterWrite = def.expireAfterWrite();
|
||||||
if (has(def.configKey(), "maxAge")) {
|
if (has(def.configKey(), "maxAge")) {
|
||||||
builder.expireAfterWrite(
|
builder.expireAfterWrite(
|
||||||
|
ConfigUtil.getTimeUnit(
|
||||||
|
cfg, "cache", def.configKey(), "maxAge", toSeconds(expireAfterWrite), SECONDS),
|
||||||
|
SECONDS);
|
||||||
|
} else if (expireAfterWrite != null) {
|
||||||
|
builder.expireAfterWrite(expireAfterWrite.toNanos(), NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration expireAfterAccess = def.expireFromMemoryAfterAccess();
|
||||||
|
if (has(def.configKey(), "expireFromMemoryAfterAccess")) {
|
||||||
|
builder.expireAfterAccess(
|
||||||
ConfigUtil.getTimeUnit(
|
ConfigUtil.getTimeUnit(
|
||||||
cfg,
|
cfg,
|
||||||
"cache",
|
"cache",
|
||||||
def.configKey(),
|
def.configKey(),
|
||||||
"maxAge",
|
"expireFromMemoryAfterAccess",
|
||||||
age != null ? age.getSeconds() : 0,
|
toSeconds(expireAfterAccess),
|
||||||
TimeUnit.SECONDS),
|
SECONDS),
|
||||||
TimeUnit.SECONDS);
|
SECONDS);
|
||||||
} else if (age != null) {
|
} else if (expireAfterAccess != null) {
|
||||||
builder.expireAfterWrite(age.toNanos(), TimeUnit.NANOSECONDS);
|
builder.expireAfterAccess(expireAfterAccess.toNanos(), NANOSECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long toSeconds(@Nullable Duration duration) {
|
||||||
|
return duration != null ? duration.getSeconds() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean has(String name, String var) {
|
private boolean has(String name, String var) {
|
||||||
return !Strings.isNullOrEmpty(cfg.getString("cache", name, var));
|
return !Strings.isNullOrEmpty(cfg.getString("cache", name, var));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user