Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Use separate endpoint for interactive indexing
  Use separate FlusherRunner to fix NullPointerExceptions
  Reuse repository object when calculating target and meta SHA
  Check meta ref sha1 during the index propagation

Since stable-3.0, ReviewDb was removed in result ChangeCheckerNoteDbIT
is renamed to ChangeCheckerIT.

Change-Id: Icf2ebe40c89e6583b253efcdf176450c2b6855cb
diff --git a/.bazelversion b/.bazelversion
index fd2a018..7c69a55 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.1.0
+3.7.0
diff --git a/BUILD b/BUILD
index 577b49d..c3f5a11 100644
--- a/BUILD
+++ b/BUILD
@@ -41,7 +41,6 @@
     visibility = ["//visibility:public"],
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":high-availability__plugin",
-        "@mockito//jar",
         "@wiremock//jar",
     ],
 )
diff --git a/WORKSPACE b/WORKSPACE
index 456e679..666572c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,26 +3,15 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "6c39deb06f58bb62162ccb6865964f531739f512",
+    commit = "a029d8e41d6211c8b23052aa0a0c2c7649577e85",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
-# Snapshot Plugin API
-#load(
-#    "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
-#    "gerrit_api_maven_local",
-#)
-
-# Load snapshot Plugin API
-#gerrit_api_maven_local()
-
-# Release Plugin API
 load(
     "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
     "gerrit_api",
 )
 
-# Load release Plugin API
 gerrit_api()
 
 load("//:external_plugin_deps.bzl", "external_plugin_deps")
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 12bf919..0f8b43b 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -3,39 +3,8 @@
 def external_plugin_deps():
     maven_jar(
         name = "wiremock",
-        artifact = "com.github.tomakehurst:wiremock-standalone:2.26.3",
-        sha1 = "245c6efae2cbcb4e4f3457caf3d1c030cbaf2eb5",
-    )
-
-    maven_jar(
-        name = "mockito",
-        artifact = "org.mockito:mockito-core:2.28.2",
-        sha1 = "91110215a8cb9b77a46e045ee758f77d79167cc0",
-        deps = [
-            "@byte-buddy//jar",
-            "@byte-buddy-agent//jar",
-            "@objenesis//jar",
-        ],
-    )
-
-    BYTE_BUDDY_VERSION = "1.9.10"
-
-    maven_jar(
-        name = "byte-buddy",
-        artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
-        sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840",
-    )
-
-    maven_jar(
-        name = "byte-buddy-agent",
-        artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
-        sha1 = "9674aba5ee793e54b864952b001166848da0f26b",
-    )
-
-    maven_jar(
-        name = "objenesis",
-        artifact = "org.objenesis:objenesis:2.6",
-        sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+        artifact = "com.github.tomakehurst:wiremock-standalone:2.27.2",
+        sha1 = "327647a19b2319af2526b9c33a5733a2241723e0",
     )
 
     maven_jar(
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index 58a21b6..4743bee 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -45,6 +45,9 @@
 public class Configuration {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
+  public static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
+  public static final int DEFAULT_TIMEOUT_MS = 5000;
+
   // common parameter to peerInfo section
   static final String PEER_INFO_SECTION = "peerInfo";
 
@@ -54,7 +57,6 @@
   static final int DEFAULT_INDEX_RETRY_INTERVAL = 30000;
   static final int DEFAULT_THREAD_POOL_SIZE = 4;
   static final String NUM_STRIPED_LOCKS = "numStripedLocks";
-  static final int DEFAULT_NUM_STRIPED_LOCKS = 10;
 
   private final Main main;
   private final AutoReindex autoReindex;
@@ -333,6 +335,9 @@
   }
 
   public static class Http {
+    public static final int DEFAULT_MAX_TRIES = 360;
+    public static final int DEFAULT_RETRY_INTERVAL = 10000;
+
     static final String HTTP_SECTION = "http";
     static final String USER_KEY = "user";
     static final String PASSWORD_KEY = "password";
@@ -341,10 +346,6 @@
     static final String MAX_TRIES_KEY = "maxTries";
     static final String RETRY_INTERVAL_KEY = "retryInterval";
 
-    static final int DEFAULT_TIMEOUT_MS = 5000;
-    static final int DEFAULT_MAX_TRIES = 360;
-    static final int DEFAULT_RETRY_INTERVAL = 10000;
-
     private final String user;
     private final String password;
     private final int connectionTimeout;
@@ -446,6 +447,7 @@
   public static class Index extends Forwarding {
     static final String INDEX_SECTION = "index";
     static final String MAX_TRIES_KEY = "maxTries";
+    static final String WAIT_TIMEOUT_KEY = "waitTimeout";
     static final String RETRY_INTERVAL_KEY = "retryInterval";
     static final String SYNCHRONIZE_FORCED_KEY = "synchronizeForced";
     static final boolean DEFAULT_SYNCHRONIZE_FORCED = true;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
index 6635b80..8a267d6 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
@@ -24,6 +24,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.CACHE_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY;
@@ -33,7 +34,6 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
index de156a2..eb65106 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/AccountReindexRunnable.java
@@ -19,11 +19,9 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.sql.Timestamp;
@@ -48,12 +46,12 @@
   }
 
   @Override
-  protected Iterable<AccountState> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<AccountState> fetchItems() throws Exception {
     return accounts.all();
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, AccountState as, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(AccountState as, Timestamp sinceTs) {
     try {
       Account a = as.getAccount();
       Timestamp accountTs = a.getRegisteredOn();
@@ -63,7 +61,7 @@
         accountIdx.index(a.getId(), Operation.INDEX, Optional.empty());
         return Optional.of(accountTs);
       }
-    } catch (IOException | OrmException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
index 67c8325..dd323b4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ChangeReindexRunnable.java
@@ -21,13 +21,11 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.sql.Timestamp;
@@ -77,14 +75,14 @@
   }
 
   @Override
-  protected Iterable<Change> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<Change> fetchItems() throws Exception {
     Stream<Change> allChangesStream = Stream.empty();
     Iterable<Project.NameKey> projects = projectCache.all();
     for (Project.NameKey projectName : projects) {
       try (Repository repo = repoManager.openRepository(projectName)) {
         Stream<Change> projectChangesStream =
             notesFactory
-                .scan(repo, db, projectName)
+                .scan(repo, projectName)
                 .map((ChangeNotesResult changeNotes) -> changeNotes.notes().getChange());
         allChangesStream = Streams.concat(allChangesStream, projectChangesStream);
       }
@@ -93,7 +91,7 @@
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Change c, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(Change c, Timestamp sinceTs) {
     try {
       Timestamp changeTs = c.getLastUpdatedOn();
       if (changeTs.after(sinceTs)) {
@@ -102,7 +100,7 @@
         changeIdx.index(c.getProject() + "~" + c.getId(), Operation.INDEX, Optional.empty());
         return Optional.of(changeTs);
       }
-    } catch (OrmException | IOException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Reindex failed");
     }
     return Optional.empty();
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
index 71a0280..e37234c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/GroupReindexRunnable.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.group.db.Groups;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -34,12 +33,12 @@
   }
 
   @Override
-  protected Iterable<GroupReference> fetchItems(ReviewDb db) throws Exception {
+  protected Iterable<GroupReference> fetchItems() throws Exception {
     return groups.getAllGroupReferences()::iterator;
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, GroupReference g, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(GroupReference g, Timestamp sinceTs) {
     return Optional.empty();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
index 8abdb59..561bb19 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/IndexTs.java
@@ -23,11 +23,9 @@
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -54,7 +52,6 @@
   private final FlusherRunner accountFlusher;
   private final FlusherRunner groupFlusher;
   private final FlusherRunner projectFlusher;
-  private final SchemaFactory<ReviewDb> schemaFactory;
   private final ChangeFinder changeFinder;
   private final CurrentRequestContext currCtx;
 
@@ -104,7 +101,6 @@
   public IndexTs(
       @PluginData Path dataDir,
       WorkQueue queue,
-      SchemaFactory<ReviewDb> schemaFactory,
       ChangeFinder changeFinder,
       CurrentRequestContext currCtx) {
     this.dataDir = dataDir;
@@ -113,7 +109,6 @@
     this.accountFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.ACCOUNT);
     this.groupFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.GROUP);
     this.projectFlusher = new FlusherRunner(AbstractIndexRestApiServlet.IndexName.PROJECT);
-    this.schemaFactory = schemaFactory;
     this.changeFinder = changeFinder;
     this.currCtx = currCtx;
   }
@@ -137,7 +132,7 @@
   public void onChangeIndexed(String projectName, int id) {
     currCtx.onlyWithContext(
         (ctx) -> {
-          try (ReviewDb db = schemaFactory.open()) {
+          try {
             ChangeNotes changeNotes = changeFinder.findOne(projectName + "~" + id);
             update(
                 IndexName.CHANGE,
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
index 582227d..ff5a965 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ProjectReindexRunnable.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -35,12 +34,12 @@
   }
 
   @Override
-  protected Iterable<Project.NameKey> fetchItems(ReviewDb db) {
+  protected Iterable<Project.NameKey> fetchItems() {
     return projectCache.all();
   }
 
   @Override
-  protected Optional<Timestamp> indexIfNeeded(ReviewDb db, Project.NameKey g, Timestamp sinceTs) {
+  protected Optional<Timestamp> indexIfNeeded(Project.NameKey g, Timestamp sinceTs) {
     return Optional.empty();
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
index 7a5669e..3cb0ed0 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/autoreindex/ReindexRunnable.java
@@ -17,7 +17,6 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.AbstractIndexRestApiServlet;
 import com.google.common.base.Stopwatch;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.inject.Inject;
@@ -27,6 +26,7 @@
 import java.util.concurrent.TimeUnit;
 
 abstract class ReindexRunnable<T> implements Runnable {
+
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
 
   private final AbstractIndexRestApiServlet.IndexName itemName;
@@ -49,14 +49,13 @@
     if (maybeIndexTs.isPresent()) {
       newLastIndexTs = maxTimestamp(newLastIndexTs, Timestamp.valueOf(maybeIndexTs.get()));
       log.atFine().log("Scanning for all the %ss after %s", itemNameString, newLastIndexTs);
-      try (ManualRequestContext mctx = ctx.open();
-          ReviewDb db = mctx.getReviewDbProvider().get()) {
+      try (ManualRequestContext mctx = ctx.open()) {
         int count = 0;
         int errors = 0;
         Stopwatch stopwatch = Stopwatch.createStarted();
-        for (T c : fetchItems(db)) {
+        for (T c : fetchItems()) {
           try {
-            Optional<Timestamp> itemTs = indexIfNeeded(db, c, newLastIndexTs);
+            Optional<Timestamp> itemTs = indexIfNeeded(c, newLastIndexTs);
             if (itemTs.isPresent()) {
               count++;
               newLastIndexTs = maxTimestamp(newLastIndexTs, itemTs.get());
@@ -102,7 +101,7 @@
     return ts2;
   }
 
-  protected abstract Iterable<T> fetchItems(ReviewDb db) throws Exception;
+  protected abstract Iterable<T> fetchItems() throws Exception;
 
-  protected abstract Optional<Timestamp> indexIfNeeded(ReviewDb db, T item, Timestamp sinceTs);
+  protected abstract Optional<Timestamp> indexIfNeeded(T item, Timestamp sinceTs);
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
index 27cb5c2..0f43eaf 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcher.java
@@ -26,14 +26,18 @@
 @Singleton
 class CachePatternMatcher {
   private static final List<String> DEFAULT_PATTERNS =
-      ImmutableList.of(
-          "accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys", "web_sessions");
+      ImmutableList.of("accounts", "^groups.*", "ldap_usernames", "projects", "sshkeys");
 
   private final Pattern pattern;
 
   @Inject
   CachePatternMatcher(Configuration cfg) {
     List<String> patterns = new ArrayList<>(DEFAULT_PATTERNS);
+
+    if (cfg.websession().synchronize()) {
+      patterns.add("web_sessions");
+    }
+
     patterns.addAll(cfg.cache().patterns());
     this.pattern = Pattern.compile(Joiner.on("|").join(patterns));
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java
new file mode 100644
index 0000000..8c00282
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/event/ProjectNameKeyAdapter.java
@@ -0,0 +1,44 @@
+// 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.ericsson.gerrit.plugins.highavailability.event;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+
+public class ProjectNameKeyAdapter
+    implements JsonSerializer<Project.NameKey>, JsonDeserializer<Project.NameKey> {
+  @Override
+  public JsonElement serialize(
+      Project.NameKey project, Type typeOfSrc, JsonSerializationContext context) {
+    return new JsonPrimitive(project.get());
+  }
+
+  @Override
+  public Project.NameKey deserialize(
+      JsonElement json, Type typeOfT, JsonDeserializationContext context)
+      throws JsonParseException {
+    if (json.isJsonObject()) {
+      return Project.nameKey(json.getAsJsonObject().get("name").getAsJsonPrimitive().getAsString());
+    }
+    return Project.nameKey(json.getAsString());
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
index 3d9fd60..f156da4 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBroker.java
@@ -14,7 +14,6 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventBroker;
 import com.google.gerrit.server.events.EventListener;
@@ -24,7 +23,6 @@
 import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 class ForwardedAwareEventBroker extends EventBroker {
 
@@ -34,15 +32,8 @@
       PluginSetContext<EventListener> unrestrictedListeners,
       PermissionBackend permissionBackend,
       ProjectCache projectCache,
-      Factory notesFactory,
-      Provider<ReviewDb> dbProvider) {
-    super(
-        listeners,
-        unrestrictedListeners,
-        permissionBackend,
-        projectCache,
-        notesFactory,
-        dbProvider);
+      Factory notesFactory) {
+    super(listeners, unrestrictedListeners, permissionBackend, projectCache, notesFactory);
   }
 
   @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
index 51db006..414e795 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandler.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
@@ -42,9 +41,8 @@
    * Dispatch an event in the local node, event will not be forwarded to the other node.
    *
    * @param event The event to dispatch
-   * @throws OrmException If an error occur while retrieving the change the event belongs to.
    */
-  public void dispatch(Event event) throws OrmException, PermissionBackendException {
+  public void dispatch(Event event) throws PermissionBackendException {
     try {
       Context.setForwardedEvent(true);
       log.atFine().log("dispatching event %s", event.getType());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
index ba60830..f31331d 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,13 +31,12 @@
   private final AccountIndexer indexer;
 
   @Inject
-  ForwardedIndexAccountHandler(AccountIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexAccountHandler(AccountIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) throws IOException {
+  protected void doIndex(Account.Id id, Optional<IndexEvent> indexEvent) {
     indexer.index(id);
     log.atFine().log("Account %s successfully indexed", id);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
index d7c7e88..add93d7 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -18,17 +18,14 @@
 import com.ericsson.gerrit.plugins.highavailability.Configuration.Index;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl;
-import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
 import com.ericsson.gerrit.plugins.highavailability.index.ForwardedIndexExecutor;
 import com.google.common.base.Splitter;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -45,7 +42,6 @@
 @Singleton
 public class ForwardedIndexChangeHandler extends ForwardedIndexingHandler<String> {
   private final ChangeIndexer indexer;
-  private final ChangeDb changeDb;
   private final ScheduledExecutorService indexExecutor;
   private final OneOffRequestContext oneOffCtx;
   private final int retryInterval;
@@ -55,14 +51,11 @@
   @Inject
   ForwardedIndexChangeHandler(
       ChangeIndexer indexer,
-      ChangeDb changeDb,
       Configuration config,
       @ForwardedIndexExecutor ScheduledExecutorService indexExecutor,
       OneOffRequestContext oneOffCtx,
       ChangeCheckerImpl.Factory changeCheckerFactory) {
-    super(config.index());
     this.indexer = indexer;
-    this.changeDb = changeDb;
     this.indexExecutor = indexExecutor;
     this.oneOffCtx = oneOffCtx;
     this.changeCheckerFactory = changeCheckerFactory;
@@ -73,13 +66,12 @@
   }
 
   @Override
-  protected void doIndex(String id, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException {
+  protected void doIndex(String id, Optional<IndexEvent> indexEvent) throws IOException {
     doIndex(id, indexEvent, 0);
   }
 
   private void doIndex(String id, Optional<IndexEvent> indexEvent, int retryCount)
-      throws IOException, OrmException {
+      throws IOException {
     try {
       ChangeChecker checker = changeCheckerFactory.create(id);
       Optional<ChangeNotes> changeNotes = checker.getChangeNotes();
@@ -121,11 +113,9 @@
     }
   }
 
-  private void reindex(ChangeNotes notes) throws IOException, OrmException {
-    try (ReviewDb db = changeDb.open()) {
-      notes.reload();
-      indexer.index(db, notes.getChange());
-    }
+  private void reindex(ChangeNotes notes) {
+    notes.reload();
+    indexer.index(notes.getChange());
   }
 
   private boolean rescheduleIndex(String id, Optional<IndexEvent> indexEvent, int retryCount) {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
index fb4d2f2..127af36 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,14 +31,12 @@
   private final GroupIndexer indexer;
 
   @Inject
-  ForwardedIndexGroupHandler(GroupIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexGroupHandler(GroupIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent)
-      throws IOException {
+  protected void doIndex(AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent) {
     indexer.index(uuid);
     log.atFine().log("Group %s successfully indexed", uuid);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
index 5ae4cc0..b6daf41 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandler.java
@@ -14,12 +14,10 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.gerrit.index.project.ProjectIndexer;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -33,14 +31,12 @@
   private final ProjectIndexer indexer;
 
   @Inject
-  ForwardedIndexProjectHandler(ProjectIndexer indexer, Configuration config) {
-    super(config.index());
+  ForwardedIndexProjectHandler(ProjectIndexer indexer) {
     this.indexer = indexer;
   }
 
   @Override
-  protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent)
-      throws IOException {
+  protected void doIndex(Project.NameKey projectName, Optional<IndexEvent> indexEvent) {
     indexer.index(projectName);
     log.atFine().log("Project %s successfully indexed", projectName);
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
index 78e103d..44e35a8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexingHandler.java
@@ -14,13 +14,12 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.flogger.FluentLogger;
-import com.google.common.util.concurrent.Striped;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Optional;
-import java.util.concurrent.locks.Lock;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Base class to handle forwarded indexing. This class is meant to be extended by classes used on
@@ -30,6 +29,7 @@
  */
 public abstract class ForwardedIndexingHandler<T> {
   protected static final FluentLogger log = FluentLogger.forEnclosingClass();
+  private final Set<T> inFlightIndexing = Collections.newSetFromMap(new ConcurrentHashMap<>());
 
   public enum Operation {
     INDEX,
@@ -41,17 +41,10 @@
     }
   }
 
-  private final Striped<Lock> idLocks;
-
-  protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException;
+  protected abstract void doIndex(T id, Optional<IndexEvent> indexEvent) throws IOException;
 
   protected abstract void doDelete(T id, Optional<IndexEvent> indexEvent) throws IOException;
 
-  protected ForwardedIndexingHandler(Configuration.Index indexConfig) {
-    idLocks = Striped.lock(indexConfig.numStripedLocks());
-  }
-
   /**
    * Index an item in the local node, indexing will not be forwarded to the other node.
    *
@@ -59,16 +52,12 @@
    * @param operation The operation to do; index or delete
    * @param indexEvent The index event details.
    * @throws IOException If an error occur while indexing.
-   * @throws OrmException If an error occur while retrieving a change related to the item to index
    */
-  public void index(T id, Operation operation, Optional<IndexEvent> indexEvent)
-      throws IOException, OrmException {
+  public void index(T id, Operation operation, Optional<IndexEvent> indexEvent) throws IOException {
     log.atFine().log("%s %s %s", operation, id, indexEvent);
-    try {
-      Context.setForwardedEvent(true);
-      Lock idLock = idLocks.get(id);
-      idLock.lock();
+    if (inFlightIndexing.add(id)) {
       try {
+        Context.setForwardedEvent(true);
         switch (operation) {
           case INDEX:
             doIndex(id, indexEvent);
@@ -81,10 +70,12 @@
             break;
         }
       } finally {
-        idLock.unlock();
+        Context.unsetForwardedEvent();
+        inFlightIndexing.remove(id);
       }
-    } finally {
-      Context.unsetForwardedEvent();
+    } else {
+      throw new InFlightIndexedException(
+          String.format("Indexing for %s %s %s already in flight", operation, id, indexEvent));
     }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
index 851d7e8..b73b676 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -15,6 +15,7 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import com.google.gerrit.server.events.Event;
+import java.util.concurrent.CompletableFuture;
 
 /** Forward indexing, stream events and cache evictions to the other master */
 public interface Forwarder {
@@ -24,9 +25,10 @@
    *
    * @param accountId the account to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexAccount(int accountId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other master.
@@ -34,9 +36,10 @@
    * @param projectName the project of the change to index.
    * @param changeId the change to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexChange(String projectName, int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexChange(String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a change indexing event to the other master using batch index endpoint.
@@ -44,67 +47,76 @@
    * @param projectName the project of the change to index.
    * @param changeId the change to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean batchIndexChange(String projectName, int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> batchIndexChange(
+      String projectName, int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a delete change from index event to the other master.
    *
    * @param changeId the change to remove from the index.
    * @param indexEvent the details of the index event.
-   * @return rue if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
+  CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent);
 
   /**
    * Forward a group indexing event to the other master.
    *
    * @param uuid the group to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexGroup(String uuid, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent);
 
   /**
    * Forward a project indexing event to the other master.
    *
    * @param projectName the project to index.
    * @param indexEvent the details of the index event.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean indexProject(String projectName, IndexEvent indexEvent);
+  CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent);
 
   /**
    * Forward a stream event to the other master.
    *
    * @param event the event to forward.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean send(Event event);
+  CompletableFuture<Boolean> send(Event event);
 
   /**
    * Forward a cache eviction event to the other master.
    *
    * @param cacheName the name of the cache to evict an entry from.
    * @param key the key identifying the entry to evict from the cache.
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean evict(String cacheName, Object key);
+  CompletableFuture<Boolean> evict(String cacheName, Object key);
 
   /**
    * Forward an addition to the project list cache to the other master.
    *
    * @param projectName the name of the project to add to the project list cache
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean addToProjectList(String projectName);
+  CompletableFuture<Boolean> addToProjectList(String projectName);
 
   /**
    * Forward a removal from the project list cache to the other master.
    *
    * @param projectName the name of the project to remove from the project list cache
-   * @return true if successful, otherwise false.
+   * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of
+   *     false.
    */
-  boolean removeFromProjectList(String projectName);
+  CompletableFuture<Boolean> removeFromProjectList(String projectName);
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java
new file mode 100644
index 0000000..87d28b3
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InFlightIndexedException.java
@@ -0,0 +1,26 @@
+// 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.ericsson.gerrit.plugins.highavailability.forwarder;
+
+import java.io.IOException;
+
+public class InFlightIndexedException extends IOException {
+
+  private static final long serialVersionUID = 1L;
+
+  public InFlightIndexedException(String msg) {
+    super(msg);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
index 410aa23..5603fc2 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -16,7 +16,6 @@
 
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
 import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler;
@@ -25,7 +24,6 @@
 import com.google.common.base.Charsets;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.Optional;
@@ -93,10 +91,6 @@
     } catch (IOException e) {
       sendError(rsp, SC_CONFLICT, e.getMessage());
       log.atSevere().withCause(e).log("Unable to update %s index", indexName);
-    } catch (OrmException e) {
-      String msg = String.format("Error trying to find %s", indexName);
-      sendError(rsp, SC_NOT_FOUND, msg);
-      log.atFine().withCause(e).log(msg);
     }
   }
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
index 61b714c..3d2d25e 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.net.MediaType.JSON_UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 
@@ -25,7 +24,6 @@
 import com.google.common.net.MediaType;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -55,9 +53,6 @@
       }
       forwardedEventHandler.dispatch(getEventFromRequest(req));
       rsp.setStatus(SC_NO_CONTENT);
-    } catch (OrmException e) {
-      log.atFine().withCause(e).log("Error trying to find a change");
-      sendError(rsp, SC_NOT_FOUND, "Change not found\n");
     } catch (IOException | PermissionBackendException e) {
       log.atSevere().withCause(e).log("Unable to re-trigger event");
       sendError(rsp, SC_BAD_REQUEST, e.getMessage());
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
index e62cec2..c600019 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/GsonProvider.java
@@ -14,10 +14,13 @@
 
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
+import com.ericsson.gerrit.plugins.highavailability.event.ProjectNameKeyAdapter;
 import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDeserializer;
 import com.google.gerrit.server.events.SupplierDeserializer;
+import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.inject.Provider;
@@ -28,6 +31,8 @@
   public Gson get() {
     return new GsonBuilder()
         .registerTypeAdapter(Event.class, new EventDeserializer())
+        .registerTypeAdapter(Project.NameKey.class, new ProjectNameKeyAdapter())
+        .registerTypeAdapter(Supplier.class, new SupplierSerializer())
         .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
         .create();
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
index f2ac080..cf98aae 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -15,11 +15,9 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult;
-import com.google.common.base.Supplier;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.net.MediaType;
-import com.google.gerrit.server.events.SupplierSerializer;
 import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.net.URI;
@@ -32,12 +30,12 @@
 
 class HttpSession {
   private final CloseableHttpClient httpClient;
-  private final Gson gson =
-      new GsonBuilder().registerTypeAdapter(Supplier.class, new SupplierSerializer()).create();
+  private final Gson gson;
 
   @Inject
-  HttpSession(CloseableHttpClient httpClient) {
+  HttpSession(CloseableHttpClient httpClient, GsonProvider gsonProvider) {
     this.httpClient = httpClient;
+    this.gson = gsonProvider.get();
   }
 
   HttpResult post(String uri) throws IOException {
@@ -67,7 +65,8 @@
     }
   }
 
-  private String jsonEncode(Object content) {
+  @VisibleForTesting
+  String jsonEncode(Object content) {
     if (content instanceof String) {
       return (String) content;
     }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
index 52065ed..6426b5a 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -28,10 +28,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
-import java.util.stream.Collectors;
 import javax.net.ssl.SSLException;
 import org.apache.http.HttpException;
 import org.apache.http.client.ClientProtocolException;
@@ -49,6 +47,7 @@
   private final Configuration cfg;
   private final Provider<Set<PeerInfo>> peerInfoProvider;
   private final GsonProvider gson;
+  private final RestForwarderScheduler scheduler;
 
   @Inject
   RestForwarder(
@@ -56,21 +55,24 @@
       @PluginName String pluginName,
       Configuration cfg,
       Provider<Set<PeerInfo>> peerInfoProvider,
-      GsonProvider gson) {
+      GsonProvider gson,
+      RestForwarderScheduler scheduler) {
     this.httpSession = httpClient;
     this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName);
     this.cfg = cfg;
     this.peerInfoProvider = peerInfoProvider;
     this.gson = gson;
+    this.scheduler = scheduler;
   }
 
   @Override
-  public boolean indexAccount(final int accountId, IndexEvent event) {
+  public CompletableFuture<Boolean> indexAccount(final int accountId, IndexEvent event) {
     return execute(RequestMethod.POST, "index account", "index/account", accountId, event);
   }
 
   @Override
-  public boolean indexChange(String projectName, int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> indexChange(
+      String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         "index change",
@@ -79,8 +81,8 @@
         event);
   }
 
-  @Override
-  public boolean batchIndexChange(String projectName, int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> batchIndexChange(
+      String projectName, int changeId, IndexEvent event) {
     return execute(
         RequestMethod.POST,
         "index change",
@@ -90,13 +92,13 @@
   }
 
   @Override
-  public boolean deleteChangeFromIndex(final int changeId, IndexEvent event) {
+  public CompletableFuture<Boolean> deleteChangeFromIndex(final int changeId, IndexEvent event) {
     return execute(
         RequestMethod.DELETE, "delete change", "index/change", buildIndexEndpoint(changeId), event);
   }
 
   @Override
-  public boolean indexGroup(final String uuid, IndexEvent event) {
+  public CompletableFuture<Boolean> indexGroup(final String uuid, IndexEvent event) {
     return execute(RequestMethod.POST, "index group", "index/group", uuid, event);
   }
 
@@ -110,24 +112,24 @@
   }
 
   @Override
-  public boolean indexProject(String projectName, IndexEvent event) {
+  public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent event) {
     return execute(
         RequestMethod.POST, "index project", "index/project", Url.encode(projectName), event);
   }
 
   @Override
-  public boolean send(final Event event) {
+  public CompletableFuture<Boolean> send(final Event event) {
     return execute(RequestMethod.POST, "send event", "event", event.type, event);
   }
 
   @Override
-  public boolean evict(final String cacheName, final Object key) {
+  public CompletableFuture<Boolean> evict(final String cacheName, final Object key) {
     String json = gson.get().toJson(key);
     return execute(RequestMethod.POST, "invalidate cache " + cacheName, "cache", cacheName, json);
   }
 
   @Override
-  public boolean addToProjectList(String projectName) {
+  public CompletableFuture<Boolean> addToProjectList(String projectName) {
     return execute(
         RequestMethod.POST,
         "Update project_list, add ",
@@ -136,7 +138,7 @@
   }
 
   @Override
-  public boolean removeFromProjectList(String projectName) {
+  public CompletableFuture<Boolean> removeFromProjectList(String projectName) {
     return execute(
         RequestMethod.DELETE,
         "Update project_list, remove ",
@@ -148,19 +150,19 @@
     return Joiner.on("/").join("cache", Constants.PROJECT_LIST);
   }
 
-  private boolean execute(RequestMethod method, String action, String endpoint, Object id) {
+  private CompletableFuture<Boolean> execute(
+      RequestMethod method, String action, String endpoint, Object id) {
     return execute(method, action, endpoint, id, null);
   }
 
-  private boolean execute(
+  private CompletableFuture<Boolean> execute(
       RequestMethod method, String action, String endpoint, Object id, Object payload) {
-    List<CompletableFuture<Boolean>> futures =
-        peerInfoProvider.get().stream()
-            .map(peer -> createRequest(method, peer, action, endpoint, id, payload))
-            .map(request -> CompletableFuture.supplyAsync(request::execute))
-            .collect(Collectors.toList());
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-    return futures.stream().allMatch(CompletableFuture::join);
+    return peerInfoProvider.get().stream()
+        .map(peer -> createRequest(method, peer, action, endpoint, id, payload))
+        .map(scheduler::execute)
+        .reduce(
+            CompletableFuture.completedFuture(true),
+            (a, b) -> a.thenCombine(b, (left, right) -> left && right));
   }
 
   private Request createRequest(
@@ -186,7 +188,7 @@
     };
   }
 
-  private abstract class Request {
+  protected abstract class Request {
     private final String action;
     private final Object key;
     private final String destination;
@@ -199,42 +201,36 @@
       this.destination = destination;
     }
 
-    boolean execute() {
-      log.atFine().log("Executing %s %s towards %s", action, key, destination);
-      for (; ; ) {
-        try {
-          execCnt++;
-          tryOnce();
-          log.atFine().log("%s %s towards %s OK", action, key, destination);
-          return true;
-        } catch (ForwardingException e) {
-          int maxTries = cfg.http().maxTries();
-          log.atFine().withCause(e).log(
-              "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries);
-          if (!e.isRecoverable()) {
-            log.atSevere().withCause(e).log(
-                "%s %s towards %s failed with unrecoverable error; giving up",
-                action, key, destination);
-            return false;
-          }
-          if (execCnt >= maxTries) {
-            log.atSevere().log(
-                "Failed to %s %s on %s after %d tries; giving up",
-                action, key, destination, maxTries);
-            return false;
-          }
+    @Override
+    public String toString() {
+      return String.format("%s:%s => %s (try #%d)", action, key, destination, execCnt);
+    }
 
-          log.atFine().log("Retrying to %s %s on %s", action, key, destination);
-          try {
-            Thread.sleep(cfg.http().retryInterval());
-          } catch (InterruptedException ie) {
-            log.atSevere().withCause(ie).log(
-                "%s %s towards %s was interrupted; giving up", action, key, destination);
-            Thread.currentThread().interrupt();
-            return false;
-          }
+    boolean execute() throws ForwardingException {
+      log.atFine().log("Executing %s %s towards %s", action, key, destination);
+      try {
+        execCnt++;
+        tryOnce();
+        log.atFine().log("%s %s towards %s OK", action, key, destination);
+        return true;
+      } catch (ForwardingException e) {
+        int maxTries = cfg.http().maxTries();
+        log.atFine().withCause(e).log(
+            "Failed to %s %s on %s [%d/%d]", action, key, destination, execCnt, maxTries);
+        if (!e.isRecoverable()) {
+          log.atSevere().withCause(e).log(
+              "%s %s towards %s failed with unrecoverable error; giving up",
+              action, key, destination);
+          throw e;
+        }
+        if (execCnt >= maxTries) {
+          log.atSevere().log(
+              "Failed to %s %s on %s after %d tries; giving up",
+              action, key, destination, maxTries);
+          throw e;
         }
       }
+      return false;
     }
 
     void tryOnce() throws ForwardingException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java
new file mode 100644
index 0000000..ebbd940
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderScheduler.java
@@ -0,0 +1,146 @@
+// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+@Singleton
+public class RestForwarderScheduler {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+  private final ScheduledExecutorService executor;
+  private final long retryIntervalMs;
+
+  public class CompletablePromise<V> extends CompletableFuture<V> {
+    private Future<V> future;
+
+    public CompletablePromise(Future<V> future) {
+      this.future = future;
+      executor.execute(this::tryToComplete);
+    }
+
+    private void tryToComplete() {
+      if (future.isDone()) {
+        try {
+          complete(future.get());
+        } catch (InterruptedException e) {
+          completeExceptionally(e);
+        } catch (ExecutionException e) {
+          completeExceptionally(e.getCause());
+        }
+        return;
+      }
+
+      if (future.isCancelled()) {
+        cancel(true);
+        return;
+      }
+
+      executor.execute(this::tryToComplete);
+    }
+  }
+
+  @Inject
+  public RestForwarderScheduler(
+      WorkQueue workQueue, Configuration cfg, Provider<Set<PeerInfo>> peerInfoProvider) {
+    int executorSize = peerInfoProvider.get().size() * cfg.index().threadPoolSize();
+    retryIntervalMs = cfg.index().retryInterval();
+    this.executor = workQueue.createQueue(executorSize, "RestForwarderScheduler");
+  }
+
+  @VisibleForTesting
+  public RestForwarderScheduler(ScheduledExecutorService executor) {
+    this.executor = executor;
+    retryIntervalMs = 0;
+  }
+
+  public CompletableFuture<Boolean> execute(RestForwarder.Request request) {
+    return execute(request, 0);
+  }
+
+  public CompletableFuture<Boolean> execute(RestForwarder.Request request, long delayMs) {
+    return supplyAsync(
+        request.toString(),
+        () -> {
+          try {
+            if (!request.execute()) {
+              log.atWarning().log(
+                  "Rescheduling %s for retry after %d msec", request, retryIntervalMs);
+              return execute(request, retryIntervalMs);
+            }
+            return CompletableFuture.completedFuture(true);
+          } catch (ForwardingException e) {
+            log.atSevere().withCause(e).log("Forwarding of %s has failed", request);
+            return CompletableFuture.completedFuture(false);
+          }
+        },
+        executor,
+        delayMs);
+  }
+
+  private CompletableFuture<Boolean> supplyAsync(
+      String taskName,
+      Supplier<CompletableFuture<Boolean>> fn,
+      ScheduledExecutorService executor,
+      long delayMs) {
+    BooleanAsyncSupplier asyncSupplier = new BooleanAsyncSupplier(taskName, fn);
+    executor.schedule(asyncSupplier, delayMs, TimeUnit.MILLISECONDS);
+    return asyncSupplier.future();
+  }
+
+  static class BooleanAsyncSupplier implements Runnable {
+    private CompletableFuture<CompletableFuture<Boolean>> dep;
+    private Supplier<CompletableFuture<Boolean>> fn;
+    private String taskName;
+
+    BooleanAsyncSupplier(String taskName, Supplier<CompletableFuture<Boolean>> fn) {
+      this.taskName = taskName;
+      this.dep = new CompletableFuture<>();
+      this.fn = fn;
+    }
+
+    public CompletableFuture<Boolean> future() {
+      return dep.thenCompose(Function.identity());
+    }
+
+    @Override
+    public void run() {
+      try {
+        dep.complete(fn.get());
+      } catch (Throwable ex) {
+        dep.completeExceptionally(ex);
+      }
+    }
+
+    @Override
+    public String toString() {
+      return taskName;
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
index ce04589..ef7f942 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
@@ -16,7 +16,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 
@@ -27,18 +26,16 @@
    * Return the Change notes read from ReviewDb or NoteDb.
    *
    * @return notes of the Change
-   * @throws OrmException if ReviewDb or NoteDb cannot be opened
    */
-  Optional<ChangeNotes> getChangeNotes() throws OrmException;
+  Optional<ChangeNotes> getChangeNotes();
 
   /**
    * Create a new index event POJO associated with the current Change.
    *
    * @return new IndexEvent
    * @throws IOException if the current Change cannot read
-   * @throws OrmException if ReviewDb cannot be opened
    */
-  Optional<IndexEvent> newIndexEvent() throws IOException, OrmException;
+  Optional<IndexEvent> newIndexEvent() throws IOException;
 
   /**
    * Check if the local Change is aligned with the indexEvent received.
@@ -46,9 +43,8 @@
    * @param indexEvent indexing event
    * @return true if the local Change is up-to-date, false otherwise.
    * @throws IOException if an I/O error occurred while reading the local Change
-   * @throws OrmException if the local ReviewDb cannot be opened
    */
-  boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException, OrmException;
+  boolean isChangeUpToDate(Optional<IndexEvent> indexEvent) throws IOException;
 
   /**
    * Return the last computed up-to-date Change time-stamp.
@@ -57,7 +53,6 @@
    *
    * @return the Change timestamp epoch in seconds
    * @throws IOException if an I/O error occurred while reading the local Change
-   * @throws OrmException if the local ReviewDb cannot be opened
    */
-  Optional<Long> getComputedChangeTs() throws IOException, OrmException;
+  Optional<Long> getComputedChangeTs() throws IOException;
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
index a1b797f..1656eb9 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -19,15 +19,12 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeFinder;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.io.IOException;
@@ -41,7 +38,6 @@
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private final GitRepositoryManager gitRepoMgr;
   private final CommentsUtil commentsUtil;
-  private final ChangeDb changeDb;
   private final OneOffRequestContext oneOffReqCtx;
   private final String changeId;
   private final ChangeFinder changeFinder;
@@ -56,20 +52,18 @@
   public ChangeCheckerImpl(
       GitRepositoryManager gitRepoMgr,
       CommentsUtil commentsUtil,
-      ChangeDb changeDb,
       ChangeFinder changeFinder,
       OneOffRequestContext oneOffReqCtx,
       @Assisted String changeId) {
     this.changeFinder = changeFinder;
     this.gitRepoMgr = gitRepoMgr;
     this.commentsUtil = commentsUtil;
-    this.changeDb = changeDb;
     this.oneOffReqCtx = oneOffReqCtx;
     this.changeId = changeId;
   }
 
   @Override
-  public Optional<IndexEvent> newIndexEvent() throws IOException, OrmException {
+  public Optional<IndexEvent> newIndexEvent() throws IOException {
     Optional<Long> changeTs = getComputedChangeTs();
     if (!changeTs.isPresent()) {
       return Optional.empty();
@@ -91,7 +85,7 @@
   }
 
   @Override
-  public Optional<ChangeNotes> getChangeNotes() throws OrmException {
+  public Optional<ChangeNotes> getChangeNotes() {
     try (ManualRequestContext ctx = oneOffReqCtx.open()) {
       changeNotes = Optional.ofNullable(changeFinder.findOne(changeId));
       return changeNotes;
@@ -99,8 +93,7 @@
   }
 
   @Override
-  public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption)
-      throws IOException, OrmException {
+  public boolean isChangeUpToDate(Optional<IndexEvent> indexEventOption) throws IOException {
     getComputedChangeTs();
     log.atFine().log("Checking change %s against index event %s", this, indexEventOption);
     if (!computedChangeTs.isPresent()) {
@@ -128,7 +121,7 @@
   }
 
   @Override
-  public Optional<Long> getComputedChangeTs() throws IOException, OrmException {
+  public Optional<Long> getComputedChangeTs() {
     if (!computedChangeTs.isPresent()) {
       computedChangeTs = computeLastChangeTs();
     }
@@ -146,7 +139,7 @@
           + getBranchTargetSha(repo)
           + "/meta:"
           + getMetaSha(repo);
-    } catch (IOException | OrmException e) {
+    } catch (IOException e) {
       log.atSevere().withCause(e).log("Unable to render change %s", changeId);
       return "change-id=" + changeId;
     }
@@ -168,36 +161,26 @@
     }
   }
 
+  private Optional<Long> computeLastChangeTs() {
+    return getChangeNotes().map(this::getTsFromChangeAndDraftComments);
+  }
+
   private String getMetaSha(Repository repo) throws IOException {
-    if (PrimaryStorage.NOTE_DB.equals(PrimaryStorage.of(changeNotes.get().getChange()))) {
-      String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId());
-      Ref ref = repo.exactRef(refName);
-      if (ref == null) {
-        throw new IOException(
-            String.format("Unable to find meta ref %s for change %s", refName, changeId));
-      }
-      return ref.getTarget().getObjectId().getName();
+    String refName = RefNames.changeMetaRef(changeNotes.get().getChange().getId());
+    Ref ref = repo.exactRef(refName);
+    if (ref == null) {
+      throw new IOException(
+          String.format("Unable to find meta ref %s for change %s", refName, changeId));
     }
-
-    return null;
+    return ref.getTarget().getObjectId().getName();
   }
 
-  private Optional<Long> computeLastChangeTs() throws OrmException {
-    try (ReviewDb db = changeDb.open()) {
-      return getChangeNotes().map(notes -> getTsFromChangeAndDraftComments(db, notes));
-    }
-  }
-
-  private long getTsFromChangeAndDraftComments(ReviewDb db, ChangeNotes notes) {
+  private long getTsFromChangeAndDraftComments(ChangeNotes notes) {
     Change change = notes.getChange();
     Timestamp changeTs = change.getLastUpdatedOn();
-    try {
-      for (Comment comment : commentsUtil.draftByChange(db, changeNotes.get())) {
-        Timestamp commentTs = comment.writtenOn;
-        changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
-      }
-    } catch (OrmException e) {
-      log.atWarning().withCause(e).log("Unable to access draft comments for change %s", change);
+    for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) {
+      Timestamp commentTs = comment.writtenOn;
+      changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
     }
     return changeTs.getTime() / 1000;
   }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java
deleted file mode 100644
index bef5363..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeDb.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2018 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.ericsson.gerrit.plugins.highavailability.index;
-
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-
-public class ChangeDb {
-  private static final DisabledReviewDb disabledReviewDb = new DisabledReviewDb();
-
-  private final NotesMigration migration;
-  private final SchemaFactory<ReviewDb> schemaFactory;
-
-  @Inject
-  public ChangeDb(NotesMigration migration, SchemaFactory<ReviewDb> schemaFactory) {
-    this.migration = migration;
-    this.schemaFactory = schemaFactory;
-  }
-
-  public ReviewDb open() throws OrmException {
-    return migration.readChanges() ? disabledReviewDb : schemaFactory.open();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
index f8c6e31..46aadbb 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/CurrentRequestContext.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.function.Consumer;
@@ -51,8 +50,6 @@
     if (ctx == null) {
       try (ManualRequestContext manualCtx = oneOffCtx.open()) {
         body.accept(manualCtx);
-      } catch (OrmException e) {
-        logger.atSevere().withCause(e).log("Unable to open request context");
       }
     } else {
       body.accept(ctx);
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
deleted file mode 100644
index 48c6ecb..0000000
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/DisabledReviewDb.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright (C) 2018 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.ericsson.gerrit.plugins.highavailability.index;
-
-import com.google.common.util.concurrent.CheckedFuture;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
-import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
-import com.google.gerrit.reviewdb.server.PatchSetAccess;
-import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.SchemaVersionAccess;
-import com.google.gwtorm.server.Access;
-import com.google.gwtorm.server.AtomicUpdate;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.gwtorm.server.StatementExecutor;
-import java.util.Map;
-
-/** ReviewDb that is disabled. */
-@SuppressWarnings("deprecation")
-public class DisabledReviewDb implements ReviewDb {
-  public static class Disabled extends RuntimeException {
-    private static final long serialVersionUID = 1L;
-
-    private Disabled() {
-      super("ReviewDb is disabled for changes");
-    }
-  }
-
-  public static class DisabledChangeAccess implements ChangeAccess {
-
-    @Override
-    public String getRelationName() {
-      throw new Disabled();
-    }
-
-    @Override
-    public int getRelationID() {
-      throw new Disabled();
-    }
-
-    @Override
-    public ResultSet<Change> iterateAllEntities() throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change.Id primaryKey(Change entity) {
-      throw new Disabled();
-    }
-
-    @Override
-    public Map<Change.Id, Change> toMap(Iterable<Change> c) {
-      throw new Disabled();
-    }
-
-    @Override
-    public CheckedFuture<Change, OrmException> getAsync(Change.Id key) {
-      throw new Disabled();
-    }
-
-    @Override
-    public ResultSet<Change> get(Iterable<Change.Id> keys) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void insert(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void update(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void upsert(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void deleteKeys(Iterable<Change.Id> keys) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void delete(Iterable<Change> instances) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public void beginTransaction(Change.Id key) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change atomicUpdate(Change.Id key, AtomicUpdate<Change> update) throws OrmException {
-      throw new Disabled();
-    }
-
-    @Override
-    public Change get(Change.Id id) throws OrmException {
-      return null;
-    }
-
-    @Override
-    public ResultSet<Change> all() throws OrmException {
-      return null;
-    }
-  }
-
-  @Override
-  public void close() {
-    // Do nothing.
-  }
-
-  @Override
-  public void commit() {
-    throw new Disabled();
-  }
-
-  @Override
-  public void rollback() {
-    throw new Disabled();
-  }
-
-  @Override
-  public void updateSchema(StatementExecutor e) {
-    throw new Disabled();
-  }
-
-  @Override
-  public void pruneSchema(StatementExecutor e) {
-    throw new Disabled();
-  }
-
-  @Override
-  public Access<?, ?>[] allRelations() {
-    throw new Disabled();
-  }
-
-  @Override
-  public SchemaVersionAccess schemaVersion() {
-    throw new Disabled();
-  }
-
-  @Override
-  public ChangeAccess changes() {
-    return new DisabledChangeAccess();
-  }
-
-  @Override
-  public PatchSetApprovalAccess patchSetApprovals() {
-    throw new Disabled();
-  }
-
-  @Override
-  public ChangeMessageAccess changeMessages() {
-    throw new Disabled();
-  }
-
-  @Override
-  public PatchSetAccess patchSets() {
-    throw new Disabled();
-  }
-
-  @Override
-  public PatchLineCommentAccess patchComments() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextAccountId() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextAccountGroupId() {
-    throw new Disabled();
-  }
-
-  @Override
-  public int nextChangeId() {
-    throw new Disabled();
-  }
-}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
index 633a098..2344b06 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -14,6 +14,7 @@
 
 package com.ericsson.gerrit.plugins.highavailability.index;
 
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Context;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
@@ -27,8 +28,10 @@
 import com.google.inject.Inject;
 import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 class IndexEventHandler
     implements ChangeIndexedListener,
@@ -36,25 +39,34 @@
         GroupIndexedListener,
         ProjectIndexedListener {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
-  private final Executor executor;
+  private final ScheduledExecutorService executor;
   private final Forwarder forwarder;
   private final String pluginName;
   private final Set<IndexTask> queuedTasks = Collections.newSetFromMap(new ConcurrentHashMap<>());
   private final ChangeCheckerImpl.Factory changeChecker;
   private final CurrentRequestContext currCtx;
+  private final IndexEventLocks locks;
+
+  private final int retryInterval;
+  private final int maxTries;
 
   @Inject
   IndexEventHandler(
-      @IndexExecutor Executor executor,
+      @IndexExecutor ScheduledExecutorService executor,
       @PluginName String pluginName,
       Forwarder forwarder,
       ChangeCheckerImpl.Factory changeChecker,
-      CurrentRequestContext currCtx) {
+      CurrentRequestContext currCtx,
+      Configuration cfg,
+      IndexEventLocks locks) {
     this.forwarder = forwarder;
     this.executor = executor;
     this.pluginName = pluginName;
     this.changeChecker = changeChecker;
     this.currCtx = currCtx;
+    this.locks = locks;
+    this.retryInterval = cfg.http().retryInterval();
+    this.maxTries = cfg.http().maxTries();
   }
 
   @Override
@@ -134,6 +146,7 @@
 
   abstract class IndexTask implements Runnable {
     protected final IndexEvent indexEvent;
+    private int retryCount = 0;
 
     IndexTask() {
       indexEvent = new IndexEvent();
@@ -145,11 +158,27 @@
 
     @Override
     public void run() {
-      queuedTasks.remove(this);
-      execute();
+      locks.withLock(
+          this,
+          () -> {
+            queuedTasks.remove(this);
+            return execute();
+          },
+          this::reschedule);
     }
 
-    abstract void execute();
+    private void reschedule() {
+      if (++retryCount <= maxTries) {
+        log.atFine().log("Retrying %d times to %s", retryCount, this);
+        executor.schedule(this, retryInterval, TimeUnit.MILLISECONDS);
+      } else {
+        log.atSevere().log("Failed to %s after %d tries; giving up", this, maxTries);
+      }
+    }
+
+    abstract CompletableFuture<Boolean> execute();
+
+    abstract String indexId();
   }
 
   class IndexChangeTask extends IndexTask {
@@ -163,8 +192,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexChange(projectName, changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexChange(projectName, changeId, indexEvent);
     }
 
     @Override
@@ -185,6 +214,11 @@
     public String toString() {
       return String.format("[%s] Index change %s in target instance", pluginName, changeId);
     }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
+    }
   }
 
   class BatchIndexChangeTask extends IndexTask {
@@ -198,8 +232,13 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.batchIndexChange(projectName, changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.batchIndexChange(projectName, changeId, indexEvent);
+    }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
     }
 
     @Override
@@ -231,8 +270,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.deleteChangeFromIndex(changeId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.deleteChangeFromIndex(changeId, indexEvent);
     }
 
     @Override
@@ -253,6 +292,11 @@
     public String toString() {
       return String.format("[%s] Delete change %s in target instance", pluginName, changeId);
     }
+
+    @Override
+    String indexId() {
+      return "change/" + changeId;
+    }
   }
 
   class IndexAccountTask extends IndexTask {
@@ -263,8 +307,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexAccount(accountId, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexAccount(accountId, indexEvent);
     }
 
     @Override
@@ -285,6 +329,11 @@
     public String toString() {
       return String.format("[%s] Index account %s in target instance", pluginName, accountId);
     }
+
+    @Override
+    String indexId() {
+      return "account/" + accountId;
+    }
   }
 
   class IndexGroupTask extends IndexTask {
@@ -295,8 +344,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexGroup(groupUUID, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexGroup(groupUUID, indexEvent);
     }
 
     @Override
@@ -317,6 +366,11 @@
     public String toString() {
       return String.format("[%s] Index group %s in target instance", pluginName, groupUUID);
     }
+
+    @Override
+    String indexId() {
+      return "group/" + groupUUID;
+    }
   }
 
   class IndexProjectTask extends IndexTask {
@@ -327,8 +381,8 @@
     }
 
     @Override
-    public void execute() {
-      forwarder.indexProject(projectName, indexEvent);
+    public CompletableFuture<Boolean> execute() {
+      return forwarder.indexProject(projectName, indexEvent);
     }
 
     @Override
@@ -349,5 +403,10 @@
     public String toString() {
       return String.format("[%s] Index project %s in target instance", pluginName, projectName);
     }
+
+    @Override
+    String indexId() {
+      return "project/" + projectName;
+    }
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java
new file mode 100644
index 0000000..3fd26db
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventLocks.java
@@ -0,0 +1,95 @@
+// 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.ericsson.gerrit.plugins.highavailability.index;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
+import com.google.common.util.concurrent.Striped;
+import com.google.inject.Inject;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class IndexEventLocks {
+  private static final FluentLogger log = FluentLogger.forEnclosingClass();
+
+  private static final int NUMBER_OF_INDEX_TASK_TYPES = 4;
+  private static final int WAIT_TIMEOUT_MS = 5;
+
+  private final Striped<Semaphore> semaphores;
+
+  @Inject
+  public IndexEventLocks(Configuration cfg) {
+    this.semaphores =
+        Striped.semaphore(NUMBER_OF_INDEX_TASK_TYPES * cfg.index().numStripedLocks(), 1);
+  }
+
+  public CompletableFuture<?> withLock(
+      IndexTask id, IndexCallFunction function, VoidFunction lockAcquireTimeoutCallback) {
+    String indexId = id.indexId();
+    Semaphore idSemaphore = getSemaphore(indexId);
+    try {
+      log.atFine().log("Trying to acquire %s", id);
+      if (idSemaphore.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+        log.atFine().log("Acquired %s", id);
+        return function
+            .invoke()
+            .whenComplete(
+                (result, error) -> {
+                  try {
+                    log.atFine().log("Trying to release %s", id);
+                    idSemaphore.release();
+                    log.atFine().log("Released %s", id);
+                  } catch (Throwable t) {
+                    log.atSevere().withCause(t).log("Unable to release %s", id);
+                    throw t;
+                  }
+                });
+      }
+
+      String timeoutMessage =
+          String.format(
+              "Acquisition of the locking of %s timed out after %d msec: consider increasing the number of shards",
+              indexId, WAIT_TIMEOUT_MS);
+      log.atWarning().log(timeoutMessage);
+      lockAcquireTimeoutCallback.invoke();
+      CompletableFuture<?> failureFuture = new CompletableFuture<>();
+      failureFuture.completeExceptionally(new InterruptedException(timeoutMessage));
+      return failureFuture;
+    } catch (InterruptedException e) {
+      CompletableFuture<?> failureFuture = new CompletableFuture<>();
+      failureFuture.completeExceptionally(e);
+      log.atSevere().withCause(e).log("Locking of %s was interrupted; giving up", indexId);
+      return failureFuture;
+    }
+  }
+
+  @VisibleForTesting
+  protected Semaphore getSemaphore(String indexId) {
+    return semaphores.get(indexId);
+  }
+
+  @FunctionalInterface
+  public interface VoidFunction {
+    void invoke();
+  }
+
+  @FunctionalInterface
+  public interface IndexCallFunction {
+    CompletableFuture<?> invoke();
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
index c17731f..4a25fc8 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexModule.java
@@ -20,20 +20,25 @@
 import com.google.gerrit.extensions.events.ProjectIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.inject.Scopes;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
-import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
 public class IndexModule extends LifecycleModule {
 
   @Override
   protected void configure() {
-    bind(Executor.class).annotatedWith(IndexExecutor.class).toProvider(IndexExecutorProvider.class);
+    bind(ScheduledExecutorService.class)
+        .annotatedWith(IndexExecutor.class)
+        .toProvider(IndexExecutorProvider.class);
     bind(ScheduledExecutorService.class)
         .annotatedWith(ForwardedIndexExecutor.class)
         .toProvider(ForwardedIndexExecutorProvider.class);
+    bind(IndexEventLocks.class).in(Scopes.SINGLETON);
     listener().to(IndexExecutorProvider.class);
-    DynamicSet.bind(binder(), ChangeIndexedListener.class).to(IndexEventHandler.class);
+    DynamicSet.bind(binder(), ChangeIndexedListener.class)
+        .to(IndexEventHandler.class)
+        .in(Scopes.SINGLETON);
     DynamicSet.bind(binder(), AccountIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), GroupIndexedListener.class).to(IndexEventHandler.class);
     DynamicSet.bind(binder(), ProjectIndexedListener.class).to(IndexEventHandler.class);
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index c7196c6..fd0b56e 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,20 +1,19 @@
 
 This plugin allows making Gerrit highly available by having redundant Gerrit
-masters.
+active/passive instances.
 
-The masters must be:
+The Gerrit instances must be:
 
-* connecting to the same database
 * sharing the git repositories using a shared file system (e.g. NFS)
 * behind a load balancer (e.g. HAProxy)
 
-Currently, the mode supported is one primary (active) master and multiple backup
-(passive) masters but eventually the plan is to support `n` active masters. In
-the active/passive mode, the active master is handling all traffic while the
-passives are kept updated to be always ready to take over.
+Currently, the mode supported is one active instance and multiple backup
+(passive) instances but eventually the plan is to support `n` active instances.
+In the active/passive mode, the active instance is handling all traffic while
+the passives are kept updated to be always ready to take over.
 
-Even if database and git repositories are shared by the masters, there are a few
-areas of concern in order to be able to switch traffic between masters in a
+Even if git repositories are shared by the instances, there are a few areas
+of concern in order to be able to switch traffic between instances in a
 transparent manner from the user's perspective. The 4 areas of concern are
 things that Gerrit stores either in memory or locally in the review site:
 
@@ -23,68 +22,67 @@
 * stream-events
 * web sessions
 
-They need either to be shared or kept local to each master but synchronized.
-This plugin needs to be installed in all the masters and it will take care of sharing
-or synchronizing them.
+They need either to be shared or kept local to each instances but synchronized.
+This plugin needs to be installed in all the instances and it will take care of
+sharing or synchronizing them.
 
 #### Caches
-Every time a cache eviction occurs in one of the masters, the eviction will be
-forwarded the other masters so their caches do not contain stale entries.
+Every time a cache eviction occurs in one of the instances, the eviction will be
+forwarded the other nodes so their caches do not contain stale entries.
 
 #### Secondary indexes
-Every time the secondary index is modified in one of the masters, e.g., a change
-is added, updated or removed from the index, the others master's index are
-updated accordingly. This way, both indexes are kept synchronized.
+Every time the secondary index is modified in one of the instances, e.g., a change
+is added, updated or removed from the index, the others instances index are updated
+accordingly. This way, both indexes are kept synchronized.
 
 #### Stream events
-Every time a stream event occurs in one of the masters (see [more events info]
-(https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)),
-the event is forwarded to the other masters which re-plays it. This way, the
-output of the stream-events command is the same, no matter which master a client
-is connected to.
+Every time a stream event occurs in one of the instances
+(see [more events info](https://gerrit-review.googlesource.com/Documentation/cmd-stream-events.html#events)),
+the event is forwarded to the other instances which re-plays it. This way, the output
+of the stream-events command is the same, no matter which  instance a client is
+connected to.
 
 #### Web session
 The built-in Gerrit H2 based web session cache is replaced with a file based
-implementation that is shared amongst the masters.
+implementation that is shared amongst the instances.
 
 ## Setup
 
 Prerequisites:
 
-* Unique database server must be accessible from all the masters
 * Git repositories must be located on a shared file system
 * A directory on a shared file system must be available for @PLUGIN@ to use
 
-For the masters:
+For the instances:
 
-* Configure database section in gerrit.config to use the shared database
 * Configure gerrit.basePath in gerrit.config to the shared repositories location
-* Install and configure @PLUGIN@ plugin
+* Configure gerrit.serverId in gerrit.config based on [config](config.md)'s introduction
+* Install and configure this @PLUGIN@ plugin [further](config.md) or based on below.
 
 Here is an example of the minimal @PLUGIN@.config:
 
-Primary master
+Active instance
 
 ```
 [main]
-  sharedDirectory = /directory/accessible/from/both/masters
+  sharedDirectory = /directory/accessible/from/both/nodes
 
 [peerInfo "static"]
-  url = http://backupMasterHost1:8081/
+  url = http://backupNodeHost1:8081/
 
 [http]
   user = username
   password = password
 ```
 
-Backup master
+Backup instance
 
 ```
 [main]
-  sharedDirectory = /directory/accessible/from/both/masters
+  sharedDirectory = /directory/accessible/from/both/nodes
 
 [peerInfo "static"]
-  url = http://primaryMasterHost:8080/
+  url = http://primaryNodeHost:8080/
 
 [http]
   user = username
@@ -93,10 +91,10 @@
 
 ### HA replica site
 
-It is possible to create a copy of the master site and configure both sites to run
-in HA mode as peers. This is possible when the directory where the copy will be
-created is accessible from this machine. Such a replica site can be created by
-means of a gerrit [site init](../../../Documentation/pgm-init.html) step,
+It is possible to create a copy of the instance site and configure both
+sites to run in HA mode as peers. This is possible when the directory where
+the copy will be created is accessible from this machine. Such a replica site
+can be created by means of a gerrit [site init](../../../Documentation/pgm-init.html) step,
 contributed by the plugin under its init section.
 
 This init step is optional but defaults to creating the replica. If you want to
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index e79cbdd..489c9f4 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -2,8 +2,14 @@
 @PLUGIN@ Configuration
 =========================
 
-The @PLUGIN@ plugin must be installed on all the instances and the following fields
-should be specified in `$site_path/etc/@PLUGIN@.config` file:
+The @PLUGIN@ plugin must be installed on all the instances. Each instance should
+be configured with the same [gerrit.serverId](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/config-gerrit.html#gerrit.serverId).
+If there are existing changes in [NoteDb](https://gerrit-documentation.storage.googleapis.com/Documentation/3.0.0/note-db.html)
+made with another `serverId`, then this plugin might not be able to access them.
+Likewise, if the HA gerrit.serverIds differ, then changes conveyed by one
+instance will not be accessible by the other.
+
+The following fields should be specified in `$site_path/etc/@PLUGIN@.config` files:
 
 File '@PLUGIN@.config'
 --------------------
@@ -49,7 +55,7 @@
 ```
 
 ```main.sharedDirectory```
-:   Path to a directory accessible from both master instances.
+:   Path to a directory accessible from both instances.
     When given as a relative path, then it is resolved against the $SITE_PATH
     or Gerrit server. For example, if $SITE_PATH is "/gerrit/root" and
     sharedDirectory is given as "shared/dir" then the real path of the shared
@@ -75,13 +81,6 @@
     Delay is expressed in Gerrit time values as in [websession.cleanupInterval](#websessioncleanupInterval).
     When not specified, polling of conditional reindexing is disabled.
 
-```autoReindex.interval```
-:   Enable the tracking of the latest change indexed under data/high-availability
-    for each of the indexes. At startup scans all the changes, accounts and groups
-    and reindex the ones that have been updated by other nodes while the server was down.
-    When not specified, the default is "false", that means no automatic tracking
-    and indexing at start.
-
 ```peerInfo.strategy```
 :   Strategy to find other peers. Supported strategies are `static` or `jgroups`.
     Defaults to `jgroups`.
@@ -161,9 +160,6 @@
 :   The interval of time in milliseconds between the subsequent auto-retries.
     When not specified, the default value is set to 10000ms.
 
-NOTE: the default settings for `http.timeout` and `http.maxTries` ensure that
-the plugin will keep retrying to forward a message for one hour.
-
 ```cache.synchronize```
 :   Whether to synchronize cache evictions.
     Defaults to true.
@@ -211,6 +207,9 @@
 :   The interval of time in milliseconds between the subsequent auto-retries.
     Defaults to 30000 (30 seconds).
 
+NOTE: the default settings for `http.socketTimeout` and `http.maxTries` ensure
+that the plugin will keep retrying to forward a message for one hour.
+
 ```websession.synchronize```
 :   Whether to synchronize web sessions.
     Defaults to true.
diff --git a/src/test/docker/docker-compose.yaml b/src/test/docker/docker-compose.yaml
index 93971b3..647aa6d 100644
--- a/src/test/docker/docker-compose.yaml
+++ b/src/test/docker/docker-compose.yaml
@@ -2,17 +2,6 @@
 
 services:
 
-  postgres:
-    image: postgres:9.5.4
-    environment:
-      - POSTGRES_USER=gerrit
-      - POSTGRES_PASSWORD=secret
-      - POSTGRES_DB=reviewdb
-    networks:
-      - gerrit-net
-    volumes:
-      - ./pgdata:/var/lib/postgresql/data
-
   gerrit-01:
     build: gerrit
     ports:
@@ -20,8 +9,6 @@
       - "29411:29418"
     networks:
       - gerrit-net
-    depends_on:
-      - postgres
     volumes:
       - /dev/urandom:/dev/random
       - ./gitvolume:/var/gerrit/git
@@ -39,7 +26,6 @@
     networks:
       - gerrit-net
     depends_on:
-      - postgres
       - gerrit-01
     volumes:
       - /dev/urandom:/dev/random
diff --git a/src/test/docker/etc/gerrit.config b/src/test/docker/etc/gerrit.config
index c110835..90a4057 100644
--- a/src/test/docker/etc/gerrit.config
+++ b/src/test/docker/etc/gerrit.config
@@ -1,12 +1,7 @@
 [gerrit]
 	basePath = git
 	canonicalWebUrl = http://gerrit:8080/
-[database]
-	type = postgresql
-	hostname = postgres
-	database = reviewdb
-	username = gerrit
-	password = secret
+	serverId = f7696647-8efd-41b1-bd60-d321bc071ea9
 [index]
 	type = LUCENE
 [auth]
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile
index 537cb3c..707e5ae 100644
--- a/src/test/docker/gerrit/Dockerfile
+++ b/src/test/docker/gerrit/Dockerfile
@@ -1,19 +1,17 @@
-FROM gerritcodereview/gerrit:2.16.22
+FROM gerritcodereview/gerrit:3.0.12
 
-ENV GERRIT_BRANCH=stable-2.16
+ENV GERRIT_BRANCH=stable-3.0
 
 ENV GERRIT_CI_URL=https://gerrit-ci.gerritforge.com/job
 
 USER root
 
-RUN yum install -y iputils-ping netcat postgresql curl lsof gettext moreutils net-tools netcat inetutils-ping sudo
+RUN yum install -y iputils-ping netcat curl lsof gettext moreutils net-tools netcat inetutils-ping sudo
 
 USER gerrit
 
 ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody.jar /var/gerrit/plugins/javamelody.jar
-ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-javamelody-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/javamelody/javamelody-deps_deploy.jar /var/gerrit/lib/javamelody-deps_deploy.jar
 ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-high-availability-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/high-availability/high-availability.jar /var/gerrit/plugins/high-availability.jar
-ADD --chown=gerrit:gerrit $GERRIT_CI_URL/plugin-delete-project-bazel-$GERRIT_BRANCH/lastSuccessfulBuild/artifact/bazel-bin/plugins/delete-project/delete-project.jar /var/gerrit/plugins/delete-project.jar
 
 USER root
 
@@ -24,4 +22,6 @@
 
 ARG GERRIT_UID=1000
 RUN usermod -u ${GERRIT_UID} gerrit &> /dev/null
+
+ENTRYPOINT ["/usr/bin/env"]
 CMD /bin/start.sh
diff --git a/src/test/docker/gerrit/start.sh b/src/test/docker/gerrit/start.sh
index 7aa3550..0dae4c3 100755
--- a/src/test/docker/gerrit/start.sh
+++ b/src/test/docker/gerrit/start.sh
@@ -1,18 +1,14 @@
 #!/bin/bash -e
 
-wait-for-it.sh postgres:5432 -t 600 -- echo "Postgres is up"
-
 if [[ ! -z "$WAIT_FOR" ]]
 then
   wait-for-it.sh $WAIT_FOR -t 600 -- echo "$WAIT_FOR is up"
 fi
 
-chown -R gerrit: /var/gerrit
-
 sudo -u gerrit cp /var/gerrit/etc/gerrit.config.orig /var/gerrit/etc/gerrit.config
 sudo -u gerrit cp /var/gerrit/etc/high-availability.config.orig /var/gerrit/etc/high-availability.config
 
-if [[ ! -f /var/gerrit/git/All-Projects.git/config ]]
+if [[ ! -f /var/gerrit/etc/ssh_host_ed25519_key ]]
 then
   echo "Initializing Gerrit site ..."
   sudo -u gerrit java -jar /var/gerrit/bin/gerrit.war init -d /var/gerrit --batch
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index 2cf12fc..a9537bd 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -18,6 +18,7 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Cache.PATTERN_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_NUM_STRIPED_LOCKS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Event.EVENT_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.DEFAULT_SYNCHRONIZE;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Forwarding.SYNCHRONIZE_KEY;
@@ -27,7 +28,6 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.CONNECTION_TIMEOUT_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_MAX_TRIES;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_RETRY_INTERVAL;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.DEFAULT_TIMEOUT_MS;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.HTTP_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.MAX_TRIES_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Http.PASSWORD_KEY;
@@ -114,15 +114,16 @@
 
   @Test
   public void testGetPeerInfoStrategy() {
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(DEFAULT_PEER_INFO_STRATEGY);
+    assertThat(getConfiguration().peerInfo().strategy())
+        .isSameInstanceAs(DEFAULT_PEER_INFO_STRATEGY);
 
     globalPluginConfig.setString(
         PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.STATIC.name());
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.STATIC);
+    assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.STATIC);
 
     globalPluginConfig.setString(
         PEER_INFO_SECTION, null, STRATEGY_KEY, PeerInfoStrategy.JGROUPS.name());
-    assertThat(getConfiguration().peerInfo().strategy()).isSameAs(PeerInfoStrategy.JGROUPS);
+    assertThat(getConfiguration().peerInfo().strategy()).isSameInstanceAs(PeerInfoStrategy.JGROUPS);
   }
 
   @Test
@@ -131,7 +132,7 @@
 
     globalPluginConfig.setStringList(PEER_INFO_SECTION, STATIC_SUBSECTION, URL_KEY, URLS);
     assertThat(getConfiguration().peerInfoStatic().urls())
-        .containsAllIn(ImmutableList.of(URL, "http://anotherUrl"));
+        .containsAtLeastElementsIn(ImmutableList.of(URL, "http://anotherUrl"));
   }
 
   @Test
@@ -155,7 +156,9 @@
 
     globalPluginConfig.setStringList(
         JGROUPS_SECTION, null, SKIP_INTERFACE_KEY, ImmutableList.of("lo*", "eth0"));
-    assertThat(getConfiguration().jgroups().skipInterface()).containsAllOf("lo*", "eth0").inOrder();
+    assertThat(getConfiguration().jgroups().skipInterface())
+        .containsExactly("lo*", "eth0")
+        .inOrder();
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
index 03c1928..9b6e66b 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CachePatternMatcherTest.java
@@ -20,6 +20,7 @@
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.collect.ImmutableList;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -76,4 +77,28 @@
       assertWithMessage(cache + " should not match").that(matcher.matches(cache)).isFalse();
     }
   }
+
+  @Test
+  public void testShouldNotMatchWebSessionsWhenNotSynchronized() {
+    String cache = "web_sessions";
+    when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList());
+    when(configurationMock.websession().synchronize()).thenReturn(false);
+    CachePatternMatcher matcher = new CachePatternMatcher(configurationMock);
+
+    assertWithMessage(cache + " should NOT match when websession.synchronize is false")
+        .that(matcher.matches(cache))
+        .isFalse();
+  }
+
+  @Test
+  public void testShouldMatchWebSessionsWhenSynchronized() {
+    String cache = "web_sessions";
+    when(configurationMock.cache().patterns()).thenReturn(Collections.emptyList());
+    when(configurationMock.websession().synchronize()).thenReturn(true);
+    CachePatternMatcher matcher = new CachePatternMatcher(configurationMock);
+
+    assertWithMessage(cache + " should match when websession.synchronize is true")
+        .that(matcher.matches(cache))
+        .isTrue();
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java
new file mode 100644
index 0000000..2b9ee35
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/event/EventDeserializerTest.java
@@ -0,0 +1,73 @@
+// 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.ericsson.gerrit.plugins.highavailability.event;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.GsonProvider;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.RefUpdateAttribute;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gson.Gson;
+import org.junit.Test;
+
+public class EventDeserializerTest {
+  private static final String LEGACY_PROJECT_KEY = "{\"name\": \"project\"}";
+
+  private static final String NEW_PROJECT_KEY = "\"project\"";
+
+  private final Gson gson = new GsonProvider().get();
+
+  @Test
+  public void deserializePatchSetCreatedEventLegacyProjectKey() {
+    Project.NameKey n = gson.fromJson(LEGACY_PROJECT_KEY, Project.NameKey.class);
+    assertThat(n.get()).isEqualTo("project");
+  }
+
+  @Test
+  public void deserializePatchSetCreatedEventNewProjectKey() {
+    Project.NameKey n = gson.fromJson(NEW_PROJECT_KEY, Project.NameKey.class);
+    assertThat(n.get()).isEqualTo("project");
+  }
+
+  @Test
+  public void refUpdatedEventRoundTrip() {
+    RefUpdatedEvent refUpdatedEvent = new RefUpdatedEvent();
+
+    RefUpdateAttribute refUpdatedAttribute = new RefUpdateAttribute();
+    refUpdatedAttribute.refName = "refs/heads/master";
+    refUpdatedEvent.refUpdate = createSupplier(refUpdatedAttribute);
+
+    AccountAttribute accountAttribute = new AccountAttribute();
+    accountAttribute.email = "some.user@domain.com";
+    refUpdatedEvent.submitter = createSupplier(accountAttribute);
+
+    String serializedEvent = gson.toJson(refUpdatedEvent);
+    RefUpdatedEvent e = gson.fromJson(serializedEvent, RefUpdatedEvent.class);
+
+    assertThat(e).isNotNull();
+    assertThat(e.refUpdate).isInstanceOf(Supplier.class);
+    assertThat(e.refUpdate.get().refName).isEqualTo(refUpdatedAttribute.refName);
+    assertThat(e.submitter).isInstanceOf(Supplier.class);
+    assertThat(e.submitter.get().email).isEqualTo(accountAttribute.email);
+  }
+
+  private static <T> Supplier<T> createSupplier(T value) {
+    return Suppliers.memoize(() -> value);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
index ec5b1de..36cc953 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedAwareEventBrokerTest.java
@@ -39,7 +39,7 @@
     DynamicSet<EventListener> set = DynamicSet.emptySet();
     set.add("high-availability", listenerMock);
     PluginSetContext<EventListener> listeners = new PluginSetContext<>(set, mockMetrics);
-    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null, null);
+    broker = new ForwardedAwareEventBroker(null, listeners, null, null, null);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
index a855c5e..8ef8e2f 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedEventHandlerTest.java
@@ -15,14 +15,14 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,14 +76,18 @@
             (Answer<Void>)
                 invocation -> {
                   assertThat(Context.isForwardedEvent()).isTrue();
-                  throw new OrmException("someMessage");
+                  throw new PermissionBackendException("someMessage");
                 })
         .when(dispatcherMock)
         .postEvent(event);
 
     assertThat(Context.isForwardedEvent()).isFalse();
-    OrmException thrown = assertThrows(OrmException.class, () -> handler.dispatch(event));
-    assertThat(thrown).hasMessageThat().contains("someMessage");
+    try {
+      handler.dispatch(event);
+      fail("should have throw a PermissionBackendException");
+    } catch (PermissionBackendException e) {
+      assertThat(e.getMessage()).isEqualTo("someMessage");
+    }
     assertThat(Context.isForwardedEvent()).isFalse();
 
     verify(dispatcherMock).postEvent(event);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
index b88e9c0..14f3d54 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.account.AccountIndexer;
@@ -38,16 +36,12 @@
 public class ForwardedIndexAccountHandlerTest {
 
   @Mock private AccountIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexAccountHandler handler;
   private Account.Id id;
 
   @Before
   public void setUp() throws Exception {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexAccountHandler(indexerMock, configMock);
+    handler = new ForwardedIndexAccountHandler(indexerMock);
     id = new Account.Id(123);
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
index 6bfd47d..a6da8e2 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -15,11 +15,9 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -28,14 +26,11 @@
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeChecker;
 import com.ericsson.gerrit.plugins.highavailability.index.ChangeCheckerImpl;
-import com.ericsson.gerrit.plugins.highavailability.index.ChangeDb;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
@@ -54,16 +49,10 @@
   private static String TEST_CHANGE_ID = TEST_PROJECT + "~" + TEST_CHANGE_NUMBER;
   private static final boolean CHANGE_EXISTS = true;
   private static final boolean CHANGE_DOES_NOT_EXIST = false;
-  private static final boolean DO_NOT_THROW_IO_EXCEPTION = false;
-  private static final boolean DO_NOT_THROW_ORM_EXCEPTION = false;
-  private static final boolean THROW_IO_EXCEPTION = true;
-  private static final boolean THROW_ORM_EXCEPTION = true;
   private static final boolean CHANGE_UP_TO_DATE = true;
   private static final boolean CHANGE_OUTDATED = false;
 
   @Mock private ChangeIndexer indexerMock;
-  @Mock private ChangeDb changeDbMock;
-  @Mock private ReviewDb dbMock;
   @Mock private ChangeNotes changeNotes;
   @Mock private Configuration configMock;
   @Mock private Configuration.Index indexMock;
@@ -77,35 +66,28 @@
 
   @Before
   public void setUp() throws Exception {
-    when(changeDbMock.open()).thenReturn(dbMock);
     id = new Change.Id(TEST_CHANGE_NUMBER);
     Change change = new Change(null, id, null, null, TimeUtil.nowTs());
     when(changeNotes.getChange()).thenReturn(change);
     when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
     when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerAbsentMock);
     handler =
         new ForwardedIndexChangeHandler(
-            indexerMock,
-            changeDbMock,
-            configMock,
-            indexExecutorMock,
-            ctxMock,
-            changeCheckerFactoryMock);
+            indexerMock, configMock, indexExecutorMock, ctxMock, changeCheckerFactoryMock);
   }
 
   @Test
   public void changeIsIndexedWhenUpToDate() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
   public void changeIsStillIndexedEvenWhenOutdated() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_OUTDATED);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.of(new IndexEvent()));
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
@@ -122,21 +104,6 @@
   }
 
   @Test
-  public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception {
-    setupChangeAccessRelatedMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION, CHANGE_UP_TO_DATE);
-    assertThrows(
-        OrmException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()));
-  }
-
-  @Test
-  public void indexerThrowsIOExceptionTryingToIndexChange() throws Exception {
-    setupChangeAccessRelatedMocks(
-        CHANGE_EXISTS, DO_NOT_THROW_ORM_EXCEPTION, THROW_IO_EXCEPTION, CHANGE_UP_TO_DATE);
-    assertThrows(
-        IOException.class, () -> handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty()));
-  }
-
-  @Test
   public void shouldSetAndUnsetForwardedContext() throws Exception {
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_UP_TO_DATE);
     // this doAnswer is to allow to assert that context is set to forwarded
@@ -148,13 +115,13 @@
                   return null;
                 })
         .when(indexerMock)
-        .index(any(ReviewDb.class), any(Change.class));
+        .index(any(Change.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
@@ -167,7 +134,7 @@
                   throw new IOException("someMessage");
                 })
         .when(indexerMock)
-        .index(any(ReviewDb.class), any(Change.class));
+        .index(any(Change.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     try {
@@ -178,39 +145,14 @@
     }
     assertThat(Context.isForwardedEvent()).isFalse();
 
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
-  private void setupChangeAccessRelatedMocks(boolean changeExist, boolean changeUpToDate)
-      throws Exception {
-    setupChangeAccessRelatedMocks(
-        changeExist, DO_NOT_THROW_ORM_EXCEPTION, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate);
-  }
-
-  private void setupChangeAccessRelatedMocks(
-      boolean changeExist, boolean ormException, boolean changeUpToDate)
-      throws OrmException, IOException {
-    setupChangeAccessRelatedMocks(
-        changeExist, ormException, DO_NOT_THROW_IO_EXCEPTION, changeUpToDate);
-  }
-
-  private void setupChangeAccessRelatedMocks(
-      boolean changeExists, boolean ormException, boolean ioException, boolean changeIsUpToDate)
-      throws OrmException, IOException {
-    if (ormException) {
-      doThrow(new OrmException("")).when(changeDbMock).open();
-    } else {
-      when(changeDbMock.open()).thenReturn(dbMock);
-    }
-
+  private void setupChangeAccessRelatedMocks(boolean changeExists, boolean changeIsUpToDate)
+      throws IOException {
     if (changeExists) {
       when(changeCheckerFactoryMock.create(TEST_CHANGE_ID)).thenReturn(changeCheckerPresentMock);
       when(changeCheckerPresentMock.getChangeNotes()).thenReturn(Optional.of(changeNotes));
-      if (ioException) {
-        doThrow(new IOException("io-error"))
-            .when(indexerMock)
-            .index(any(ReviewDb.class), any(Change.class));
-      }
     }
 
     when(changeCheckerPresentMock.isChangeUpToDate(any())).thenReturn(changeIsUpToDate);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
index 845abb0..7e30dea 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
@@ -38,16 +36,12 @@
 public class ForwardedIndexGroupHandlerTest {
 
   @Mock private GroupIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexGroupHandler handler;
   private AccountGroup.UUID uuid;
 
   @Before
   public void setUp() throws Exception {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexGroupHandler(indexerMock, configMock);
+    handler = new ForwardedIndexGroupHandler(indexerMock);
     uuid = new AccountGroup.UUID("123");
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
index a0c6979..ea36532 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexProjectHandlerTest.java
@@ -19,9 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
 import com.google.gerrit.index.project.ProjectIndexer;
 import com.google.gerrit.reviewdb.client.Project;
@@ -38,16 +36,12 @@
 public class ForwardedIndexProjectHandlerTest {
 
   @Mock private ProjectIndexer indexerMock;
-  @Mock private Configuration configMock;
-  @Mock private Configuration.Index indexMock;
   private ForwardedIndexProjectHandler handler;
   private Project.NameKey nameKey;
 
   @Before
   public void setUp() {
-    when(configMock.index()).thenReturn(indexMock);
-    when(indexMock.numStripedLocks()).thenReturn(10);
-    handler = new ForwardedIndexProjectHandler(indexerMock, configMock);
+    handler = new ForwardedIndexProjectHandler(indexerMock);
     nameKey = new Project.NameKey("project/name");
   }
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
index 835977f..7872785 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.net.MediaType.JSON_UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
 import static org.mockito.ArgumentMatchers.any;
@@ -29,7 +28,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.events.EventTypes;
 import com.google.gerrit.server.events.RefEvent;
-import com.google.gwtorm.server.OrmException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
@@ -83,11 +82,11 @@
             + "\"refs/changes/76/669676/2\",\"nodesCount\":1,\"type\":"
             + "\"ref-replication-done\",\"eventCreatedOn\":1451415011}";
     when(requestMock.getReader()).thenReturn(new BufferedReader(new StringReader(event)));
-    doThrow(new OrmException(ERR_MSG))
+    doThrow(new PermissionBackendException(ERR_MSG))
         .when(forwardedEventHandlerMock)
         .dispatch(any(RefReplicationDoneEvent.class));
     eventRestApiServlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Change not found\n");
+    verify(responseMock).sendError(SC_BAD_REQUEST, ERR_MSG);
   }
 
   @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
index 5e0d4c9..4071363 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -28,6 +28,7 @@
 import com.github.tomakehurst.wiremock.http.Fault;
 import com.github.tomakehurst.wiremock.junit.WireMockRule;
 import com.github.tomakehurst.wiremock.stubbing.Scenario;
+import com.google.gerrit.reviewdb.client.Project;
 import java.net.SocketTimeoutException;
 import org.junit.Before;
 import org.junit.Rule;
@@ -70,7 +71,7 @@
     when(configMock.http().socketTimeout()).thenReturn(TIMEOUT);
     when(configMock.http().retryInterval()).thenReturn(RETRY_INTERVAL);
 
-    httpSession = new HttpSession(new HttpClientProvider(configMock).get());
+    httpSession = new HttpSession(new HttpClientProvider(configMock).get(), new GsonProvider());
   }
 
   @Test
@@ -170,4 +171,12 @@
 
     assertThat(httpSession.post(uri).isSuccessful()).isFalse();
   }
+
+  @Test
+  public void encodeProjectName() {
+    String projectStr = "project";
+    Project.NameKey project = Project.nameKey(projectStr);
+    String json = httpSession.jsonEncode(project);
+    assertThat(json).isEqualTo("\"" + projectStr + "\"");
+  }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
index c1fa765..fb3649e 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
@@ -15,7 +15,6 @@
 package com.ericsson.gerrit.plugins.highavailability.forwarder.rest;
 
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
-import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -26,7 +25,6 @@
 
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler;
 import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -81,15 +79,6 @@
   }
 
   @Test
-  public void indexerThrowsOrmExceptionTryingToIndexChange() throws Exception {
-    doThrow(new OrmException("some message"))
-        .when(handlerMock)
-        .index(eq(CHANGE_ID), eq(Operation.INDEX), any());
-    servlet.doPost(requestMock, responseMock);
-    verify(responseMock).sendError(SC_NOT_FOUND, "Error trying to find change");
-  }
-
-  @Test
   public void sendErrorThrowsIOException() throws Exception {
     doThrow(new IOException(IO_ERROR))
         .when(handlerMock)
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
index 5bd0cf8..cb90461 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -37,6 +37,8 @@
 import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLException;
 import org.junit.Before;
 import org.junit.Test;
@@ -91,17 +93,22 @@
   private static final String EVENT_ENDPOINT =
       Joiner.on("/").join(URL, PLUGINS, PLUGIN_NAME, "event", event.type);
 
+  private static final long TEST_TIMEOUT = 10;
+  private static final TimeUnit TEST_TIMEOUT_UNITS = TimeUnit.SECONDS;
+
   private RestForwarder forwarder;
   private HttpSession httpSessionMock;
+  private Configuration configMock;
+  Provider<Set<PeerInfo>> peersMock;
 
   @SuppressWarnings("unchecked")
   @Before
   public void setUp() {
     httpSessionMock = mock(HttpSession.class);
-    Configuration configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
+    configMock = mock(Configuration.class, Answers.RETURNS_DEEP_STUBS);
     when(configMock.http().maxTries()).thenReturn(3);
     when(configMock.http().retryInterval()).thenReturn(10);
-    Provider<Set<PeerInfo>> peersMock = mock(Provider.class);
+    peersMock = mock(Provider.class);
     when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL)));
     forwarder =
         new RestForwarder(
@@ -109,126 +116,169 @@
             PLUGIN_NAME,
             configMock,
             peersMock,
-            gsonProvider); // TODO: Create provider
+            gsonProvider, // TODO: Create provider
+            new RestForwarderScheduler(Executors.newScheduledThreadPool(1)));
   }
 
   @Test
   public void testIndexAccountOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexAccountFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexAccountThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_ACCOUNT_ENDPOINT), any());
-    assertThat(forwarder.indexAccount(ACCOUNT_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexAccount(ACCOUNT_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexGroupOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isTrue();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexGroupFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexGroupThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_GROUP_ENDPOINT), any());
-    assertThat(forwarder.indexGroup(UUID, new IndexEvent())).isFalse();
+    assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexChangeOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testIndexChangeFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexChangeThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_CHANGE_ENDPOINT), any());
-    assertThat(forwarder.indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testIndexBatchChangeOK() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isTrue();
   }
 
   @Test
   public void testIndexBatchChangeFailed() throws Exception {
     when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isFalse();
   }
 
   @Test
   public void testIndexBatchChangeThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any());
-    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get())
+        .isFalse();
   }
 
   @Test
   public void testChangeDeletedFromIndexOK() throws Exception {
     when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT)))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isTrue();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
   public void testChangeDeletedFromIndexFailed() throws Exception {
     when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT)))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testChangeDeletedFromThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).delete(eq(DELETE_CHANGE_ENDPOINT));
-    assertThat(forwarder.deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())).isFalse();
+    assertThat(
+            forwarder
+                .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
   public void testEventSentOK() throws Exception {
     when(httpSessionMock.post(EVENT_ENDPOINT, event))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isTrue();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isTrue();
   }
 
   @Test
   public void testEventSentFailed() throws Exception {
     when(httpSessionMock.post(EVENT_ENDPOINT, event)).thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.send(event)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
   }
 
   @Test
   public void testEventSentThrowsException() throws Exception {
     doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT, event);
-    assertThat(forwarder.send(event)).isFalse();
+    assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse();
   }
 
   @Test
@@ -237,7 +287,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -246,7 +297,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.ACCOUNTS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -255,7 +307,8 @@
     String keyJson = gson.toJson(key);
     String endpoint = buildCacheEndpoint(Constants.GROUPS);
     when(httpSessionMock.post(endpoint, keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -264,7 +317,9 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS_BYINCLUDE, key)).isTrue();
+    assertThat(
+            forwarder.evict(Constants.GROUPS_BYINCLUDE, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -273,7 +328,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS), keyJson))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key)).isTrue();
+    assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -282,7 +338,8 @@
     String keyJson = gson.toJson(key);
     when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -292,7 +349,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .post(buildCacheEndpoint(Constants.PROJECTS), keyJson);
-    assertThat(forwarder.evict(Constants.PROJECTS, key)).isFalse();
+    assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   private static String buildCacheEndpoint(String name) {
@@ -304,7 +362,8 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName)).isTrue();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -312,7 +371,8 @@
     String projectName = PROJECT_TO_ADD;
     when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.addToProjectList(projectName)).isFalse();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -321,7 +381,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .post(buildProjectListCacheEndpoint(projectName), null);
-    assertThat(forwarder.addToProjectList(projectName)).isFalse();
+    assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -329,7 +390,8 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName)))
         .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName)).isTrue();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
@@ -337,7 +399,8 @@
     String projectName = PROJECT_TO_DELETE;
     when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName)))
         .thenReturn(new HttpResult(FAILED, EMPTY_MSG));
-    assertThat(forwarder.removeFromProjectList(projectName)).isFalse();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
@@ -346,7 +409,8 @@
     doThrow(new IOException())
         .when(httpSessionMock)
         .delete((buildProjectListCacheEndpoint(projectName)));
-    assertThat(forwarder.removeFromProjectList(projectName)).isFalse();
+    assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   private static String buildProjectListCacheEndpoint(String projectName) {
@@ -354,41 +418,57 @@
   }
 
   @Test
-  public void testRetryOnErrorThenSuccess() throws IOException {
+  public void testRetryOnErrorThenSuccess() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
-  public void testRetryOnIoExceptionThenSuccess() throws IOException {
+  public void testRetryOnIoExceptionThenSuccess() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new IOException())
         .thenThrow(new IOException())
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isTrue();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isTrue();
   }
 
   @Test
-  public void testNoRetryAfterNonRecoverableException() throws IOException {
+  public void testNoRetryAfterNonRecoverableException() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenThrow(new SSLException("Non Recoverable"))
         .thenReturn(new HttpResult(true, SUCCESS));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 
   @Test
-  public void testFailureAfterMaxTries() throws IOException {
+  public void testFailureAfterMaxTries() throws Exception {
     when(httpSessionMock.post(anyString(), anyString()))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR))
         .thenReturn(new HttpResult(false, ERROR));
 
-    assertThat(forwarder.evict(Constants.PROJECT_LIST, new Object())).isFalse();
+    assertThat(
+            forwarder
+                .evict(Constants.PROJECT_LIST, new Object())
+                .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS))
+        .isFalse();
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
index 4b2462e..a66e896 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/AccountIndexForwardingIT.java
@@ -26,11 +26,11 @@
 
   @Override
   public String getExpectedRequest() {
-    return "/plugins/high-availability/index/account/" + testAccount.id;
+    return "/plugins/high-availability/index/account/" + testAccount.id();
   }
 
   @Override
   public void doAction() throws Exception {
-    gApi.accounts().id(testAccount.id.get()).setActive(false);
+    gApi.accounts().id(testAccount.id().get()).setActive(false);
   }
 }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
similarity index 90%
rename from src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java
rename to src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
index b691034..bc5210d 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerNoteDbIT.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerIT.java
@@ -21,28 +21,20 @@
 import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.TestPlugin;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.testing.NoteDbMode;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 @TestPlugin(
     name = "high-availability",
     sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
     httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
-public class ChangeCheckerNoteDbIT extends LightweightPluginDaemonTest {
+public class ChangeCheckerIT extends LightweightPluginDaemonTest {
 
   ChangeCheckerImpl.Factory changeCheckerFactory;
 
-  @BeforeClass
-  public static void setupTestSuite() {
-    System.setProperty("gerrit.notedb", NoteDbMode.ON.name());
-  }
-
   @Override
   public void setUpTestPlugin() throws Exception {
     super.setUpTestPlugin();
@@ -50,7 +42,7 @@
   }
 
   @Test
-  public void shouldPopulateMetaShaWhenNoteDb() throws Exception {
+  public void shouldPopulateMetaSha() throws Exception {
     Result change = createChange();
     ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
     Optional<IndexEvent> eventOption = changeChecker.newIndexEvent();
@@ -121,7 +113,7 @@
     assertThat(changeChecker.isChangeUpToDate(event)).isFalse();
   }
 
-  private String readMetaSha(Result change) throws IOException, OrmException {
+  private String readMetaSha(Result change) throws IOException {
     try (Repository repo = repoManager.openRepository(change.getChange().change().getProject())) {
       String refName = RefNames.changeMetaRef(change.getChange().getId());
       Ref ref = repo.exactRef(refName);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java
deleted file mode 100644
index a6cb449..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerReviewDbIT.java
+++ /dev/null
@@ -1,116 +0,0 @@
-// 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.ericsson.gerrit.plugins.highavailability.index;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent;
-import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
-import com.google.gerrit.acceptance.PushOneCommit.Result;
-import com.google.gerrit.acceptance.TestPlugin;
-import com.google.gerrit.testing.NoteDbMode;
-import java.util.Optional;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-@TestPlugin(
-    name = "high-availability",
-    sysModule = "com.ericsson.gerrit.plugins.highavailability.Module",
-    httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule")
-public class ChangeCheckerReviewDbIT extends LightweightPluginDaemonTest {
-
-  ChangeCheckerImpl.Factory changeCheckerFactory;
-
-  @BeforeClass
-  public static void setupTestSuite() {
-    System.setProperty("gerrit.notedb", NoteDbMode.OFF.name());
-  }
-
-  @Override
-  public void setUpTestPlugin() throws Exception {
-    super.setUpTestPlugin();
-    changeCheckerFactory = plugin.getSysInjector().getInstance(ChangeCheckerImpl.Factory.class);
-  }
-
-  @Test
-  public void shouldNotPopulateMetaShaWhenReviewDb() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> eventOption = changeChecker.newIndexEvent();
-
-    assertThat(eventOption.isPresent()).isTrue();
-    IndexEvent event = eventOption.get();
-    assertThat(event.metaSha).isNull();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenEventContainsCorrectMetaAndTargetSha()
-      throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event = changeChecker.newIndexEvent();
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenMetaShaIsNull() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.metaSha = null;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnIsUpToDateTrueWhenTargetShaIsNull() throws Exception {
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.targetSha = null;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isTrue();
-  }
-
-  @Test
-  public void shouldReturnFalseWhenTargetShaIsNotUpToDate() throws Exception {
-    String testTargetRefSha = "abed47baf2818a86b68cf712073a748a6b5b293e";
-    Result change = createChange();
-    ChangeChecker changeChecker = changeCheckerFactory.create(change.getChangeId());
-    Optional<IndexEvent> event =
-        changeChecker
-            .newIndexEvent()
-            .map(
-                e -> {
-                  e.targetSha = testTargetRefSha;
-                  return e;
-                });
-
-    assertThat(changeChecker.isChangeUpToDate(event)).isFalse();
-  }
-}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
index 2d12ca8..0f21ff7 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,20 +33,37 @@
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexAccountTask;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexChangeTask;
 import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexGroupTask;
-import com.google.common.util.concurrent.MoreExecutors;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexTask;
+import com.ericsson.gerrit.plugins.highavailability.index.IndexEventLocks.VoidFunction;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -56,6 +74,8 @@
   private static final int ACCOUNT_ID = 2;
   private static final String UUID = "3";
   private static final String OTHER_UUID = "4";
+  private static final Integer INDEX_WAIT_TIMEOUT_MS = 5;
+  private static final int MAX_TEST_PARALLELISM = 4;
 
   private IndexEventHandler indexEventHandler;
   @Mock private Forwarder forwarder;
@@ -64,7 +84,12 @@
   private Change.Id changeId;
   private Account.Id accountId;
   private AccountGroup.UUID accountGroupUUID;
+  private ScheduledExecutorService executor = new CurrentThreadScheduledExecutorService();
+  private ScheduledExecutorService testExecutor =
+      Executors.newScheduledThreadPool(MAX_TEST_PARALLELISM);
   @Mock private RequestContext mockCtx;
+  @Mock private Configuration configuration;
+  private IndexEventLocks idLocks;
 
   private CurrentRequestContext currCtx =
       new CurrentRequestContext(null, null, null) {
@@ -82,17 +107,47 @@
     when(changeCheckerFactoryMock.create(any())).thenReturn(changeCheckerMock);
     when(changeCheckerMock.newIndexEvent()).thenReturn(Optional.of(new IndexEvent()));
 
+    Configuration.Index cfgIndex = mock(Configuration.Index.class);
+    when(configuration.index()).thenReturn(cfgIndex);
+    when(cfgIndex.numStripedLocks()).thenReturn(Configuration.DEFAULT_NUM_STRIPED_LOCKS);
+
+    Configuration.Http http = mock(Configuration.Http.class);
+    when(configuration.http()).thenReturn(http);
+    when(http.maxTries()).thenReturn(Configuration.Http.DEFAULT_MAX_TRIES);
+    when(http.retryInterval()).thenReturn(Configuration.Http.DEFAULT_RETRY_INTERVAL);
+    when(forwarder.indexAccount(eq(ACCOUNT_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.indexGroup(eq(UUID), any())).thenReturn(CompletableFuture.completedFuture(true));
+    when(forwarder.indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any()))
+        .thenReturn(CompletableFuture.completedFuture(true));
+
+    idLocks = new IndexEventLocks(configuration);
     setUpIndexEventHandler(currCtx);
   }
 
   public void setUpIndexEventHandler(CurrentRequestContext currCtx) throws Exception {
+    setUpIndexEventHandler(currCtx, idLocks, configuration);
+  }
+
+  public void setUpIndexEventHandler(CurrentRequestContext currCtx, IndexEventLocks idLocks)
+      throws Exception {
+    setUpIndexEventHandler(currCtx, idLocks, configuration);
+  }
+
+  public void setUpIndexEventHandler(
+      CurrentRequestContext currCtx, IndexEventLocks idLocks, Configuration configuration)
+      throws Exception {
     indexEventHandler =
         new IndexEventHandler(
-            MoreExecutors.directExecutor(),
+            executor,
             PLUGIN_NAME,
             forwarder,
             changeCheckerFactoryMock,
-            currCtx);
+            currCtx,
+            configuration,
+            idLocks);
   }
 
   @Test
@@ -115,6 +170,160 @@
   }
 
   @Test
+  public void shouldNotIndexChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotIndexAccountWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onAccountIndexed(accountId.get());
+
+    verify(forwarder, never()).indexAccount(eq(ACCOUNT_ID), any());
+  }
+
+  @Test
+  public void shouldNotDeleteChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeDeleted(changeId.get());
+
+    verify(forwarder, never()).deleteChangeFromIndex(eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotIndexGroupWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onGroupIndexed(accountGroupUUID.get());
+
+    verify(forwarder, never()).indexGroup(eq(UUID), any());
+  }
+
+  @Test
+  public void shouldNotIndexProjectWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onProjectIndexed(PROJECT_NAME);
+
+    verify(forwarder, never()).indexProject(eq(PROJECT_NAME), any());
+  }
+
+  @Test
+  public void shouldRetryIndexChangeWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false, true);
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(2)).withLock(any(), any(), any());
+    verify(forwarder, times(1)).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldRetryUpToMaxTriesWhenCannotAcquireLock() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+
+    Configuration cfg = mock(Configuration.class);
+    Configuration.Http httpCfg = mock(Configuration.Http.class);
+    when(httpCfg.maxTries()).thenReturn(10);
+    when(cfg.http()).thenReturn(httpCfg);
+    setUpIndexEventHandler(currCtx, locks, cfg);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(11)).withLock(any(), any(), any());
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldNotRetryWhenMaxTriesLowerThanOne() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore semaphore = mock(Semaphore.class);
+    when(locks.getSemaphore(anyString())).thenReturn(semaphore);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    when(semaphore.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+
+    Configuration cfg = mock(Configuration.class);
+    Configuration.Http httpCfg = mock(Configuration.Http.class);
+    when(httpCfg.maxTries()).thenReturn(0);
+    when(cfg.http()).thenReturn(httpCfg);
+    setUpIndexEventHandler(currCtx, locks, cfg);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+
+    verify(locks, times(1)).withLock(any(), any(), any());
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+  }
+
+  @Test
+  public void shouldLockPerIndexEventType() throws Exception {
+    IndexEventLocks locks = mock(IndexEventLocks.class);
+    Semaphore indexChangeLock = mock(Semaphore.class);
+    when(indexChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(false);
+    Semaphore accountChangeLock = mock(Semaphore.class);
+    when(accountChangeLock.tryAcquire(Mockito.anyLong(), Mockito.eq(TimeUnit.MILLISECONDS)))
+        .thenReturn(true);
+    when(locks.getSemaphore(eq("change/" + CHANGE_ID))).thenReturn(indexChangeLock);
+    when(locks.getSemaphore(eq("account/" + ACCOUNT_ID))).thenReturn(accountChangeLock);
+    Mockito.doCallRealMethod().when(locks).withLock(any(), any(), any());
+    setUpIndexEventHandler(currCtx, locks);
+
+    indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
+    indexEventHandler.onAccountIndexed(accountId.get());
+
+    verify(forwarder, never()).indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any());
+    verify(forwarder).indexAccount(eq(ACCOUNT_ID), any());
+  }
+
+  @Test
   public void shouldReindexInRemoteWhenContextIsMissingButForcedIndexingEnabled() throws Exception {
     ThreadLocalRequestContext threadLocalCtxMock = mock(ThreadLocalRequestContext.class);
     OneOffRequestContext oneOffCtxMock = mock(OneOffRequestContext.class);
@@ -179,7 +388,14 @@
   public void duplicateChangeEventOfAQueuedEventShouldGetDiscarded() {
     ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
-        new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+        new IndexEventHandler(
+            poolMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
     indexEventHandler.onChangeIndexed(PROJECT_NAME, changeId.get());
     verify(poolMock, times(1))
@@ -190,7 +406,14 @@
   public void duplicateAccountEventOfAQueuedEventShouldGetDiscarded() {
     ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
-        new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+        new IndexEventHandler(
+            poolMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onAccountIndexed(accountId.get());
     indexEventHandler.onAccountIndexed(accountId.get());
     verify(poolMock, times(1)).execute(indexEventHandler.new IndexAccountTask(ACCOUNT_ID));
@@ -200,7 +423,14 @@
   public void duplicateGroupEventOfAQueuedEventShouldGetDiscarded() {
     ScheduledThreadPoolExecutor poolMock = mock(ScheduledThreadPoolExecutor.class);
     indexEventHandler =
-        new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder, changeCheckerFactoryMock, currCtx);
+        new IndexEventHandler(
+            poolMock,
+            PLUGIN_NAME,
+            forwarder,
+            changeCheckerFactoryMock,
+            currCtx,
+            configuration,
+            idLocks);
     indexEventHandler.onGroupIndexed(accountGroupUUID.get());
     indexEventHandler.onGroupIndexed(accountGroupUUID.get());
     verify(poolMock, times(1)).execute(indexEventHandler.new IndexGroupTask(UUID));
@@ -316,4 +546,226 @@
     assertThat(task.equals(differentGroupIdTask)).isFalse();
     assertThat(task.hashCode()).isNotEqualTo(differentGroupIdTask.hashCode());
   }
+
+  class TestTask<T> implements Runnable {
+    private IndexTask task;
+    private CyclicBarrier testBarrier;
+    private Supplier<T> successFunc;
+    private VoidFunction failureFunc;
+    private CompletableFuture<T> future;
+
+    public TestTask(
+        IndexTask task,
+        CyclicBarrier testBarrier,
+        Supplier<T> successFunc,
+        VoidFunction failureFunc) {
+      this.task = task;
+      this.testBarrier = testBarrier;
+      this.successFunc = successFunc;
+      this.failureFunc = failureFunc;
+      this.future = new CompletableFuture<>();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void run() {
+      try {
+        testBarrier.await();
+        idLocks
+            .withLock(
+                task,
+                () ->
+                    runLater(
+                        INDEX_WAIT_TIMEOUT_MS * 2,
+                        () -> CompletableFuture.completedFuture(successFunc.get())),
+                failureFunc)
+            .whenComplete(
+                (v, t) -> {
+                  if (t == null) {
+                    future.complete((T) v);
+                  } else {
+                    future.completeExceptionally(t);
+                  }
+                });
+      } catch (Throwable t) {
+        future = new CompletableFuture<>();
+        future.completeExceptionally(t);
+      }
+    }
+
+    public void join() {
+      try {
+        future.join();
+      } catch (Exception e) {
+      }
+    }
+
+    private CompletableFuture<T> runLater(
+        long scheduledTimeMsec, Supplier<CompletableFuture<T>> supplier) {
+      CompletableFuture<T> resFuture = new CompletableFuture<>();
+      testExecutor.schedule(
+          () -> {
+            try {
+              return supplier
+                  .get()
+                  .whenComplete(
+                      (v, t) -> {
+                        if (t == null) {
+                          resFuture.complete(v);
+                        }
+                        resFuture.completeExceptionally(t);
+                      });
+            } catch (Throwable t) {
+              return resFuture.completeExceptionally(t);
+            }
+          },
+          scheduledTimeMsec,
+          TimeUnit.MILLISECONDS);
+      return resFuture;
+    }
+  }
+
+  @Test
+  public void indexLocksShouldBlockConcurrentIndexChange() throws Exception {
+    IndexChangeTask indexTask1 =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    IndexChangeTask indexTask2 =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    testIsolationOfCuncurrentIndexTasks(indexTask1, indexTask2);
+  }
+
+  @Test
+  public void indexLocksShouldBlockConcurrentIndexAndDeleteChange() throws Exception {
+    IndexChangeTask indexTask =
+        indexEventHandler.new IndexChangeTask(PROJECT_NAME, CHANGE_ID, new IndexEvent());
+    DeleteChangeTask deleteTask =
+        indexEventHandler.new DeleteChangeTask(CHANGE_ID, new IndexEvent());
+    testIsolationOfCuncurrentIndexTasks(indexTask, deleteTask);
+  }
+
+  private void testIsolationOfCuncurrentIndexTasks(IndexTask indexTask1, IndexTask indexTask2)
+      throws Exception {
+    AtomicInteger changeIndexedCount = new AtomicInteger();
+    AtomicInteger lockFailedCounts = new AtomicInteger();
+    CyclicBarrier changeThreadsSync = new CyclicBarrier(2);
+
+    TestTask<Integer> task1 =
+        new TestTask<>(
+            indexTask1,
+            changeThreadsSync,
+            () -> changeIndexedCount.incrementAndGet(),
+            () -> lockFailedCounts.incrementAndGet());
+    TestTask<Integer> task2 =
+        new TestTask<>(
+            indexTask2,
+            changeThreadsSync,
+            () -> changeIndexedCount.incrementAndGet(),
+            () -> lockFailedCounts.incrementAndGet());
+
+    new Thread(task1).start();
+    new Thread(task2).start();
+    task1.join();
+    task2.join();
+
+    /* Both assertions needs to be true, the order doesn't really matter:
+     * - Only one of the two tasks should succeed
+     * - Only one of the two tasks should fail to acquire the lock
+     */
+    assertThat(changeIndexedCount.get()).isEqualTo(1);
+    assertThat(lockFailedCounts.get()).isEqualTo(1);
+  }
+
+  private class CurrentThreadScheduledExecutorService implements ScheduledExecutorService {
+
+    @Override
+    public void shutdown() {}
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return false;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return false;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      return null;
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      return null;
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+        throws InterruptedException, ExecutionException {
+      return null;
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+        throws InterruptedException, ExecutionException, TimeoutException {
+      return null;
+    }
+
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+      command.run();
+      return null;
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(
+        Runnable command, long initialDelay, long period, TimeUnit unit) {
+      return null;
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(
+        Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      return null;
+    }
+  }
 }
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
index 281515d..43ee6ec 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/config/server/caches/projects",
     "entries": "PROJECTS_ENTRIES"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
index 3fd506d..f128e73 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/_PROJECT",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/_PROJECT",
     "cmd": "clone"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
index b535c1d..a167922 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/changes/",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/changes/",
     "project": "_PROJECT"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
index da3f028..2e5073b 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT1/a/projects/PROJECT"
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/projects/PROJECT"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
index 8e0a304..4ecbe68 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.json
@@ -1,6 +1,6 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/a/changes/",
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/changes/",
     "number": "NUMBER"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
index be47699..2405b03 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME/a/projects/PROJECT/delete-project~delete"
+    "url": "HTTP_SCHEME://HOSTNAME/a/projects/PROJECT/delete-project~delete"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
index c938973..ae904f5 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush"
+    "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT2/a/config/server/caches/projects/flush"
   }
 ]
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
index f7450a4..25521c7 100644
--- a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.json
@@ -1,5 +1,5 @@
 [
   {
-    "url": "http://HOSTNAME/a/config/server/caches/projects"
+    "url": "HTTP_SCHEME://HOSTNAME/a/config/server/caches/projects"
   }
 ]
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
index 10374f8..6a0d07f 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CheckProjectsCacheFlushEntriesUsingHAGerrit1.scala
@@ -32,7 +32,7 @@
     this.producer = Some(producer)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(session => {
       if (producer.nonEmpty) {
@@ -41,12 +41,12 @@
         session
       }
     })
-    .exec(http(unique).get("${url}")
+    .exec(http(uniqueName).get("${url}")
       .check(regex("\"" + memKey + "\": (\\d+)")
         .is(session => session(entriesKey).as[String])))
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
index 13a741a..4fa0ca5 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CloneUsingHAGerrit2.scala
@@ -23,37 +23,37 @@
 
 class CloneUsingHAGerrit2 extends GitSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private var default: String = name
+  private var projectName = className
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
   override def replaceOverride(in: String): String = {
     val next = replaceProperty("http_port2", 8082, in)
-    replaceKeyWith("_project", default, next)
+    replaceKeyWith("_project", projectName, next)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(gitRequest)
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
-    ),
+      atOnceUsers(single)
+    ).protocols(gitProtocol),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
-  ).protocols(gitProtocol, httpProtocol)
+  ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
index f07aabd..26732b6 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateChangeUsingHAGerrit1.scala
@@ -24,7 +24,7 @@
 
 class CreateChangeUsingHAGerrit1 extends GerritSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private val default: String = name
+  private val projectName = className
   private val numberKey = "_number"
 
   override def relativeRuntimeWeight = 10
@@ -33,7 +33,7 @@
     replaceProperty("http_port1", 8081, in)
   }
 
-  private val test: ScenarioBuilder = scenario(unique)
+  private val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest
       .body(ElFileBody(body)).asJson
@@ -43,26 +43,26 @@
       session
     })
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
   private val deleteChange = new DeleteChangeUsingHAGerrit2
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteChange.test.inject(
       nothingFor(stepWaitTime(deleteChange) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
index 964aadb..5773bcf 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerrit1.scala
@@ -22,9 +22,9 @@
 class CreateProjectUsingHAGerrit1 extends ProjectSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
   override def replaceOverride(in: String): String = {
@@ -32,12 +32,12 @@
     super.replaceOverride(next)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest.body(RawFileBody(body)).asJson)
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
index a4da93c..fd83e7a 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/CreateProjectUsingHAGerritTwice.scala
@@ -20,34 +20,34 @@
 import scala.concurrent.duration._
 
 class CreateProjectUsingHAGerritTwice extends GitSimulation {
-  private val default: String = name
+  private val projectName = className
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
-  private val createItAgain = new CreateProjectUsingHAGerrit1(default)
-  private val verifyProject = new CloneUsingHAGerrit2(default)
-  private val deleteItAfter = new DeleteProjectUsingHAGerrit(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
+  private val createItAgain = new CreateProjectUsingHAGerrit1(projectName)
+  private val verifyProject = new CloneUsingHAGerrit2(projectName)
+  private val deleteItAfter = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     createItAgain.test.inject(
       nothingFor(stepWaitTime(createItAgain) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     verifyProject.test.inject(
       nothingFor(stepWaitTime(verifyProject) seconds),
-      atOnceUsers(1)
-    ),
+      atOnceUsers(single)
+    ).protocols(gitProtocol),
     deleteItAfter.test.inject(
       nothingFor(stepWaitTime(deleteItAfter) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
-  ).protocols(gitProtocol, httpProtocol)
+  ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
index 5b0f461..d3dc7c1 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteChangeUsingHAGerrit2.scala
@@ -30,7 +30,7 @@
     replaceProperty("http_port2", 8082, in)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(session => {
       if (number.nonEmpty) {
@@ -39,10 +39,10 @@
         session
       }
     })
-    .exec(http(unique).delete("${url}${number}"))
+    .exec(http(uniqueName).delete("${url}${number}"))
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
index 428085d..cdc6e9e 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/DeleteProjectUsingHAGerrit.scala
@@ -22,17 +22,17 @@
 class DeleteProjectUsingHAGerrit extends ProjectSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
 
-  def this(default: String) {
+  def this(projectName: String) {
     this()
-    this.default = default
+    this.projectName = projectName
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest)
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
index 9c317d4..4f619b8 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/FlushProjectsCacheUsingHAGerrit2.scala
@@ -23,7 +23,7 @@
 
 class FlushProjectsCacheUsingHAGerrit2 extends CacheFlushSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
-  private val default: String = name
+  private val projectName = className
 
   override def relativeRuntimeWeight = 2
 
@@ -31,35 +31,35 @@
     replaceProperty("http_port2", 8082, in)
   }
 
-  private val flushCache: ScenarioBuilder = scenario(unique)
+  private val flushCache: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
     .exec(httpRequest)
 
-  private val createProject = new CreateProjectUsingHAGerrit1(default)
+  private val createProject = new CreateProjectUsingHAGerrit1(projectName)
   private val getCacheEntriesAfterProject = new GetProjectsCacheEntries(this)
   private val checkCacheEntriesAfterFlush = new CheckProjectsCacheFlushEntriesUsingHAGerrit1(this)
-  private val deleteProject = new DeleteProjectUsingHAGerrit(default)
+  private val deleteProject = new DeleteProjectUsingHAGerrit(projectName)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     getCacheEntriesAfterProject.test.inject(
       nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     flushCache.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     checkCacheEntriesAfterFlush.test.inject(
       nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
index 9565365..ed19c0b 100644
--- a/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
+++ b/src/test/scala/com/ericsson/gerrit/plugins/highavailability/scenarios/GetProjectsCacheEntries.scala
@@ -28,9 +28,9 @@
     this.consumer = Some(consumer)
   }
 
-  val test: ScenarioBuilder = scenario(unique)
+  val test: ScenarioBuilder = scenario(uniqueName)
     .feed(data)
-    .exec(http(unique).get("${url}")
+    .exec(http(uniqueName).get("${url}")
       .check(regex("\"" + memKey + "\": (\\d+)").saveAs(entriesKey)))
     .exec(session => {
       if (consumer.nonEmpty) {
@@ -41,6 +41,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }