Back in-memory caches with Guava, disk caches with H2

Instead of using Ehcache for in-memory caches, use Guava. The Guava
cache code has been more completely tested by Google in high load
production environments, and it tends to have fewer bugs. It enables
caches to be built at any time, rather than only at server startup.

By creating a Guava cache as soon as it is declared, rather than
during the LifecycleListener.start() for the CachePool, we can promise
any downstream consumer of the cache that the cache is ready to
execute requests the moment it is supplied by Guice. This fixes a
startup ordering problem in the GroupCache and the ProjectCache, where
code wants to use one of these caches during startup to resolve a
group or project by name.

Tracking the Gauva backend caches with a DynamicMap makes it possible
for plugins to define their own in-memory caches using CacheModule's
cache() function to declare the cache. It allows the core server to
make the cache available to administrators over SSH with the gerrit
show-caches and gerrit flush-caches commands.

Persistent caches store in a private H2 database per cache, with a
simple one-table schema that stores each entry in a table row as a
pair of serialized objects (key and value). Database reads are gated
by a BloomFilter, to reduce the number of calls made to H2 during
cache misses. In theory less than 3% of cache misses will reach H2 and
find nothing. Stores happen on a background thread quickly after the
put is made to the cache, reducing the risk that a diff or web_session
record is lost during an ungraceful shutdown.

Cache databases are capped around 128M worth of stored data by running
a prune cycle each day at 1 AM local server time. Records are removed
from the database by ordering on the last access time, where last
accessed is the last time the record was moved from disk to memory.

Change-Id: Ia82d056796b5af9bcb1f219fe06d905c9c0fbc84
This commit is contained in:
Shawn O. Pearce
2012-05-24 14:28:40 -07:00
parent 34d4d1929a
commit 2e1cb2b849
76 changed files with 2432 additions and 1674 deletions

View File

@@ -114,6 +114,9 @@ public class IntraLineDiffKey implements Serializable {
public String toString() {
StringBuilder n = new StringBuilder();
n.append("IntraLineDiffKey[");
if (projectKey != null) {
n.append(projectKey.get()).append(" ");
}
n.append(aId.name());
n.append("..");
n.append(bId.name());

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.patch;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.common.cache.CacheLoader;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
@@ -35,9 +35,8 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
private static final Logger log = LoggerFactory
.getLogger(IntraLineLoader.class);
class IntraLineLoader extends CacheLoader<IntraLineDiffKey, IntraLineDiff> {
static final Logger log = LoggerFactory.getLogger(IntraLineLoader.class);
private static final Pattern BLANK_LINE_RE = Pattern
.compile("^[ \\t]*(|[{}]|/\\*\\*?|\\*)[ \\t]*$");
@@ -62,7 +61,7 @@ class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
}
@Override
public IntraLineDiff createEntry(IntraLineDiffKey key) throws Exception {
public IntraLineDiff load(IntraLineDiffKey key) throws Exception {
Worker w = workerPool.poll();
if (w == null) {
w = new Worker();
@@ -119,7 +118,7 @@ class IntraLineLoader extends EntryCreator<IntraLineDiffKey, IntraLineDiff> {
throws Exception {
if (!input.offer(new Input(key))) {
log.error("Cannot enqueue task to thread " + thread.getName());
return null;
return Result.TIMEOUT;
}
Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS);

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.patch;
import com.google.common.cache.Weigher;
/** Approximates memory usage for IntralineDiff in bytes of memory used. */
public class IntraLineWeigher implements
Weigher<IntraLineDiffKey, IntraLineDiff> {
@Override
public int weigh(IntraLineDiffKey key, IntraLineDiff value) {
return 16 + 8*8 + 2*36 // Size of IntraLineDiffKey, 64 bit JVM
+ 16 + 2*8 + 16+8+4+20 // Size of IntraLineDiff, 64 bit JVM
+ (8 + 16 + 4*4) * value.getEdits().size();
}
}

View File

@@ -19,9 +19,10 @@ import com.google.gerrit.reviewdb.client.PatchSet;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
public PatchList get(PatchListKey key);
public PatchList get(PatchListKey key) throws PatchListNotAvailableException;
public PatchList get(Change change, PatchSet patchSet);
public PatchList get(Change change, PatchSet patchSet)
throws PatchListNotAvailableException;
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key);
}

View File

@@ -15,24 +15,23 @@
package com.google.gerrit.server.patch;
import com.google.common.cache.LoadingCache;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import java.util.concurrent.ExecutionException;
/** Provides a cached list of {@link PatchListEntry}. */
@Singleton
public class PatchListCacheImpl implements PatchListCache {
@@ -43,21 +42,15 @@ public class PatchListCacheImpl implements PatchListCache {
return new CacheModule() {
@Override
protected void configure() {
final TypeLiteral<Cache<PatchListKey, PatchList>> fileType =
new TypeLiteral<Cache<PatchListKey, PatchList>>() {};
disk(fileType, FILE_NAME) //
.memoryLimit(128) // very large items, cache only a few
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
.populateWith(PatchListLoader.class) //
;
persist(FILE_NAME, PatchListKey.class, PatchList.class)
.maximumWeight(10 << 20)
.loader(PatchListLoader.class)
.weigher(PatchListWeigher.class);
final TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>> intraType =
new TypeLiteral<Cache<IntraLineDiffKey, IntraLineDiff>>() {};
disk(intraType, INTRA_NAME) //
.memoryLimit(128) // very large items, cache only a few
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
.populateWith(IntraLineLoader.class) //
;
persist(INTRA_NAME, IntraLineDiffKey.class, IntraLineDiff.class)
.maximumWeight(10 << 20)
.loader(IntraLineLoader.class)
.weigher(IntraLineWeigher.class);
bind(PatchListCacheImpl.class);
bind(PatchListCache.class).to(PatchListCacheImpl.class);
@@ -65,14 +58,14 @@ public class PatchListCacheImpl implements PatchListCache {
};
}
private final Cache<PatchListKey, PatchList> fileCache;
private final Cache<IntraLineDiffKey, IntraLineDiff> intraCache;
private final LoadingCache<PatchListKey, PatchList> fileCache;
private final LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache;
private final boolean computeIntraline;
@Inject
PatchListCacheImpl(
@Named(FILE_NAME) final Cache<PatchListKey, PatchList> fileCache,
@Named(INTRA_NAME) final Cache<IntraLineDiffKey, IntraLineDiff> intraCache,
@Named(FILE_NAME) LoadingCache<PatchListKey, PatchList> fileCache,
@Named(INTRA_NAME) LoadingCache<IntraLineDiffKey, IntraLineDiff> intraCache,
@GerritServerConfig Config cfg) {
this.fileCache = fileCache;
this.intraCache = intraCache;
@@ -82,11 +75,19 @@ public class PatchListCacheImpl implements PatchListCache {
cfg.getBoolean("cache", "diff", "intraline", true));
}
public PatchList get(final PatchListKey key) {
return fileCache.get(key);
@Override
public PatchList get(PatchListKey key) throws PatchListNotAvailableException {
try {
return fileCache.get(key);
} catch (ExecutionException e) {
PatchListLoader.log.warn("Error computing " + key, e);
throw new PatchListNotAvailableException(e.getCause());
}
}
public PatchList get(final Change change, final PatchSet patchSet) {
@Override
public PatchList get(final Change change, final PatchSet patchSet)
throws PatchListNotAvailableException {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
@@ -97,11 +98,12 @@ public class PatchListCacheImpl implements PatchListCache {
@Override
public IntraLineDiff getIntraLineDiff(IntraLineDiffKey key) {
if (computeIntraline) {
IntraLineDiff d = intraCache.get(key);
if (d == null) {
d = new IntraLineDiff(IntraLineDiff.Status.ERROR);
try {
return intraCache.get(key);
} catch (ExecutionException e) {
IntraLineLoader.log.warn("Error computing " + key, e);
return new IntraLineDiff(IntraLineDiff.Status.ERROR);
}
return d;
} else {
return new IntraLineDiff(IntraLineDiff.Status.DISABLED);
}

View File

@@ -122,6 +122,22 @@ public class PatchListEntry {
this.deletions = deletions;
}
int weigh() {
int size = 16 + 6*8 + 2*4 + 20 + 16+8+4+20;
size += stringSize(oldName);
size += stringSize(newName);
size += header.length;
size += (8 + 16 + 4*4) * edits.size();
return size;
}
private static int stringSize(String str) {
if (str != null) {
return 16 + 3*4 + 16 + str.length() * 2;
}
return 0;
}
public ChangeType getChangeType() {
return changeType;
}

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.server.patch;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.common.cache.CacheLoader;
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
@@ -54,6 +54,8 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@@ -62,7 +64,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
private final GitRepositoryManager repoManager;
@Inject
@@ -71,7 +75,7 @@ class PatchListLoader extends EntryCreator<PatchListKey, PatchList> {
}
@Override
public PatchList createEntry(final PatchListKey key) throws Exception {
public PatchList load(final PatchListKey key) throws Exception {
final Repository repo = repoManager.openRepository(key.projectKey);
try {
return readPatchList(key, repo);

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.patch;
public class PatchListNotAvailableException extends Exception {
private static final long serialVersionUID = 1L;
public PatchListNotAvailableException(String message) {
super(message);
}
public PatchListNotAvailableException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.patch;
import com.google.common.cache.Weigher;
/** Approximates memory usage for PatchList in bytes of memory used. */
public class PatchListWeigher implements Weigher<PatchListKey, PatchList> {
@Override
public int weigh(PatchListKey key, PatchList value) {
int size = 16 + 4*8 + 2*36 // Size of PatchListKey, 64 bit JVM
+ 16 + 3*8 + 3*4 + 20; // Size of PatchList, 64 bit JVM
for (PatchListEntry e : value.getPatches()) {
size += e.weigh();
}
return size;
}
}