Merge branch 'stable-3.3'

* stable-3.3:
  Use wrapper to store cache key

Change-Id: I46cd2d89b141eefa09b452e9c6229751d3775eab
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheSerializers.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheSerializers.java
new file mode 100644
index 0000000..8546981
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheSerializers.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 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.googlesource.gerrit.modules.cache.chroniclemap;
+
+import com.google.gerrit.server.cache.PersistentCacheDef;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.inject.Singleton;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Singleton
+public class CacheSerializers {
+  private static final Map<String, CacheSerializer<?>> keySerializers = new ConcurrentHashMap<>();
+  private static final Map<String, CacheSerializer<?>> valueSerializers = new ConcurrentHashMap<>();
+
+  static <K, V> void registerCacheDef(PersistentCacheDef<K, V> def) {
+    String cacheName = def.name();
+    registerCacheKeySerializer(cacheName, def.keySerializer());
+    registerCacheValueSerializer(cacheName, def.valueSerializer());
+  }
+
+  static <K, V> void registerCacheKeySerializer(
+      String cacheName, CacheSerializer<K> keySerializer) {
+    keySerializers.computeIfAbsent(cacheName, (name) -> keySerializer);
+  }
+
+  static <K, V> void registerCacheValueSerializer(
+      String cacheName, CacheSerializer<V> valueSerializer) {
+    valueSerializers.computeIfAbsent(cacheName, (name) -> valueSerializer);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <K> CacheSerializer<K> getKeySerializer(String name) {
+    if (keySerializers.containsKey(name)) {
+      return (CacheSerializer<K>) keySerializers.get(name);
+    }
+    throw new IllegalStateException("Could not find key serializer for " + name);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <V> CacheSerializer<V> getValueSerializer(String name) {
+    if (valueSerializers.containsKey(name)) {
+      return (CacheSerializer<V>) valueSerializers.get(name);
+    }
+    throw new IllegalStateException("Could not find value serializer for " + name);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
index 311445b..bde4fd1 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
@@ -39,7 +39,7 @@
 
   private final ChronicleMapCacheConfig config;
   private final CacheLoader<K, V> loader;
-  private final ChronicleMap<K, TimedValue<V>> store;
+  private final ChronicleMap<KeyWrapper<K>, TimedValue<V>> store;
   private final LongAdder hitCount = new LongAdder();
   private final LongAdder missCount = new LongAdder();
   private final LongAdder loadSuccessCount = new LongAdder();
@@ -55,6 +55,8 @@
       CacheLoader<K, V> loader,
       MetricMaker metricMaker)
       throws IOException {
+    CacheSerializers.registerCacheDef(def);
+
     this.config = config;
     this.loader = loader;
     this.hotEntries =
@@ -63,11 +65,11 @@
 
     ChronicleMapStorageMetrics metrics = new ChronicleMapStorageMetrics(metricMaker);
 
-    final Class<K> keyClass = (Class<K>) def.keyType().getRawType();
+    final Class<KeyWrapper<K>> keyWrapperClass = (Class<KeyWrapper<K>>) (Class) KeyWrapper.class;
     final Class<TimedValue<V>> valueWrapperClass = (Class<TimedValue<V>>) (Class) TimedValue.class;
 
-    final ChronicleMapBuilder<K, TimedValue<V>> mapBuilder =
-        ChronicleMap.of(keyClass, valueWrapperClass).name(def.name());
+    final ChronicleMapBuilder<KeyWrapper<K>, TimedValue<V>> mapBuilder =
+        ChronicleMap.of(keyWrapperClass, valueWrapperClass).name(def.name());
 
     // Chronicle-map does not allow to custom-serialize boxed primitives
     // such as Boolean, Integer, for which size is statically determined.
@@ -75,11 +77,11 @@
     // it cannot be used.
     if (!mapBuilder.constantlySizedKeys()) {
       mapBuilder.averageKeySize(config.getAverageKeySize());
-      mapBuilder.keyMarshaller(new ChronicleMapMarshallerAdapter<>(def.keySerializer()));
+      mapBuilder.keyMarshaller(new KeyWrapperMarshaller<>(def.name()));
     }
 
     mapBuilder.averageValueSize(config.getAverageValueSize());
-    mapBuilder.valueMarshaller(new TimedValueMarshaller<>(def.valueSerializer()));
+    mapBuilder.valueMarshaller(new TimedValueMarshaller<>(def.name()));
 
     mapBuilder.entries(config.getMaxEntries());
 
@@ -117,7 +119,7 @@
     }
 
     <K, V> void registerCallBackMetrics(
-        String name, ChronicleMap<K, TimedValue<V>> store, InMemoryLRU<K> hotEntries) {
+        String name, ChronicleMap<KeyWrapper<K>, TimedValue<V>> store, InMemoryLRU<K> hotEntries) {
       String sanitizedName = metricMaker.sanitizeMetricName(name);
       String PERCENTAGE_FREE_SPACE_METRIC =
           "cache/chroniclemap/percentage_free_space_" + sanitizedName;
@@ -162,10 +164,12 @@
     return config;
   }
 
+  @SuppressWarnings("unchecked")
   @Override
   public V getIfPresent(Object objKey) {
-    if (store.containsKey(objKey)) {
-      TimedValue<V> vTimedValue = store.get(objKey);
+    KeyWrapper<K> keyWrapper = (KeyWrapper<K>) new KeyWrapper<>(objKey);
+    if (store.containsKey(keyWrapper)) {
+      TimedValue<V> vTimedValue = store.get(keyWrapper);
       if (!expired(vTimedValue.getCreated())) {
         hitCount.increment();
         hotEntries.add((K) objKey);
@@ -180,8 +184,9 @@
 
   @Override
   public V get(K key) throws ExecutionException {
-    if (store.containsKey(key)) {
-      TimedValue<V> vTimedValue = store.get(key);
+    KeyWrapper<K> keyWrapper = new KeyWrapper<>(key);
+    if (store.containsKey(keyWrapper)) {
+      TimedValue<V> vTimedValue = store.get(keyWrapper);
       if (!needsRefresh(vTimedValue.getCreated())) {
         hitCount.increment();
         hotEntries.add(key);
@@ -211,8 +216,9 @@
 
   @Override
   public V get(K key, Callable<? extends V> valueLoader) throws ExecutionException {
-    if (store.containsKey(key)) {
-      TimedValue<V> vTimedValue = store.get(key);
+    KeyWrapper<K> keyWrapper = new KeyWrapper<>(key);
+    if (store.containsKey(keyWrapper)) {
+      TimedValue<V> vTimedValue = store.get(keyWrapper);
       if (!needsRefresh(vTimedValue.getCreated())) {
         hitCount.increment();
         return vTimedValue.getValue();
@@ -235,14 +241,16 @@
 
   @SuppressWarnings("unchecked")
   public void putUnchecked(Object key, Object value, Timestamp created) {
-    TimedValue<?> wrapped = new TimedValue<>(value, created.toInstant().toEpochMilli());
-    store.put((K) key, (TimedValue<V>) wrapped);
+    TimedValue<?> wrappedValue = new TimedValue<>(value, created.toInstant().toEpochMilli());
+    KeyWrapper<?> wrappedKey = new KeyWrapper<>(key);
+    store.put((KeyWrapper<K>) wrappedKey, (TimedValue<V>) wrappedValue);
   }
 
   @Override
   public void put(K key, V val) {
-    TimedValue<V> wrapped = new TimedValue<>(val);
-    store.put(key, wrapped);
+    KeyWrapper<K> wrappedKey = new KeyWrapper<>(key);
+    TimedValue<V> wrappedValue = new TimedValue<>(val);
+    store.put(wrappedKey, wrappedValue);
     hotEntries.add(key);
   }
 
@@ -251,7 +259,7 @@
       store.forEachEntry(
           c -> {
             if (expired(c.value().get().getCreated())) {
-              hotEntries.remove(c.key().get());
+              hotEntries.remove(c.key().get().getValue());
               c.context().remove(c);
             }
           });
@@ -282,17 +290,19 @@
   private void evictColdEntries() {
     store.forEachEntryWhile(
         e -> {
-          if (!hotEntries.contains(e.key().get())) {
+          if (!hotEntries.contains(e.key().get().getValue())) {
             e.doRemove();
           }
           return runningOutOfFreeSpace();
         });
   }
 
+  @SuppressWarnings("unchecked")
   @Override
   public void invalidate(Object key) {
-    store.remove(key);
-    hotEntries.remove((K) key);
+    KeyWrapper<K> wrappedKey = (KeyWrapper<K>) new KeyWrapper<>(key);
+    store.remove(wrappedKey);
+    hotEntries.remove(wrappedKey.getValue());
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapter.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapter.java
deleted file mode 100644
index 0c60dca..0000000
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2020 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.googlesource.gerrit.modules.cache.chroniclemap;
-
-import com.google.gerrit.server.cache.serialize.CacheSerializer;
-import net.openhft.chronicle.bytes.Bytes;
-import net.openhft.chronicle.core.util.ReadResolvable;
-import net.openhft.chronicle.hash.serialization.BytesReader;
-import net.openhft.chronicle.hash.serialization.BytesWriter;
-
-public class ChronicleMapMarshallerAdapter<T>
-    implements BytesWriter<T>, BytesReader<T>, ReadResolvable<ChronicleMapMarshallerAdapter<T>> {
-
-  private final CacheSerializer<T> cacheSerializer;
-
-  ChronicleMapMarshallerAdapter(CacheSerializer<T> cacheSerializer) {
-    this.cacheSerializer = cacheSerializer;
-  }
-
-  @Override
-  public ChronicleMapMarshallerAdapter<T> readResolve() {
-    return new ChronicleMapMarshallerAdapter<>(cacheSerializer);
-  }
-
-  @Override
-  public T read(Bytes in, T using) {
-    int serializedLength = (int) in.readUnsignedInt();
-    byte[] serialized = new byte[serializedLength];
-    in.read(serialized, 0, serializedLength);
-    using = cacheSerializer.deserialize(serialized);
-    return using;
-  }
-
-  @Override
-  public void write(Bytes out, T toWrite) {
-    final byte[] serialized = cacheSerializer.serialize(toWrite);
-    out.writeUnsignedInt(serialized.length);
-    out.write(serialized);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapper.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapper.java
new file mode 100644
index 0000000..cfab341
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapper.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 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.googlesource.gerrit.modules.cache.chroniclemap;
+
+import com.google.common.base.Objects;
+
+public class KeyWrapper<V> {
+
+  private final V value;
+
+  KeyWrapper(V value) {
+    this.value = value;
+  }
+
+  public V getValue() {
+    return value;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof KeyWrapper)) return false;
+    KeyWrapper<?> that = (KeyWrapper<?>) o;
+    return Objects.equal(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(value);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshaller.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshaller.java
new file mode 100644
index 0000000..7c7801c
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshaller.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 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.googlesource.gerrit.modules.cache.chroniclemap;
+
+import net.openhft.chronicle.bytes.Bytes;
+import net.openhft.chronicle.core.util.ReadResolvable;
+import net.openhft.chronicle.hash.serialization.BytesReader;
+import net.openhft.chronicle.hash.serialization.BytesWriter;
+
+public class KeyWrapperMarshaller<V>
+    implements BytesWriter<KeyWrapper<V>>,
+        BytesReader<KeyWrapper<V>>,
+        ReadResolvable<KeyWrapperMarshaller<V>> {
+
+  private final String name;
+
+  KeyWrapperMarshaller(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public KeyWrapperMarshaller<V> readResolve() {
+    return new KeyWrapperMarshaller<>(name);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public KeyWrapper<V> read(Bytes in, KeyWrapper<V> using) {
+    int serializedLength = (int) in.readUnsignedInt();
+    byte[] serialized = new byte[serializedLength];
+    in.read(serialized, 0, serializedLength);
+    V v = (V) CacheSerializers.getKeySerializer(name).deserialize(serialized);
+    using = new KeyWrapper<>(v);
+
+    return using;
+  }
+
+  @Override
+  public void write(Bytes out, KeyWrapper<V> toWrite) {
+    final byte[] serialized = CacheSerializers.getKeySerializer(name).serialize(toWrite.getValue());
+    out.writeUnsignedInt(serialized.length);
+    out.write(serialized);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
index 60897bf..ec30043 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshaller.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.googlesource.gerrit.modules.cache.chroniclemap;
 
-import com.google.gerrit.server.cache.serialize.CacheSerializer;
 import java.nio.ByteBuffer;
 import net.openhft.chronicle.bytes.Bytes;
 import net.openhft.chronicle.core.util.ReadResolvable;
@@ -25,17 +24,18 @@
         BytesReader<TimedValue<V>>,
         ReadResolvable<TimedValueMarshaller<V>> {
 
-  private final CacheSerializer<V> serializer;
+  private final String name;
 
-  TimedValueMarshaller(CacheSerializer<V> serializer) {
-    this.serializer = serializer;
+  TimedValueMarshaller(String name) {
+    this.name = name;
   }
 
   @Override
   public TimedValueMarshaller<V> readResolve() {
-    return new TimedValueMarshaller<>(serializer);
+    return new TimedValueMarshaller<>(name);
   }
 
+  @SuppressWarnings("unchecked")
   @Override
   public TimedValue<V> read(Bytes in, TimedValue<V> using) {
     long initialPosition = in.readPosition();
@@ -57,7 +57,7 @@
     // Deserialize object V (remaining bytes)
     byte[] serializedV = new byte[vLength];
     in.read(serializedV, 0, vLength);
-    V v = serializer.deserialize(serializedV);
+    V v = (V) CacheSerializers.getValueSerializer(name).deserialize(serializedV);
 
     using = new TimedValue<>(v, created);
 
@@ -66,7 +66,7 @@
 
   @Override
   public void write(Bytes out, TimedValue<V> toWrite) {
-    byte[] serialized = serializer.serialize(toWrite.getValue());
+    byte[] serialized = CacheSerializers.getValueSerializer(name).serialize(toWrite.getValue());
 
     // Serialize as follows:
     // created | length of serialized V | serialized value V
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
index df6fc38..36ce53c 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheIT.java
@@ -48,7 +48,7 @@
     final int negativeDiskLimit = -1;
     final Cache<String, String> cache =
         persistentCacheFactory.build(
-            new TestPersistentCacheDef("foo", negativeDiskLimit), CacheBackend.CAFFEINE);
+            new TestPersistentCacheDef("foo", null, negativeDiskLimit), CacheBackend.CAFFEINE);
 
     assertThat(cache.getClass().getSimpleName()).isEqualTo("CaffeinatedGuavaCache");
   }
@@ -58,7 +58,7 @@
     final int positiveDiskLimit = 1024;
     assertThat(
             persistentCacheFactory.build(
-                new TestPersistentCacheDef("foo", positiveDiskLimit), CacheBackend.CAFFEINE))
+                new TestPersistentCacheDef("foo", null, positiveDiskLimit), CacheBackend.CAFFEINE))
         .isInstanceOf(ChronicleMapCacheImpl.class);
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
index e8c1f4a..5d380ff 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
+import com.google.gerrit.server.cache.serialize.CacheSerializer;
 import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Guice;
@@ -47,6 +48,7 @@
 import org.junit.rules.TemporaryFolder;
 
 public class ChronicleMapCacheTest {
+  private static final String TEST_CACHE_NAME = "test-cache-name";
   @Inject MetricMaker metricMaker;
   @Inject MetricRegistry metricRegistry;
 
@@ -58,6 +60,8 @@
 
   @Before
   public void setUp() throws Exception {
+    CacheSerializers.registerCacheKeySerializer(TEST_CACHE_NAME, StringCacheSerializer.INSTANCE);
+    CacheSerializers.registerCacheValueSerializer(TEST_CACHE_NAME, StringCacheSerializer.INSTANCE);
     sitePaths = new SitePaths(temporaryFolder.newFolder().toPath());
     Files.createDirectories(sitePaths.etc_dir);
 
@@ -82,7 +86,7 @@
   }
 
   @Test
-  public void getIfPresentShouldReturnNullWhenThereisNoCachedValue() throws Exception {
+  public void getIfPresentShouldReturnNullWhenThereIsNoCachedValue() throws Exception {
     assertThat(newCacheWithLoader(null).getIfPresent("foo")).isNull();
   }
 
@@ -237,7 +241,7 @@
   @Test
   public void getIfPresentShouldReturnNullWhenValueIsExpired() throws Exception {
     ChronicleMapCacheImpl<String, String> cache =
-        newCache(true, null, Duration.ofSeconds(1), null, 1);
+        newCache(true, TEST_CACHE_NAME, null, Duration.ofSeconds(1), null, 1);
     cache.put("foo", "some-stale-value");
     Thread.sleep(1010); // Allow cache entry to expire
     assertThat(cache.getIfPresent("foo")).isNull();
@@ -247,7 +251,7 @@
   public void getShouldRefreshValueWhenExpired() throws Exception {
     String newCachedValue = UUID.randomUUID().toString();
     ChronicleMapCacheImpl<String, String> cache =
-        newCache(true, newCachedValue, null, Duration.ofSeconds(1), 1);
+        newCache(true, TEST_CACHE_NAME, newCachedValue, null, Duration.ofSeconds(1), 1);
     cache.put("foo", "some-stale-value");
     Thread.sleep(1010); // Allow cache to be flagged as needing refresh
     assertThat(cache.get("foo")).isEqualTo(newCachedValue);
@@ -256,7 +260,7 @@
   @Test
   public void shouldPruneExpiredValues() throws Exception {
     ChronicleMapCacheImpl<String, String> cache =
-        newCache(true, null, Duration.ofSeconds(1), null, 1);
+        newCache(true, TEST_CACHE_NAME, null, Duration.ofSeconds(1), null, 1);
     cache.put("foo1", "some-stale-value1");
     cache.put("foo2", "some-stale-value1");
     Thread.sleep(1010); // Allow cache entries to expire
@@ -293,10 +297,10 @@
   public void shouldEvictOldestElementInCacheWhenIsNeverAccessed() throws Exception {
     final String fooValue = "foo";
 
-    gerritConfig.setInt("cache", "foo", "maxEntries", 2);
-    gerritConfig.setInt("cache", "foo", "percentageHotKeys", 10);
-    gerritConfig.setInt("cache", "foo", "avgKeySize", "foo1".getBytes().length);
-    gerritConfig.setInt("cache", "foo", "avgValueSize", valueSize(fooValue));
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", 2);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "percentageHotKeys", 10);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgKeySize", "foo1".getBytes().length);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgValueSize", valueSize(fooValue));
     gerritConfig.save();
 
     ChronicleMapCacheImpl<String, String> cache = newCacheWithLoader(fooValue);
@@ -313,10 +317,10 @@
   public void shouldEvictRecentlyInsertedElementInCacheWhenOldestElementIsAccessed()
       throws Exception {
     final String fooValue = "foo";
-    gerritConfig.setInt("cache", "foo", "maxEntries", 2);
-    gerritConfig.setInt("cache", "foo", "percentageHotKeys", 10);
-    gerritConfig.setInt("cache", "foo", "avgKeySize", "foo1".getBytes().length);
-    gerritConfig.setInt("cache", "foo", "avgValueSize", valueSize(fooValue));
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", 2);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "percentageHotKeys", 10);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgKeySize", "foo1".getBytes().length);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgValueSize", valueSize(fooValue));
     gerritConfig.save();
 
     ChronicleMapCacheImpl<String, String> cache = newCacheWithLoader(fooValue);
@@ -354,13 +358,13 @@
   @Test
   public void shouldTriggerPercentageFreeMetric() throws Exception {
     String cachedValue = UUID.randomUUID().toString();
-    String freeSpaceMetricName = "cache/chroniclemap/percentage_free_space_" + cachedValue;
-    gerritConfig.setInt("cache", cachedValue, "maxEntries", 2);
-    gerritConfig.setInt("cache", cachedValue, "avgKeySize", cachedValue.getBytes().length);
-    gerritConfig.setInt("cache", cachedValue, "avgValueSize", valueSize(cachedValue));
+    String freeSpaceMetricName = "cache/chroniclemap/percentage_free_space_" + TEST_CACHE_NAME;
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", 2);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgKeySize", cachedValue.getBytes().length);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgValueSize", valueSize(cachedValue));
     gerritConfig.save();
 
-    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(TEST_CACHE_NAME, cachedValue);
 
     assertThat(getMetric(freeSpaceMetricName).getValue()).isEqualTo(100);
 
@@ -373,13 +377,13 @@
   @Test
   public void shouldTriggerRemainingAutoResizeMetric() throws Exception {
     String cachedValue = UUID.randomUUID().toString();
-    String autoResizeMetricName = "cache/chroniclemap/remaining_autoresizes_" + cachedValue;
-    gerritConfig.setInt("cache", cachedValue, "maxEntries", 2);
-    gerritConfig.setInt("cache", cachedValue, "avgKeySize", cachedValue.getBytes().length);
-    gerritConfig.setInt("cache", cachedValue, "avgValueSize", valueSize(cachedValue));
+    String autoResizeMetricName = "cache/chroniclemap/remaining_autoresizes_" + TEST_CACHE_NAME;
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", 2);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgKeySize", cachedValue.getBytes().length);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "avgValueSize", valueSize(cachedValue));
     gerritConfig.save();
 
-    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(TEST_CACHE_NAME, cachedValue);
 
     assertThat(getMetric(autoResizeMetricName).getValue()).isEqualTo(1);
 
@@ -397,12 +401,12 @@
     int percentageHotKeys = 60;
     int maxEntries = 10;
     int expectedCapacity = 6;
-    String hotKeysCapacityMetricName = "cache/chroniclemap/hot_keys_capacity_" + cachedValue;
-    gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries);
-    gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys);
+    String hotKeysCapacityMetricName = "cache/chroniclemap/hot_keys_capacity_" + TEST_CACHE_NAME;
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", maxEntries);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "percentageHotKeys", percentageHotKeys);
     gerritConfig.save();
 
-    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(TEST_CACHE_NAME, cachedValue);
 
     assertThat(getMetric(hotKeysCapacityMetricName).getValue()).isEqualTo(expectedCapacity);
   }
@@ -414,12 +418,12 @@
     int maxEntries = 10;
     int maxHotKeyCapacity = 3;
     final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2);
-    String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + cachedValue;
-    gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries);
-    gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys);
+    String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + TEST_CACHE_NAME;
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", maxEntries);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "percentageHotKeys", percentageHotKeys);
     gerritConfig.save();
 
-    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(TEST_CACHE_NAME, cachedValue);
 
     assertThat(getMetric(hotKeysSizeMetricName).getValue()).isEqualTo(0);
 
@@ -448,12 +452,12 @@
     int maxEntries = 10;
     int maxHotKeyCapacity = 3;
     final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2);
-    String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + cachedValue;
-    gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries);
-    gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys);
+    String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + TEST_CACHE_NAME;
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "maxEntries", maxEntries);
+    gerritConfig.setInt("cache", TEST_CACHE_NAME, "percentageHotKeys", percentageHotKeys);
     gerritConfig.save();
 
-    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+    ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(TEST_CACHE_NAME, cachedValue);
 
     for (int i = 0; i < maxHotKeyCapacity; i++) {
       cache.put(cachedValue + i, cachedValue);
@@ -478,7 +482,7 @@
     String autoResizeMetricName = "cache/chroniclemap/remaining_autoresizes_" + sanitized;
     String hotKeyCapacityMetricName = "cache/chroniclemap/hot_keys_capacity_" + sanitized;
 
-    newCacheWithMetrics(cacheName);
+    newCacheWithMetrics(cacheName, null);
 
     getMetric(hotKeySizeMetricName);
     getMetric(percentageFreeMetricName);
@@ -487,44 +491,61 @@
   }
 
   private int valueSize(String value) {
-    final TimedValueMarshaller<String> marshaller =
-        new TimedValueMarshaller<>(StringCacheSerializer.INSTANCE);
+    final TimedValueMarshaller<String> marshaller = new TimedValueMarshaller<>(TEST_CACHE_NAME);
 
     Bytes<ByteBuffer> out = Bytes.elasticByteBuffer();
     marshaller.write(out, new TimedValue<>(value));
     return out.toByteArray().length;
   }
 
-  private ChronicleMapCacheImpl<String, String> newCacheWithMetrics(String cachedValue)
+  private ChronicleMapCacheImpl<String, String> newCacheWithMetrics(
+      String cacheName, @Nullable String cachedValue) throws IOException {
+    return newCache(true, cacheName, cachedValue, null, null, null, null, 1, metricMaker);
+  }
+
+  private ChronicleMapCacheImpl<String, String> newCacheWithMetrics(
+      String cacheName,
+      String cachedValue,
+      CacheSerializer<String> keySerializer,
+      CacheSerializer<String> valueSerializer)
       throws IOException {
-    return newCache(true, cachedValue, null, null, 1, metricMaker);
+    return newCache(
+        true, cacheName, cachedValue, null, null, null, null, 1, new DisabledMetricMaker());
   }
 
   private ChronicleMapCacheImpl<String, String> newCache(
       Boolean withLoader,
-      @Nullable String cachedValue,
+      String cacheName,
+      @Nullable String loadedValue,
       @Nullable Duration expireAfterWrite,
       @Nullable Duration refreshAfterWrite,
       Integer version)
       throws IOException {
     return newCache(
         withLoader,
-        cachedValue,
+        cacheName,
+        loadedValue,
         expireAfterWrite,
         refreshAfterWrite,
+        null,
+        null,
         version,
         new DisabledMetricMaker());
   }
 
   private ChronicleMapCacheImpl<String, String> newCache(
       Boolean withLoader,
+      String cacheName,
       @Nullable String cachedValue,
       @Nullable Duration expireAfterWrite,
       @Nullable Duration refreshAfterWrite,
+      @Nullable CacheSerializer<String> keySerializer,
+      @Nullable CacheSerializer<String> valueSerializer,
       Integer version,
       MetricMaker metricMaker)
       throws IOException {
-    TestPersistentCacheDef cacheDef = new TestPersistentCacheDef(cachedValue);
+    TestPersistentCacheDef cacheDef =
+        new TestPersistentCacheDef(cacheName, cachedValue, keySerializer, valueSerializer);
 
     File persistentFile =
         ChronicleMapCacheFactory.fileName(
@@ -542,21 +563,21 @@
         cacheDef, config, withLoader ? cacheDef.loader() : null, metricMaker);
   }
 
-  private ChronicleMapCacheImpl<String, String> newCacheWithLoader(@Nullable String cachedValue)
+  private ChronicleMapCacheImpl<String, String> newCacheWithLoader(@Nullable String loadedValue)
       throws IOException {
-    return newCache(true, cachedValue, null, null, 1);
+    return newCache(true, TEST_CACHE_NAME, loadedValue, null, null, 1);
   }
 
   private ChronicleMapCacheImpl<String, String> newCacheWithLoader() throws IOException {
-    return newCache(true, null, null, null, 1);
+    return newCache(true, TEST_CACHE_NAME, null, null, null, 1);
   }
 
   private ChronicleMapCacheImpl<String, String> newCacheVersion(int version) throws IOException {
-    return newCache(true, null, null, null, version);
+    return newCache(true, TEST_CACHE_NAME, null, null, null, version);
   }
 
   private ChronicleMapCacheImpl<String, String> newCacheWithoutLoader() throws IOException {
-    return newCache(false, null, null, null, 1);
+    return newCache(false, TEST_CACHE_NAME, null, null, null, 1);
   }
 
   private <V> Gauge<V> getMetric(String name) {
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapterTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapterTest.java
deleted file mode 100644
index 09f7f17..0000000
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapMarshallerAdapterTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2020 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.
-// limitations under the License.
-// See the License for the specific language governing permissions and
-package com.googlesource.gerrit.modules.cache.chroniclemap;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import net.openhft.chronicle.bytes.Bytes;
-import org.eclipse.jgit.lib.ObjectId;
-import org.junit.Test;
-
-public class ChronicleMapMarshallerAdapterTest {
-
-  @Test
-  public void shouldDeserializeToTheSameObject() {
-    ObjectId id = ObjectId.fromString("aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
-    byte[] serializedId = ObjectIdCacheSerializer.INSTANCE.serialize(id);
-    ByteBuffer serializedIdWithLen = ByteBuffer.allocate(serializedId.length + 4);
-    serializedIdWithLen.order(ByteOrder.LITTLE_ENDIAN);
-    serializedIdWithLen.putInt(serializedId.length);
-    serializedIdWithLen.put(serializedId);
-
-    ChronicleMapMarshallerAdapter<ObjectId> marshaller =
-        new ChronicleMapMarshallerAdapter<>(ObjectIdCacheSerializer.INSTANCE);
-    ObjectId out = marshaller.read(Bytes.allocateDirect(serializedIdWithLen.array()), null);
-
-    assertThat(id).isEqualTo(out);
-  }
-
-  @Test
-  public void shouldSerializeToTheSameObject() {
-    ObjectId id = ObjectId.fromString("aabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
-    ChronicleMapMarshallerAdapter<ObjectId> marshaller =
-        new ChronicleMapMarshallerAdapter<>(ObjectIdCacheSerializer.INSTANCE);
-
-    Bytes<ByteBuffer> out = Bytes.elasticByteBuffer();
-    marshaller.write(out, id);
-    assertThat(marshaller.read(out, null)).isEqualTo(id);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshallerTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshallerTest.java
new file mode 100644
index 0000000..44a5dd6
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/KeyWrapperMarshallerTest.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2021 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.googlesource.gerrit.modules.cache.chroniclemap;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.server.cache.serialize.ObjectIdCacheSerializer;
+import java.nio.ByteBuffer;
+import net.openhft.chronicle.bytes.Bytes;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
+import org.junit.Test;
+
+public class KeyWrapperMarshallerTest {
+  private static final String TEST_CACHE_NAME = "key-wrapper-test";
+
+  @Before
+  public void setup() {
+    CacheSerializers.registerCacheKeySerializer(TEST_CACHE_NAME, ObjectIdCacheSerializer.INSTANCE);
+  }
+
+  @Test
+  public void shouldSerializeAndDeserializeBack() {
+    ObjectId id = ObjectId.fromString("1234567890123456789012345678901234567890");
+    KeyWrapperMarshaller<ObjectId> marshaller = new KeyWrapperMarshaller<>(TEST_CACHE_NAME);
+
+    final KeyWrapper<ObjectId> wrapped = new KeyWrapper<>(id);
+
+    Bytes<ByteBuffer> out = Bytes.elasticByteBuffer();
+    marshaller.write(out, wrapped);
+    final KeyWrapper<ObjectId> actual = marshaller.read(out, null);
+    assertThat(actual).isEqualTo(wrapped);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java
index b81baec..0e52f1d 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TestPersistentCacheDef.java
@@ -21,42 +21,61 @@
 import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
 import com.google.inject.TypeLiteral;
 import java.time.Duration;
+import java.util.Optional;
 import java.util.UUID;
 
 public class TestPersistentCacheDef implements PersistentCacheDef<String, String> {
 
   private static final Integer DEFAULT_DISK_LIMIT = 1024;
 
+  private final String name;
   private final String loadedValue;
   private final Duration expireAfterWrite;
   private final Duration refreshAfterWrite;
   private final Integer diskLimit;
+  private final CacheSerializer<String> keySerializer;
+  private final CacheSerializer<String> valueSerializer;
 
   public TestPersistentCacheDef(
-      String loadedValue,
+      String name,
+      @Nullable String loadedValue,
       @Nullable Duration expireAfterWrite,
       @Nullable Duration refreshAfterWrite) {
 
+    this.name = name;
     this.loadedValue = loadedValue;
     this.expireAfterWrite = expireAfterWrite;
     this.refreshAfterWrite = refreshAfterWrite;
     this.diskLimit = DEFAULT_DISK_LIMIT;
+    this.keySerializer = StringCacheSerializer.INSTANCE;
+    this.valueSerializer = StringCacheSerializer.INSTANCE;
   }
 
-  public TestPersistentCacheDef(String loadedValue, Integer diskLimit) {
+  public TestPersistentCacheDef(String name, @Nullable String loadedValue, Integer diskLimit) {
 
+    this.name = name;
     this.loadedValue = loadedValue;
     this.expireAfterWrite = null;
     this.refreshAfterWrite = null;
     this.diskLimit = diskLimit;
+    this.keySerializer = StringCacheSerializer.INSTANCE;
+    this.valueSerializer = StringCacheSerializer.INSTANCE;
   }
 
-  public TestPersistentCacheDef(String loadedValue) {
+  public TestPersistentCacheDef(
+      String name,
+      @Nullable String loadedValue,
+      @Nullable CacheSerializer<String> keySerializer,
+      @Nullable CacheSerializer<String> valueSerializer) {
 
+    this.name = name;
     this.loadedValue = loadedValue;
     this.expireAfterWrite = Duration.ZERO;
     this.refreshAfterWrite = Duration.ZERO;
     this.diskLimit = DEFAULT_DISK_LIMIT;
+    this.keySerializer = Optional.ofNullable(keySerializer).orElse(StringCacheSerializer.INSTANCE);
+    this.valueSerializer =
+        Optional.ofNullable(valueSerializer).orElse(StringCacheSerializer.INSTANCE);
   }
 
   @Override
@@ -71,17 +90,17 @@
 
   @Override
   public CacheSerializer<String> keySerializer() {
-    return StringCacheSerializer.INSTANCE;
+    return keySerializer;
   }
 
   @Override
   public CacheSerializer<String> valueSerializer() {
-    return StringCacheSerializer.INSTANCE;
+    return valueSerializer;
   }
 
   @Override
   public String name() {
-    return loadedValue;
+    return name;
   }
 
   @Override
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshallerTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshallerTest.java
index 62c1d44..b630e36 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshallerTest.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/TimedValueMarshallerTest.java
@@ -19,16 +19,23 @@
 import java.nio.ByteBuffer;
 import net.openhft.chronicle.bytes.Bytes;
 import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
 import org.junit.Test;
 
 public class TimedValueMarshallerTest {
+  private static final String TEST_CACHE_NAME = "timed-value-cache";
+
+  @Before
+  public void setup() {
+    CacheSerializers.registerCacheValueSerializer(
+        TEST_CACHE_NAME, ObjectIdCacheSerializer.INSTANCE);
+  }
 
   @Test
   public void shouldSerializeAndDeserializeBack() {
     ObjectId id = ObjectId.fromString("1234567890123456789012345678901234567890");
     long timestamp = 1600329018L;
-    TimedValueMarshaller<ObjectId> marshaller =
-        new TimedValueMarshaller<>(ObjectIdCacheSerializer.INSTANCE);
+    TimedValueMarshaller<ObjectId> marshaller = new TimedValueMarshaller<>(TEST_CACHE_NAME);
 
     final TimedValue<ObjectId> wrapped = new TimedValue<>(id, timestamp);