Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  DeleteChangeUsingMultiGerrit1: Fix json change number

Change-Id: I433d79f6fdebfdc32d7f86392f303a1b1481614f
diff --git a/BUILD b/BUILD
index c933c98..ec555d5 100644
--- a/BUILD
+++ b/BUILD
@@ -17,12 +17,9 @@
     ],
     resources = glob(["src/main/resources/**/*"]),
     deps = [
+        "@global-refdb//jar",
+        "@events-broker//jar",
         ":replication-neverlink",
-        "@curator-client//jar",
-        "@curator-framework//jar",
-        "@curator-recipes//jar",
-        "@kafka-client//jar",
-        "@zookeeper//jar",
     ],
 )
 
@@ -51,13 +48,9 @@
     visibility = ["//visibility:public"],
     exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
         ":multi-site__plugin",
-        "@curator-client//jar",
-        "@curator-framework//jar",
-        "@curator-test//jar",
-        "@mockito//jar",
-        "@testcontainers-kafka//jar",
         "@wiremock//jar",
-        "//lib/testcontainers",
+        "@global-refdb//jar",
+        "@events-broker//jar",
         "//plugins/replication",
     ],
 )
diff --git a/README.md b/README.md
index 49e96df..0bf8486 100644
--- a/README.md
+++ b/README.md
@@ -78,29 +78,10 @@
 
 ```
 [gerrit]
+  installDbModule = com.googlesource.gerrit.plugins.multisite.GitModule
   installModule = com.googlesource.gerrit.plugins.multisite.Module
 ```
 
-Create the `$GERRIT_SITE/etc/multi-site.config` on all Gerrit servers with the
-following basic settings:
-
-```
-[kafka]
-  bootstrapServers = <kafka-host>:<kafka-port>
-
-[kafka "publisher"]
-  enabled = true
-
-[kafka "subscriber"]
-  enabled = true
-
-[ref-database]
-  enabled = true
-
-[ref-database "zookeeper"]
-  connectString = "localhost:2181"
-```
-
 For more details on the configuration settings, please refer to the
 [multi-site configuration documentation](src/main/resources/Documentation/config.md).
 
diff --git a/dockerised_local_env/gerrit-common/multi-site.config b/dockerised_local_env/gerrit-common/multi-site.config
index 04b9c2c..deec00f 100644
--- a/dockerised_local_env/gerrit-common/multi-site.config
+++ b/dockerised_local_env/gerrit-common/multi-site.config
@@ -12,14 +12,10 @@
 	cacheEventTopic = gerrit_cache_eviction
 
 [kafka "subscriber"]
-	enabled = true
 	pollingIntervalMs = 1000
 	KafkaProp-enableAutoCommit = true
 	KafkaProp-autoCommitIntervalMs = 1000
 	KafkaProp-autoOffsetReset = latest
 
-[kafka "publisher"]
-	enabled = true
-
 [ref-database "zookeeper"]
 	connectString = "zookeeper:2181"
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index 369f6e0..c10e32a 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -8,76 +8,13 @@
     )
 
     maven_jar(
-        name = "mockito",
-        artifact = "org.mockito:mockito-core:2.27.0",
-        sha1 = "835fc3283b481f4758b8ef464cd560c649c08b00",
-        deps = [
-            "@byte-buddy//jar",
-            "@byte-buddy-agent//jar",
-            "@objenesis//jar",
-        ],
-    )
-
-    BYTE_BUDDY_VER = "1.9.10"
-
-    maven_jar(
-        name = "byte-buddy",
-        artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VER,
-        sha1 = "211a2b4d3df1eeef2a6cacf78d74a1f725e7a840",
+        name = "global-refdb",
+        artifact = "com.gerritforge:global-refdb:3.0.2",
+        sha1 = "293a807bd82a284c215213b442b3930258e01f5e"
     )
 
     maven_jar(
-        name = "byte-buddy-agent",
-        artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VER,
-        sha1 = "9674aba5ee793e54b864952b001166848da0f26b",
-    )
-
-    maven_jar(
-        name = "objenesis",
-        artifact = "org.objenesis:objenesis:2.6",
-        sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
-    )
-
-    maven_jar(
-        name = "kafka-client",
-        artifact = "org.apache.kafka:kafka-clients:2.1.0",
-        sha1 = "34d9983705c953b97abb01e1cd04647f47272fe5",
-    )
-
-    maven_jar(
-        name = "testcontainers-kafka",
-        artifact = "org.testcontainers:kafka:1.11.3",
-        sha1 = "932d1baa2541f218b1b44a0546ae83d530011468",
-    )
-
-    CURATOR_VER = "4.2.0"
-
-    maven_jar(
-        name = "curator-test",
-        artifact = "org.apache.curator:curator-test:" + CURATOR_VER,
-        sha1 = "98ac2dd69b8c07dcaab5e5473f93fdb9e320cd73",
-    )
-
-    maven_jar(
-        name = "curator-framework",
-        artifact = "org.apache.curator:curator-framework:" + CURATOR_VER,
-        sha1 = "5b1cc87e17b8fe4219b057f6025662a693538861",
-    )
-
-    maven_jar(
-        name = "curator-recipes",
-        artifact = "org.apache.curator:curator-recipes:" + CURATOR_VER,
-        sha1 = "7f775be5a7062c2477c51533b9d008f70411ba8e",
-    )
-
-    maven_jar(
-        name = "curator-client",
-        artifact = "org.apache.curator:curator-client:" + CURATOR_VER,
-        sha1 = "d5d50930b8dd189f92c40258a6ba97675fea3e15",
-    )
-
-    maven_jar(
-        name = "zookeeper",
-        artifact = "org.apache.zookeeper:zookeeper:3.4.14",
-        sha1 = "c114c1e1c8172a7cd3f6ae39209a635f7a06c1a1",
+        name = "events-broker",
+        artifact = "com.gerritforge:events-broker:3.0.5",
+        sha1 = "7abf72d2252f975baff666fbbf28b7036767aa81",
     )
diff --git a/setup_local_env/configs/gerrit.config b/setup_local_env/configs/gerrit.config
index 18eeca4..f9eca89 100644
--- a/setup_local_env/configs/gerrit.config
+++ b/setup_local_env/configs/gerrit.config
@@ -2,8 +2,9 @@
     basePath = git
     serverId = 69ec38f0-350e-4d9c-96d4-bc956f2faaac
     canonicalWebUrl = $GERRIT_CANONICAL_WEB_URL
+    installModule = com.gerritforge.gerrit.eventbroker.BrokerApiModule # events-broker module to setup BrokerApi dynamic item
     installModule = com.googlesource.gerrit.plugins.multisite.Module # multi-site needs to be a gerrit lib
-
+    installDbModule = com.googlesource.gerrit.plugins.multisite.GitModule
 [database]
     type = h2
     database = $LOCATION_TEST_SITE/db/ReviewDB
@@ -31,9 +32,22 @@
     advertisedAddress = *:$SSH_ADVERTISED_PORT
 [httpd]
     listenUrl = proxy-$HTTP_PROTOCOL://*:$GERRIT_HTTPD_PORT/
+    requestLog = true
 [cache]
     directory = cache
 [plugins]
     allowRemoteAdmin = true
 [plugin "websession-flatfile"]
     directory = $FAKE_NFS
+[plugin "kafka-events"]
+    sendAsync = true
+    bootstrapServers = localhost:$KAFKA_PORT
+    groupId = $KAFKA_GROUP_ID
+    numberOfSubscribers = 6
+    securityProtocol = PLAINTEXT
+    pollingIntervalMs = 1000
+    enableAutoCommit = true
+    autoCommitIntervalMs = 1000
+    autoOffsetReset = latest
+[plugin "metrics-reporter-prometheus"]
+    prometheusBearerToken = token
diff --git a/setup_local_env/configs/multi-site.config b/setup_local_env/configs/multi-site.config
index 6b631a2..9571513 100644
--- a/setup_local_env/configs/multi-site.config
+++ b/setup_local_env/configs/multi-site.config
@@ -1,21 +1,11 @@
 [index]
 	maxTries = 50
 	retryInterval = 30000
-[kafka]
-	bootstrapServers = localhost:$KAFKA_PORT
-	securityProtocol = PLAINTEXT
-	indexEventTopic = gerrit_index
-	batchIndexEventTopic = gerrit_batch_index
-	streamEventTopic = gerrit_stream
-	projectListEventTopic = gerrit_list_project
-	cacheEventTopic = gerrit_cache_eviction
-[kafka "subscriber"]
-	enabled = true
-	pollingIntervalMs = 1000
-	KafkaProp-enableAutoCommit = true
-	KafkaProp-autoCommitIntervalMs = 1000
-	KafkaProp-autoOffsetReset = latest
-[kafka "publisher"]
-	enabled = true
-[ref-database "zookeeper"]
-	connectString = localhost:$ZK_PORT
+
+[broker]
+        indexEventTopic = gerrit_index
+        batchIndexEventTopic = gerrit_batch_index
+        streamEventTopic = gerrit_stream
+        projectListEventTopic = gerrit_list_project
+        cacheEventTopic = gerrit_cache_eviction
+
diff --git a/setup_local_env/configs/prometheus.yml b/setup_local_env/configs/prometheus.yml
new file mode 100644
index 0000000..8eaa989
--- /dev/null
+++ b/setup_local_env/configs/prometheus.yml
@@ -0,0 +1,17 @@
+global:
+  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
+  # scrape_timeout is set to the global default (10s).
+
+scrape_configs:
+ - job_name: 'metrics'
+   scheme: http
+   metrics_path: '/plugins/metrics-reporter-prometheus/metrics'
+   params:
+      format: ['prometheus']
+   bearer_token: token
+   scrape_interval: 5s
+   static_configs:
+      - targets: ['$GERRIT_SITE_HOST:18080','$GERRIT_SITE_HOST:18081']
+        labels:
+          env: 'unit'
diff --git a/setup_local_env/configs/zookeeper-refdb.config b/setup_local_env/configs/zookeeper-refdb.config
new file mode 100644
index 0000000..2c84a05
--- /dev/null
+++ b/setup_local_env/configs/zookeeper-refdb.config
@@ -0,0 +1,2 @@
+[ref-database "zookeeper"]
+	connectString = localhost:$ZK_PORT
\ No newline at end of file
diff --git a/setup_local_env/docker-compose.kafka-broker.yaml b/setup_local_env/docker-compose.yaml
similarity index 63%
rename from setup_local_env/docker-compose.kafka-broker.yaml
rename to setup_local_env/docker-compose.yaml
index b7e91f0..c386d46 100644
--- a/setup_local_env/docker-compose.kafka-broker.yaml
+++ b/setup_local_env/docker-compose.yaml
@@ -13,3 +13,11 @@
     environment:
       KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
       KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+  prometheus:
+    image: prom/prometheus:v2.16.0
+    user: root
+    volumes:
+     - $COMMON_LOCATION/prometheus.yml:/etc/prometheus/prometheus.yml
+    ports:
+      - "9090:9090"
+    network_mode: $NETWORK_MODE
diff --git a/setup_local_env/prometheus-config/prometheus.yml b/setup_local_env/prometheus-config/prometheus.yml
new file mode 100644
index 0000000..e6d3ea1
--- /dev/null
+++ b/setup_local_env/prometheus-config/prometheus.yml
@@ -0,0 +1,14 @@
+# my global config
+global:
+  scrape_interval:     10s
+  evaluation_interval: 10s
+
+scrape_configs:
+  - job_name: gerrit
+    static_configs:
+      - targets: ['localhost:18080','localhost:18081']
+    metrics_path: '/plugins/metrics-reporter-prometheus/metrics'
+    params:
+      format: ['prometheus']
+    bearer_token: token
+
diff --git a/setup_local_env/setup.sh b/setup_local_env/setup.sh
index e6ebcd7..a97afc3 100755
--- a/setup_local_env/setup.sh
+++ b/setup_local_env/setup.sh
@@ -16,252 +16,273 @@
 
 
 SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+GERRIT_BRANCH=stable-3.0
+GERRIT_CI=https://gerrit-ci.gerritforge.com/view/Plugins-$GERRIT_BRANCH/job
+LAST_BUILD=lastSuccessfulBuild/artifact/bazel-bin/plugins
 
 function check_application_requirements {
-	type haproxy >/dev/null 2>&1 || { echo >&2 "Require haproxy but it's not installed. Aborting."; exit 1; }
-	type java >/dev/null 2>&1 || { echo >&2 "Require java but it's not installed. Aborting."; exit 1; }
-	type docker >/dev/null 2>&1 || { echo >&2 "Require docker but it's not installed. Aborting."; exit 1; }
-	type docker-compose >/dev/null 2>&1 || { echo >&2 "Require docker-compose but it's not installed. Aborting."; exit 1; }
-	type wget >/dev/null 2>&1 || { echo >&2 "Require wget but it's not installed. Aborting."; exit 1; }
-	type envsubst >/dev/null 2>&1 || { echo >&2 "Require envsubst but it's not installed. Aborting."; exit 1; }
-	type openssl >/dev/null 2>&1 || { echo >&2 "Require openssl but it's not installed. Aborting."; exit 1; }
+  type haproxy >/dev/null 2>&1 || { echo >&2 "Require haproxy but it's not installed. Aborting."; exit 1; }
+  type java >/dev/null 2>&1 || { echo >&2 "Require java but it's not installed. Aborting."; exit 1; }
+  type docker >/dev/null 2>&1 || { echo >&2 "Require docker but it's not installed. Aborting."; exit 1; }
+  type docker-compose >/dev/null 2>&1 || { echo >&2 "Require docker-compose but it's not installed. Aborting."; exit 1; }
+  type wget >/dev/null 2>&1 || { echo >&2 "Require wget but it's not installed. Aborting."; exit 1; }
+  type envsubst >/dev/null 2>&1 || { echo >&2 "Require envsubst but it's not installed. Aborting."; exit 1; }
+  type openssl >/dev/null 2>&1 || { echo >&2 "Require openssl but it's not installed. Aborting."; exit 1; }
 }
 
 function get_replication_url {
-	REPLICATION_LOCATION_TEST_SITE=$1
-	REPLICATION_HOSTNAME=$2
-	USER=$REPLICATION_SSH_USER
+  REPLICATION_LOCATION_TEST_SITE=$1
+  REPLICATION_HOSTNAME=$2
+  USER=$REPLICATION_SSH_USER
 
-	if [ "$REPLICATION_TYPE" = "file" ];then
-		echo "url = file://$REPLICATION_LOCATION_TEST_SITE/git/#{name}#.git"
-	elif [ "$REPLICATION_TYPE" = "ssh" ];then
-		echo "url = ssh://$USER@$REPLICATION_HOSTNAME:$REPLICATION_LOCATION_TEST_SITE/git/#{name}#.git"
-	fi
+  if [ "$REPLICATION_TYPE" = "file" ];then
+    echo "url = file://$REPLICATION_LOCATION_TEST_SITE/git/#{name}#.git"
+  elif [ "$REPLICATION_TYPE" = "ssh" ];then
+    echo "url = ssh://$USER@$REPLICATION_HOSTNAME:$REPLICATION_LOCATION_TEST_SITE/git/#{name}#.git"
+  fi
 }
 
 function deploy_tls_certificates {
-	echo "Deplying certificates in $HA_PROXY_CERTIFICATES_DIR..."
-	openssl req -new -newkey rsa:2048 -x509 -sha256 -days 365 -nodes \
-	-out $HA_PROXY_CERTIFICATES_DIR/MyCertificate.crt \
-	-keyout $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.key \
-	-subj "/C=GB/ST=London/L=London/O=Gerrit Org/OU=IT Department/CN=localhost"
-	cat $HA_PROXY_CERTIFICATES_DIR/MyCertificate.crt $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.key | tee $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.pem
+  echo "Deplying certificates in $HA_PROXY_CERTIFICATES_DIR..."
+  openssl req -new -newkey rsa:2048 -x509 -sha256 -days 365 -nodes \
+  -out $HA_PROXY_CERTIFICATES_DIR/MyCertificate.crt \
+  -keyout $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.key \
+  -subj "/C=GB/ST=London/L=London/O=Gerrit Org/OU=IT Department/CN=localhost"
+  cat $HA_PROXY_CERTIFICATES_DIR/MyCertificate.crt $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.key | tee $HA_PROXY_CERTIFICATES_DIR/GerritLocalKey.pem
 }
 
 function copy_config_files {
-	for file in `ls $SCRIPT_DIR/configs/*.config`
-	do
-		file_name=`basename $file`
+  for file in `ls $SCRIPT_DIR/configs/*.config`
+  do
+    file_name=`basename $file`
 
-		CONFIG_TEST_SITE=$1
-		export GERRIT_HTTPD_PORT=$2
-		export LOCATION_TEST_SITE=$3
-		export GERRIT_SSHD_PORT=$4
-		export REPLICATION_HTTPD_PORT=$5
-		export REPLICATION_LOCATION_TEST_SITE=$6
-		export GERRIT_HOSTNAME=$7
-		export REPLICATION_HOSTNAME=$8
-		export REMOTE_DEBUG_PORT=$9
-		export REPLICATION_URL=$(get_replication_url $REPLICATION_LOCATION_TEST_SITE $REPLICATION_HOSTNAME)
+    CONFIG_TEST_SITE=$1
+    export GERRIT_HTTPD_PORT=$2
+    export LOCATION_TEST_SITE=$3
+    export GERRIT_SSHD_PORT=$4
+    export REPLICATION_HTTPD_PORT=$5
+    export REPLICATION_LOCATION_TEST_SITE=$6
+    export GERRIT_HOSTNAME=$7
+    export REPLICATION_HOSTNAME=$8
+    export REMOTE_DEBUG_PORT=$9
+    export KAFKA_GROUP_ID=${10}
+    export REPLICATION_URL=$(get_replication_url $REPLICATION_LOCATION_TEST_SITE $REPLICATION_HOSTNAME)
 
-		echo "Replacing variables for file $file and copying to $CONFIG_TEST_SITE/$file_name"
+    echo "Replacing variables for file $file and copying to $CONFIG_TEST_SITE/$file_name"
 
-		cat $file | envsubst | sed 's/#{name}#/${name}/g' > $CONFIG_TEST_SITE/$file_name
-	done
+    cat $file | envsubst | sed 's/#{name}#/${name}/g' > $CONFIG_TEST_SITE/$file_name
+  done
 }
+
 function start_ha_proxy {
 
-	export HA_GERRIT_CANONICAL_HOSTNAME=$GERRIT_CANONICAL_HOSTNAME
-	export HA_GERRIT_CANONICAL_PORT=$GERRIT_CANONICAL_PORT
+  export HA_GERRIT_CANONICAL_HOSTNAME=$GERRIT_CANONICAL_HOSTNAME
+  export HA_GERRIT_CANONICAL_PORT=$GERRIT_CANONICAL_PORT
 
-	export HA_HTTPS_BIND=$HTTPS_BIND
+  export HA_HTTPS_BIND=$HTTPS_BIND
 
-	export HA_GERRIT_SITE1_HOSTNAME=$GERRIT_1_HOSTNAME
-	export HA_GERRIT_SITE2_HOSTNAME=$GERRIT_2_HOSTNAME
-	export HA_GERRIT_SITE1_HTTPD_PORT=$GERRIT_1_HTTPD_PORT
-	export HA_GERRIT_SITE2_HTTPD_PORT=$GERRIT_2_HTTPD_PORT
+  export HA_GERRIT_SITE1_HOSTNAME=$GERRIT_1_HOSTNAME
+  export HA_GERRIT_SITE2_HOSTNAME=$GERRIT_2_HOSTNAME
+  export HA_GERRIT_SITE1_HTTPD_PORT=$GERRIT_1_HTTPD_PORT
+  export HA_GERRIT_SITE2_HTTPD_PORT=$GERRIT_2_HTTPD_PORT
 
-	export HA_GERRIT_SITE1_SSHD_PORT=$GERRIT_1_SSHD_PORT
-	export HA_GERRIT_SITE2_SSHD_PORT=$GERRIT_2_SSHD_PORT
+  export HA_GERRIT_SITE1_SSHD_PORT=$GERRIT_1_SSHD_PORT
+  export HA_GERRIT_SITE2_SSHD_PORT=$GERRIT_2_SSHD_PORT
 
-	cat $SCRIPT_DIR/haproxy-config/haproxy.cfg | envsubst > $HA_PROXY_CONFIG_DIR/haproxy.cfg
+  cat $SCRIPT_DIR/haproxy-config/haproxy.cfg | envsubst > $HA_PROXY_CONFIG_DIR/haproxy.cfg
 
-	echo "Starting HA-PROXY..."
-	echo "THE SCRIPT LOCATION $SCRIPT_DIR"
-	echo "THE HA SCRIPT_LOCATION $HA_SCRIPT_DIR"
-	haproxy -f $HA_PROXY_CONFIG_DIR/haproxy.cfg &
+  echo "Starting HA-PROXY..."
+  echo "THE SCRIPT LOCATION $SCRIPT_DIR"
+  echo "THE HA SCRIPT_LOCATION $HA_SCRIPT_DIR"
+  haproxy -f $HA_PROXY_CONFIG_DIR/haproxy.cfg &
 }
 
 function deploy_config_files {
-	# KAFKA configuration
-	export KAFKA_PORT=9092
+  # KAFKA configuration
+  export KAFKA_PORT=9092
 
-	# ZK configuration
-	export ZK_PORT=2181
+  # ZK configuration
+  export ZK_PORT=2181
 
-	# SITE 1
-	GERRIT_SITE1_HOSTNAME=$1
-	GERRIT_SITE1_HTTPD_PORT=$2
-	GERRIT_SITE1_SSHD_PORT=$3
-	CONFIG_TEST_SITE_1=$LOCATION_TEST_SITE_1/etc
-	GERRIT_SITE1_REMOTE_DEBUG_PORT="5005"
-	# SITE 2
-	GERRIT_SITE2_HOSTNAME=$4
-	GERRIT_SITE2_HTTPD_PORT=$5
-	GERRIT_SITE2_SSHD_PORT=$6
-	CONFIG_TEST_SITE_2=$LOCATION_TEST_SITE_2/etc
-	GERRIT_SITE2_REMOTE_DEBUG_PORT="5006"
+  # SITE 1
+  GERRIT_SITE1_HOSTNAME=$1
+  GERRIT_SITE1_HTTPD_PORT=$2
+  GERRIT_SITE1_SSHD_PORT=$3
+  CONFIG_TEST_SITE_1=$LOCATION_TEST_SITE_1/etc
+  GERRIT_SITE1_REMOTE_DEBUG_PORT="5005"
+  GERRIT_SITE1_KAFKA_GROUP_ID="instance-1"
+  # SITE 2
+  GERRIT_SITE2_HOSTNAME=$4
+  GERRIT_SITE2_HTTPD_PORT=$5
+  GERRIT_SITE2_SSHD_PORT=$6
+  CONFIG_TEST_SITE_2=$LOCATION_TEST_SITE_2/etc
+  GERRIT_SITE2_REMOTE_DEBUG_PORT="5006"
+  GERRIT_SITE2_KAFKA_GROUP_ID="instance-2"
 
-	# Set config SITE1
-	copy_config_files $CONFIG_TEST_SITE_1 $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_SSHD_PORT $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE1_REMOTE_DEBUG_PORT
+  # Set config SITE1
+  copy_config_files $CONFIG_TEST_SITE_1 $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_SSHD_PORT $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE1_REMOTE_DEBUG_PORT $GERRIT_SITE1_KAFKA_GROUP_ID
 
 
-	# Set config SITE2
-	copy_config_files $CONFIG_TEST_SITE_2 $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE2_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_REMOTE_DEBUG_PORT
+  # Set config SITE2
+  copy_config_files $CONFIG_TEST_SITE_2 $GERRIT_SITE2_HTTPD_PORT $LOCATION_TEST_SITE_2 $GERRIT_SITE2_SSHD_PORT $GERRIT_SITE1_HTTPD_PORT $LOCATION_TEST_SITE_1 $GERRIT_SITE1_HOSTNAME $GERRIT_SITE2_HOSTNAME $GERRIT_SITE2_REMOTE_DEBUG_PORT $GERRIT_SITE2_KAFKA_GROUP_ID
+}
+
+function is_docker_desktop {
+  echo $(docker info | grep "Operating System: Docker Desktop" | wc -l)
+}
+
+function docker_host_env {
+  IS_DOCKER_DESKTOP=$(is_docker_desktop)
+  if [ "$IS_DOCKER_DESKTOP" = "1" ];then
+    echo "mac"
+  else
+    echo "linux"
+  fi
 }
 
 
 function cleanup_environment {
-	echo "Killing existing HA-PROXY setup"
-	kill $(ps -ax | grep haproxy | grep "gerrit_setup/ha-proxy-config" | awk '{print $1}') 2> /dev/null
-	echo "Stoping kafka and zk"
-	docker-compose -f $SCRIPT_DIR/docker-compose.kafka-broker.yaml down 2> /dev/null
+  echo "Killing existing HA-PROXY setup"
+  kill $(ps -ax | grep haproxy | grep "gerrit_setup/ha-proxy-config" | awk '{print $1}') 2> /dev/null
 
-	echo "Stoping GERRIT instances"
-	$1/bin/gerrit.sh stop 2> /dev/null
-	$2/bin/gerrit.sh stop 2> /dev/null
+  echo "Stopping docker containers"
+  docker-compose -f $SCRIPT_DIR/docker-compose.yaml down 2> /dev/null
 
-	echo "REMOVING setup directory $3"
-	rm -rf $3 2> /dev/null
+  echo "Stopping GERRIT instances"
+  $1/bin/gerrit.sh stop 2> /dev/null
+  $2/bin/gerrit.sh stop 2> /dev/null
+
+  echo "REMOVING setup directory $3"
+  rm -rf $3 2> /dev/null
 }
 
 function check_if_kafka_is_running {
-	echo $(docker inspect kafka_test_node 2> /dev/null | grep '"Running": true' | wc -l)
+  echo $(docker inspect kafka_test_node 2> /dev/null | grep '"Running": true' | wc -l)
 }
 
 while [ $# -ne 0 ]
 do
 case "$1" in
   "--help" )
-		echo "Usage: sh $0 [--option $value]"
-		echo
-		echo "[--release-war-file]            Location to release.war file"
-		echo "[--multisite-lib-file]          Location to lib multi-site.jar file"
-		echo
-		echo "[--new-deployment]              Cleans up previous gerrit deployment and re-installs it. default true"
-		echo "[--get-websession-plugin]       Download websession-flatfile plugin from CI lastSuccessfulBuild; default true"
-		echo "[--deployment-location]         Base location for the test deployment; default /tmp"
-		echo
-		echo "[--gerrit-canonical-host]       The default host for Gerrit to be accessed through; default localhost"
-		echo "[--gerrit-canonical-port]       The default port for Gerrit to be accessed throug; default 8080"
-		echo
-		echo "[--gerrit-ssh-advertised-port]  Gerrit Instance 1 sshd port; default 29418"
-		echo
-		echo "[--gerrit1-httpd-port]          Gerrit Instance 1 http port; default 18080"
-		echo "[--gerrit1-sshd-port]           Gerrit Instance 1 sshd port; default 39418"
-		echo
-		echo "[--gerrit2-httpd-port]          Gerrit Instance 2 http port; default 18081"
-		echo "[--gerrit2-sshd-port]           Gerrit Instance 2 sshd port; default 49418"
-		echo
-		echo "[--replication-type]            Options [file,ssh]; default ssh"
-		echo "[--replication-ssh-user]        SSH user for the replication plugin; default $(whoami)"
-		echo "[--replication-delay]           Replication delay across the two instances in seconds"
-		echo
-		echo "[--just-cleanup-env]            Cleans up previous deployment; default false"
-		echo
-		echo "[--enabled-https]               Enabled https; default true"
-		echo
-		exit 0
+    echo "Usage: sh $0 [--option $value]"
+    echo
+    echo "[--release-war-file]            Location to release.war file"
+    echo "[--multisite-lib-file]          Location to lib multi-site.jar file"
+    echo
+    echo "[--new-deployment]              Cleans up previous gerrit deployment and re-installs it. default true"
+    echo "[--get-websession-plugin]       Download websession-broker plugin from CI lastSuccessfulBuild; default true"
+    echo "[--deployment-location]         Base location for the test deployment; default /tmp"
+    echo
+    echo "[--gerrit-canonical-host]       The default host for Gerrit to be accessed through; default localhost"
+    echo "[--gerrit-canonical-port]       The default port for Gerrit to be accessed throug; default 8080"
+    echo
+    echo "[--gerrit-ssh-advertised-port]  Gerrit Instance 1 sshd port; default 29418"
+    echo
+    echo "[--gerrit1-httpd-port]          Gerrit Instance 1 http port; default 18080"
+    echo "[--gerrit1-sshd-port]           Gerrit Instance 1 sshd port; default 39418"
+    echo
+    echo "[--gerrit2-httpd-port]          Gerrit Instance 2 http port; default 18081"
+    echo "[--gerrit2-sshd-port]           Gerrit Instance 2 sshd port; default 49418"
+    echo
+    echo "[--replication-type]            Options [file,ssh]; default ssh"
+    echo "[--replication-ssh-user]        SSH user for the replication plugin; default $(whoami)"
+    echo "[--replication-delay]           Replication delay across the two instances in seconds"
+    echo
+    echo "[--just-cleanup-env]            Cleans up previous deployment; default false"
+    echo
+    echo "[--enabled-https]               Enabled https; default true"
+    echo
+    exit 0
   ;;
   "--new-deployment")
         NEW_INSTALLATION=$2
-		shift
-		shift
+    shift
+    shift
   ;;
   "--get-websession-plugin")
-		DOWNLOAD_WEBSESSION_FLATFILE=$2
-		shift
-		shift
+    DOWNLOAD_WEBSESSION_PLUGIN=$2
+    shift
+    shift
   ;;
   "--deployment-location" )
-		DEPLOYMENT_LOCATION=$2
-		shift
-		shift
+    DEPLOYMENT_LOCATION=$2
+    shift
+    shift
   ;;
   "--release-war-file" )
-		RELEASE_WAR_FILE_LOCATION=$2
-		shift
-		shift
+    RELEASE_WAR_FILE_LOCATION=$2
+    shift
+    shift
   ;;
   "--multisite-lib-file" )
-		MULTISITE_LIB_LOCATION=$2
-		shift
-		shift
+    MULTISITE_LIB_LOCATION=$2
+    shift
+    shift
   ;;
   "--gerrit-canonical-host" )
-		export GERRIT_CANONICAL_HOSTNAME=$2
-		shift
-		shift
+    export GERRIT_CANONICAL_HOSTNAME=$2
+    shift
+    shift
   ;;
   "--gerrit-canonical-port" )
-		export GERRIT_CANONICAL_PORT=$2
-		shift
-		shift
+    export GERRIT_CANONICAL_PORT=$2
+    shift
+    shift
   ;;
   "--gerrit-ssh-advertised-port" )
-		export SSH_ADVERTISED_PORT=$2
-		shift
-		shift
+    export SSH_ADVERTISED_PORT=$2
+    shift
+    shift
   ;;
   "--gerrit1-httpd-port" )
-       	GERRIT_1_HTTPD_PORT=$2
-		shift
-		shift
+         GERRIT_1_HTTPD_PORT=$2
+    shift
+    shift
   ;;
   "--gerrit2-httpd-port" )
-       	GERRIT_2_HTTPD_PORT=$2
-		shift
-		shift
+         GERRIT_2_HTTPD_PORT=$2
+    shift
+    shift
   ;;
   "--gerrit1-sshd-port" )
-       	GERRIT_1_SSHD_PORT=$2
-		shift
-		shift
+         GERRIT_1_SSHD_PORT=$2
+    shift
+    shift
   ;;
   "--gerrit2-sshd-port" )
-       	GERRIT_2_SSHD_PORT=$2
-		shift
-		shift
+         GERRIT_2_SSHD_PORT=$2
+    shift
+    shift
   ;;
   "--replication-ssh-user" )
-		export REPLICATION_SSH_USER=$2
-		shift
-		shift
+    export REPLICATION_SSH_USER=$2
+    shift
+    shift
   ;;
   "--replication-type")
-		export REPLICATION_TYPE=$2
-		shift
-		shift
+    export REPLICATION_TYPE=$2
+    shift
+    shift
   ;;
   "--replication-delay")
-		export REPLICATION_DELAY_SEC=$2
-		shift
-		shift
+    export REPLICATION_DELAY_SEC=$2
+    shift
+    shift
   ;;
   "--just-cleanup-env" )
-       	JUST_CLEANUP_ENV=$2
-		shift
-		shift
+         JUST_CLEANUP_ENV=$2
+    shift
+    shift
   ;;
   "--enabled-https" )
-		HTTPS_ENABLED=$2
-		shift
-		shift
+    HTTPS_ENABLED=$2
+    shift
+    shift
   ;;
-  *	   )
-		echo "Unknown option argument: $1"
-		shift
-		shift
+  *     )
+    echo "Unknown option argument: $1"
+    shift
+    shift
   ;;
 esac
 done
@@ -271,7 +292,7 @@
 
 # Defaults
 NEW_INSTALLATION=${NEW_INSTALLATION:-"true"}
-DOWNLOAD_WEBSESSION_FLATFILE=${DOWNLOAD_WEBSESSION_FLATFILE:-"true"}
+DOWNLOAD_WEBSESSION_PLUGIN=${DOWNLOAD_WEBSESSION_PLUGIN:-"true"}
 DEPLOYMENT_LOCATION=${DEPLOYMENT_LOCATION:-"/tmp"}
 export GERRIT_CANONICAL_HOSTNAME=${GERRIT_CANONICAL_HOSTNAME:-"localhost"}
 export GERRIT_CANONICAL_PORT=${GERRIT_CANONICAL_PORT:-"8080"}
@@ -287,11 +308,12 @@
 export SSH_ADVERTISED_PORT=${SSH_ADVERTISED_PORT:-"29418"}
 HTTPS_ENABLED=${HTTPS_ENABLED:-"false"}
 
-COMMON_LOCATION=$DEPLOYMENT_LOCATION/gerrit_setup
+export COMMON_LOCATION=$DEPLOYMENT_LOCATION/gerrit_setup
 LOCATION_TEST_SITE_1=$COMMON_LOCATION/instance-1
 LOCATION_TEST_SITE_2=$COMMON_LOCATION/instance-2
 HA_PROXY_CONFIG_DIR=$COMMON_LOCATION/ha-proxy-config
 HA_PROXY_CERTIFICATES_DIR="$HA_PROXY_CONFIG_DIR/certificates"
+PROMETHEUS_CONFIG_DIR=$COMMON_LOCATION/prometheus-config
 
 RELEASE_WAR_FILE_LOCATION=${RELEASE_WAR_FILE_LOCATION:-bazel-bin/release.war}
 MULTISITE_LIB_LOCATION=${MULTISITE_LIB_LOCATION:-bazel-bin/plugins/multi-site/multi-site.jar}
@@ -300,93 +322,136 @@
 export FAKE_NFS=$COMMON_LOCATION/fake_nfs
 
 if [ "$JUST_CLEANUP_ENV" = "true" ];then
-	cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
-	exit 0
+  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
+  exit 0
 fi
 
 if [ -z $RELEASE_WAR_FILE_LOCATION ];then
-	echo "A release.war file is required. Usage: sh $0 --release-war-file /path/to/release.war"
-	exit 1
+  echo "A release.war file is required. Usage: sh $0 --release-war-file /path/to/release.war"
+  exit 1
 else
-	cp -f $RELEASE_WAR_FILE_LOCATION $DEPLOYMENT_LOCATION/gerrit.war >/dev/null 2>&1 || { echo >&2 "$RELEASE_WAR_FILE_LOCATION: Not able to copy the file. Aborting"; exit 1; }
+  cp -f $RELEASE_WAR_FILE_LOCATION $DEPLOYMENT_LOCATION/gerrit.war >/dev/null 2>&1 || { echo >&2 "$RELEASE_WAR_FILE_LOCATION: Not able to copy the file. Aborting"; exit 1; }
 fi
 if [ -z $MULTISITE_LIB_LOCATION ];then
-	echo "The multi-site library is required. Usage: sh $0 --multisite-lib-file /path/to/multi-site.jar"
-	exit 1
+  echo "The multi-site library is required. Usage: sh $0 --multisite-lib-file /path/to/multi-site.jar"
+  exit 1
 else
-	cp -f $MULTISITE_LIB_LOCATION $DEPLOYMENT_LOCATION/multi-site.jar  >/dev/null 2>&1 || { echo >&2 "$MULTISITE_LIB_LOCATION: Not able to copy the file. Aborting"; exit 1; }
+  cp -f $MULTISITE_LIB_LOCATION $DEPLOYMENT_LOCATION/multi-site.jar  >/dev/null 2>&1 || { echo >&2 "$MULTISITE_LIB_LOCATION: Not able to copy the file. Aborting"; exit 1; }
 fi
-if [ $DOWNLOAD_WEBSESSION_FLATFILE = "true" ];then
-	echo "Downloading websession-flatfile plugin stable 2.16"
-	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.16/job/plugin-websession-flatfile-bazel-master-stable-2.16/lastSuccessfulBuild/artifact/bazel-bin/plugins/websession-flatfile/websession-flatfile.jar \
-	-O $DEPLOYMENT_LOCATION/websession-flatfile.jar || { echo >&2 "Cannot download websession-flatfile plugin: Check internet connection. Abort\
+if [ $DOWNLOAD_WEBSESSION_PLUGIN = "true" ];then
+  echo "Downloading websession-broker plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-websession-broker-bazel-$GERRIT_BRANCH/$LAST_BUILD/websession-broker/websession-broker.jar \
+  -O $DEPLOYMENT_LOCATION/websession-broker.jar || { echo >&2 "Cannot download websession-broker plugin: Check internet connection. Abort\
 ing"; exit 1; }
-	wget https://gerrit-ci.gerritforge.com/view/Plugins-stable-2.16/job/plugin-healthcheck-bazel-stable-2.16/lastSuccessfulBuild/artifact/bazel-bin/plugins/healthcheck/healthcheck.jar \
-	-O $DEPLOYMENT_LOCATION/healthcheck.jar || { echo >&2 "Cannot download healthcheck plugin: Check internet connection. Abort\
+  wget $GERRIT_CI/plugin-healthcheck-bazel-$GERRIT_BRANCH/$LAST_BUILD/healthcheck/healthcheck.jar \
+  -O $DEPLOYMENT_LOCATION/healthcheck.jar || { echo >&2 "Cannot download healthcheck plugin: Check internet connection. Abort\
 ing"; exit 1; }
 else
-	echo "Without the websession-flatfile; user login via haproxy will fail."
+  echo "Without the websession-broker; user login via haproxy will fail."
 fi
 
+echo "Downloading zookeeper plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-zookeeper-refdb-bazel-$GERRIT_BRANCH/$LAST_BUILD/zookeeper-refdb/zookeeper-refdb.jar \
+  -O $DEPLOYMENT_LOCATION/zookeeper-refdb.jar || { echo >&2 "Cannot download zookeeper plugin: Check internet connection. Abort\
+ing"; exit 1; }
+
+echo "Downloading events-broker library $GERRIT_BRANCH"
+  wget https://repo1.maven.org/maven2/com/gerritforge/events-broker/3.0.5/events-broker-3.0.5.jar \
+  -O $DEPLOYMENT_LOCATION/events-broker.jar || { echo >&2 "Cannot download events-broker library: Check internet connection. Abort\
+ing"; exit 1; }
+
+echo "Downloading kafka-events plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-kafka-events-bazel-$GERRIT_BRANCH/$LAST_BUILD/kafka-events/kafka-events.jar \
+  -O $DEPLOYMENT_LOCATION/kafka-events.jar || { echo >&2 "Cannot download kafka-events plugin: Check internet connection. Abort\
+ing"; exit 1; }
+
+echo "Downloading metrics-reporter-prometheus plugin $GERRIT_BRANCH"
+  wget $GERRIT_CI/plugin-metrics-reporter-prometheus-bazel-master-$GERRIT_BRANCH/$LAST_BUILD/metrics-reporter-prometheus/metrics-reporter-prometheus.jar \
+  -O $DEPLOYMENT_LOCATION/metrics-reporter-prometheus.jar || { echo >&2 "Cannot download metrics-reporter-prometheus plugin: Check internet connection. Abort\
+ing"; exit 1; }
+
 if [ "$REPLICATION_TYPE" = "ssh" ];then
-	echo "Using 'SSH' replication type"
-	echo "Make sure ~/.ssh/authorized_keys and ~/.ssh/known_hosts are configured correctly"
+  echo "Using 'SSH' replication type"
+  echo "Make sure ~/.ssh/authorized_keys and ~/.ssh/known_hosts are configured correctly"
 fi
 
 if [ "$HTTPS_ENABLED" = "true" ];then
-	export HTTP_PROTOCOL="https"
-	export GERRIT_CANONICAL_WEB_URL="$HTTP_PROTOCOL://$GERRIT_CANONICAL_HOSTNAME/"
-	export HTTPS_BIND="bind *:443 ssl crt $HA_PROXY_CONFIG_DIR/certificates/GerritLocalKey.pem"
-	HTTPS_CLONE_MSG="Using self-signed certificates, to clone via https - 'git config --global http.sslVerify false'"
+  export HTTP_PROTOCOL="https"
+  export GERRIT_CANONICAL_WEB_URL="$HTTP_PROTOCOL://$GERRIT_CANONICAL_HOSTNAME/"
+  export HTTPS_BIND="bind *:443 ssl crt $HA_PROXY_CONFIG_DIR/certificates/GerritLocalKey.pem"
+  HTTPS_CLONE_MSG="Using self-signed certificates, to clone via https - 'git config --global http.sslVerify false'"
 else
-	export HTTP_PROTOCOL="http"
-	export GERRIT_CANONICAL_WEB_URL="$HTTP_PROTOCOL://$GERRIT_CANONICAL_HOSTNAME:$GERRIT_CANONICAL_PORT/"
+  export HTTP_PROTOCOL="http"
+  export GERRIT_CANONICAL_WEB_URL="$HTTP_PROTOCOL://$GERRIT_CANONICAL_HOSTNAME:$GERRIT_CANONICAL_PORT/"
 fi
 
 # New installation
 if [ $NEW_INSTALLATION = "true" ]; then
 
-	cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
+  cleanup_environment $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $COMMON_LOCATION
 
-	echo "Setting up directories"
-	mkdir -p $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $HA_PROXY_CERTIFICATES_DIR $FAKE_NFS
-	java -jar $DEPLOYMENT_LOCATION/gerrit.war init --batch --no-auto-start --install-all-plugins --dev -d $LOCATION_TEST_SITE_1
+  echo "Setting up directories"
+  mkdir -p $LOCATION_TEST_SITE_1 $LOCATION_TEST_SITE_2 $HA_PROXY_CERTIFICATES_DIR $FAKE_NFS
+  java -jar $DEPLOYMENT_LOCATION/gerrit.war init --batch --no-auto-start --install-all-plugins --dev -d $LOCATION_TEST_SITE_1
 
-	# Deploying TLS certificates
-	if [ "$HTTPS_ENABLED" = "true" ];then deploy_tls_certificates;fi
+  # Deploying TLS certificates
+  if [ "$HTTPS_ENABLED" = "true" ];then deploy_tls_certificates;fi
 
-	echo "Copy multi-site library to lib directory"
-	cp -f $DEPLOYMENT_LOCATION/multi-site.jar $LOCATION_TEST_SITE_1/lib/multi-site.jar
+  echo "Copy multi-site library to lib directory"
+  cp -f $DEPLOYMENT_LOCATION/multi-site.jar $LOCATION_TEST_SITE_1/lib/multi-site.jar
 
-	echo "Copy websession-flatfile plugin"
-	cp -f $DEPLOYMENT_LOCATION/websession-flatfile.jar $LOCATION_TEST_SITE_1/plugins/websession-flatfile.jar
+  echo "Copy websession-broker plugin"
+  cp -f $DEPLOYMENT_LOCATION/websession-broker.jar $LOCATION_TEST_SITE_1/plugins/websession-broker.jar
 
-	echo "Copy healthcheck plugin"
-	cp -f $DEPLOYMENT_LOCATION/healthcheck.jar $LOCATION_TEST_SITE_1/plugins/healthcheck.jar
+  echo "Copy healthcheck plugin"
+  cp -f $DEPLOYMENT_LOCATION/healthcheck.jar $LOCATION_TEST_SITE_1/plugins/healthcheck.jar
 
-	echo "Re-indexing"
-	java -jar $DEPLOYMENT_LOCATION/gerrit.war reindex -d $LOCATION_TEST_SITE_1
-	# Replicating environment
-	echo "Replicating environment"
-	cp -fR $LOCATION_TEST_SITE_1/* $LOCATION_TEST_SITE_2
+  echo "Copy zookeeper plugin"
+  cp -f $DEPLOYMENT_LOCATION/zookeeper-refdb.jar $LOCATION_TEST_SITE_1/plugins/zookeeper-refdb.jar
 
-	echo "Link replication plugin"
-	ln -s $LOCATION_TEST_SITE_1/plugins/replication.jar $LOCATION_TEST_SITE_1/lib/replication.jar
-	ln -s $LOCATION_TEST_SITE_2/plugins/replication.jar $LOCATION_TEST_SITE_2/lib/replication.jar
+  echo "Copy events broker library"
+  cp -f $DEPLOYMENT_LOCATION/events-broker.jar $LOCATION_TEST_SITE_1/lib/events-broker.jar
 
-	echo "Link multi-site library to plugin directory"
-	ln -s $LOCATION_TEST_SITE_1/lib/multi-site.jar $LOCATION_TEST_SITE_1/plugins/multi-site.jar
-	ln -s $LOCATION_TEST_SITE_2/lib/multi-site.jar $LOCATION_TEST_SITE_2/plugins/multi-site.jar
+  echo "Copy kafka events plugin"
+  cp -f $DEPLOYMENT_LOCATION/kafka-events.jar $LOCATION_TEST_SITE_1/plugins/kafka-events.jar
+
+  echo "Copy metrics-reporter-prometheus plugin"
+  cp -f $DEPLOYMENT_LOCATION/metrics-reporter-prometheus.jar $LOCATION_TEST_SITE_1/plugins/metrics-reporter-prometheus.jar
+
+  echo "Re-indexing"
+  java -jar $DEPLOYMENT_LOCATION/gerrit.war reindex -d $LOCATION_TEST_SITE_1
+  # Replicating environment
+  echo "Replicating environment"
+  cp -fR $LOCATION_TEST_SITE_1/* $LOCATION_TEST_SITE_2
+
+  echo "Link replication plugin"
+  ln -s $LOCATION_TEST_SITE_1/plugins/replication.jar $LOCATION_TEST_SITE_1/lib/replication.jar
+  ln -s $LOCATION_TEST_SITE_2/plugins/replication.jar $LOCATION_TEST_SITE_2/lib/replication.jar
+
+  echo "Link multi-site library to plugin directory"
+  ln -s $LOCATION_TEST_SITE_1/lib/multi-site.jar $LOCATION_TEST_SITE_1/plugins/multi-site.jar
+  ln -s $LOCATION_TEST_SITE_2/lib/multi-site.jar $LOCATION_TEST_SITE_2/plugins/multi-site.jar
 fi
 
+DOCKER_HOST_ENV=$(docker_host_env)
+echo "Docker host environment: $DOCKER_HOST_ENV"
+if [ "$DOCKER_HOST_ENV" = "mac" ];then
+  export GERRIT_SITE_HOST="host.docker.internal"
+  export NETWORK_MODE="bridge"
+else
+  export GERRIT_SITE_HOST="localhost"
+  export NETWORK_MODE="host"
+fi
+
+cat $SCRIPT_DIR/configs/prometheus.yml | envsubst > $COMMON_LOCATION/prometheus.yml
 
 IS_KAFKA_RUNNING=$(check_if_kafka_is_running)
 if [ $IS_KAFKA_RUNNING -lt 1 ];then
 
-	echo "Starting zk and kafka"
-	docker-compose -f $SCRIPT_DIR/docker-compose.kafka-broker.yaml up -d
-	echo "Waiting for kafka to start..."
-	while [[ $(check_if_kafka_is_running) -lt 1 ]];do sleep 10s; done
+  echo "Starting zk and kafka"
+  docker-compose -f $SCRIPT_DIR/docker-compose.yaml up -d
+  echo "Waiting for kafka to start..."
+  while [[ $(check_if_kafka_is_running) -lt 1 ]];do sleep 10s; done
 fi
 
 echo "Re-deploying configuration files"
@@ -398,8 +463,8 @@
 
 
 if [[ $(ps -ax | grep haproxy | grep "gerrit_setup/ha-proxy-config" | awk '{print $1}' | wc -l) -lt 1 ]];then
-	echo "Starting haproxy"
-	start_ha_proxy
+  echo "Starting haproxy"
+  start_ha_proxy
 fi
 
 echo "==============================="
@@ -415,6 +480,7 @@
 echo "GERRIT HA-PROXY: $GERRIT_CANONICAL_WEB_URL"
 echo "GERRIT-1: http://$GERRIT_1_HOSTNAME:$GERRIT_1_HTTPD_PORT"
 echo "GERRIT-2: http://$GERRIT_2_HOSTNAME:$GERRIT_2_HTTPD_PORT"
+echo "Prometheus: http://localhost:9090"
 echo
 echo "Site-1: $LOCATION_TEST_SITE_1"
 echo "Site-2: $LOCATION_TEST_SITE_2"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
index ca01471..ebe28d4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Configuration.java
@@ -18,6 +18,7 @@
 import static com.google.common.base.Suppliers.ofInstance;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Multimap;
@@ -49,7 +50,6 @@
   static final String INSTANCE_ID_FILE = "instanceId.data";
   static final String THREAD_POOL_SIZE_KEY = "threadPoolSize";
   static final int DEFAULT_THREAD_POOL_SIZE = 4;
-  static final String ENABLE_KEY = "enabled";
 
   private static final String REPLICATION_CONFIG = "replication.config";
   // common parameters to cache and index sections
@@ -63,6 +63,7 @@
   private final Supplier<Index> index;
   private final Supplier<SharedRefDatabase> sharedRefDb;
   private final Supplier<Collection<Message>> replicationConfigValidation;
+  private final Supplier<Broker> broker;
   private final Config multiSiteConfig;
 
   @Inject
@@ -79,6 +80,7 @@
     event = memoize(() -> new Event(lazyMultiSiteCfg));
     index = memoize(() -> new Index(lazyMultiSiteCfg));
     sharedRefDb = memoize(() -> new SharedRefDatabase(lazyMultiSiteCfg));
+    broker = memoize(() -> new Broker(lazyMultiSiteCfg));
   }
 
   public Config getMultiSiteConfig() {
@@ -101,6 +103,10 @@
     return index.get();
   }
 
+  public Broker broker() {
+    return broker.get();
+  }
+
   public Collection<Message> validate() {
     return replicationConfigValidation.get();
   }
@@ -162,6 +168,7 @@
 
   public static class SharedRefDatabase {
     public static final String SECTION = "ref-database";
+    public static final String ENABLE_KEY = "enabled";
     public static final String SUBSECTION_ENFORCEMENT_RULES = "enforcementRules";
 
     private final boolean enabled;
@@ -278,6 +285,19 @@
     }
   }
 
+  public static class Broker {
+    static final String BROKER_SECTION = "broker";
+    private final Config cfg;
+
+    Broker(Supplier<Config> cfgSupplier) {
+      cfg = cfgSupplier.get();
+    }
+
+    public String getTopic(String topicKey, String defValue) {
+      return MoreObjects.firstNonNull(cfg.getString(BROKER_SECTION, null, topicKey), defValue);
+    }
+  }
+
   static boolean getBoolean(
       Supplier<Config> cfg, String section, String subsection, String name, boolean defaultValue) {
     try {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
similarity index 64%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java
rename to src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
index ff932da..4f7205d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/GitModule.java
@@ -14,21 +14,22 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.validation.ValidationModule;
 
-@Singleton
-public class GerritNoteDbStatus implements NoteDbStatus {
-  private final NotesMigration notesMigration;
+public class GitModule extends AbstractModule {
+  private final Configuration config;
 
   @Inject
-  public GerritNoteDbStatus(NotesMigration notesMigration) {
-    this.notesMigration = notesMigration;
+  public GitModule(Configuration config) {
+    this.config = config;
   }
 
   @Override
-  public boolean enabled() {
-    return notesMigration.commitChangeWrites();
+  protected void configure() {
+    if (config.getSharedRefDb().isEnabled()) {
+      install(new ValidationModule(config));
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jMessageLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jMessageLogger.java
index db7dd63..7c88655 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jMessageLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jMessageLogger.java
@@ -14,12 +14,14 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.eventbroker.EventGsonProvider;
+import com.gerritforge.gerrit.eventbroker.EventMessage;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.server.util.PluginLogFile;
 import com.google.gerrit.server.util.SystemLog;
+import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
 import org.apache.log4j.PatternLayout;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,15 +30,18 @@
 public class Log4jMessageLogger extends PluginLogFile implements MessageLogger {
   private static final String LOG_NAME = "message_log";
   private final Logger msgLog;
+  private final Gson gson;
 
   @Inject
-  public Log4jMessageLogger(SystemLog systemLog, ServerInformation serverInfo) {
+  public Log4jMessageLogger(
+      SystemLog systemLog, ServerInformation serverInfo, EventGsonProvider gsonProvider) {
     super(systemLog, serverInfo, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
-    msgLog = LoggerFactory.getLogger(LOG_NAME);
+    this.msgLog = LoggerFactory.getLogger(LOG_NAME);
+    this.gson = gsonProvider.get();
   }
 
   @Override
-  public void log(Direction direction, SourceAwareEventWrapper event) {
-    msgLog.info("{} Header[{}] Body[{}]", direction, event.getHeader(), event.getBody());
+  public void log(Direction direction, String topic, EventMessage event) {
+    msgLog.info("{} {} {}", direction, topic, gson.toJson(event));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java
new file mode 100644
index 0000000..7e38e06
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jProjectVersionLogger.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite;
+
+import com.google.gerrit.reviewdb.client.Project.NameKey;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import org.apache.log4j.PatternLayout;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class Log4jProjectVersionLogger extends LibModuleLogFile implements ProjectVersionLogger {
+  private static final String LOG_NAME = "project_version_log";
+  private final Logger verLog;
+
+  @Inject
+  public Log4jProjectVersionLogger(SystemLog systemLog) {
+    super(systemLog, LOG_NAME, new PatternLayout("[%d{ISO8601}] [%t] %-5p : %m%n"));
+    this.verLog = LoggerFactory.getLogger(LOG_NAME);
+  }
+
+  @Override
+  public void log(NameKey projectName, long currentVersion, long replicationLag) {
+    if (replicationLag > 0) {
+      verLog.warn(
+          "{ \"project\":\"{}\", \"version\":{}, \"lag\":{} }",
+          projectName,
+          currentVersion,
+          replicationLag);
+    } else {
+      verLog.info("{ \"project\":\"{}\", \"version\":{} }", projectName, currentVersion);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
index 8830e19..95202b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Log4jSharedRefLogger.java
@@ -20,9 +20,9 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.common.GitPerson;
+import com.google.gerrit.json.OutputFormat;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CommonConverters;
-import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.gson.Gson;
@@ -101,6 +101,19 @@
   }
 
   @Override
+  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {
+    if (newRefValue != null) {
+      sharedRefDBLog.info(
+          gson.toJson(
+              new SharedRefLogEntry.UpdateRef(
+                  project, refName, safeToString(currRef), safeToString(newRefValue), null, null)));
+    } else {
+      sharedRefDBLog.info(
+          gson.toJson(new SharedRefLogEntry.DeleteRef(project, refName, safeToString(currRef))));
+    }
+  }
+
+  @Override
   public void logProjectDelete(String project) {
     sharedRefDBLog.info(gson.toJson(new SharedRefLogEntry.DeleteProject(project)));
   }
@@ -119,4 +132,11 @@
   public void setLogger(Logger logger) {
     this.sharedRefDBLog = logger;
   }
+
+  private <T> String safeToString(T currRef) {
+    if (currRef == null) {
+      return "<null>";
+    }
+    return currRef.toString();
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/MessageLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/MessageLogger.java
index 8b07115..b1f3e79 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/MessageLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/MessageLogger.java
@@ -14,7 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
+import com.gerritforge.gerrit.eventbroker.EventMessage;
 
 public interface MessageLogger {
 
@@ -23,5 +23,5 @@
     CONSUME;
   }
 
-  public void log(Direction direction, SourceAwareEventWrapper event);
+  public void log(Direction direction, String topic, EventMessage event);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
index 5f4f635..708e707 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/Module.java
@@ -14,25 +14,22 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.google.common.annotations.VisibleForTesting;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gson.Gson;
 import com.google.inject.CreationException;
 import com.google.inject.Inject;
 import com.google.inject.Provides;
-import com.google.inject.ProvisionException;
+import com.google.inject.Scopes;
 import com.google.inject.Singleton;
 import com.google.inject.spi.Message;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerModule;
-import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
 import com.googlesource.gerrit.plugins.multisite.cache.CacheModule;
 import com.googlesource.gerrit.plugins.multisite.event.EventModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwarderModule;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.RouterModule;
 import com.googlesource.gerrit.plugins.multisite.index.IndexModule;
-import com.googlesource.gerrit.plugins.multisite.validation.ValidationModule;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
@@ -47,44 +44,26 @@
 public class Module extends LifecycleModule {
   private static final Logger log = LoggerFactory.getLogger(Module.class);
   private Configuration config;
-  private NoteDbStatus noteDb;
-  private BrokerModule brokerModule;
-  private final boolean disableGitRepositoryValidation;
 
   @Inject
-  public Module(Configuration config, NoteDbStatus noteDb, BrokerModule brokerModule) {
-    this(config, noteDb, brokerModule, false);
-  }
-
-  // TODO: It is not possible to properly test the libModules in Gerrit.
-  // Disable the Git repository validation during integration test and then build the necessary
-  // support
-  // in Gerrit for it.
-  @VisibleForTesting
-  public Module(
-      Configuration config,
-      NoteDbStatus noteDb,
-      BrokerModule brokerModule,
-      boolean disableGitRepositoryValidation) {
+  public Module(Configuration config) {
     this.config = config;
-    this.noteDb = noteDb;
-    this.brokerModule = brokerModule;
-    this.disableGitRepositoryValidation = disableGitRepositoryValidation;
   }
 
   @Override
   protected void configure() {
-    if (!noteDb.enabled()) {
-      throw new ProvisionException(
-          "Gerrit is still running on ReviewDb: please migrate to NoteDb "
-              + "and then reload the multi-site plugin.");
-    }
 
     Collection<Message> validationErrors = config.validate();
     if (!validationErrors.isEmpty()) {
       throw new CreationException(validationErrors);
     }
 
+    DynamicItem.itemOf(binder(), GlobalRefDatabase.class);
+    DynamicItem.bind(binder(), GlobalRefDatabase.class)
+        .to(NoopSharedRefDatabase.class)
+        .in(Scopes.SINGLETON);
+    log.info("Shared ref-db engine: none");
+
     listener().to(Log4jMessageLogger.class);
     bind(MessageLogger.class).to(Log4jMessageLogger.class);
 
@@ -100,18 +79,7 @@
       install(new IndexModule());
     }
 
-    install(brokerModule);
-
     install(new RouterModule());
-
-    install(
-        new ValidationModule(
-            config, disableGitRepositoryValidation || !config.getSharedRefDb().isEnabled()));
-
-    bind(Gson.class)
-        .annotatedWith(BrokerGson.class)
-        .toProvider(GsonProvider.class)
-        .in(Singleton.class);
   }
 
   @Provides
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/NoteDbStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/NoteDbStatus.java
deleted file mode 100644
index f47e503..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/NoteDbStatus.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite;
-
-import com.google.inject.ImplementedBy;
-
-/** Returns the status of changes migration. */
-@ImplementedBy(GerritNoteDbStatus.class)
-public interface NoteDbStatus {
-
-  /**
-   * Status of NoteDb migration.
-   *
-   * @return true if Gerrit has been migrated to NoteDb
-   */
-  boolean enabled();
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
index d445251..2eaa5d9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2019 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.
@@ -14,43 +14,37 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.Inject;
 import com.google.inject.Scopes;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
-import com.googlesource.gerrit.plugins.multisite.kafka.KafkaBrokerApi;
-import com.googlesource.gerrit.plugins.multisite.kafka.KafkaBrokerModule;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkValidationModule;
+import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
+import com.googlesource.gerrit.plugins.multisite.consumer.MultiSiteConsumerRunner;
+import com.googlesource.gerrit.plugins.multisite.consumer.SubscriberModule;
+import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerForwarderModule;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
 
 public class PluginModule extends LifecycleModule {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private Configuration config;
-  private ZkValidationModule zkValidationModule;
-  private KafkaBrokerModule kafkaBrokerModule;
 
   @Inject
-  public PluginModule(
-      Configuration config,
-      ZkValidationModule zkValidationModule,
-      KafkaBrokerModule kafkaBrokerModule) {
+  public PluginModule(Configuration config) {
     this.config = config;
-    this.zkValidationModule = zkValidationModule;
-    this.kafkaBrokerModule = kafkaBrokerModule;
   }
 
   @Override
   protected void configure() {
+    bind(BrokerApiWrapper.class).in(Scopes.SINGLETON);
+    install(new SubscriberModule());
+
+    install(new BrokerForwarderModule());
+    listener().to(MultiSiteConsumerRunner.class);
+
     if (config.getSharedRefDb().isEnabled()) {
-      logger.atInfo().log("Shared ref-db engine: Zookeeper");
-      install(zkValidationModule);
+      listener().to(PluginStartup.class);
+      DynamicSet.bind(binder(), ProjectDeletedListener.class)
+          .to(ProjectDeletedSharedDbCleanup.class);
     }
-
-    DynamicItem.bind(binder(), BrokerApi.class).to(KafkaBrokerApi.class).in(Scopes.SINGLETON);
-    listener().to(KafkaBrokerApi.class);
-
-    install(kafkaBrokerModule);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
similarity index 60%
copy from src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java
copy to src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
index ff932da..33b54d2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/GerritNoteDbStatus.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/PluginStartup.java
@@ -14,21 +14,25 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
-import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
-import com.google.inject.Singleton;
+import com.google.inject.Injector;
 
-@Singleton
-public class GerritNoteDbStatus implements NoteDbStatus {
-  private final NotesMigration notesMigration;
+public class PluginStartup implements LifecycleListener {
+  private SharedRefDatabaseWrapper sharedRefDb;
+  private Injector injector;
 
   @Inject
-  public GerritNoteDbStatus(NotesMigration notesMigration) {
-    this.notesMigration = notesMigration;
+  public PluginStartup(SharedRefDatabaseWrapper sharedRefDb, Injector injector) {
+    this.sharedRefDb = sharedRefDb;
+    this.injector = injector;
   }
 
   @Override
-  public boolean enabled() {
-    return notesMigration.commitChangeWrites();
+  public void start() {
+    injector.injectMembers(sharedRefDb);
   }
+
+  @Override
+  public void stop() {}
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerSession.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java
similarity index 65%
rename from src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerSession.java
rename to src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java
index b04fbff..0ae1cc2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerSession.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/ProjectVersionLogger.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// 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.
@@ -12,15 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.broker;
+package com.googlesource.gerrit.plugins.multisite;
 
-public interface BrokerSession {
+import com.google.gerrit.reviewdb.client.Project;
 
-  boolean isOpen();
+public interface ProjectVersionLogger {
 
-  void connect();
-
-  void disconnect();
-
-  boolean publish(String topic, String payload);
+  public void log(Project.NameKey projectName, long currentVersion, long replicationLag);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
index e6ebc08..0ea78a9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefDatabaseWrapper.java
@@ -14,60 +14,87 @@
 
 package com.googlesource.gerrit.plugins.multisite;
 
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import java.io.IOException;
+import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 
-public class SharedRefDatabaseWrapper implements SharedRefDatabase {
+public class SharedRefDatabaseWrapper implements GlobalRefDatabase {
 
-  private final DynamicItem<SharedRefDatabase> sharedRefDbDynamicItem;
+  @Inject(optional = true)
+  private DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem;
+
   private final SharedRefLogger sharedRefLogger;
 
   @Inject
-  public SharedRefDatabaseWrapper(
-      DynamicItem<SharedRefDatabase> sharedRefDbDynamicItem, SharedRefLogger sharedRefLogger) {
-    this.sharedRefDbDynamicItem = sharedRefDbDynamicItem;
+  public SharedRefDatabaseWrapper(SharedRefLogger sharedRefLogger) {
     this.sharedRefLogger = sharedRefLogger;
   }
 
+  @VisibleForTesting
+  public SharedRefDatabaseWrapper(
+      DynamicItem<GlobalRefDatabase> sharedRefDbDynamicItem, SharedRefLogger sharedRefLogger) {
+    this.sharedRefLogger = sharedRefLogger;
+    this.sharedRefDbDynamicItem = sharedRefDbDynamicItem;
+  }
+
   @Override
-  public boolean isUpToDate(String project, Ref ref) throws SharedLockException {
+  public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
     return sharedRefDb().isUpToDate(project, ref);
   }
 
   @Override
-  public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue)
-      throws IOException {
+  public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
+      throws GlobalRefDbSystemError {
     boolean succeeded = sharedRefDb().compareAndPut(project, currRef, newRefValue);
     if (succeeded) {
-      sharedRefLogger.logRefUpdate(project, currRef, newRefValue);
+      sharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
     }
     return succeeded;
   }
 
   @Override
-  public AutoCloseable lockRef(String project, String refName) throws SharedLockException {
+  public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
+      throws GlobalRefDbSystemError {
+    boolean succeeded = sharedRefDb().compareAndPut(project, refName, currValue, newValue);
+    if (succeeded) {
+      sharedRefLogger.logRefUpdate(project.get(), refName, currValue, newValue);
+    }
+    return succeeded;
+  }
+
+  @Override
+  public AutoCloseable lockRef(Project.NameKey project, String refName)
+      throws GlobalRefDbLockException {
     AutoCloseable locker = sharedRefDb().lockRef(project, refName);
-    sharedRefLogger.logLockAcquisition(project, refName);
+    sharedRefLogger.logLockAcquisition(project.get(), refName);
     return locker;
   }
 
   @Override
-  public boolean exists(String project, String refName) {
+  public boolean exists(Project.NameKey project, String refName) {
     return sharedRefDb().exists(project, refName);
   }
 
   @Override
-  public void removeProject(String project) throws IOException {
-    sharedRefDb().removeProject(project);
-    sharedRefLogger.logProjectDelete(project);
+  public void remove(Project.NameKey project) throws GlobalRefDbSystemError {
+    sharedRefDb().remove(project);
+    sharedRefLogger.logProjectDelete(project.get());
   }
 
-  private SharedRefDatabase sharedRefDb() {
+  @Override
+  public <T> Optional<T> get(Project.NameKey nameKey, String s, Class<T> clazz)
+      throws GlobalRefDbSystemError {
+    return sharedRefDb().get(nameKey, s, clazz);
+  }
+
+  private GlobalRefDatabase sharedRefDb() {
     return sharedRefDbDynamicItem.get();
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
index 51f9ff0..3853af6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/SharedRefLogger.java
@@ -21,6 +21,8 @@
 
   void logRefUpdate(String project, Ref currRef, ObjectId newRefValue);
 
+  <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue);
+
   void logProjectDelete(String project);
 
   void logLockAcquisition(String project, String refName);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/ZookeeperConfig.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/ZookeeperConfig.java
deleted file mode 100644
index 35471fa..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/ZookeeperConfig.java
+++ /dev/null
@@ -1,247 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Suppliers.memoize;
-import static com.google.common.base.Suppliers.ofInstance;
-
-import com.google.common.base.Strings;
-import com.google.common.base.Supplier;
-import com.google.gerrit.server.config.SitePaths;
-import java.io.IOException;
-import org.apache.commons.lang.StringUtils;
-import org.apache.curator.RetryPolicy;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.CuratorFrameworkFactory;
-import org.apache.curator.retry.BoundedExponentialBackoffRetry;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ZookeeperConfig {
-  private static final Logger log = LoggerFactory.getLogger(ZookeeperConfig.class);
-  public static final int defaultSessionTimeoutMs;
-  public static final int defaultConnectionTimeoutMs;
-  public static final String DEFAULT_ZK_CONNECT = "localhost:2181";
-  private final int DEFAULT_RETRY_POLICY_BASE_SLEEP_TIME_MS = 1000;
-  private final int DEFAULT_RETRY_POLICY_MAX_SLEEP_TIME_MS = 3000;
-  private final int DEFAULT_RETRY_POLICY_MAX_RETRIES = 3;
-  private final int DEFAULT_CAS_RETRY_POLICY_BASE_SLEEP_TIME_MS = 100;
-  private final int DEFAULT_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS = 300;
-  private final int DEFAULT_CAS_RETRY_POLICY_MAX_RETRIES = 3;
-  private final int DEFAULT_TRANSACTION_LOCK_TIMEOUT = 1000;
-
-  static {
-    CuratorFrameworkFactory.Builder b = CuratorFrameworkFactory.builder();
-    defaultSessionTimeoutMs = b.getSessionTimeoutMs();
-    defaultConnectionTimeoutMs = b.getConnectionTimeoutMs();
-  }
-
-  public static final String SUBSECTION = "zookeeper";
-  public static final String KEY_CONNECT_STRING = "connectString";
-  public static final String KEY_SESSION_TIMEOUT_MS = "sessionTimeoutMs";
-  public static final String KEY_CONNECTION_TIMEOUT_MS = "connectionTimeoutMs";
-  public static final String KEY_RETRY_POLICY_BASE_SLEEP_TIME_MS = "retryPolicyBaseSleepTimeMs";
-  public static final String KEY_RETRY_POLICY_MAX_SLEEP_TIME_MS = "retryPolicyMaxSleepTimeMs";
-  public static final String KEY_RETRY_POLICY_MAX_RETRIES = "retryPolicyMaxRetries";
-  public static final String KEY_ROOT_NODE = "rootNode";
-  public final String KEY_CAS_RETRY_POLICY_BASE_SLEEP_TIME_MS = "casRetryPolicyBaseSleepTimeMs";
-  public final String KEY_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS = "casRetryPolicyMaxSleepTimeMs";
-  public final String KEY_CAS_RETRY_POLICY_MAX_RETRIES = "casRetryPolicyMaxRetries";
-  public final String TRANSACTION_LOCK_TIMEOUT_KEY = "transactionLockTimeoutMs";
-
-  private final String connectionString;
-  private final String root;
-  private final int sessionTimeoutMs;
-  private final int connectionTimeoutMs;
-  private final int baseSleepTimeMs;
-  private final int maxSleepTimeMs;
-  private final int maxRetries;
-  private final int casBaseSleepTimeMs;
-  private final int casMaxSleepTimeMs;
-  private final int casMaxRetries;
-
-  public static final String SECTION = "ref-database";
-  private final Long transactionLockTimeOut;
-
-  private CuratorFramework build;
-
-  public ZookeeperConfig(Config zkCfg) {
-    Supplier<Config> lazyZkConfig = lazyLoad(zkCfg);
-    connectionString =
-        getString(lazyZkConfig, SECTION, SUBSECTION, KEY_CONNECT_STRING, DEFAULT_ZK_CONNECT);
-    root = getString(lazyZkConfig, SECTION, SUBSECTION, KEY_ROOT_NODE, "gerrit/multi-site");
-    sessionTimeoutMs =
-        getInt(lazyZkConfig, SECTION, SUBSECTION, KEY_SESSION_TIMEOUT_MS, defaultSessionTimeoutMs);
-    connectionTimeoutMs =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_CONNECTION_TIMEOUT_MS,
-            defaultConnectionTimeoutMs);
-
-    baseSleepTimeMs =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_RETRY_POLICY_BASE_SLEEP_TIME_MS,
-            DEFAULT_RETRY_POLICY_BASE_SLEEP_TIME_MS);
-
-    maxSleepTimeMs =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_RETRY_POLICY_MAX_SLEEP_TIME_MS,
-            DEFAULT_RETRY_POLICY_MAX_SLEEP_TIME_MS);
-
-    maxRetries =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_RETRY_POLICY_MAX_RETRIES,
-            DEFAULT_RETRY_POLICY_MAX_RETRIES);
-
-    casBaseSleepTimeMs =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_CAS_RETRY_POLICY_BASE_SLEEP_TIME_MS,
-            DEFAULT_CAS_RETRY_POLICY_BASE_SLEEP_TIME_MS);
-
-    casMaxSleepTimeMs =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS,
-            DEFAULT_CAS_RETRY_POLICY_MAX_SLEEP_TIME_MS);
-
-    casMaxRetries =
-        getInt(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            KEY_CAS_RETRY_POLICY_MAX_RETRIES,
-            DEFAULT_CAS_RETRY_POLICY_MAX_RETRIES);
-
-    transactionLockTimeOut =
-        getLong(
-            lazyZkConfig,
-            SECTION,
-            SUBSECTION,
-            TRANSACTION_LOCK_TIMEOUT_KEY,
-            DEFAULT_TRANSACTION_LOCK_TIMEOUT);
-
-    checkArgument(StringUtils.isNotEmpty(connectionString), "zookeeper.%s contains no servers");
-  }
-
-  public CuratorFramework buildCurator() {
-    if (build == null) {
-      this.build =
-          CuratorFrameworkFactory.builder()
-              .connectString(connectionString)
-              .sessionTimeoutMs(sessionTimeoutMs)
-              .connectionTimeoutMs(connectionTimeoutMs)
-              .retryPolicy(
-                  new BoundedExponentialBackoffRetry(baseSleepTimeMs, maxSleepTimeMs, maxRetries))
-              .namespace(root)
-              .build();
-      this.build.start();
-    }
-
-    return this.build;
-  }
-
-  public Long getZkInterProcessLockTimeOut() {
-    return transactionLockTimeOut;
-  }
-
-  public RetryPolicy buildCasRetryPolicy() {
-    return new BoundedExponentialBackoffRetry(casBaseSleepTimeMs, casMaxSleepTimeMs, casMaxRetries);
-  }
-
-  private static FileBasedConfig getConfigFile(SitePaths sitePaths, String configFileName) {
-    return new FileBasedConfig(sitePaths.etc_dir.resolve(configFileName).toFile(), FS.DETECTED);
-  }
-
-  private long getLong(
-      Supplier<Config> cfg, String section, String subSection, String name, long defaultValue) {
-    try {
-      return cfg.get().getLong(section, subSection, name, defaultValue);
-    } catch (IllegalArgumentException e) {
-      log.error("invalid value for {}; using default value {}", name, defaultValue);
-      log.debug("Failed to retrieve long value: {}", e.getMessage(), e);
-      return defaultValue;
-    }
-  }
-
-  private int getInt(
-      Supplier<Config> cfg, String section, String subSection, String name, int defaultValue) {
-    try {
-      return cfg.get().getInt(section, subSection, name, defaultValue);
-    } catch (IllegalArgumentException e) {
-      log.error("invalid value for {}; using default value {}", name, defaultValue);
-      log.debug("Failed to retrieve integer value: {}", e.getMessage(), e);
-      return defaultValue;
-    }
-  }
-
-  private Supplier<Config> lazyLoad(Config config) {
-    if (config instanceof FileBasedConfig) {
-      return memoize(
-          () -> {
-            FileBasedConfig fileConfig = (FileBasedConfig) config;
-            String fileConfigFileName = fileConfig.getFile().getPath();
-            try {
-              log.info("Loading configuration from {}", fileConfigFileName);
-              fileConfig.load();
-            } catch (IOException | ConfigInvalidException e) {
-              log.error("Unable to load configuration from " + fileConfigFileName, e);
-            }
-            return fileConfig;
-          });
-    }
-    return ofInstance(config);
-  }
-
-  private boolean getBoolean(
-      Supplier<Config> cfg, String section, String subsection, String name, boolean defaultValue) {
-    try {
-      return cfg.get().getBoolean(section, subsection, name, defaultValue);
-    } catch (IllegalArgumentException e) {
-      log.error("invalid value for {}; using default value {}", name, defaultValue);
-      log.debug("Failed to retrieve boolean value: {}", e.getMessage(), e);
-      return defaultValue;
-    }
-  }
-
-  private String getString(
-      Supplier<Config> cfg, String section, String subsection, String name, String defaultValue) {
-    String value = cfg.get().getString(section, subsection, name);
-    if (!Strings.isNullOrEmpty(value)) {
-      return value;
-    }
-    return defaultValue;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApi.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApi.java
deleted file mode 100644
index 35350e9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApi.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker;
-
-import com.google.gerrit.server.events.Event;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import java.util.function.Consumer;
-
-/** API for sending/receiving events through a message Broker. */
-public interface BrokerApi {
-
-  /**
-   * Send an event to a topic.
-   *
-   * @param topic
-   * @param event
-   * @return true if the event was successfully sent. False otherwise.
-   */
-  boolean send(String topic, Event event);
-
-  /**
-   * Receive asynchronously events from a topic.
-   *
-   * @param topic
-   * @param eventConsumer
-   */
-  void receiveAync(String topic, Consumer<SourceAwareEventWrapper> eventConsumer);
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiNoOp.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiNoOp.java
deleted file mode 100644
index 19cf0f7..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiNoOp.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker;
-
-import com.google.gerrit.server.events.Event;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import java.util.function.Consumer;
-
-public class BrokerApiNoOp implements BrokerApi {
-
-  @Override
-  public boolean send(String topic, Event event) {
-    return true;
-  }
-
-  @Override
-  public void receiveAync(String topic, Consumer<SourceAwareEventWrapper> eventConsumer) {}
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapper.java
index e83fe53..71be5e6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapper.java
@@ -14,29 +14,53 @@
 
 package com.googlesource.gerrit.plugins.multisite.broker;
 
+import com.gerritforge.gerrit.eventbroker.BrokerApi;
+import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.gerritforge.gerrit.eventbroker.TopicSubscriber;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.Event;
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
+import com.googlesource.gerrit.plugins.multisite.InstanceId;
+import com.googlesource.gerrit.plugins.multisite.MessageLogger;
+import com.googlesource.gerrit.plugins.multisite.MessageLogger.Direction;
+import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import java.util.Set;
+import java.util.UUID;
 import java.util.function.Consumer;
 
 public class BrokerApiWrapper implements BrokerApi {
   private final DynamicItem<BrokerApi> apiDelegate;
   private final BrokerMetrics metrics;
+  private final MessageLogger msgLog;
+  private final UUID instanceId;
 
   @Inject
-  public BrokerApiWrapper(DynamicItem<BrokerApi> apiDelegate, BrokerMetrics metrics) {
+  public BrokerApiWrapper(
+      DynamicItem<BrokerApi> apiDelegate,
+      BrokerMetrics metrics,
+      MessageLogger msgLog,
+      @InstanceId UUID instanceId) {
     this.apiDelegate = apiDelegate;
     this.metrics = metrics;
+    this.msgLog = msgLog;
+    this.instanceId = instanceId;
+  }
+
+  public boolean send(String topic, Event event) {
+    return send(topic, apiDelegate.get().newMessage(instanceId, event));
   }
 
   @Override
-  public boolean send(String topic, Event event) {
+  public boolean send(String topic, EventMessage message) {
+    if (Context.isForwardedEvent()) {
+      return true;
+    }
     boolean succeeded = false;
     try {
-      succeeded = apiDelegate.get().send(topic, event);
+      succeeded = apiDelegate.get().send(topic, message);
     } finally {
       if (succeeded) {
+        msgLog.log(Direction.PUBLISH, topic, message);
         metrics.incrementBrokerPublishedMessage();
       } else {
         metrics.incrementBrokerFailedToPublishMessage();
@@ -46,7 +70,22 @@
   }
 
   @Override
-  public void receiveAync(String topic, Consumer<SourceAwareEventWrapper> eventConsumer) {
-    apiDelegate.get().receiveAync(topic, eventConsumer);
+  public void receiveAsync(String topic, Consumer<EventMessage> messageConsumer) {
+    apiDelegate.get().receiveAsync(topic, messageConsumer);
+  }
+
+  @Override
+  public void disconnect() {
+    apiDelegate.get().disconnect();
+  }
+
+  @Override
+  public Set<TopicSubscriber> topicSubscribers() {
+    return apiDelegate.get().topicSubscribers();
+  }
+
+  @Override
+  public void replayAllEvents(String topic) {
+    apiDelegate.get().replayAllEvents(topic);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerGson.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerGson.java
deleted file mode 100644
index 219aa96..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerGson.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker;
-
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.inject.BindingAnnotation;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-@Retention(RUNTIME)
-@Target(PARAMETER)
-@BindingAnnotation
-public @interface BrokerGson {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerModule.java
deleted file mode 100644
index 6983984..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerModule.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker;
-
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.inject.AbstractModule;
-import com.google.inject.Scopes;
-import com.googlesource.gerrit.plugins.multisite.consumer.SubscriberModule;
-
-public class BrokerModule extends AbstractModule {
-
-  @Override
-  protected void configure() {
-    DynamicItem.itemOf(binder(), BrokerApi.class);
-    DynamicItem.bind(binder(), BrokerApi.class).to(BrokerApiNoOp.class).in(Scopes.SINGLETON);
-
-    bind(BrokerApiWrapper.class).in(Scopes.SINGLETON);
-
-    install(new SubscriberModule());
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java
deleted file mode 100644
index 0791e6a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/GsonProvider.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker;
-
-import com.google.common.base.Supplier;
-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;
-
-public class GsonProvider implements Provider<Gson> {
-  @Override
-  public Gson get() {
-    return new GsonBuilder()
-        .registerTypeAdapter(Event.class, new EventDeserializer())
-        .registerTypeAdapter(Supplier.class, new SupplierSerializer())
-        .registerTypeAdapter(Supplier.class, new SupplierDeserializer())
-        .create();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisher.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisher.java
deleted file mode 100644
index ba5b532..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisher.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker.kafka;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.events.Event;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.InstanceId;
-import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.MessageLogger.Direction;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerSession;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
-import java.util.UUID;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class BrokerPublisher implements LifecycleListener {
-  protected final Logger log = LoggerFactory.getLogger(getClass());
-
-  private final BrokerSession session;
-  private final Gson gson;
-  private final UUID instanceId;
-  private final MessageLogger msgLog;
-
-  @Inject
-  public BrokerPublisher(
-      BrokerSession session,
-      @BrokerGson Gson gson,
-      @InstanceId UUID instanceId,
-      MessageLogger msgLog) {
-    this.session = session;
-    this.gson = gson;
-    this.instanceId = instanceId;
-    this.msgLog = msgLog;
-  }
-
-  @Override
-  public void start() {
-    if (!session.isOpen()) {
-      session.connect();
-    }
-  }
-
-  @Override
-  public void stop() {
-    if (session.isOpen()) {
-      session.disconnect();
-    }
-  }
-
-  public boolean publish(String topic, Event event) {
-    if (Context.isForwardedEvent()) {
-      return true;
-    }
-
-    SourceAwareEventWrapper brokerEvent = toBrokerEvent(event);
-    Boolean eventPublished = session.publish(topic, getPayload(brokerEvent));
-    if (eventPublished) {
-      msgLog.log(Direction.PUBLISH, brokerEvent);
-    }
-    return eventPublished;
-  }
-
-  private String getPayload(SourceAwareEventWrapper event) {
-    return gson.toJson(event);
-  }
-
-  private SourceAwareEventWrapper toBrokerEvent(Event event) {
-    JsonObject body = eventToJson(event);
-    return new SourceAwareEventWrapper(
-        new SourceAwareEventWrapper.EventHeader(
-            UUID.randomUUID(), event.getType(), instanceId, event.eventCreatedOn),
-        body);
-  }
-
-  @VisibleForTesting
-  public JsonObject eventToJson(Event event) {
-    return gson.toJsonTree(event).getAsJsonObject();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/KafkaSession.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/KafkaSession.java
deleted file mode 100644
index 6594544..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/KafkaSession.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker.kafka;
-
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.InstanceId;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerSession;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import org.apache.kafka.clients.producer.KafkaProducer;
-import org.apache.kafka.clients.producer.Producer;
-import org.apache.kafka.clients.producer.ProducerRecord;
-import org.apache.kafka.clients.producer.RecordMetadata;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class KafkaSession implements BrokerSession {
-  private static final Logger LOGGER = LoggerFactory.getLogger(KafkaSession.class);
-  private KafkaConfiguration properties;
-  private final UUID instanceId;
-  private volatile Producer<String, String> producer;
-
-  @Inject
-  public KafkaSession(KafkaConfiguration kafkaConfig, @InstanceId UUID instanceId) {
-    this.properties = kafkaConfig;
-    this.instanceId = instanceId;
-  }
-
-  @Override
-  public boolean isOpen() {
-    if (producer != null) {
-      return true;
-    }
-    return false;
-  }
-
-  @Override
-  public void connect() {
-    if (isOpen()) {
-      LOGGER.debug("Already connected.");
-      return;
-    }
-
-    LOGGER.info("Connect to {}...", properties.getKafka().getBootstrapServers());
-    /* Need to make sure that the thread of the running connection uses
-     * the correct class loader otherwize you can endup with hard to debug
-     * ClassNotFoundExceptions
-     */
-    setConnectionClassLoader();
-    producer = new KafkaProducer<>(properties.kafkaPublisher());
-    LOGGER.info("Connection established.");
-  }
-
-  private void setConnectionClassLoader() {
-    Thread.currentThread().setContextClassLoader(KafkaSession.class.getClassLoader());
-  }
-
-  @Override
-  public void disconnect() {
-    LOGGER.info("Disconnecting...");
-    if (producer != null) {
-      LOGGER.info("Closing Producer {}...", producer);
-      producer.close();
-    }
-    producer = null;
-  }
-
-  @Override
-  public boolean publish(String topic, String payload) {
-    return publishToTopic(properties.getKafka().getTopicAlias(EventTopic.of(topic)), payload);
-  }
-
-  private boolean publishToTopic(String topic, String payload) {
-    Future<RecordMetadata> future =
-        producer.send(new ProducerRecord<>(topic, instanceId.toString(), payload));
-    try {
-      RecordMetadata metadata = future.get();
-      LOGGER.debug("The offset of the record we just sent is: {}", metadata.offset());
-      return true;
-    } catch (InterruptedException | ExecutionException e) {
-      LOGGER.error("Cannot send the message", e);
-      return false;
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
index 2308c0e..816edc9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/AbstractSubcriber.java
@@ -14,77 +14,74 @@
 
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
+import com.gerritforge.gerrit.eventbroker.EventMessage;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gson.Gson;
-import com.google.gwtorm.server.OrmException;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger.Direction;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.ForwardedEventRouter;
 import java.io.IOException;
 import java.util.UUID;
+import java.util.function.Consumer;
 
-public abstract class AbstractSubcriber implements Runnable {
+public abstract class AbstractSubcriber {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private final BrokerApi brokerApi;
   private final ForwardedEventRouter eventRouter;
   private final DynamicSet<DroppedEventListener> droppedEventListeners;
-  private final Gson gson;
   private final UUID instanceId;
   private final MessageLogger msgLog;
   private SubscriberMetrics subscriberMetrics;
+  private final Configuration cfg;
+  private final String topic;
 
   public AbstractSubcriber(
-      BrokerApiWrapper brokerApi,
       ForwardedEventRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gson,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
     this.eventRouter = eventRouter;
     this.droppedEventListeners = droppedEventListeners;
-    this.gson = gson;
     this.instanceId = instanceId;
     this.msgLog = msgLog;
     this.subscriberMetrics = subscriberMetrics;
-    this.brokerApi = brokerApi;
-  }
-
-  @Override
-  public void run() {
-    brokerApi.receiveAync(getTopic().topic(), this::processRecord);
+    this.cfg = cfg;
+    this.topic = getTopic().topic(cfg);
   }
 
   protected abstract EventTopic getTopic();
 
-  private void processRecord(SourceAwareEventWrapper event) {
+  public Consumer<EventMessage> getConsumer() {
+    return this::processRecord;
+  }
 
-    if (event.getHeader().getSourceInstanceId().equals(instanceId)) {
+  private void processRecord(EventMessage event) {
+
+    if (event.getHeader().sourceInstanceId.equals(instanceId)) {
       logger.atFiner().log(
           "Dropping event %s produced by our instanceId %s",
           event.toString(), instanceId.toString());
       droppedEventListeners.forEach(l -> l.onEventDropped(event));
     } else {
       try {
-        msgLog.log(Direction.CONSUME, event);
-        eventRouter.route(event.getEventBody(gson));
+        msgLog.log(Direction.CONSUME, topic, event);
+        eventRouter.route(event.getEvent());
         subscriberMetrics.incrementSubscriberConsumedMessage();
+        subscriberMetrics.updateReplicationStatusMetrics(event);
       } catch (IOException e) {
         logger.atSevere().withCause(e).log(
-            "Malformed event '%s': [Exception: %s]", event.getHeader().getEventType());
+            "Malformed event '%s': [Exception: %s]", event.getHeader());
         subscriberMetrics.incrementSubscriberFailedToConsumeMessage();
-      } catch (PermissionBackendException | OrmException | CacheNotFoundException e) {
+      } catch (PermissionBackendException | CacheNotFoundException e) {
         logger.atSevere().withCause(e).log(
-            "Cannot handle message %s: [Exception: %s]", event.getHeader().getEventType());
+            "Cannot handle message %s: [Exception: %s]", event.getHeader());
         subscriberMetrics.incrementSubscriberFailedToConsumeMessage();
       }
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/BatchIndexEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/BatchIndexEventSubscriber.java
index 9c430c6..5bbaee0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/BatchIndexEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/BatchIndexEventSubscriber.java
@@ -15,13 +15,11 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.IndexEventRouter;
 import java.util.UUID;
@@ -30,21 +28,13 @@
 public class BatchIndexEventSubscriber extends AbstractSubcriber {
   @Inject
   public BatchIndexEventSubscriber(
-      BrokerApiWrapper brokerApi,
       IndexEventRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gsonProvider,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
-    super(
-        brokerApi,
-        eventRouter,
-        droppedEventListeners,
-        gsonProvider,
-        instanceId,
-        msgLog,
-        subscriberMetrics);
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
+    super(eventRouter, droppedEventListeners, instanceId, msgLog, subscriberMetrics, cfg);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/CacheEvictionEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/CacheEvictionEventSubscriber.java
index 572d1a3..eae66b4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/CacheEvictionEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/CacheEvictionEventSubscriber.java
@@ -15,13 +15,11 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.CacheEvictionEventRouter;
 import java.util.UUID;
@@ -30,21 +28,14 @@
 public class CacheEvictionEventSubscriber extends AbstractSubcriber {
   @Inject
   public CacheEvictionEventSubscriber(
-      BrokerApiWrapper brokerApi,
       CacheEvictionEventRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gsonProvider,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
-    super(
-        brokerApi,
-        eventRouter,
-        droppedEventListeners,
-        gsonProvider,
-        instanceId,
-        msgLog,
-        subscriberMetrics);
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
+
+    super(eventRouter, droppedEventListeners, instanceId, msgLog, subscriberMetrics, cfg);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ConsumerExecutor.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ConsumerExecutor.java
deleted file mode 100644
index 936d07a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ConsumerExecutor.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.consumer;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.google.inject.BindingAnnotation;
-import java.lang.annotation.Retention;
-
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface ConsumerExecutor {}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/DroppedEventListener.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/DroppedEventListener.java
index 680e8ed..6f4680c 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/DroppedEventListener.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/DroppedEventListener.java
@@ -14,11 +14,13 @@
 
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
+import com.gerritforge.gerrit.eventbroker.EventMessage;
+
 public interface DroppedEventListener {
   /**
    * Invoked when any event is dropped.
    *
    * @param event information about the event.
    */
-  void onEventDropped(SourceAwareEventWrapper event);
+  void onEventDropped(EventMessage event);
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/IndexEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/IndexEventSubscriber.java
index df55040..49d470a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/IndexEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/IndexEventSubscriber.java
@@ -15,13 +15,11 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.IndexEventRouter;
 import java.util.UUID;
@@ -30,21 +28,13 @@
 public class IndexEventSubscriber extends AbstractSubcriber {
   @Inject
   public IndexEventSubscriber(
-      BrokerApiWrapper brokerApi,
       IndexEventRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gsonProvider,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
-    super(
-        brokerApi,
-        eventRouter,
-        droppedEventListeners,
-        gsonProvider,
-        instanceId,
-        msgLog,
-        subscriberMetrics);
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
+    super(eventRouter, droppedEventListeners, instanceId, msgLog, subscriberMetrics, cfg);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/MultiSiteConsumerRunner.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/MultiSiteConsumerRunner.java
index 1778961..d858b89 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/MultiSiteConsumerRunner.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/MultiSiteConsumerRunner.java
@@ -14,36 +14,41 @@
 
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
+import com.gerritforge.gerrit.eventbroker.BrokerApi;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.util.concurrent.ExecutorService;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 
 @Singleton
 public class MultiSiteConsumerRunner implements LifecycleListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final DynamicSet<AbstractSubcriber> consumers;
-  private final ExecutorService executor;
+  private DynamicItem<BrokerApi> brokerApi;
+  private Configuration cfg;
 
   @Inject
   public MultiSiteConsumerRunner(
-      @ConsumerExecutor ExecutorService executor, DynamicSet<AbstractSubcriber> consumers) {
+      DynamicItem<BrokerApi> brokerApi,
+      DynamicSet<AbstractSubcriber> consumers,
+      Configuration cfg) {
     this.consumers = consumers;
-    this.executor = executor;
+    this.brokerApi = brokerApi;
+    this.cfg = cfg;
   }
 
   @Override
   public void start() {
     logger.atInfo().log("starting consumers");
-    consumers.forEach(c -> executor.execute(c));
+    consumers.forEach(
+        consumer ->
+            brokerApi.get().receiveAsync(consumer.getTopic().topic(cfg), consumer.getConsumer()));
   }
 
   @Override
-  public void stop() {
-    logger.atInfo().log("shutting down consumers");
-    executor.shutdown();
-  }
+  public void stop() {}
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ProjectUpdateEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ProjectUpdateEventSubscriber.java
index 5c42ea6..6ff0969 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ProjectUpdateEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/ProjectUpdateEventSubscriber.java
@@ -15,13 +15,11 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.ProjectListUpdateRouter;
 import java.util.UUID;
@@ -30,15 +28,13 @@
 public class ProjectUpdateEventSubscriber extends AbstractSubcriber {
   @Inject
   public ProjectUpdateEventSubscriber(
-      BrokerApiWrapper brokerApi,
       ProjectListUpdateRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gson,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
-    super(
-        brokerApi, eventRouter, droppedEventListeners, gson, instanceId, msgLog, subscriberMetrics);
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
+    super(eventRouter, droppedEventListeners, instanceId, msgLog, subscriberMetrics, cfg);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SourceAwareEventWrapper.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SourceAwareEventWrapper.java
deleted file mode 100644
index b8bd0d8..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SourceAwareEventWrapper.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.consumer;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.gerrit.server.events.Event;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import java.util.UUID;
-
-public class SourceAwareEventWrapper {
-
-  private final EventHeader header;
-  private final JsonObject body;
-
-  public EventHeader getHeader() {
-    return header;
-  }
-
-  public JsonObject getBody() {
-    return body;
-  }
-
-  public Event getEventBody(Gson gson) {
-    return gson.fromJson(this.body, Event.class);
-  }
-
-  public static class EventHeader {
-    private final UUID eventId;
-    private final String eventType;
-    private final UUID sourceInstanceId;
-    private final Long eventCreatedOn;
-
-    public EventHeader(UUID eventId, String eventType, UUID sourceInstanceId, Long eventCreatedOn) {
-      this.eventId = eventId;
-      this.eventType = eventType;
-      this.sourceInstanceId = sourceInstanceId;
-      this.eventCreatedOn = eventCreatedOn;
-    }
-
-    public UUID getEventId() {
-      return eventId;
-    }
-
-    public String getEventType() {
-      return eventType;
-    }
-
-    public UUID getSourceInstanceId() {
-      return sourceInstanceId;
-    }
-
-    public Long getEventCreatedOn() {
-      return eventCreatedOn;
-    }
-
-    public void validate() {
-      requireNonNull(eventId, "EventId cannot be null");
-      requireNonNull(eventType, "EventType cannot be null");
-      requireNonNull(sourceInstanceId, "Source Instance ID cannot be null");
-    }
-
-    @Override
-    public String toString() {
-      return "{"
-          + "eventId="
-          + eventId
-          + ", eventType='"
-          + eventType
-          + '\''
-          + ", sourceInstanceId="
-          + sourceInstanceId
-          + ", eventCreatedOn="
-          + eventCreatedOn
-          + '}';
-    }
-  }
-
-  public SourceAwareEventWrapper(EventHeader header, JsonObject body) {
-    this.header = header;
-    this.body = body;
-  }
-
-  public void validate() {
-    requireNonNull(header, "Header cannot be null");
-    requireNonNull(body, "Body cannot be null");
-    header.validate();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/StreamEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/StreamEventSubscriber.java
index b48ab31..20c355e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/StreamEventSubscriber.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/StreamEventSubscriber.java
@@ -15,13 +15,11 @@
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
 import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gson.Gson;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.InstanceId;
 import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.router.StreamEventRouter;
 import java.util.UUID;
@@ -30,15 +28,13 @@
 public class StreamEventSubscriber extends AbstractSubcriber {
   @Inject
   public StreamEventSubscriber(
-      BrokerApiWrapper brokerApi,
       StreamEventRouter eventRouter,
       DynamicSet<DroppedEventListener> droppedEventListeners,
-      @BrokerGson Gson gson,
       @InstanceId UUID instanceId,
       MessageLogger msgLog,
-      SubscriberMetrics subscriberMetrics) {
-    super(
-        brokerApi, eventRouter, droppedEventListeners, gson, instanceId, msgLog, subscriberMetrics);
+      SubscriberMetrics subscriberMetrics,
+      Configuration cfg) {
+    super(eventRouter, droppedEventListeners, instanceId, msgLog, subscriberMetrics, cfg);
   }
 
   @Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
index 996b581..1474262 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetrics.java
@@ -14,28 +14,53 @@
 
 package com.googlesource.gerrit.plugins.multisite.consumer;
 
+import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.metrics.Counter1;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
 import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import com.googlesource.gerrit.plugins.replication.RefReplicatedEvent;
+import com.googlesource.gerrit.plugins.replication.RefReplicationDoneEvent;
+import com.googlesource.gerrit.plugins.replication.ReplicationScheduledEvent;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
 @Singleton
 public class SubscriberMetrics {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   private static final String SUBSCRIBER_SUCCESS_COUNTER = "subscriber_msg_consumer_counter";
   private static final String SUBSCRIBER_FAILURE_COUNTER =
       "subscriber_msg_consumer_failure_counter";
-  private static final String SUBSCRIBER_POLL_FAILURE_COUNTER =
-      "subscriber_msg_consumer_poll_failure_counter";
+  private static final String REPLICATION_LAG_SEC =
+      "multi_site/subscriber/subscriber_replication_status/sec_behind";
 
   private final Counter1<String> subscriberSuccessCounter;
   private final Counter1<String> subscriberFailureCounter;
-  private final Counter1<String> subscriberPollFailureCounter;
+  private final ProjectVersionLogger verLogger;
+
+  private final Map<String, Long> replicationStatusPerProject = new HashMap<>();
+  private final Map<String, Long> localVersionPerProject = new HashMap<>();
+
+  private ProjectVersionRefUpdate projectVersionRefUpdate;
 
   @Inject
-  public SubscriberMetrics(MetricMaker metricMaker) {
+  public SubscriberMetrics(
+      MetricMaker metricMaker,
+      ProjectVersionRefUpdate projectVersionRefUpdate,
+      ProjectVersionLogger verLogger) {
 
+    this.projectVersionRefUpdate = projectVersionRefUpdate;
     this.subscriberSuccessCounter =
         metricMaker.newCounter(
             "multi_site/subscriber/subscriber_message_consumer_counter",
@@ -51,15 +76,19 @@
                 .setUnit("errors"),
             Field.ofString(
                 SUBSCRIBER_FAILURE_COUNTER, "Subscriber failed to consume messages count"));
+    metricMaker.newCallbackMetric(
+        REPLICATION_LAG_SEC,
+        Long.class,
+        new Description("Replication lag (sec)").setGauge().setUnit(Description.Units.SECONDS),
+        () -> {
+          Collection<Long> lags = replicationStatusPerProject.values();
+          if (lags.isEmpty()) {
+            return 0L;
+          }
+          return Collections.max(lags);
+        });
 
-    this.subscriberPollFailureCounter =
-        metricMaker.newCounter(
-            "multi_site/subscriber/subscriber_message_consumer_poll_failure_counter",
-            new Description("Number of failed attempts to poll messages by the subscriber")
-                .setRate()
-                .setUnit("errors"),
-            Field.ofString(
-                SUBSCRIBER_POLL_FAILURE_COUNTER, "Subscriber failed to poll messages count"));
+    this.verLogger = verLogger;
   }
 
   public void incrementSubscriberConsumedMessage() {
@@ -70,7 +99,44 @@
     subscriberFailureCounter.increment(SUBSCRIBER_FAILURE_COUNTER);
   }
 
-  public void incrementSubscriberFailedToPollMessages() {
-    subscriberPollFailureCounter.increment(SUBSCRIBER_POLL_FAILURE_COUNTER);
+  public void updateReplicationStatusMetrics(EventMessage eventMessage) {
+    Event event = eventMessage.getEvent();
+    if (event instanceof RefReplicationDoneEvent) {
+      RefReplicationDoneEvent replicationDone = (RefReplicationDoneEvent) event;
+      updateReplicationLagMetrics(
+          replicationDone.getProjectNameKey(), replicationDone.getRefName());
+    } else if (event instanceof RefReplicatedEvent) {
+      RefReplicatedEvent replicated = (RefReplicatedEvent) event;
+      updateReplicationLagMetrics(replicated.getProjectNameKey(), replicated.getRefName());
+    } else if (event instanceof ReplicationScheduledEvent) {
+      ReplicationScheduledEvent updated = (ReplicationScheduledEvent) event;
+      updateReplicationLagMetrics(updated.getProjectNameKey(), updated.getRefName());
+    } else if (event instanceof RefUpdatedEvent) {
+      RefUpdatedEvent updated = (RefUpdatedEvent) event;
+      updateReplicationLagMetrics(updated.getProjectNameKey(), updated.getRefName());
+    }
+  }
+
+  private void updateReplicationLagMetrics(Project.NameKey projectName, String ref) {
+    Optional<Long> remoteVersion =
+        projectVersionRefUpdate.getProjectRemoteVersion(projectName.get());
+    Optional<Long> localVersion = projectVersionRefUpdate.getProjectLocalVersion(projectName.get());
+    if (remoteVersion.isPresent() && localVersion.isPresent()) {
+      long lag = remoteVersion.get() - localVersion.get();
+
+      if (!localVersion.get().equals(localVersionPerProject.get(projectName.get()))
+          || lag != replicationStatusPerProject.get(projectName.get())) {
+        logger.atFine().log(
+            "Published replication lag metric for project '%s' of %d sec(s) [local-ref=%d global-ref=%d]",
+            projectName, lag, localVersion.get(), remoteVersion.get());
+        replicationStatusPerProject.put(projectName.get(), lag);
+        localVersionPerProject.put(projectName.get(), localVersion.get());
+        verLogger.log(projectName, localVersion.get(), lag);
+      }
+    } else {
+      logger.atFine().log(
+          "Did not publish replication lag metric for %s because the %s version is not defined",
+          projectName, localVersion.isPresent() ? "remote" : "local");
+    }
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberModule.java
index 0a0c350..09adb18 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberModule.java
@@ -16,22 +16,21 @@
 
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.MultiSiteEvent;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 public class SubscriberModule extends LifecycleModule {
 
   @Override
   protected void configure() {
     MultiSiteEvent.registerEventTypes();
-    bind(ExecutorService.class)
-        .annotatedWith(ConsumerExecutor.class)
-        .toInstance(Executors.newFixedThreadPool(EventTopic.values().length));
-    listener().to(MultiSiteConsumerRunner.class);
 
     DynamicSet.setOf(binder(), AbstractSubcriber.class);
     DynamicSet.setOf(binder(), DroppedEventListener.class);
+
+    DynamicSet.bind(binder(), AbstractSubcriber.class).to(IndexEventSubscriber.class);
+    DynamicSet.bind(binder(), AbstractSubcriber.class).to(BatchIndexEventSubscriber.class);
+    DynamicSet.bind(binder(), AbstractSubcriber.class).to(StreamEventSubscriber.class);
+    DynamicSet.bind(binder(), AbstractSubcriber.class).to(CacheEvictionEventSubscriber.class);
+    DynamicSet.bind(binder(), AbstractSubcriber.class).to(ProjectUpdateEventSubscriber.class);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
index 8889935..1c0c644 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/event/EventModule.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.events.EventListener;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
 import java.util.concurrent.Executor;
 
 public class EventModule extends LifecycleModule {
@@ -26,5 +27,6 @@
     bind(Executor.class).annotatedWith(EventExecutor.class).toProvider(EventExecutorProvider.class);
     listener().to(EventExecutorProvider.class);
     DynamicSet.bind(binder(), EventListener.class).to(EventHandler.class);
+    DynamicSet.bind(binder(), EventListener.class).to(ProjectVersionRefUpdate.class);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedAwareEventBroker.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedAwareEventBroker.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedAwareEventBroker.java
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
index 85dab30..278ba4e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandler.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 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 org.slf4j.Logger;
@@ -47,9 +46,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 (ManualRequestContext ctx = oneOffCtx.open()) {
       Context.setForwardedEvent(true);
       log.debug("dispatching event {}", event.getType());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexAccountHandler.java
index fb7aef1..6d1d6cf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexAccountHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexAccountHandler.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.account.AccountIndexer;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
@@ -49,7 +48,7 @@
   }
 
   @Override
-  protected void doIndex(Account.Id id, Optional<AccountIndexEvent> event) throws IOException {
+  protected void doIndex(Account.Id id, Optional<AccountIndexEvent> event) {
     indexer.index(id);
     log.debug("Account {} successfully indexed", id);
   }
@@ -74,7 +73,7 @@
     try {
       index(account.getKey(), account.getValue(), Optional.empty());
       return true;
-    } catch (IOException | OrmException e) {
+    } catch (IOException e) {
       log.error("Account {} index failed", account.getKey(), e);
       return false;
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
index 1913b00..8340a5f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandler.java
@@ -18,10 +18,8 @@
 import com.google.gerrit.reviewdb.client.Change;
 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 com.googlesource.gerrit.plugins.multisite.Configuration;
@@ -30,7 +28,6 @@
 import com.googlesource.gerrit.plugins.multisite.index.ChangeChecker;
 import com.googlesource.gerrit.plugins.multisite.index.ChangeCheckerImpl;
 import com.googlesource.gerrit.plugins.multisite.index.ForwardedIndexExecutor;
-import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
@@ -71,72 +68,48 @@
   }
 
   @Override
-  protected void doIndex(String id, Optional<ChangeIndexEvent> indexEvent)
-      throws IOException, OrmException {
+  protected void doIndex(String id, Optional<ChangeIndexEvent> indexEvent) {
     doIndex(id, indexEvent, 0);
   }
 
-  private void doIndex(String id, Optional<ChangeIndexEvent> indexEvent, int retryCount)
-      throws IOException, OrmException {
-    try {
-      ChangeChecker checker = changeCheckerFactory.create(id);
-      Optional<ChangeNotes> changeNotes = checker.getChangeNotes();
-      if (changeNotes.isPresent()) {
-        ChangeNotes notes = changeNotes.get();
-        reindex(notes);
+  private void doIndex(String id, Optional<ChangeIndexEvent> indexEvent, int retryCount) {
+    ChangeChecker checker = changeCheckerFactory.create(id);
+    Optional<ChangeNotes> changeNotes = checker.getChangeNotes();
+    if (changeNotes.isPresent()) {
+      ChangeNotes notes = changeNotes.get();
+      reindex(notes);
 
-        if (checker.isChangeUpToDate(indexEvent)) {
-          if (retryCount > 0) {
-            log.warn("Change {} has been eventually indexed after {} attempt(s)", id, retryCount);
-          } else {
-            log.debug("Change {} successfully indexed", id);
-          }
+      if (checker.isChangeUpToDate(indexEvent)) {
+        if (retryCount > 0) {
+          log.warn("Change {} has been eventually indexed after {} attempt(s)", id, retryCount);
         } else {
-          log.warn(
-              "Change {} seems too old compared to the event timestamp (event={} >> change-Ts={})",
-              id,
-              indexEvent,
-              checker);
-          rescheduleIndex(id, indexEvent, retryCount + 1);
+          log.debug("Change {} successfully indexed", id);
         }
       } else {
         log.warn(
-            "Change {} not present yet in local Git repository (event={}) after {} attempt(s)",
+            "Change {} seems too old compared to the event timestamp (event={} >> change-Ts={})",
             id,
             indexEvent,
-            retryCount);
-        if (!rescheduleIndex(id, indexEvent, retryCount + 1)) {
-          log.error(
-              "Change {} could not be found in the local Git repository (event={})",
-              id,
-              indexEvent);
-        }
+            checker);
+        rescheduleIndex(id, indexEvent, retryCount + 1);
       }
-    } catch (Exception e) {
-      if (isCausedByNoSuchChangeException(e)) {
-        indexer.delete(parseChangeId(id));
-        log.warn("Error trying to index Change {}. Deleted from index", id, e);
-        return;
+    } else {
+      log.warn(
+          "Change {} not present yet in local Git repository (event={}) after {} attempt(s)",
+          id,
+          indexEvent,
+          retryCount);
+      if (!rescheduleIndex(id, indexEvent, retryCount + 1)) {
+        log.error(
+            "Change {} could not be found in the local Git repository (event={})", id, indexEvent);
       }
-
-      throw e;
     }
   }
 
-  private static boolean isCausedByNoSuchChangeException(Throwable throwable) {
-    while (throwable != null) {
-      if (throwable instanceof NoSuchChangeException) {
-        return true;
-      }
-      throwable = throwable.getCause();
-    }
-    return false;
-  }
-
-  private void reindex(ChangeNotes notes) throws IOException, OrmException {
+  private void reindex(ChangeNotes notes) {
     try (ManualRequestContext ctx = oneOffCtx.open()) {
       notes.reload();
-      indexer.index(ctx.getReviewDbProvider().get(), notes.getChange());
+      indexer.index(notes.getChange());
     }
   }
 
@@ -172,7 +145,7 @@
   }
 
   @Override
-  protected void doDelete(String id, Optional<ChangeIndexEvent> indexEvent) throws IOException {
+  protected void doDelete(String id, Optional<ChangeIndexEvent> indexEvent) {
     indexer.delete(parseChangeId(id));
     log.debug("Change {} successfully deleted from index", id);
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandler.java
index 76ce260..368dffe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexGroupHandler.java
@@ -16,12 +16,10 @@
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.GroupIndexEvent;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -41,8 +39,7 @@
   }
 
   @Override
-  protected void doIndex(String uuid, Optional<GroupIndexEvent> event)
-      throws IOException, OrmException {
+  protected void doIndex(String uuid, Optional<GroupIndexEvent> event) {
     indexer.index(new AccountGroup.UUID(uuid));
     log.debug("Group {} successfully indexed", uuid);
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexProjectHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexProjectHandler.java
index ff2e111..e5f7e10 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexProjectHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexProjectHandler.java
@@ -22,7 +22,6 @@
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectIndexEvent;
 import com.googlesource.gerrit.plugins.multisite.index.ForwardedIndexExecutor;
 import com.googlesource.gerrit.plugins.multisite.index.ProjectChecker;
-import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -58,15 +57,14 @@
   }
 
   @Override
-  protected void doIndex(String projectName, Optional<ProjectIndexEvent> event) throws IOException {
+  protected void doIndex(String projectName, Optional<ProjectIndexEvent> event) {
     if (!attemptIndex(projectName, event)) {
       log.warn("First Attempt failed, scheduling again after {} msecs", retryInterval);
       rescheduleIndex(projectName, event, 1);
     }
   }
 
-  public boolean attemptIndex(String projectName, Optional<ProjectIndexEvent> event)
-      throws IOException {
+  public boolean attemptIndex(String projectName, Optional<ProjectIndexEvent> event) {
     log.debug("Attempt to index project {}, event: [{}]", projectName, event);
     final Project.NameKey projectNameKey = new Project.NameKey(projectName);
     if (projectChecker.isProjectUpToDate(projectNameKey)) {
@@ -97,17 +95,13 @@
     indexExecutor.schedule(
         () -> {
           Context.setForwardedEvent(true);
-          try {
-            if (!attemptIndex(projectName, event)) {
-              log.warn(
-                  "Attempt {} to index project {} failed, scheduling again after {} msecs",
-                  retryCount,
-                  projectName,
-                  retryInterval);
-              rescheduleIndex(projectName, event, retryCount + 1);
-            }
-          } catch (IOException e) {
-            log.warn("Project {} could not be indexed", projectName, e);
+          if (!attemptIndex(projectName, event)) {
+            log.warn(
+                "Attempt {} to index project {} failed, scheduling again after {} msecs",
+                retryCount,
+                projectName,
+                retryInterval);
+            rescheduleIndex(projectName, event, retryCount + 1);
           }
         },
         retryInterval,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexingHandler.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexingHandler.java
index 67662f6..de6b836 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexingHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexingHandler.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.multisite.forwarder;
 
 import com.google.common.util.concurrent.Striped;
-import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.Optional;
 import java.util.concurrent.locks.Lock;
@@ -43,9 +42,9 @@
 
   private final Striped<Lock> idLocks;
 
-  protected abstract void doIndex(T id, Optional<E> indexEvent) throws IOException, OrmException;
+  protected abstract void doIndex(T id, Optional<E> indexEvent);
 
-  protected abstract void doDelete(T id, Optional<E> indexEvent) throws IOException;
+  protected abstract void doDelete(T id, Optional<E> indexEvent);
 
   protected ForwardedIndexingHandler(int lockStripes) {
     idLocks = Striped.lock(lockStripes);
@@ -58,9 +57,8 @@
    * @param operation The operation to do; index or delete
    * @param event 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<E> event) throws IOException, OrmException {
+  public void index(T id, Operation operation, Optional<E> event) throws IOException {
     log.debug("{} {} {}", operation, id, event);
     try {
       Context.setForwardedEvent(true);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
index dff21c1..b32e5ae 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerCacheEvictionForwarder.java
@@ -16,7 +16,7 @@
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.CacheEvictionEvent;
@@ -24,15 +24,17 @@
 
 @Singleton
 public class BrokerCacheEvictionForwarder implements CacheEvictionForwarder {
-  private final BrokerApi broker;
+  private final BrokerApiWrapper broker;
+  private final Configuration cfg;
 
   @Inject
-  BrokerCacheEvictionForwarder(BrokerApiWrapper broker) {
+  BrokerCacheEvictionForwarder(BrokerApiWrapper broker, Configuration cfg) {
     this.broker = broker;
+    this.cfg = cfg;
   }
 
   @Override
   public boolean evict(CacheEvictionEvent event) {
-    return broker.send(EventTopic.CACHE_TOPIC.topic(), event);
+    return broker.send(EventTopic.CACHE_TOPIC.topic(cfg), event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarderModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarderModule.java
new file mode 100644
index 0000000..6bd6437
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerForwarderModule.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.forwarder.broker;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
+import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
+
+public class BrokerForwarderModule extends LifecycleModule {
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), IndexEventForwarder.class).to(BrokerIndexEventForwarder.class);
+    DynamicSet.bind(binder(), CacheEvictionForwarder.class).to(BrokerCacheEvictionForwarder.class);
+    DynamicSet.bind(binder(), ProjectListUpdateForwarder.class)
+        .to(BrokerProjectListUpdateForwarder.class);
+    DynamicSet.bind(binder(), StreamEventForwarder.class).to(BrokerStreamEventForwarder.class);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
index 214d47d..a86c62e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerIndexEventForwarder.java
@@ -15,27 +15,29 @@
 package com.googlesource.gerrit.plugins.multisite.forwarder.broker;
 
 import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.IndexEvent;
 
 public class BrokerIndexEventForwarder implements IndexEventForwarder {
-  private final BrokerApi broker;
+  private final BrokerApiWrapper broker;
+  private final Configuration cfg;
 
   @Inject
-  BrokerIndexEventForwarder(BrokerApiWrapper broker) {
+  BrokerIndexEventForwarder(BrokerApiWrapper broker, Configuration cfg) {
     this.broker = broker;
+    this.cfg = cfg;
   }
 
   @Override
   public boolean index(IndexEvent event) {
-    return broker.send(EventTopic.INDEX_TOPIC.topic(), event);
+    return broker.send(EventTopic.INDEX_TOPIC.topic(cfg), event);
   }
 
   @Override
   public boolean batchIndex(IndexEvent event) {
-    return broker.send(EventTopic.BATCH_INDEX_TOPIC.topic(), event);
+    return broker.send(EventTopic.BATCH_INDEX_TOPIC.topic(cfg), event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
index 1a8b652..34e0300 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerProjectListUpdateForwarder.java
@@ -18,22 +18,24 @@
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ProjectListUpdateEvent;
 
 @Singleton
 public class BrokerProjectListUpdateForwarder implements ProjectListUpdateForwarder {
-  private final BrokerApi broker;
+  private final BrokerApiWrapper broker;
+  private final Configuration cfg;
 
   @Inject
-  BrokerProjectListUpdateForwarder(BrokerApiWrapper broker) {
+  BrokerProjectListUpdateForwarder(BrokerApiWrapper broker, Configuration cfg) {
     this.broker = broker;
+    this.cfg = cfg;
   }
 
   @Override
   public boolean updateProjectList(ProjectListUpdateEvent event) {
-    return broker.send(PROJECT_LIST_TOPIC.topic(), event);
+    return broker.send(PROJECT_LIST_TOPIC.topic(cfg), event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerStreamEventForwarder.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerStreamEventForwarder.java
index ed3a717..9ff4688 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerStreamEventForwarder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/broker/BrokerStreamEventForwarder.java
@@ -17,22 +17,24 @@
 import com.google.gerrit.server.events.Event;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
+import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.broker.BrokerApiWrapper;
 import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
 
 @Singleton
 public class BrokerStreamEventForwarder implements StreamEventForwarder {
-  private final BrokerApi broker;
+  private final BrokerApiWrapper broker;
+  private final Configuration cfg;
 
   @Inject
-  BrokerStreamEventForwarder(BrokerApiWrapper broker) {
+  BrokerStreamEventForwarder(BrokerApiWrapper broker, Configuration cfg) {
     this.broker = broker;
+    this.cfg = cfg;
   }
 
   @Override
   public boolean send(Event event) {
-    return broker.send(EventTopic.STREAM_EVENT_TOPIC.topic(), event);
+    return broker.send(EventTopic.STREAM_EVENT_TOPIC.topic(cfg), event);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/EventTopic.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/EventTopic.java
index 11e77a9..4e7a781 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/EventTopic.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/events/EventTopic.java
@@ -14,6 +14,8 @@
 
 package com.googlesource.gerrit.plugins.multisite.forwarder.events;
 
+import com.googlesource.gerrit.plugins.multisite.Configuration;
+
 public enum EventTopic {
   INDEX_TOPIC("GERRIT.EVENT.INDEX", "indexEvent"),
   BATCH_INDEX_TOPIC("GERRIT.EVENT.BATCH.INDEX", "batchIndexEvent"),
@@ -29,18 +31,14 @@
     this.aliasKey = aliasKey;
   }
 
-  public String topic() {
-    return topic;
+  public String topic(Configuration config) {
+    return config.broker().getTopic(topicAliasKey(), topic);
   }
 
   public String topicAliasKey() {
     return aliasKey + "Topic";
   }
 
-  public String enabledKey() {
-    return aliasKey + "Enabled";
-  }
-
   public static EventTopic of(String topicString) {
     EventTopic[] topics = EventTopic.values();
     for (EventTopic topic : topics) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/ForwardedEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/ForwardedEventRouter.java
index 139020b..f9ad0c9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/ForwardedEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/ForwardedEventRouter.java
@@ -15,11 +15,10 @@
 package com.googlesource.gerrit.plugins.multisite.forwarder.router;
 
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gwtorm.server.OrmException;
 import com.googlesource.gerrit.plugins.multisite.forwarder.CacheNotFoundException;
 import java.io.IOException;
 
 public interface ForwardedEventRouter<EventType> {
   void route(EventType sourceEvent)
-      throws IOException, OrmException, PermissionBackendException, CacheNotFoundException;
+      throws IOException, PermissionBackendException, CacheNotFoundException;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java
index 45c8e53..6ab8c72 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/IndexEventRouter.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Account.Id;
 import com.google.gerrit.server.config.AllUsersName;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexAccountHandler;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexChangeHandler;
@@ -63,7 +62,7 @@
   }
 
   @Override
-  public void route(IndexEvent sourceEvent) throws IOException, OrmException {
+  public void route(IndexEvent sourceEvent) throws IOException {
     if (sourceEvent instanceof ChangeIndexEvent) {
       ChangeIndexEvent changeIndexEvent = (ChangeIndexEvent) sourceEvent;
       ForwardedIndexingHandler.Operation operation = changeIndexEvent.deleted ? DELETE : INDEX;
@@ -87,8 +86,7 @@
     }
   }
 
-  public void onRefReplicated(RefReplicationDoneEvent replicationEvent)
-      throws IOException, OrmException {
+  public void onRefReplicated(RefReplicationDoneEvent replicationEvent) throws IOException {
     if (replicationEvent.getProjectNameKey().equals(allUsersName)) {
       Account.Id accountId = Account.Id.fromRef(replicationEvent.getRefName());
       if (accountId != null) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
index 1ff6992..4ef3426 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/forwarder/router/StreamEventRouter.java
@@ -16,7 +16,6 @@
 
 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.googlesource.gerrit.plugins.multisite.forwarder.ForwardedEventHandler;
 import com.googlesource.gerrit.plugins.replication.RefReplicationDoneEvent;
@@ -34,8 +33,7 @@
   }
 
   @Override
-  public void route(Event sourceEvent)
-      throws OrmException, PermissionBackendException, IOException {
+  public void route(Event sourceEvent) throws PermissionBackendException, IOException {
     if (RefReplicationDoneEvent.TYPE.equals(sourceEvent.getType())) {
       /* TODO: We currently explicitly ignore the status and result of the replication
        * event because there isn't a reliable way to understand if the current node was
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeChecker.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeChecker.java
index 1b0fea8..3646b3a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeChecker.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeChecker.java
@@ -15,7 +15,6 @@
 package com.googlesource.gerrit.plugins.multisite.index;
 
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gwtorm.server.OrmException;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
 import java.io.IOException;
 import java.util.Optional;
@@ -27,9 +26,8 @@
    * Return the Change nodes read from ReviewDb or NoteDb.
    *
    * @return notes of the Change
-   * @throws OrmException if ReviewDb or NoteDb cannot be opened
    */
-  public Optional<ChangeNotes> getChangeNotes() throws OrmException;
+  public Optional<ChangeNotes> getChangeNotes();
 
   /**
    * Create a new index event POJO associated with the current Change.
@@ -40,21 +38,17 @@
    *     index
    * @return new IndexEvent
    * @throws IOException if the current Change cannot read
-   * @throws OrmException if ReviewDb cannot be opened
    */
   public Optional<ChangeIndexEvent> newIndexEvent(String projectName, int changeId, boolean deleted)
-      throws IOException, OrmException;
+      throws IOException;
 
   /**
    * Check if the local Change is aligned with the indexEvent received.
    *
    * @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
    */
-  public boolean isChangeUpToDate(Optional<ChangeIndexEvent> indexEvent)
-      throws IOException, OrmException;
+  public boolean isChangeUpToDate(Optional<ChangeIndexEvent> indexEvent);
 
   /**
    * Return the last computed up-to-date Change time-stamp.
@@ -63,7 +57,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
    */
-  public Optional<Long> getComputedChangeTs() throws IOException, OrmException;
+  public Optional<Long> getComputedChangeTs() throws IOException;
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
index 8cb2fec..f1e80cc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/index/ChangeCheckerImpl.java
@@ -14,16 +14,15 @@
 
 package com.googlesource.gerrit.plugins.multisite.index;
 
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
-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.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 com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
@@ -66,7 +65,7 @@
 
   @Override
   public Optional<ChangeIndexEvent> newIndexEvent(String projectName, int changeId, boolean deleted)
-      throws IOException, OrmException {
+      throws IOException {
     return getComputedChangeTs()
         .map(
             ts -> {
@@ -78,7 +77,7 @@
   }
 
   @Override
-  public Optional<ChangeNotes> getChangeNotes() throws OrmException {
+  public Optional<ChangeNotes> getChangeNotes() {
     try (ManualRequestContext ctx = oneOffReqCtx.open()) {
       this.changeNotes = Optional.ofNullable(changeFinder.findOne(changeId));
       return changeNotes;
@@ -86,8 +85,7 @@
   }
 
   @Override
-  public boolean isChangeUpToDate(Optional<ChangeIndexEvent> indexEvent)
-      throws IOException, OrmException {
+  public boolean isChangeUpToDate(Optional<ChangeIndexEvent> indexEvent) {
     getComputedChangeTs();
     if (!computedChangeTs.isPresent()) {
       log.warn("Unable to compute last updated ts for change {}", changeId);
@@ -108,7 +106,7 @@
   }
 
   @Override
-  public Optional<Long> getComputedChangeTs() throws IOException, OrmException {
+  public Optional<Long> getComputedChangeTs() {
     if (!computedChangeTs.isPresent()) {
       computedChangeTs = computeLastChangeTs();
     }
@@ -117,17 +115,12 @@
 
   @Override
   public String toString() {
-    try {
-      return "change-id="
-          + changeId
-          + "@"
-          + getComputedChangeTs().map(ChangeIndexEvent::format)
-          + "/"
-          + getBranchTargetSha();
-    } catch (IOException | OrmException e) {
-      log.error("Unable to render change {}", changeId, e);
-      return "change-id=" + changeId;
-    }
+    return "change-id="
+        + changeId
+        + "@"
+        + getComputedChangeTs().map(ChangeIndexEvent::format)
+        + "/"
+        + getBranchTargetSha();
   }
 
   private String getBranchTargetSha() {
@@ -147,21 +140,19 @@
     }
   }
 
-  private Optional<Long> computeLastChangeTs() throws OrmException {
-    try (ReviewDb db = oneOffReqCtx.open().getReviewDbProvider().get()) {
-      return getChangeNotes().map(notes -> getTsFromChangeAndDraftComments(db, notes));
-    }
+  private Optional<Long> computeLastChangeTs() {
+    return getChangeNotes().map(notes -> getTsFromChangeAndDraftComments(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())) {
+      for (Comment comment : commentsUtil.draftByChange(changeNotes.get())) {
         Timestamp commentTs = comment.writtenOn;
         changeTs = commentTs.after(changeTs) ? commentTs : changeTs;
       }
-    } catch (OrmException e) {
+    } catch (StorageException e) {
       log.warn("Unable to access draft comments for change {}", change, e);
     }
     return changeTs.getTime() / 1000;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerApi.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerApi.java
deleted file mode 100644
index ff23b78..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerApi.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka;
-
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.events.Event;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerApi;
-import com.googlesource.gerrit.plugins.multisite.broker.kafka.BrokerPublisher;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import com.googlesource.gerrit.plugins.multisite.kafka.consumer.KafkaEventSubscriber;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public class KafkaBrokerApi implements BrokerApi, LifecycleListener {
-
-  private final BrokerPublisher publisher;
-  private final Provider<KafkaEventSubscriber> subscriberProvider;
-  private List<KafkaEventSubscriber> subscribers;
-
-  @Inject
-  public KafkaBrokerApi(
-      BrokerPublisher publisher, Provider<KafkaEventSubscriber> subscriberProvider) {
-    this.publisher = publisher;
-    this.subscriberProvider = subscriberProvider;
-    subscribers = new ArrayList<>();
-  }
-
-  @Override
-  public boolean send(String topic, Event event) {
-    return publisher.publish(topic, event);
-  }
-
-  @Override
-  public void receiveAync(String topic, Consumer<SourceAwareEventWrapper> eventConsumer) {
-    KafkaEventSubscriber subscriber = subscriberProvider.get();
-    synchronized (subscribers) {
-      subscribers.add(subscriber);
-    }
-    subscriber.subscribe(EventTopic.of(topic), eventConsumer);
-  }
-
-  @Override
-  public void start() {}
-
-  @Override
-  public void stop() {
-    for (KafkaEventSubscriber subscriber : subscribers) {
-      subscriber.shutdown();
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerModule.java
deleted file mode 100644
index ad04fb8..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaBrokerModule.java
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka;
-
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.inject.Inject;
-import com.google.inject.TypeLiteral;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerSession;
-import com.googlesource.gerrit.plugins.multisite.broker.kafka.BrokerPublisher;
-import com.googlesource.gerrit.plugins.multisite.broker.kafka.KafkaSession;
-import com.googlesource.gerrit.plugins.multisite.consumer.AbstractSubcriber;
-import com.googlesource.gerrit.plugins.multisite.consumer.BatchIndexEventSubscriber;
-import com.googlesource.gerrit.plugins.multisite.consumer.CacheEvictionEventSubscriber;
-import com.googlesource.gerrit.plugins.multisite.consumer.IndexEventSubscriber;
-import com.googlesource.gerrit.plugins.multisite.consumer.ProjectUpdateEventSubscriber;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import com.googlesource.gerrit.plugins.multisite.consumer.StreamEventSubscriber;
-import com.googlesource.gerrit.plugins.multisite.forwarder.CacheEvictionForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.IndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.ProjectListUpdateForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.StreamEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerCacheEvictionForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerIndexEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerProjectListUpdateForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.broker.BrokerStreamEventForwarder;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import com.googlesource.gerrit.plugins.multisite.kafka.consumer.KafkaEventDeserializer;
-import org.apache.kafka.common.serialization.ByteArrayDeserializer;
-import org.apache.kafka.common.serialization.Deserializer;
-
-public class KafkaBrokerModule extends LifecycleModule {
-  private KafkaConfiguration config;
-
-  @Inject
-  public KafkaBrokerModule(KafkaConfiguration config) {
-    this.config = config;
-  }
-
-  @Override
-  protected void configure() {
-    if (config.kafkaSubscriber().enabled()) {
-      bind(new TypeLiteral<Deserializer<byte[]>>() {}).toInstance(new ByteArrayDeserializer());
-      bind(new TypeLiteral<Deserializer<SourceAwareEventWrapper>>() {})
-          .to(KafkaEventDeserializer.class);
-
-      if (config.kafkaSubscriber().enabledEvent(EventTopic.INDEX_TOPIC)) {
-        DynamicSet.bind(binder(), AbstractSubcriber.class).to(IndexEventSubscriber.class);
-      }
-      if (config.kafkaSubscriber().enabledEvent(EventTopic.BATCH_INDEX_TOPIC)) {
-        DynamicSet.bind(binder(), AbstractSubcriber.class).to(BatchIndexEventSubscriber.class);
-      }
-      if (config.kafkaSubscriber().enabledEvent(EventTopic.STREAM_EVENT_TOPIC)) {
-        DynamicSet.bind(binder(), AbstractSubcriber.class).to(StreamEventSubscriber.class);
-      }
-      if (config.kafkaSubscriber().enabledEvent(EventTopic.CACHE_TOPIC)) {
-        DynamicSet.bind(binder(), AbstractSubcriber.class).to(CacheEvictionEventSubscriber.class);
-      }
-      if (config.kafkaSubscriber().enabledEvent(EventTopic.PROJECT_LIST_TOPIC)) {
-        DynamicSet.bind(binder(), AbstractSubcriber.class).to(ProjectUpdateEventSubscriber.class);
-      }
-    }
-
-    if (config.kafkaPublisher().enabled()) {
-      listener().to(BrokerPublisher.class);
-      bind(BrokerSession.class).to(KafkaSession.class);
-
-      if (config.kafkaPublisher().enabledEvent(EventTopic.INDEX_TOPIC)) {
-        DynamicSet.bind(binder(), IndexEventForwarder.class).to(BrokerIndexEventForwarder.class);
-      }
-      if (config.kafkaPublisher().enabledEvent(EventTopic.CACHE_TOPIC)) {
-        DynamicSet.bind(binder(), CacheEvictionForwarder.class)
-            .to(BrokerCacheEvictionForwarder.class);
-      }
-      if (config.kafkaPublisher().enabledEvent(EventTopic.PROJECT_LIST_TOPIC)) {
-        DynamicSet.bind(binder(), ProjectListUpdateForwarder.class)
-            .to(BrokerProjectListUpdateForwarder.class);
-      }
-      if (config.kafkaPublisher().enabledEvent(EventTopic.STREAM_EVENT_TOPIC)) {
-        DynamicSet.bind(binder(), StreamEventForwarder.class).to(BrokerStreamEventForwarder.class);
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfiguration.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfiguration.java
deleted file mode 100644
index dee7382..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfiguration.java
+++ /dev/null
@@ -1,283 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka;
-
-import static com.google.common.base.Suppliers.memoize;
-import static com.google.common.base.Suppliers.ofInstance;
-
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Strings;
-import com.google.common.base.Supplier;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.UUID;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class KafkaConfiguration {
-
-  private static final Logger log = LoggerFactory.getLogger(KafkaConfiguration.class);
-  static final String KAFKA_PROPERTY_PREFIX = "KafkaProp-";
-  static final String KAFKA_SECTION = "kafka";
-  static final String ENABLE_KEY = "enabled";
-  private static final String DEFAULT_KAFKA_BOOTSTRAP_SERVERS = "localhost:9092";
-  private static final boolean DEFAULT_ENABLE_PROCESSING = true;
-  private static final int DEFAULT_POLLING_INTERVAL_MS = 1000;
-
-  private final Supplier<KafkaSubscriber> subscriber;
-  private final Supplier<Kafka> kafka;
-  private final Supplier<KafkaPublisher> publisher;
-
-  @Inject
-  public KafkaConfiguration(Configuration configuration) {
-    Supplier<Config> lazyCfg = lazyLoad(configuration.getMultiSiteConfig());
-    kafka = memoize(() -> new Kafka(lazyCfg));
-    publisher = memoize(() -> new KafkaPublisher(lazyCfg));
-    subscriber = memoize(() -> new KafkaSubscriber(lazyCfg));
-  }
-
-  public Kafka getKafka() {
-    return kafka.get();
-  }
-
-  public KafkaSubscriber kafkaSubscriber() {
-    return subscriber.get();
-  }
-
-  private static void applyKafkaConfig(
-      Supplier<Config> configSupplier, String subsectionName, Properties target) {
-    Config config = configSupplier.get();
-    for (String section : config.getSubsections(KAFKA_SECTION)) {
-      if (section.equals(subsectionName)) {
-        for (String name : config.getNames(KAFKA_SECTION, section, true)) {
-          if (name.startsWith(KAFKA_PROPERTY_PREFIX)) {
-            Object value = config.getString(KAFKA_SECTION, subsectionName, name);
-            String configProperty = name.replaceFirst(KAFKA_PROPERTY_PREFIX, "");
-            String propName =
-                CaseFormat.LOWER_CAMEL
-                    .to(CaseFormat.LOWER_HYPHEN, configProperty)
-                    .replaceAll("-", ".");
-            log.info("[{}] Setting kafka property: {} = {}", subsectionName, propName, value);
-            target.put(propName, value);
-          }
-        }
-      }
-    }
-    target.put(
-        "bootstrap.servers",
-        getString(
-            configSupplier,
-            KAFKA_SECTION,
-            null,
-            "bootstrapServers",
-            DEFAULT_KAFKA_BOOTSTRAP_SERVERS));
-  }
-
-  private static String getString(
-      Supplier<Config> cfg, String section, String subsection, String name, String defaultValue) {
-    String value = cfg.get().getString(section, subsection, name);
-    if (!Strings.isNullOrEmpty(value)) {
-      return value;
-    }
-    return defaultValue;
-  }
-
-  private static Map<EventTopic, Boolean> eventsEnabled(
-      Supplier<Config> config, String subsection) {
-    Map<EventTopic, Boolean> eventsEnabled = new HashMap<>();
-    for (EventTopic topic : EventTopic.values()) {
-      eventsEnabled.put(
-          topic,
-          config
-              .get()
-              .getBoolean(
-                  KAFKA_SECTION, subsection, topic.enabledKey(), DEFAULT_ENABLE_PROCESSING));
-    }
-    return eventsEnabled;
-  }
-
-  public KafkaPublisher kafkaPublisher() {
-    return publisher.get();
-  }
-
-  private Supplier<Config> lazyLoad(Config config) {
-    if (config instanceof FileBasedConfig) {
-      return memoize(
-          () -> {
-            FileBasedConfig fileConfig = (FileBasedConfig) config;
-            String fileConfigFileName = fileConfig.getFile().getPath();
-            try {
-              log.info("Loading configuration from {}", fileConfigFileName);
-              fileConfig.load();
-            } catch (IOException | ConfigInvalidException e) {
-              log.error("Unable to load configuration from " + fileConfigFileName, e);
-            }
-            return fileConfig;
-          });
-    }
-    return ofInstance(config);
-  }
-
-  public static class Kafka {
-    private final Map<EventTopic, String> eventTopics;
-    private final String bootstrapServers;
-
-    Kafka(Supplier<Config> config) {
-      this.bootstrapServers =
-          getString(
-              config, KAFKA_SECTION, null, "bootstrapServers", DEFAULT_KAFKA_BOOTSTRAP_SERVERS);
-
-      this.eventTopics = new HashMap<>();
-      for (EventTopic eventTopic : EventTopic.values()) {
-        eventTopics.put(
-            eventTopic,
-            getString(config, KAFKA_SECTION, null, eventTopic.topicAliasKey(), eventTopic.topic()));
-      }
-    }
-
-    public String getTopicAlias(EventTopic topic) {
-      return eventTopics.get(topic);
-    }
-
-    public String getBootstrapServers() {
-      return bootstrapServers;
-    }
-
-    private static String getString(
-        Supplier<Config> cfg, String section, String subsection, String name, String defaultValue) {
-      String value = cfg.get().getString(section, subsection, name);
-      if (!Strings.isNullOrEmpty(value)) {
-        return value;
-      }
-      return defaultValue;
-    }
-  }
-
-  public static class KafkaPublisher extends Properties {
-    private static final long serialVersionUID = 0L;
-
-    public static final String KAFKA_STRING_SERIALIZER = StringSerializer.class.getName();
-
-    public static final String KAFKA_PUBLISHER_SUBSECTION = "publisher";
-    public static final boolean DEFAULT_BROKER_ENABLED = false;
-
-    private final boolean enabled;
-    private final Map<EventTopic, Boolean> eventsEnabled;
-
-    private KafkaPublisher(Supplier<Config> cfg) {
-      enabled =
-          cfg.get()
-              .getBoolean(
-                  KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, DEFAULT_BROKER_ENABLED);
-
-      eventsEnabled = eventsEnabled(cfg, KAFKA_PUBLISHER_SUBSECTION);
-
-      if (enabled) {
-        setDefaults();
-        applyKafkaConfig(cfg, KAFKA_PUBLISHER_SUBSECTION, this);
-      }
-    }
-
-    private void setDefaults() {
-      put("acks", "all");
-      put("retries", 10);
-      put("batch.size", 16384);
-      put("linger.ms", 1);
-      put("buffer.memory", 33554432);
-      put("key.serializer", KAFKA_STRING_SERIALIZER);
-      put("value.serializer", KAFKA_STRING_SERIALIZER);
-      put("reconnect.backoff.ms", 5000L);
-    }
-
-    public boolean enabled() {
-      return enabled;
-    }
-
-    public boolean enabledEvent(EventTopic eventType) {
-      return eventsEnabled.get(eventType);
-    }
-  }
-
-  public static class KafkaSubscriber extends Properties {
-    private static final long serialVersionUID = 1L;
-
-    static final String KAFKA_SUBSCRIBER_SUBSECTION = "subscriber";
-
-    private final boolean enabled;
-    private final Integer pollingInterval;
-    private Map<EventTopic, Boolean> eventsEnabled;
-    private final Config cfg;
-
-    public KafkaSubscriber(Supplier<Config> configSupplier) {
-      this.cfg = configSupplier.get();
-
-      this.pollingInterval =
-          cfg.getInt(
-              KAFKA_SECTION,
-              KAFKA_SUBSCRIBER_SUBSECTION,
-              "pollingIntervalMs",
-              DEFAULT_POLLING_INTERVAL_MS);
-
-      enabled = cfg.getBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, false);
-
-      eventsEnabled = eventsEnabled(configSupplier, KAFKA_SUBSCRIBER_SUBSECTION);
-
-      if (enabled) {
-        applyKafkaConfig(configSupplier, KAFKA_SUBSCRIBER_SUBSECTION, this);
-      }
-    }
-
-    public boolean enabled() {
-      return enabled;
-    }
-
-    public boolean enabledEvent(EventTopic topic) {
-      return eventsEnabled.get(topic);
-    }
-
-    public Properties initPropsWith(UUID instanceId) {
-      String groupId =
-          getString(
-              cfg, KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, "groupId", instanceId.toString());
-      this.put("group.id", groupId);
-
-      return this;
-    }
-
-    public Integer getPollingInterval() {
-      return pollingInterval;
-    }
-
-    private String getString(
-        Config cfg, String section, String subsection, String name, String defaultValue) {
-      String value = cfg.getString(section, subsection, name);
-      if (!Strings.isNullOrEmpty(value)) {
-        return value;
-      }
-      return defaultValue;
-    }
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java
deleted file mode 100644
index 9a5e19f..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaConsumerFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration;
-import java.util.UUID;
-import org.apache.kafka.clients.consumer.Consumer;
-import org.apache.kafka.clients.consumer.KafkaConsumer;
-import org.apache.kafka.common.serialization.ByteArrayDeserializer;
-import org.apache.kafka.common.serialization.Deserializer;
-
-@Singleton
-public class KafkaConsumerFactory {
-  private KafkaConfiguration config;
-
-  @Inject
-  public KafkaConsumerFactory(KafkaConfiguration configuration) {
-    this.config = configuration;
-  }
-
-  public Consumer<byte[], byte[]> create(Deserializer<byte[]> keyDeserializer, UUID instanceId) {
-    return new KafkaConsumer<>(
-        config.kafkaSubscriber().initPropsWith(instanceId),
-        keyDeserializer,
-        new ByteArrayDeserializer());
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializer.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializer.java
deleted file mode 100644
index 38b8d61..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializer.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
-
-import com.google.gson.Gson;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerGson;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import java.util.Map;
-import org.apache.kafka.common.serialization.Deserializer;
-import org.apache.kafka.common.serialization.StringDeserializer;
-
-@Singleton
-public class KafkaEventDeserializer implements Deserializer<SourceAwareEventWrapper> {
-
-  private final StringDeserializer stringDeserializer = new StringDeserializer();
-  private Gson gson;
-
-  // To be used when providing this deserializer with class name (then need to add a configuration
-  // entry to set the gson.provider
-  public KafkaEventDeserializer() {}
-
-  @Inject
-  public KafkaEventDeserializer(@BrokerGson Gson gson) {
-    this.gson = gson;
-  }
-
-  @Override
-  public void configure(Map<String, ?> configs, boolean isKey) {}
-
-  @Override
-  public SourceAwareEventWrapper deserialize(String topic, byte[] data) {
-    final SourceAwareEventWrapper result =
-        gson.fromJson(stringDeserializer.deserialize(topic, data), SourceAwareEventWrapper.class);
-
-    result.validate();
-
-    return result;
-  }
-
-  @Override
-  public void close() {}
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventSubscriber.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventSubscriber.java
deleted file mode 100644
index c72fa0d..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventSubscriber.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.InstanceId;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import com.googlesource.gerrit.plugins.multisite.consumer.SubscriberMetrics;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration;
-import java.time.Duration;
-import java.util.Collections;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.kafka.clients.consumer.Consumer;
-import org.apache.kafka.clients.consumer.ConsumerRecords;
-import org.apache.kafka.common.errors.WakeupException;
-import org.apache.kafka.common.serialization.Deserializer;
-
-public class KafkaEventSubscriber {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final Consumer<byte[], byte[]> consumer;
-  private final OneOffRequestContext oneOffCtx;
-  private final AtomicBoolean closed = new AtomicBoolean(false);
-
-  private final Deserializer<SourceAwareEventWrapper> valueDeserializer;
-  private final KafkaConfiguration configuration;
-  private final SubscriberMetrics subscriberMetrics;
-
-  @Inject
-  public KafkaEventSubscriber(
-      KafkaConfiguration configuration,
-      KafkaConsumerFactory consumerFactory,
-      Deserializer<byte[]> keyDeserializer,
-      Deserializer<SourceAwareEventWrapper> valueDeserializer,
-      @InstanceId UUID instanceId,
-      OneOffRequestContext oneOffCtx,
-      SubscriberMetrics subscriberMetrics) {
-
-    this.configuration = configuration;
-    this.oneOffCtx = oneOffCtx;
-    this.subscriberMetrics = subscriberMetrics;
-
-    final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
-    try {
-      Thread.currentThread().setContextClassLoader(KafkaEventSubscriber.class.getClassLoader());
-      this.consumer = consumerFactory.create(keyDeserializer, instanceId);
-    } finally {
-      Thread.currentThread().setContextClassLoader(previousClassLoader);
-    }
-    this.valueDeserializer = valueDeserializer;
-  }
-
-  public void subscribe(
-      EventTopic evenTopic, java.util.function.Consumer<SourceAwareEventWrapper> messageProcessor) {
-    try {
-      final String topic = configuration.getKafka().getTopicAlias(evenTopic);
-      logger.atInfo().log(
-          "Kafka consumer subscribing to topic alias [%s] for event topic [%s]", topic, evenTopic);
-      consumer.subscribe(Collections.singleton(topic));
-      while (!closed.get()) {
-        ConsumerRecords<byte[], byte[]> consumerRecords =
-            consumer.poll(Duration.ofMillis(configuration.kafkaSubscriber().getPollingInterval()));
-        consumerRecords.forEach(
-            consumerRecord -> {
-              try (ManualRequestContext ctx = oneOffCtx.open()) {
-                SourceAwareEventWrapper event =
-                    valueDeserializer.deserialize(consumerRecord.topic(), consumerRecord.value());
-                messageProcessor.accept(event);
-              } catch (Exception e) {
-                logger.atSevere().withCause(e).log(
-                    "Malformed event '%s': [Exception: %s]",
-                    new String(consumerRecord.value(), UTF_8));
-                subscriberMetrics.incrementSubscriberFailedToConsumeMessage();
-              }
-            });
-      }
-    } catch (WakeupException e) {
-      // Ignore exception if closing
-      if (!closed.get()) throw e;
-    } catch (Exception e) {
-      subscriberMetrics.incrementSubscriberFailedToPollMessages();
-      throw e;
-    } finally {
-      consumer.close();
-    }
-  }
-
-  public void shutdown() {
-    closed.set(true);
-    consumer.wakeup();
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
index 4e6bef5..cfe585d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidator.java
@@ -19,7 +19,7 @@
 import com.google.inject.assistedinject.Assisted;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import java.io.IOException;
@@ -29,6 +29,8 @@
 import java.util.stream.Stream;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.transport.ReceiveCommand;
 
@@ -99,6 +101,13 @@
       refsToUpdate = compareAndGetLatestLocalRefs(refsToUpdate, locks);
       delegateUpdate.invoke();
       updateSharedRefDb(batchRefUpdate.getCommands().stream(), refsToUpdate);
+    } catch (OutOfSyncException e) {
+      List<ReceiveCommand> receiveCommands = batchRefUpdate.getCommands();
+      logger.atWarning().withCause(e).log(
+          String.format(
+              "Batch ref-update failing because node is out of sync with the shared ref-db. Set all commands Result to LOCK_FAILURE [%d]",
+              receiveCommands.size()));
+      receiveCommands.forEach((command) -> command.setResult(ReceiveCommand.Result.LOCK_FAILURE));
     }
   }
 
@@ -124,7 +133,7 @@
     try {
       switch (command.getType()) {
         case CREATE:
-          return new RefPair(SharedRefDatabase.nullRef(command.getRefName()), getNewRef(command));
+          return new RefPair(nullRef(command.getRefName()), getNewRef(command));
 
         case UPDATE:
         case UPDATE_NONFASTFORWARD:
@@ -155,4 +164,8 @@
     }
     return latestRefsToUpdate;
   }
+
+  private static final Ref nullRef(String refName) {
+    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, ObjectId.zeroId());
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
index 630b091..e1d1c65 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabase.java
@@ -99,11 +99,6 @@
   }
 
   @Override
-  public Ref getRef(String name) throws IOException {
-    return refDatabase.getRef(name);
-  }
-
-  @Override
   public String toString() {
     return refDatabase.toString();
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
index b85ec89..b666c02 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/MultisiteReplicationPushFilter.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
 import com.google.common.base.Preconditions;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Project;
@@ -21,8 +22,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
 import java.io.IOException;
 import java.util.Collections;
@@ -32,6 +31,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -61,6 +61,7 @@
   @Override
   public List<RemoteRefUpdate> filter(String projectName, List<RemoteRefUpdate> remoteUpdatesList) {
     Set<String> outdatedChanges = new HashSet<>();
+
     try (Repository repository =
         gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
       List<RemoteRefUpdate> filteredRefUpdates =
@@ -106,7 +107,8 @@
     String ref = refUpdate.getSrcRef();
     try {
       if (sharedRefDb.isUpToDate(
-          projectName, SharedRefDatabase.newRef(ref, refUpdate.getNewObjectId()))) {
+          new Project.NameKey(projectName),
+          new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, ref, refUpdate.getNewObjectId()))) {
         return true;
       }
 
@@ -114,12 +116,13 @@
           projectName, refUpdate, ref);
 
       return sharedRefDb.isUpToDate(
-          projectName, SharedRefDatabase.newRef(ref, getNotNullExactRef(repository, ref)));
-    } catch (SharedLockException sle) {
+          new Project.NameKey(projectName),
+          new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, ref, getNotNullExactRef(repository, ref)));
+    } catch (GlobalRefDbLockException gle) {
       String message =
           String.format("%s is locked on shared-refdb and thus will NOT BE replicated", ref);
       repLog.error(message);
-      logger.atSevere().withCause(sle).log(message);
+      logger.atSevere().withCause(gle).log(message);
       return false;
     } catch (IOException ioe) {
       String message =
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
index 78e1cea..9c93793 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanup.java
@@ -14,11 +14,12 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import java.io.IOException;
 
 public class ProjectDeletedSharedDbCleanup implements ProjectDeletedListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -41,8 +42,8 @@
         "Deleting project '%s'. Will perform a cleanup in Shared-Ref database.", projectName);
 
     try {
-      sharedDb.removeProject(projectName);
-    } catch (IOException e) {
+      sharedDb.remove(new Project.NameKey(projectName));
+    } catch (GlobalRefDbSystemError e) {
       validationMetrics.incrementSplitBrain();
       logger.atSevere().withCause(e).log(
           "Project '%s' deleted from GIT but it was not able to cleanup"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
new file mode 100644
index 0000000..3b82582
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdate.java
@@ -0,0 +1,304 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.EventListener;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.IntBlob;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+@Singleton
+public class ProjectVersionRefUpdate implements EventListener {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+  private static final Set<RefUpdate.Result> SUCCESSFUL_RESULTS =
+      ImmutableSet.of(RefUpdate.Result.NEW, RefUpdate.Result.FORCED, RefUpdate.Result.NO_CHANGE);
+
+  public static final String MULTI_SITE_VERSIONING_REF = "refs/multi-site/version";
+  public static final String MULTI_SITE_VERSIONING_VALUE_REF = "refs/multi-site/version/value";
+  public static final Ref NULL_PROJECT_VERSION_REF =
+      new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId());
+
+  private final GitRepositoryManager gitRepositoryManager;
+  private final GitReferenceUpdated gitReferenceUpdated;
+  private final ProjectVersionLogger verLogger;
+
+  protected final SharedRefDatabaseWrapper sharedRefDb;
+
+  @Inject
+  public ProjectVersionRefUpdate(
+      GitRepositoryManager gitRepositoryManager,
+      SharedRefDatabaseWrapper sharedRefDb,
+      GitReferenceUpdated gitReferenceUpdated,
+      ProjectVersionLogger verLogger) {
+    this.gitRepositoryManager = gitRepositoryManager;
+    this.sharedRefDb = sharedRefDb;
+    this.gitReferenceUpdated = gitReferenceUpdated;
+    this.verLogger = verLogger;
+  }
+
+  @Override
+  public void onEvent(Event event) {
+    logger.atFine().log("Processing event type: " + event.type);
+    // Producer of the Event use RefUpdatedEvent to trigger the version update
+    if (!Context.isForwardedEvent() && event instanceof RefUpdatedEvent) {
+      updateProducerProjectVersionUpdate((RefUpdatedEvent) event);
+    }
+  }
+
+  private boolean isSpecialRefName(String refName) {
+    return refName.startsWith(RefNames.REFS_SEQUENCES)
+        || refName.startsWith(RefNames.REFS_STARRED_CHANGES)
+        || refName.equals(MULTI_SITE_VERSIONING_REF);
+  }
+
+  private void updateProducerProjectVersionUpdate(RefUpdatedEvent refUpdatedEvent) {
+    String refName = refUpdatedEvent.getRefName();
+
+    if (isSpecialRefName(refName)) {
+      logger.atFine().log(
+          "Found a special ref name %s, skipping update for %s",
+          refName, refUpdatedEvent.getProjectNameKey().get());
+      return;
+    }
+    try {
+      Project.NameKey projectNameKey = refUpdatedEvent.getProjectNameKey();
+      long newVersion = getCurrentGlobalVersionNumber();
+
+      Optional<RefUpdate> newProjectVersionRefUpdate =
+          updateLocalProjectVersion(projectNameKey, newVersion);
+
+      if (newProjectVersionRefUpdate.isPresent()) {
+        verLogger.log(projectNameKey, newVersion, 0L);
+
+        if (updateSharedProjectVersion(
+            projectNameKey, newProjectVersionRefUpdate.get().getNewObjectId(), newVersion)) {
+          gitReferenceUpdated.fire(projectNameKey, newProjectVersionRefUpdate.get(), null);
+        }
+      } else {
+        logger.atWarning().log(
+            "Ref %s not found on projet %s: skipping project version update",
+            refUpdatedEvent.getRefName(), projectNameKey);
+      }
+    } catch (LocalProjectVersionUpdateException | SharedProjectVersionUpdateException e) {
+      logger.atSevere().withCause(e).log(
+          "Issue encountered when updating version for project "
+              + refUpdatedEvent.getProjectNameKey());
+    }
+  }
+
+  private RefUpdate getProjectVersionRefUpdate(Repository repository, Long version)
+      throws IOException {
+    RefUpdate refUpdate = repository.getRefDatabase().newUpdate(MULTI_SITE_VERSIONING_REF, false);
+    refUpdate.setNewObjectId(getNewId(repository, version));
+    refUpdate.setForceUpdate(true);
+    return refUpdate;
+  }
+
+  private ObjectId getNewId(Repository repository, Long version) throws IOException {
+    ObjectInserter ins = repository.newObjectInserter();
+    ObjectId newId = ins.insert(OBJ_BLOB, Long.toString(version).getBytes(UTF_8));
+    ins.flush();
+    return newId;
+  }
+
+  private boolean updateSharedProjectVersion(
+      Project.NameKey projectNameKey, ObjectId newObjectId, Long newVersion)
+      throws SharedProjectVersionUpdateException {
+
+    Ref sharedRef =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_REF, String.class)
+            .map(
+                (String objectId) ->
+                    new ObjectIdRef.Unpeeled(
+                        Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.fromString(objectId)))
+            .orElse(
+                new ObjectIdRef.Unpeeled(
+                    Ref.Storage.NEW, MULTI_SITE_VERSIONING_REF, ObjectId.zeroId()));
+    Optional<Long> sharedVersion =
+        sharedRefDb
+            .get(projectNameKey, MULTI_SITE_VERSIONING_VALUE_REF, String.class)
+            .map(Long::parseLong);
+
+    try {
+      if (sharedVersion.isPresent() && sharedVersion.get() >= newVersion) {
+        logger.atWarning().log(
+            String.format(
+                "NOT Updating project %s version %s (value=%d) in shared ref-db because is more recent than the local one %s (value=%d) ",
+                projectNameKey.get(),
+                newObjectId,
+                newVersion,
+                sharedRef.getObjectId().getName(),
+                sharedVersion.get()));
+        return false;
+      }
+
+      logger.atFine().log(
+          String.format(
+              "Updating shared project %s version to %s (value=%d)",
+              projectNameKey.get(), newObjectId, newVersion));
+
+      boolean success = sharedRefDb.compareAndPut(projectNameKey, sharedRef, newObjectId);
+      if (!success) {
+        String message =
+            String.format(
+                "Project version blob update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+        logger.atSevere().log(message);
+        throw new SharedProjectVersionUpdateException(message);
+      }
+
+      success =
+          sharedRefDb.compareAndPut(
+              projectNameKey,
+              MULTI_SITE_VERSIONING_VALUE_REF,
+              sharedVersion.map(Object::toString).orElse(null),
+              newVersion.toString());
+      if (!success) {
+        String message =
+            String.format(
+                "Project version update failed for %s. Current value %s, new value: %s",
+                projectNameKey.get(), safeGetObjectId(sharedRef), newObjectId);
+        logger.atSevere().log(message);
+        throw new SharedProjectVersionUpdateException(message);
+      }
+
+      return true;
+    } catch (GlobalRefDbSystemError refDbSystemError) {
+      String message =
+          String.format(
+              "Error while updating shared project version for %s. Current value %s, new value: %s. Error: %s",
+              projectNameKey.get(),
+              sharedRef.getObjectId(),
+              newObjectId,
+              refDbSystemError.getMessage());
+      logger.atSevere().withCause(refDbSystemError).log(message);
+      throw new SharedProjectVersionUpdateException(message);
+    }
+  }
+
+  public Optional<Long> getProjectLocalVersion(String projectName) {
+    try (Repository repository =
+        gitRepositoryManager.openRepository(Project.NameKey.parse(projectName))) {
+      Optional<IntBlob> blob = IntBlob.parse(repository, MULTI_SITE_VERSIONING_REF);
+      if (blob.isPresent()) {
+        Long repoVersion = Integer.toUnsignedLong(blob.get().value());
+        logger.atFine().log("Local project '%s' has version %d", projectName, repoVersion);
+        return Optional.of(repoVersion);
+      }
+    } catch (RepositoryNotFoundException re) {
+      logger.atFine().log("Project '%s' not found", projectName);
+    } catch (IOException e) {
+      logger.atSevere().withCause(e).log("Cannot read local project '%s' version", projectName);
+    }
+    return Optional.empty();
+  }
+
+  public Optional<Long> getProjectRemoteVersion(String projectName) {
+    Optional<String> globalVersion =
+        sharedRefDb.get(
+            Project.NameKey.parse(projectName), MULTI_SITE_VERSIONING_VALUE_REF, String.class);
+    return globalVersion.flatMap(longString -> getLongValueOf(longString));
+  }
+
+  private Object safeGetObjectId(Ref currentRef) {
+    return currentRef == null ? "null" : currentRef.getObjectId();
+  }
+
+  private Optional<Long> getLongValueOf(String longString) {
+    try {
+      return Optional.ofNullable(Long.parseLong(longString));
+    } catch (NumberFormatException e) {
+      logger.atSevere().withCause(e).log(
+          "Unable to parse timestamp value %s into Long", longString);
+      return Optional.empty();
+    }
+  }
+
+  private Optional<RefUpdate> updateLocalProjectVersion(
+      Project.NameKey projectNameKey, long newVersionNumber)
+      throws LocalProjectVersionUpdateException {
+    logger.atFine().log(
+        "Updating local version for project %s with version %d",
+        projectNameKey.get(), newVersionNumber);
+    try (Repository repository = gitRepositoryManager.openRepository(projectNameKey)) {
+      RefUpdate refUpdate = getProjectVersionRefUpdate(repository, newVersionNumber);
+      RefUpdate.Result result = refUpdate.update();
+      if (!isSuccessful(result)) {
+        String message =
+            String.format(
+                "RefUpdate failed with result %s for: project=%s, version=%d",
+                result.name(), projectNameKey.get(), newVersionNumber);
+        logger.atSevere().log(message);
+        throw new LocalProjectVersionUpdateException(message);
+      }
+
+      return Optional.of(refUpdate);
+    } catch (IOException e) {
+      String message = "Cannot create versioning command for " + projectNameKey.get();
+      logger.atSevere().withCause(e).log(message);
+      throw new LocalProjectVersionUpdateException(message);
+    }
+  }
+
+  private long getCurrentGlobalVersionNumber() {
+    return System.currentTimeMillis() / 1000;
+  }
+
+  private Boolean isSuccessful(RefUpdate.Result result) {
+    return SUCCESSFUL_RESULTS.contains(result);
+  }
+
+  public static class LocalProjectVersionUpdateException extends Exception {
+    private static final long serialVersionUID = 7649956232401457023L;
+
+    public LocalProjectVersionUpdateException(String projectName) {
+      super("Cannot update local project version of " + projectName);
+    }
+  }
+
+  public static class SharedProjectVersionUpdateException extends Exception {
+    private static final long serialVersionUID = -9153858177700286314L;
+
+    public SharedProjectVersionUpdateException(String projectName) {
+      super("Cannot update shared project version of " + projectName);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
index e66b3ba..76c1a67 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidator.java
@@ -14,10 +14,10 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
-import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase.nullRef;
-
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.google.common.base.MoreObjects;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
@@ -25,12 +25,12 @@
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import java.io.IOException;
 import java.util.HashMap;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -86,7 +86,8 @@
   public RefUpdate.Result executeRefUpdate(
       RefUpdate refUpdate, NoParameterFunction<RefUpdate.Result> refUpdateFunction)
       throws IOException {
-    if (refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
+    if (isProjectVersionUpdate(refUpdate.getName())
+        || refEnforcement.getPolicy(projectName) == EnforcePolicy.IGNORED) {
       return refUpdateFunction.invoke();
     }
 
@@ -105,6 +106,13 @@
     return null;
   }
 
+  private Boolean isProjectVersionUpdate(String refName) {
+    Boolean isProjectVersionUpdate =
+        refName.equals(ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF);
+    logger.atFine().log("Is project version update? " + isProjectVersionUpdate);
+    return isProjectVersionUpdate;
+  }
+
   private <T extends Throwable> void softFailBasedOnEnforcement(T e, EnforcePolicy policy)
       throws T {
     logger.atWarning().withCause(e).log(
@@ -127,6 +135,11 @@
         updateSharedDbOrThrowExceptionFor(refPairForUpdate);
       }
       return result;
+    } catch (OutOfSyncException e) {
+      logger.atWarning().withCause(e).log(
+          String.format("Local node is out of sync with ref-db: %s", e.getMessage()));
+
+      return RefUpdate.Result.LOCK_FAILURE;
     }
   }
 
@@ -144,8 +157,10 @@
             projectName, refPair.getName(), refPair.putValue);
     boolean succeeded;
     try {
-      succeeded = sharedRefDb.compareAndPut(projectName, refPair.compareRef, refPair.putValue);
-    } catch (IOException e) {
+      succeeded =
+          sharedRefDb.compareAndPut(
+              new Project.NameKey(projectName), refPair.compareRef, refPair.putValue);
+    } catch (GlobalRefDbSystemError e) {
       logger.atWarning().withCause(e).log(
           "Not able to persist the data in Zookeeper for project '{}' and ref '{}', message: {}",
           projectName,
@@ -171,14 +186,17 @@
         String.format("%s-%s", projectName, refName),
         () ->
             lockWrapperFactory.create(
-                projectName, refName, sharedRefDb.lockRef(projectName, refName)));
+                projectName,
+                refName,
+                sharedRefDb.lockRef(new Project.NameKey(projectName), refName)));
 
     RefPair latestRefPair = getLatestLocalRef(refPair);
-    if (sharedRefDb.isUpToDate(projectName, latestRefPair.compareRef)) {
+    if (sharedRefDb.isUpToDate(new Project.NameKey(projectName), latestRefPair.compareRef)) {
       return latestRefPair;
     }
 
-    if (isNullRef(latestRefPair.compareRef) || sharedRefDb.exists(projectName, refName)) {
+    if (isNullRef(latestRefPair.compareRef)
+        || sharedRefDb.exists(new Project.NameKey(projectName), refName)) {
       validationMetrics.incrementSplitBrainPrevention();
 
       softFailBasedOnEnforcement(
@@ -198,6 +216,10 @@
         latestRef == null ? nullRef(refPair.getName()) : latestRef, refPair.putValue);
   }
 
+  private Ref nullRef(String name) {
+    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, name, ObjectId.zeroId());
+  }
+
   protected boolean isSuccessful(RefUpdate.Result result) {
     switch (result) {
       case NEW:
@@ -224,7 +246,7 @@
   }
 
   protected Ref getCurrentRef(String refName) throws IOException {
-    return MoreObjects.firstNonNull(refDb.getRef(refName), SharedRefDatabase.nullRef(refName));
+    return MoreObjects.firstNonNull(refDb.getRef(refName), nullRef(refName));
   }
 
   public static class CloseableSet<T extends AutoCloseable> implements AutoCloseable {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
index e331819..481d288 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ValidationModule.java
@@ -16,19 +16,18 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.extensions.events.ProjectDeletedListener;
 import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Scopes;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.LockWrapper;
+import com.googlesource.gerrit.plugins.multisite.Log4jProjectVersionLogger;
 import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.SharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.NoopSharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.replication.ReplicationExtensionPointModule;
 import com.googlesource.gerrit.plugins.replication.ReplicationPushFilter;
@@ -37,24 +36,18 @@
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final Configuration cfg;
-  private final boolean disableGitRepositoryValidation;
 
-  public ValidationModule(Configuration cfg, boolean disableGitRepositoryValidation) {
+  public ValidationModule(Configuration cfg) {
     this.cfg = cfg;
-    this.disableGitRepositoryValidation = disableGitRepositoryValidation;
   }
 
   @Override
   protected void configure() {
     install(new ReplicationExtensionPointModule());
 
-    DynamicItem.itemOf(binder(), SharedRefDatabase.class);
-    DynamicItem.bind(binder(), SharedRefDatabase.class)
-        .to(NoopSharedRefDatabase.class)
-        .in(Scopes.SINGLETON);
-    logger.atInfo().log("Shared ref-db engine: none");
-
+    bind(SharedRefDatabaseWrapper.class).in(Scopes.SINGLETON);
     bind(SharedRefLogger.class).to(Log4jSharedRefLogger.class);
+    bind(ProjectVersionLogger.class).to(Log4jProjectVersionLogger.class);
     factory(LockWrapper.Factory.class);
 
     factory(MultiSiteRepository.Factory.class);
@@ -64,12 +57,10 @@
     factory(RefUpdateValidator.Factory.class);
     factory(BatchRefUpdateValidator.Factory.class);
 
+    bind(GitRepositoryManager.class).to(MultiSiteGitRepositoryManager.class);
     DynamicItem.bind(binder(), ReplicationPushFilter.class)
         .to(MultisiteReplicationPushFilter.class);
 
-    if (!disableGitRepositoryValidation) {
-      bind(GitRepositoryManager.class).to(MultiSiteGitRepositoryManager.class);
-    }
     if (cfg.getSharedRefDb().getEnforcementRules().isEmpty()) {
       bind(SharedRefEnforcement.class).to(DefaultSharedRefEnforcement.class).in(Scopes.SINGLETON);
     } else {
@@ -77,6 +68,5 @@
           .to(CustomSharedRefEnforcementByProject.class)
           .in(Scopes.SINGLETON);
     }
-    DynamicSet.bind(binder(), ProjectDeletedListener.class).to(ProjectDeletedSharedDbCleanup.class);
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ZkConnectionConfig.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ZkConnectionConfig.java
deleted file mode 100644
index 0339f01..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/ZkConnectionConfig.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation;
-
-import org.apache.curator.RetryPolicy;
-
-public class ZkConnectionConfig {
-
-  public final RetryPolicy curatorRetryPolicy;
-  public final Long transactionLockTimeout;
-
-  public ZkConnectionConfig(RetryPolicy curatorRetryPolicy, Long transactionLockTimeout) {
-    this.curatorRetryPolicy = curatorRetryPolicy;
-    this.transactionLockTimeout = transactionLockTimeout;
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
index e40688f..ef8d9b8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabase.java
@@ -14,30 +14,50 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
 
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.google.gerrit.reviewdb.client.Project;
+import java.util.Optional;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 
-public class NoopSharedRefDatabase implements SharedRefDatabase {
+public class NoopSharedRefDatabase implements GlobalRefDatabase {
+
   @Override
-  public boolean isUpToDate(String project, Ref ref) {
+  public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
     return true;
   }
 
   @Override
-  public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue) {
+  public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
+      throws GlobalRefDbSystemError {
     return true;
   }
 
   @Override
-  public AutoCloseable lockRef(String project, String refName) {
-    return () -> {};
-  }
-
-  @Override
-  public boolean exists(String project, String refName) {
+  public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
+      throws GlobalRefDbSystemError {
     return false;
   }
 
   @Override
-  public void removeProject(String project) {}
+  public AutoCloseable lockRef(Project.NameKey project, String refName)
+      throws GlobalRefDbLockException {
+    return () -> {};
+  }
+
+  @Override
+  public boolean exists(Project.NameKey project, String refName) {
+    return false;
+  }
+
+  @Override
+  public void remove(Project.NameKey project) throws GlobalRefDbSystemError {}
+
+  @Override
+  public <T> Optional<T> get(Project.NameKey project, String refName, Class<T> clazz)
+      throws GlobalRefDbSystemError {
+    return Optional.empty();
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
deleted file mode 100644
index a93efcf..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/SharedRefDatabase.java
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
-
-import java.io.IOException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
-import org.eclipse.jgit.lib.Ref;
-
-public interface SharedRefDatabase {
-
-  /** A null ref that isn't associated to any name. */
-  Ref NULL_REF = nullRef(null);
-
-  /**
-   * Create a new in-memory ref name associated with an NULL object id.
-   *
-   * @param refName ref name
-   * @return the new NULL ref object
-   */
-  static Ref nullRef(String refName) {
-    return new Ref() {
-
-      @Override
-      public String getName() {
-        return refName;
-      }
-
-      @Override
-      public boolean isSymbolic() {
-        return false;
-      }
-
-      @Override
-      public Ref getLeaf() {
-        return null;
-      }
-
-      @Override
-      public Ref getTarget() {
-        return null;
-      }
-
-      @Override
-      public ObjectId getObjectId() {
-        return ObjectId.zeroId();
-      }
-
-      @Override
-      public ObjectId getPeeledObjectId() {
-        return ObjectId.zeroId();
-      }
-
-      @Override
-      public boolean isPeeled() {
-        return false;
-      }
-
-      @Override
-      public Storage getStorage() {
-        return Storage.NEW;
-      }
-    };
-  }
-
-  /**
-   * Create a new in-memory Ref name associated with an objectId.
-   *
-   * @param refName ref name
-   * @param objectId object id
-   */
-  static Ref newRef(String refName, ObjectId objectId) {
-    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId);
-  }
-
-  /**
-   * Verify in shared db if Ref is the most recent
-   *
-   * @param project project name of the ref
-   * @param ref to be checked against shared-ref db
-   * @return true if it is; false otherwise
-   * @throws SharedLockException if there was a problem locking the resource
-   */
-  boolean isUpToDate(String project, Ref ref) throws SharedLockException;
-
-  /**
-   * Compare a reference, and put if it matches.
-   *
-   * <p>Two reference match if and only if they satisfy the following:
-   *
-   * <ul>
-   *   <li>If one reference is a symbolic ref, the other one should be a symbolic ref.
-   *   <li>If both are symbolic refs, the target names should be same.
-   *   <li>If both are object ID refs, the object IDs should be same.
-   * </ul>
-   *
-   * @param project project name of the ref
-   * @param currRef old value to compare to. If the reference is expected to not exist the old value
-   *     has a storage of {@link org.eclipse.jgit.lib.Ref.Storage#NEW} and an ObjectId value of
-   *     {@code null}.
-   * @param newRefValue new reference to store.
-   * @return true if the put was successful; false otherwise.
-   * @throws java.io.IOException the reference cannot be put due to a system error.
-   */
-  boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue) throws IOException;
-
-  /**
-   * Lock a reference for writing.
-   *
-   * @param project project name
-   * @param refName ref to lock
-   * @return lock object
-   * @throws SharedLockException if the lock cannot be obtained
-   */
-  AutoCloseable lockRef(String project, String refName) throws SharedLockException;
-
-  /**
-   * Verify if the DB contains a value for the specific project and ref name
-   *
-   * @param project
-   * @param refName
-   * @return true if the ref exists on the project
-   */
-  boolean exists(String project, String refName);
-
-  /**
-   * Clean project path from SharedRefDatabase
-   *
-   * @param project project name
-   * @throws IOException
-   */
-  void removeProject(String project) throws IOException;
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
deleted file mode 100644
index 1179045..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabase.java
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.inject.Inject;
-import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedLockException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import org.apache.curator.RetryPolicy;
-import org.apache.curator.framework.CuratorFramework;
-import org.apache.curator.framework.recipes.atomic.AtomicValue;
-import org.apache.curator.framework.recipes.atomic.DistributedAtomicValue;
-import org.apache.curator.framework.recipes.locks.InterProcessMutex;
-import org.apache.curator.framework.recipes.locks.Locker;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-
-public class ZkSharedRefDatabase implements SharedRefDatabase {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final CuratorFramework client;
-  private final RetryPolicy retryPolicy;
-
-  private final Long transactionLockTimeOut;
-
-  @Inject
-  public ZkSharedRefDatabase(CuratorFramework client, ZkConnectionConfig connConfig) {
-    this.client = client;
-    this.retryPolicy = connConfig.curatorRetryPolicy;
-    this.transactionLockTimeOut = connConfig.transactionLockTimeout;
-  }
-
-  @Override
-  public boolean isUpToDate(String project, Ref ref) throws SharedLockException {
-    if (!exists(project, ref.getName())) {
-      return true;
-    }
-
-    try {
-      final byte[] valueInZk = client.getData().forPath(pathFor(project, ref.getName()));
-
-      // Assuming this is a delete node NULL_REF
-      if (valueInZk == null) {
-        logger.atInfo().log(
-            "%s:%s not found in Zookeeper, assumed as delete node NULL_REF",
-            project, ref.getName());
-        return false;
-      }
-
-      ObjectId objectIdInSharedRefDb = readObjectId(valueInZk);
-      Boolean isUpToDate = objectIdInSharedRefDb.equals(ref.getObjectId());
-
-      if (!isUpToDate) {
-        logger.atWarning().log(
-            "%s:%s is out of sync: local=%s zk=%s",
-            project, ref.getName(), ref.getObjectId(), objectIdInSharedRefDb);
-      }
-
-      return isUpToDate;
-    } catch (Exception e) {
-      throw new SharedLockException(project, ref.getName(), e);
-    }
-  }
-
-  @Override
-  public void removeProject(String project) throws IOException {
-    try {
-      client.delete().deletingChildrenIfNeeded().forPath("/" + project);
-    } catch (Exception e) {
-      throw new IOException(String.format("Not able to delete project '%s'", project), e);
-    }
-  }
-
-  @Override
-  public boolean exists(String project, String refName) throws ZookeeperRuntimeException {
-    try {
-      return client.checkExists().forPath(pathFor(project, refName)) != null;
-    } catch (Exception e) {
-      throw new ZookeeperRuntimeException("Failed to check if path exists in Zookeeper", e);
-    }
-  }
-
-  @Override
-  public Locker lockRef(String project, String refName) throws SharedLockException {
-    InterProcessMutex refPathMutex =
-        new InterProcessMutex(client, "/locks" + pathFor(project, refName));
-    try {
-      return new Locker(refPathMutex, transactionLockTimeOut, MILLISECONDS);
-    } catch (Exception e) {
-      throw new SharedLockException(project, refName, e);
-    }
-  }
-
-  @Override
-  public boolean compareAndPut(String projectName, Ref oldRef, ObjectId newRefValue)
-      throws IOException {
-
-    final DistributedAtomicValue distributedRefValue =
-        new DistributedAtomicValue(client, pathFor(projectName, oldRef), retryPolicy);
-
-    try {
-      if (oldRef == NULL_REF) {
-        return distributedRefValue.initialize(writeObjectId(newRefValue));
-      }
-      final ObjectId newValue = newRefValue == null ? ObjectId.zeroId() : newRefValue;
-      final AtomicValue<byte[]> newDistributedValue =
-          distributedRefValue.compareAndSet(
-              writeObjectId(oldRef.getObjectId()), writeObjectId(newValue));
-
-      if (!newDistributedValue.succeeded() && refNotInZk(projectName, oldRef)) {
-        return distributedRefValue.initialize(writeObjectId(newRefValue));
-      }
-
-      return newDistributedValue.succeeded();
-    } catch (Exception e) {
-      logger.atWarning().withCause(e).log(
-          "Error trying to perform CAS at path %s", pathFor(projectName, oldRef));
-      throw new IOException(
-          String.format("Error trying to perform CAS at path %s", pathFor(projectName, oldRef)), e);
-    }
-  }
-
-  private boolean refNotInZk(String projectName, Ref oldRef) throws Exception {
-    return client.checkExists().forPath(pathFor(projectName, oldRef)) == null;
-  }
-
-  static String pathFor(String projectName, Ref oldRef) {
-    return pathFor(projectName, oldRef.getName());
-  }
-
-  static String pathFor(String projectName, String refName) {
-    return "/" + projectName + "/" + refName;
-  }
-
-  static ObjectId readObjectId(byte[] value) {
-    return ObjectId.fromString(value, 0);
-  }
-
-  static byte[] writeObjectId(ObjectId value) {
-    return ObjectId.toString(value).getBytes(StandardCharsets.US_ASCII);
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
deleted file mode 100644
index f83b4d9..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkValidationModule.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Scopes;
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.ZookeeperConfig;
-import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import org.apache.curator.framework.CuratorFramework;
-
-public class ZkValidationModule extends AbstractModule {
-
-  private ZookeeperConfig cfg;
-
-  @Inject
-  public ZkValidationModule(Configuration cfg) {
-    this.cfg = new ZookeeperConfig(cfg.getMultiSiteConfig());
-  }
-
-  @Override
-  protected void configure() {
-    DynamicItem.bind(binder(), SharedRefDatabase.class)
-        .to(ZkSharedRefDatabase.class)
-        .in(Scopes.SINGLETON);
-    bind(CuratorFramework.class).toInstance(cfg.buildCurator());
-
-    bind(ZkConnectionConfig.class)
-        .toInstance(
-            new ZkConnectionConfig(cfg.buildCasRetryPolicy(), cfg.getZkInterProcessLockTimeOut()));
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperRuntimeException.java b/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperRuntimeException.java
deleted file mode 100644
index 9f2951b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperRuntimeException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-/** Unable to communicate with Zookeeper */
-public class ZookeeperRuntimeException extends RuntimeException {
-  private static final long serialVersionUID = 1L;
-
-  public ZookeeperRuntimeException(String description, Throwable t) {
-    super(description, t);
-  }
-}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 264c8e9..b6de0a9 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -3,12 +3,13 @@
 the masters happens using the replication plugin and an external message
 broker.
 
-This plugin allows Gerrit to publish and to consume events over a Kafka
+This plugin allows Gerrit to publish and to consume events over a
 message broker for aligning with the other masters over different sites.
 
 The masters must be:
 
-* migrated to NoteDb
+* events-broker library must be installed as a library module in the
+  `$GERRIT_SITE/lib` directory of all the masters
 * connected to the same message broker
 * behind a load balancer (e.g., HAProxy)
 
@@ -57,34 +58,12 @@
 
 Prerequisites:
 
-* Kafka message broker deployed in a multi-master setup across all the sites
+* Message broker deployed in a multi-master setup across all the sites
 
 For the masters:
 
 * Install and configure @PLUGIN@ plugin
 
-Here is an example of minimal @PLUGIN@.config:
-
-For all the masters on all the sites:
-
-```
-[kafka]
-  bootstrapServers = kafka-1:9092,kafka-2:9092,kafka-3:9092
-  eventTopic = gerrit_index
-
-[kafka "publisher"]
-  enable = true
-  indexEventTopic = gerrit_index
-  streamEventTopic = gerrit_stream
-  cacheEvictionEventTopic = gerrit_cache_eviction
-
-[kafka "subscriber"]
-  enable = true
-  pollingIntervalMs = 1000
-  autoCommitIntervalMs = 1000
-```
-
-
 For further information and supported options, refer to [config](config.md)
 documentation.
 
@@ -95,21 +74,25 @@
 ### Broker message publisher
 * Broker message published count
 
-`metric=multi_site/broker/broker_message_publisher_counter/broker_msg_publisher_counter, type=com.codahale.metrics.Meter`
+`metric=plugins/multi-site/multi_site/broker/broker_message_publisher_counter/broker_msg_publisher_counter, type=com.codahale.metrics.Meter`
 
 * Broker failed to publish message count
 
-`metric=multi_site/broker/broker_message_publisher_failure_counter/broker_msg_publisher_failure_counter, type=com.codahale.metrics.Meter`
+`metric=plugins/multi-site/multi_site/broker/broker_message_publisher_failure_counter/broker_msg_publisher_failure_counter, type=com.codahale.metrics.Meter`
 
 ### Message subscriber
 * Subscriber message consumed count
 
-`multi_site/subscriber/subscriber_message_consumer_counter/subscriber_msg_consumer_counter, type=com.codahale.metrics.Meter`
+`metric=plugins/multi-site/multi_site/subscriber/subscriber_message_consumer_counter/subscriber_msg_consumer_counter, type=com.codahale.metrics.Meter`
 
 * Subscriber failed to consume message count
 
-`multi_site/subscriber/subscriber_message_consumer_failure_counter/subscriber_msg_consumer_failure_counter, type=com.codahale.metrics.Meter`
+`metric=plugins/multi-site/multi_site/subscriber/subscriber_message_consumer_failure_counter/subscriber_msg_consumer_failure_counter, type=com.codahale.metrics.Meter`
 
 * Subscriber failed to poll messages count
 
-`multi_site/subscriber/subscriber_message_consumer_poll_failure_counter/subscriber_msg_consumer_poll_failure_counter, type=com.codahale.metrics.Meter`
+`metric=plugins/multi-site/multi_site/subscriber/subscriber_message_consumer_failure_counter/subscriber_msg_consumer_poll_failure_counter, type=com.codahale.metrics.Meter`
+
+* Subscriber replication lag (sec behind the producer)
+
+`metric=site/multi_site/subscriber/subscriber_replication_status/sec_behind, type=com.google.gerrit.metrics.dropwizard.CallbackMetricImpl`
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 227d0e8..662f46e 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -3,60 +3,8 @@
 =========================
 
 The @PLUGIN@ plugin must be installed as a library module in the
-`$GERRIT_SITE/lib` folder of all the instances and the following fields should
-be specified in the `$site_path/etc/@PLUGIN@.config` file:
-
-File '@PLUGIN@.config'
---------------------
-
-## Sample configuration.
-
-```
-[kafka]
-  bootstrapServers = kafka-1:9092,kafka-2:9092,kafka-3:9092
-
-  indexEventTopic = gerrit_index
-  streamEventTopic = gerrit_stream
-  cacheEventTopic = gerrit_cache_eviction
-  projectListEventTopic = gerrit_project_list
-
-[kafka "publisher"]
-  enabled = true
-
-  indexEventEnabled = true
-  cacheEventEnabled = true
-  projectListEventEnabled = true
-  streamEventEnabled = true
-
-  KafkaProp-compressionType = none
-  KafkaProp-deliveryTimeoutMs = 60000
-
-[kafka "subscriber"]
-  enabled = true
-  pollingIntervalMs = 1000
-
-  KafkaProp-enableAutoCommit = true
-  KafkaProp-autoCommitIntervalMs = 1000
-  KafkaProp-autoCommitIntervalMs = 5000
-
-  indexEventEnabled = true
-  cacheEventEnabled = true
-  projectListEventEnabled = true
-  streamEventEnabled = true
-
-[ref-database "zookeeper"]
-  connectString = "localhost:2181"
-  rootNode = "/gerrit/multi-site"
-  sessionTimeoutMs = 1000
-  connectionTimeoutMs = 1000
-  retryPolicyBaseSleepTimeMs = 1000
-  retryPolicyMaxSleepTimeMs = 3000
-  retryPolicyMaxRetries = 3
-  casRetryPolicyBaseSleepTimeMs = 100
-  casRetryPolicyMaxSleepTimeMs = 100
-  casRetryPolicyMaxRetries = 3
-  transactionLockTimeoutMs = 1000
-```
+`$GERRIT_SITE/lib` folder of all the instances. Configuration should
+be specified in the `$site_path/etc/@PLUGIN@.config` file.
 
 ## Configuration parameters
 
@@ -106,83 +54,22 @@
 :   The time interval in milliseconds between subsequent auto-retries.
     Defaults to 30000 (30 seconds).
 
-```kafka.bootstrapServers```
-:	  List of Kafka broker hosts (host:port) to use for publishing events to the message
-    broker
-
-```kafka.indexEventTopic```
-:   Name of the Kafka topic to use for publishing indexing events
+```broker.indexEventTopic```
+:   Name of the topic to use for publishing indexing events
     Defaults to GERRIT.EVENT.INDEX
 
-```kafka.streamEventTopic```
-:   Name of the Kafka topic to use for publishing stream events
+```broker.streamEventTopic```
+:   Name of the topic to use for publishing stream events
     Defaults to GERRIT.EVENT.STREAM
 
-```kafka.cacheEventTopic```
-:   Name of the Kafka topic to use for publishing cache eviction events
+```broker.cacheEventTopic```
+:   Name of the topic to use for publishing cache eviction events
     Defaults to GERRIT.EVENT.CACHE
 
-```kafka.projectListEventTopic```
-:   Name of the Kafka topic to use for publishing cache eviction events
+```broker.projectListEventTopic```
+:   Name of the topic to use for publishing cache eviction events
     Defaults to GERRIT.EVENT.PROJECT.LIST
 
-```kafka.publisher.indexEventEnabled```
-:   Enable publication of index events, ignored when `kafka.publisher.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.publisher.cacheEventEnabled```
-:   Enable publication of cache events, ignored when `kafka.publisher.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.publisher.projectListEventEnabled```
-:   Enable publication of project list events, ignored when `kafka.publisher.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.publisher.streamEventEnabled```
-:   Enable publication of stream events, ignored when `kafka.publisher.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.subscriber.enabled```
-:   Enable consuming of events from Kafka
-    Defaults: false
-
-```kafka.subscriber.indexEventEnabled```
-:   Enable consumption of index events, ignored when `kafka.subscriber.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.subscriber.cacheEventEnabled```
-:   Enable consumption of cache events, ignored when `kafka.subscriber.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.subscriber.projectListEventEnabled```
-:   Enable consumption of project list events, ignored when `kafka.subscriber.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.subscriber.streamEventEnabled```
-:   Enable consumption of stream events, ignored when `kafka.subscriber.enabled`
-    is false
-
-    Defaults: true
-
-```kafka.subscriber.pollingIntervalMs```
-:   Polling interval in milliseconds for checking incoming events
-
-    Defaults: 1000
-
 ```ref-database.enabled```
 :   Enable the use of a shared ref-database
     Defaults: true
@@ -213,88 +100,4 @@
 
     Relax the alignment with the shared ref-database for AProject on refs/heads/feature.
 
-    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
-
-```ref-database.zookeeper.connectString```
-:   Connection string to Zookeeper
-
-```ref-database.zookeeper.rootNode```
-:   Root node to use in Zookeeper to store/retrieve information
-
-    Defaults: "/gerrit/multi-site"
-
-
-```ref-database.zookeeper.sessionTimeoutMs```
-:   Zookeeper session timeout in milliseconds
-
-    Defaults: 1000
-
-```ref-database.zookeeper.connectionTimeoutMs```
-:   Zookeeper connection timeout in milliseconds
-
-    Defaults: 1000
-
-```ref-database.zookeeper.retryPolicyBaseSleepTimeMs```
-:   Configuration for the base sleep timeout in milliseconds of the
-    BoundedExponentialBackoffRetry policy used for the Zookeeper connection
-
-    Defaults: 1000
-
-```ref-database.zookeeper.retryPolicyMaxSleepTimeMs```
-:   Configuration for the maximum sleep timeout in milliseconds of the
-    BoundedExponentialBackoffRetry policy used for the Zookeeper connection
-
-    Defaults: 3000
-
-```ref-database.zookeeper.retryPolicyMaxRetries```
-:   Configuration for the maximum number of retries of the
-    BoundedExponentialBackoffRetry policy used for the Zookeeper connection
-
-    Defaults: 3
-
-```ref-database.zookeeper.casRetryPolicyBaseSleepTimeMs```
-:   Configuration for the base sleep timeout in milliseconds of the
-    BoundedExponentialBackoffRetry policy used for the Compare and Swap
-    operations on Zookeeper
-
-    Defaults: 1000
-
-```ref-database.zookeeper.casRetryPolicyMaxSleepTimeMs```
-:   Configuration for the maximum sleep timeout in milliseconds of the
-    BoundedExponentialBackoffRetry policy used for the Compare and Swap
-    operations on Zookeeper
-
-    Defaults: 3000
-
-```ref-database.zookeeper.casRetryPolicyMaxRetries```
-:   Configuration for the maximum number of retries of the
-    BoundedExponentialBackoffRetry policy used for the Compare and Swap
-    operations on Zookeeper
-
-    Defaults: 3
-
-```ref-database.zookeeper.transactionLockTimeoutMs```
-:   Configuration for the Zookeeper Lock timeout (in milliseconds) used when reading data
-    from Zookeeper, applying the git local changes and writing the new objectId
-    into Zookeeper
-
-    Defaults: 1000
-
-#### Custom kafka properties:
-
-In addition to the above settings, custom Kafka properties can be explicitly set
-for `publisher` and `subscriber`.
-In order to be acknowledged, these properties need to be prefixed with the
-`KafkaProp-` prefix and be formatted using camel case, as follows: `KafkaProp-yourPropertyValue`
-
-For example, if you want to set the `auto.commit.interval.ms` property for
-consumers, you need to configure this property as `KafkaProp-autoCommitIntervalMs`.
-
-**NOTE**: custom Kafka properties will be ignored when the relevant subsection is
-disabled (i.e. `kafka.subscriber.enabled` and/or `kafka.publisher.enabled` are
-set to `false`).
-
-The complete list of available settings can be found directly in the kafka website:
-
-* **Publisher**: https://kafka.apache.org/documentation/#producerconfigs
-* **Subscriber**: https://kafka.apache.org/documentation/#consumerconfigs
+    Defaults: No rules = All projects are REQUIRED to be consistent on all refs.
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/ModuleTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/ModuleTest.java
index 8b7a17b..2df60dd 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/ModuleTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/ModuleTest.java
@@ -17,7 +17,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.server.config.SitePaths;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerModule;
 import java.io.File;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -37,16 +36,13 @@
   @Mock(answer = Answers.RETURNS_DEEP_STUBS)
   private Configuration configMock;
 
-  @Mock private NoteDbStatus noteDb;
-  @Mock private BrokerModule brokerModule;
-
   @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
 
   private Module module;
 
   @Before
   public void setup() {
-    module = new Module(configMock, noteDb, brokerModule);
+    module = new Module(configMock);
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapperTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapperTest.java
index 2abc7b0..92fa101 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapperTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/BrokerApiWrapperTest.java
@@ -5,9 +5,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.gerritforge.gerrit.eventbroker.BrokerApi;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.events.Event;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
+import com.googlesource.gerrit.plugins.multisite.MessageLogger;
+import java.util.UUID;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -19,26 +21,30 @@
   @Mock private BrokerMetrics brokerMetrics;
   @Mock private BrokerApi brokerApi;
   @Mock Event event;
+  @Mock MessageLogger msgLog;
+  private UUID instanceId = UUID.randomUUID();
+  private String topic = "index";
 
   private BrokerApiWrapper objectUnderTest;
 
   @Before
   public void setUp() {
     objectUnderTest =
-        new BrokerApiWrapper(DynamicItem.itemOf(BrokerApi.class, brokerApi), brokerMetrics);
+        new BrokerApiWrapper(
+            DynamicItem.itemOf(BrokerApi.class, brokerApi), brokerMetrics, msgLog, instanceId);
   }
 
   @Test
   public void shouldIncrementBrokerMetricCounterWhenMessagePublished() {
     when(brokerApi.send(any(), any())).thenReturn(true);
-    objectUnderTest.send(EventTopic.INDEX_TOPIC.topic(), event);
+    objectUnderTest.send(topic, event);
     verify(brokerMetrics, only()).incrementBrokerPublishedMessage();
   }
 
   @Test
   public void shouldIncrementBrokerFailedMetricCounterWhenMessagePublishingFailed() {
     when(brokerApi.send(any(), any())).thenReturn(false);
-    objectUnderTest.send(EventTopic.INDEX_TOPIC.topic(), event);
+    objectUnderTest.send(topic, event);
     verify(brokerMetrics, only()).incrementBrokerFailedToPublishMessage();
   }
 
@@ -47,7 +53,7 @@
     when(brokerApi.send(any(), any()))
         .thenThrow(new RuntimeException("Unexpected runtime exception"));
     try {
-      objectUnderTest.send(EventTopic.INDEX_TOPIC.topic(), event);
+      objectUnderTest.send(topic, event);
     } catch (RuntimeException e) {
       // expected
     }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java
deleted file mode 100644
index c751a84..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/broker/kafka/BrokerPublisherTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.broker.kafka;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.google.gerrit.extensions.client.ChangeKind;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.data.AccountAttribute;
-import com.google.gerrit.server.data.ApprovalAttribute;
-import com.google.gerrit.server.events.CommentAddedEvent;
-import com.google.gerrit.server.events.Event;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.googlesource.gerrit.plugins.multisite.MessageLogger;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerMetrics;
-import com.googlesource.gerrit.plugins.multisite.broker.BrokerSession;
-import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import java.util.UUID;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class BrokerPublisherTest {
-
-  @Mock private BrokerMetrics brokerMetrics;
-  @Mock private BrokerSession session;
-  @Mock private MessageLogger msgLog;
-  private BrokerPublisher publisher;
-
-  private Gson gson = new GsonProvider().get();
-
-  private String accountName = "Foo Bar";
-  private String accountEmail = "foo@bar.com";
-  private String accountUsername = "foobar";
-  private String approvalType = ChangeKind.REWORK.toString();
-
-  private String approvalDescription = "ApprovalDescription";
-  private String approvalValue = "+2";
-  private String oldApprovalValue = "+1";
-  private Long approvalGrantedOn = 123L;
-  private String commentDescription = "Patch Set 1: Code-Review+2";
-  private String projectName = "project";
-  private String refName = "refs/heads/master";
-  private String changeId = "Iabcd1234abcd1234abcd1234abcd1234abcd1234";
-  private Long eventCreatedOn = 123L;
-
-  @Before
-  public void setUp() {
-    publisher = new BrokerPublisher(session, gson, UUID.randomUUID(), msgLog);
-  }
-
-  @Test
-  public void shouldSerializeCommentAddedEvent() {
-
-    Event event = createSampleEvent();
-
-    String expectedSerializedCommentEvent =
-        "{\"author\": {\"name\": \""
-            + accountName
-            + "\",\"email\": \""
-            + accountEmail
-            + "\",\"username\": \""
-            + accountUsername
-            + "\"},\"approvals\": [{\"type\": \""
-            + approvalType
-            + "\",\"description\": \""
-            + approvalDescription
-            + "\",\"value\": \""
-            + approvalValue
-            + "\",\"oldValue\": \""
-            + oldApprovalValue
-            + "\",\"grantedOn\": "
-            + approvalGrantedOn
-            + ",\"by\": {\"name\": \""
-            + accountName
-            + "\",\"email\": \""
-            + accountEmail
-            + "\",\"username\": \""
-            + accountUsername
-            + "\"}}],\"comment\": \""
-            + commentDescription
-            + "\",\""
-            + projectName
-            + "\": {\"name\": \""
-            + projectName
-            + "\"},\"refName\": \""
-            + refName
-            + "\",\"changeKey\": {\"id\": \""
-            + changeId
-            + "\""
-            + "  },\"type\": \"comment-added\",\"eventCreatedOn\": "
-            + eventCreatedOn
-            + "}";
-
-    JsonObject expectedCommentEventJsonObject =
-        gson.fromJson(expectedSerializedCommentEvent, JsonElement.class).getAsJsonObject();
-
-    assertThat(publisher.eventToJson(event).equals(expectedCommentEventJsonObject)).isTrue();
-  }
-
-  @Test
-  public void shouldLogEventPublishedMessageWhenPublishingSucceed() {
-    Event event = createSampleEvent();
-    when(session.publish(any(), any())).thenReturn(true);
-    publisher.publish(EventTopic.INDEX_TOPIC.topic(), event);
-    verify(msgLog, only()).log(any(), any());
-  }
-
-  @Test
-  public void shouldSkipEventPublishedLoggingWhenMessagePublishigFailed() {
-    Event event = createSampleEvent();
-    when(session.publish(any(), any())).thenReturn(false);
-
-    publisher.publish(EventTopic.INDEX_TOPIC.topic(), event);
-    verify(msgLog, never()).log(any(), any());
-  }
-
-  private Event createSampleEvent() {
-    final Change change =
-        new Change(
-            new Change.Key(changeId),
-            new Change.Id(1),
-            new Account.Id(1),
-            new Branch.NameKey(projectName, refName),
-            TimeUtil.nowTs());
-
-    CommentAddedEvent event = new CommentAddedEvent(change);
-    AccountAttribute accountAttribute = new AccountAttribute();
-    accountAttribute.email = accountEmail;
-    accountAttribute.name = accountName;
-    accountAttribute.username = accountUsername;
-
-    event.eventCreatedOn = eventCreatedOn;
-    event.approvals =
-        () -> {
-          ApprovalAttribute approvalAttribute = new ApprovalAttribute();
-          approvalAttribute.value = approvalValue;
-          approvalAttribute.oldValue = oldApprovalValue;
-          approvalAttribute.description = approvalDescription;
-          approvalAttribute.by = accountAttribute;
-          approvalAttribute.type = ChangeKind.REWORK.toString();
-          approvalAttribute.grantedOn = approvalGrantedOn;
-
-          return new ApprovalAttribute[] {approvalAttribute};
-        };
-
-    event.author = () -> accountAttribute;
-    event.comment = commentDescription;
-
-    return event;
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
new file mode 100644
index 0000000..ead3189
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/consumer/SubscriberMetricsTest.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.consumer;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.gerritforge.gerrit.eventbroker.EventMessage;
+import com.google.common.base.Suppliers;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.data.RefUpdateAttribute;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate;
+import java.util.Optional;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SubscriberMetricsTest {
+  private static final String A_TEST_PROJECT_NAME = "test-project";
+  private static final Project.NameKey A_TEST_PROJECT_NAME_KEY =
+      Project.nameKey(A_TEST_PROJECT_NAME);
+
+  @Mock private SharedRefDatabaseWrapper sharedRefDb;
+  @Mock private GitReferenceUpdated gitReferenceUpdated;
+  @Mock private MetricMaker metricMaker;
+  @Mock private ProjectVersionLogger verLogger;
+  @Mock private ProjectVersionRefUpdate projectVersionRefUpdate;
+  private SubscriberMetrics metrics;
+  private EventMessage.Header msgHeader;
+
+  @Before
+  public void setup() throws Exception {
+    msgHeader = new EventMessage.Header(UUID.randomUUID(), UUID.randomUUID());
+    metrics = new SubscriberMetrics(metricMaker, projectVersionRefUpdate, verLogger);
+  }
+
+  @Test
+  public void shouldLogProjectVersionWhenReceivingRefUpdatedEventWithoutLag() {
+    Optional<Long> globalRefDbVersion = Optional.of(System.currentTimeMillis() / 1000);
+    when(projectVersionRefUpdate.getProjectRemoteVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+    when(projectVersionRefUpdate.getProjectLocalVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+
+    EventMessage eventMessage = new EventMessage(msgHeader, newRefUpdateEvent());
+
+    metrics.updateReplicationStatusMetrics(eventMessage);
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, globalRefDbVersion.get(), 0);
+  }
+
+  @Test
+  public void shouldLogProjectVersionWhenReceivingRefUpdatedEventWithALag() {
+    Optional<Long> globalRefDbVersion = Optional.of(System.currentTimeMillis() / 1000);
+    long replicationLag = 60;
+    when(projectVersionRefUpdate.getProjectRemoteVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion.map(ts -> ts + replicationLag));
+    when(projectVersionRefUpdate.getProjectLocalVersion(A_TEST_PROJECT_NAME))
+        .thenReturn(globalRefDbVersion);
+
+    EventMessage eventMessage = new EventMessage(msgHeader, newRefUpdateEvent());
+
+    metrics.updateReplicationStatusMetrics(eventMessage);
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, globalRefDbVersion.get(), replicationLag);
+  }
+
+  private RefUpdatedEvent newRefUpdateEvent() {
+    RefUpdateAttribute refUpdate = new RefUpdateAttribute();
+    refUpdate.project = A_TEST_PROJECT_NAME;
+    refUpdate.refName = "refs/heads/foo";
+    refUpdate.newRev = "591727cfec5174368a7829f79741c41683d84c89";
+    RefUpdatedEvent refUpdateEvent = new RefUpdatedEvent();
+    refUpdateEvent.refUpdate = Suppliers.ofInstance(refUpdate);
+    return refUpdateEvent;
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
similarity index 96%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventRouterTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
index 23cbd4d..9292262 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/CacheEvictionEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/CacheEvictionEventRouterTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
+package com.googlesource.gerrit.plugins.multisite.event;
 
 import static org.mockito.Mockito.verify;
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
similarity index 98%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventRouterTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
index 81e832a..bf6f043 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/IndexEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/IndexEventRouterTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
+package com.googlesource.gerrit.plugins.multisite.event;
 
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectListUpdateRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/ProjectListUpdateRouterTest.java
similarity index 95%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectListUpdateRouterTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/event/ProjectListUpdateRouterTest.java
index 00a239b..93daf92 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/ProjectListUpdateRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/ProjectListUpdateRouterTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
+package com.googlesource.gerrit.plugins.multisite.event;
 
 import static org.mockito.Mockito.verify;
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventRouterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
similarity index 96%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventRouterTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
index ee79e34..31afa79 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/StreamEventRouterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/event/StreamEventRouterTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
+package com.googlesource.gerrit.plugins.multisite.event;
 
 import static org.mockito.Mockito.verify;
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
index 3b01c61..5c36ada 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedEventHandlerTest.java
@@ -19,11 +19,11 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.events.EventDispatcher;
 import com.google.gerrit.server.events.ProjectCreatedEvent;
 import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gwtorm.server.OrmException;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -81,7 +81,7 @@
             (Answer<Void>)
                 invocation -> {
                   assertThat(Context.isForwardedEvent()).isTrue();
-                  throw new OrmException("someMessage");
+                  throw new StorageException("someMessage");
                 })
         .when(dispatcherMock)
         .postEvent(event);
@@ -89,8 +89,8 @@
     assertThat(Context.isForwardedEvent()).isFalse();
     try {
       handler.dispatch(event);
-      fail("should have throw an OrmException");
-    } catch (OrmException e) {
+      fail("should have throw an StorageException");
+    } catch (StorageException e) {
       assertThat(e.getMessage()).isEqualTo("someMessage");
     }
     assertThat(Context.isForwardedEvent()).isFalse();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java
index 2793253..0a910c5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/forwarder/ForwardedIndexChangeHandlerTest.java
@@ -24,17 +24,15 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.gerrit.exceptions.StorageException;
 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.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.util.Providers;
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.forwarder.ForwardedIndexingHandler.Operation;
 import com.googlesource.gerrit.plugins.multisite.forwarder.events.ChangeIndexEvent;
@@ -60,10 +58,8 @@
   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 DO_NOT_THROW_STORAGE_EXCEPTION = false;
+  private static final boolean THROW_STORAGE_EXCEPTION = true;
   private static final boolean CHANGE_UP_TO_DATE = true;
   private static final boolean CHANGE_OUTDATED = false;
 
@@ -71,7 +67,6 @@
   @Mock private ChangeIndexer indexerMock;
   @Mock private OneOffRequestContext ctxMock;
   @Mock private ManualRequestContext manualRequestContextMock;
-  @Mock private ReviewDb dbMock;
   @Mock private ChangeNotes changeNotes;
   @Mock private Configuration configurationMock;
   @Mock private Configuration.Index index;
@@ -87,7 +82,6 @@
   @Before
   public void setUp() throws Exception {
     when(ctxMock.open()).thenReturn(manualRequestContextMock);
-    when(manualRequestContextMock.getReviewDbProvider()).thenReturn(Providers.of(dbMock));
     id = new Change.Id(TEST_CHANGE_NUMBER);
     change = new Change(null, id, null, null, TimeUtil.nowTs());
     when(changeNotes.getChange()).thenReturn(change);
@@ -103,7 +97,7 @@
   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
@@ -111,7 +105,7 @@
     setupChangeAccessRelatedMocks(CHANGE_EXISTS, CHANGE_OUTDATED);
     handler.index(
         TEST_CHANGE_ID, Operation.INDEX, Optional.of(new ChangeIndexEvent("foo", 1, false)));
-    verify(indexerMock, times(1)).index(any(ReviewDb.class), any(Change.class));
+    verify(indexerMock, times(1)).index(any(Change.class));
   }
 
   @Test
@@ -125,22 +119,13 @@
     setupChangeAccessRelatedMocks(CHANGE_DOES_NOT_EXIST, CHANGE_OUTDATED);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
     verify(indexerMock, never()).delete(id);
-    verify(indexerMock, never())
-        .index(any(ReviewDb.class), any(Project.NameKey.class), any(Change.Id.class));
+    verify(indexerMock, never()).index(any(Project.NameKey.class), any(Change.Id.class));
   }
 
   @Test
-  public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception {
-    setupChangeAccessRelatedMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION, CHANGE_UP_TO_DATE);
-    exception.expect(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);
-    exception.expect(IOException.class);
+  public void indexerThrowsStorageExceptionTryingToIndexChange() throws Exception {
+    setupChangeAccessRelatedMocks(CHANGE_EXISTS, THROW_STORAGE_EXCEPTION, CHANGE_UP_TO_DATE);
+    exception.expect(StorageException.class);
     handler.index(TEST_CHANGE_ID, Operation.INDEX, Optional.empty());
   }
 
@@ -156,13 +141,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
@@ -175,7 +160,7 @@
                   throw new IOException("someMessage");
                 })
         .when(indexerMock)
-        .index(any(ReviewDb.class), any(Change.class));
+        .index(any(Change.class));
 
     assertThat(Context.isForwardedEvent()).isFalse();
     try {
@@ -186,36 +171,22 @@
     }
     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);
+    setupChangeAccessRelatedMocks(changeExist, DO_NOT_THROW_STORAGE_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(ctxMock).open();
-    }
-
+      boolean changeExists, boolean storageException, boolean changeIsUpToDate)
+      throws StorageException {
     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));
+      if (storageException) {
+        doThrow(new StorageException("io-error")).when(indexerMock).index(any(Change.class));
       }
     }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfigurationTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfigurationTest.java
deleted file mode 100644
index 84e1dd8..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/KafkaConfigurationTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration.ENABLE_KEY;
-import static com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration.KAFKA_PROPERTY_PREFIX;
-import static com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration.KAFKA_SECTION;
-import static com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration.KafkaPublisher.KAFKA_PUBLISHER_SUBSECTION;
-import static com.googlesource.gerrit.plugins.multisite.kafka.KafkaConfiguration.KafkaSubscriber.KAFKA_SUBSCRIBER_SUBSECTION;
-
-import com.googlesource.gerrit.plugins.multisite.Configuration;
-import com.googlesource.gerrit.plugins.multisite.forwarder.events.EventTopic;
-import org.eclipse.jgit.lib.Config;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class KafkaConfigurationTest {
-
-  private Config globalPluginConfig;
-  private Configuration multiSiteConfig;
-
-  @Before
-  public void setup() {
-    globalPluginConfig = new Config();
-    multiSiteConfig = new Configuration(globalPluginConfig, new Config());
-  }
-
-  private KafkaConfiguration getConfiguration() {
-    return new KafkaConfiguration(multiSiteConfig);
-  }
-
-  @Test
-  public void kafkaSubscriberPropertiesAreSetWhenSectionIsEnabled() {
-    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, true);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
-
-    assertThat(property.equals(kafkaPropertyValue)).isTrue();
-  }
-
-  @Test
-  public void kafkaSubscriberPropertiesAreNotSetWhenSectionIsDisabled() {
-    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, false);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
-
-    assertThat(property).isNull();
-  }
-
-  @Test
-  public void kafkaSubscriberPropertiesAreIgnoredWhenPrefixIsNotSet() {
-    final String kafkaPropertyName = "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, ENABLE_KEY, true);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_SUBSCRIBER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaSubscriber().getProperty("foo.bar.baz");
-
-    assertThat(property).isNull();
-  }
-
-  @Test
-  public void kafkaPublisherPropertiesAreSetWhenSectionIsEnabled() {
-    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, true);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
-
-    assertThat(property.equals(kafkaPropertyValue)).isTrue();
-  }
-
-  @Test
-  public void kafkaPublisherPropertiesAreIgnoredWhenPrefixIsNotSet() {
-    final String kafkaPropertyName = "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, true);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
-
-    assertThat(property).isNull();
-  }
-
-  @Test
-  public void kafkaPublisherPropertiesAreNotSetWhenSectionIsDisabled() {
-    final String kafkaPropertyName = KAFKA_PROPERTY_PREFIX + "fooBarBaz";
-    final String kafkaPropertyValue = "aValue";
-    globalPluginConfig.setBoolean(KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, ENABLE_KEY, false);
-    globalPluginConfig.setString(
-        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, kafkaPropertyName, kafkaPropertyValue);
-
-    final String property = getConfiguration().kafkaPublisher().getProperty("foo.bar.baz");
-
-    assertThat(property).isNull();
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicAliasForIndexTopic() {
-    setKafkaTopicAlias("indexEventTopic", "gerrit_index");
-    final String property = getConfiguration().getKafka().getTopicAlias(EventTopic.INDEX_TOPIC);
-
-    assertThat(property).isEqualTo("gerrit_index");
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicAliasForStreamEventTopic() {
-    setKafkaTopicAlias("streamEventTopic", "gerrit_stream_events");
-    final String property =
-        getConfiguration().getKafka().getTopicAlias(EventTopic.STREAM_EVENT_TOPIC);
-
-    assertThat(property).isEqualTo("gerrit_stream_events");
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicAliasForProjectListEventTopic() {
-    setKafkaTopicAlias("projectListEventTopic", "gerrit_project_list");
-    final String property =
-        getConfiguration().getKafka().getTopicAlias(EventTopic.PROJECT_LIST_TOPIC);
-
-    assertThat(property).isEqualTo("gerrit_project_list");
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicAliasForCacheEventTopic() {
-    setKafkaTopicAlias("cacheEventTopic", "gerrit_cache");
-    final String property = getConfiguration().getKafka().getTopicAlias(EventTopic.CACHE_TOPIC);
-
-    assertThat(property).isEqualTo("gerrit_cache");
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicEnabledForCacheEventTopic() {
-    setKafkaTopicEnabled("cacheEventEnabled", false);
-    final Boolean property =
-        getConfiguration().kafkaPublisher().enabledEvent(EventTopic.CACHE_TOPIC);
-    assertThat(property).isFalse();
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicEnabledForIndexTopic() {
-    setKafkaTopicEnabled("indexEventEnabled", false);
-    final Boolean property =
-        getConfiguration().kafkaPublisher().enabledEvent(EventTopic.INDEX_TOPIC);
-    assertThat(property).isFalse();
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicEnabledForStreamEventTopic() {
-    setKafkaTopicEnabled("streamEventEnabled", false);
-    final Boolean property =
-        getConfiguration().kafkaPublisher().enabledEvent(EventTopic.STREAM_EVENT_TOPIC);
-    assertThat(property).isFalse();
-  }
-
-  @Test
-  public void shouldReturnKafkaTopicEnabledForProjectListEventTopic() {
-    setKafkaTopicEnabled("projectListEventEnabled", false);
-    final Boolean property =
-        getConfiguration().kafkaPublisher().enabledEvent(EventTopic.PROJECT_LIST_TOPIC);
-    assertThat(property).isFalse();
-  }
-
-  private void setKafkaTopicAlias(String topicKey, String topic) {
-    globalPluginConfig.setString(KAFKA_SECTION, null, topicKey, topic);
-  }
-
-  private void setKafkaTopicEnabled(String topicEnabledKey, Boolean isEnabled) {
-    globalPluginConfig.setBoolean(
-        KAFKA_SECTION, KAFKA_PUBLISHER_SUBSECTION, topicEnabledKey, isEnabled);
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java
deleted file mode 100644
index 239b586..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/kafka/consumer/KafkaEventDeserializerTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.kafka.consumer;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.gson.Gson;
-import com.googlesource.gerrit.plugins.multisite.broker.GsonProvider;
-import com.googlesource.gerrit.plugins.multisite.consumer.SourceAwareEventWrapper;
-import java.util.UUID;
-import org.junit.Before;
-import org.junit.Test;
-
-public class KafkaEventDeserializerTest {
-  private KafkaEventDeserializer deserializer;
-
-  @Before
-  public void setUp() {
-    final Gson gson = new GsonProvider().get();
-    deserializer = new KafkaEventDeserializer(gson);
-  }
-
-  @Test
-  public void kafkaEventDeserializerShouldParseAKafkaEvent() {
-    final UUID eventId = UUID.randomUUID();
-    final String eventType = "event-type";
-    final UUID sourceInstanceId = UUID.randomUUID();
-    final long eventCreatedOn = 10L;
-    final String eventJson =
-        String.format(
-            "{ "
-                + "\"header\": { \"eventId\": \"%s\", \"eventType\": \"%s\", \"sourceInstanceId\": \"%s\", \"eventCreatedOn\": %d },"
-                + "\"body\": {}"
-                + "}",
-            eventId, eventType, sourceInstanceId, eventCreatedOn);
-    final SourceAwareEventWrapper event =
-        deserializer.deserialize("ignored", eventJson.getBytes(UTF_8));
-
-    assertThat(event.getBody().entrySet()).isEmpty();
-    assertThat(event.getHeader().getEventId()).isEqualTo(eventId);
-    assertThat(event.getHeader().getEventType()).isEqualTo(eventType);
-    assertThat(event.getHeader().getSourceInstanceId()).isEqualTo(sourceInstanceId);
-    assertThat(event.getHeader().getEventCreatedOn()).isEqualTo(eventCreatedOn);
-  }
-
-  @Test(expected = RuntimeException.class)
-  public void kafkaEventDeserializerShouldFailForInvalidJson() {
-    deserializer.deserialize("ignored", "this is not a JSON string".getBytes(UTF_8));
-  }
-
-  @Test(expected = RuntimeException.class)
-  public void kafkaEventDeserializerShouldFailForInvalidObjectButValidJSON() {
-    deserializer.deserialize("ignored", "{}".getBytes(UTF_8));
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
index 5a68d2f..0960cf1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/BatchRefUpdateValidatorTest.java
@@ -14,27 +14,31 @@
 
 package com.googlesource.gerrit.plugins.multisite.validation;
 
-import static junit.framework.TestCase.assertFalse;
+import static com.google.common.truth.Truth.assertThat;
 import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 
-import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.google.gerrit.reviewdb.client.Project;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkSharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZookeeperTestContainerSupport;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
-import org.apache.curator.retry.RetryNTimes;
 import org.eclipse.jgit.internal.storage.file.RefDirectory;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -43,7 +47,11 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
 
+@RunWith(MockitoJUnitRunner.class)
 public class BatchRefUpdateValidatorTest extends LocalDiskRepositoryTestCase implements RefFixture {
   @Rule public TestName nameRule = new TestName();
 
@@ -53,15 +61,15 @@
   private RevCommit A;
   private RevCommit B;
 
-  ZookeeperTestContainerSupport zookeeperContainer;
-  SharedRefDatabaseWrapper zkSharedRefDatabase;
+  @Mock SharedRefDatabaseWrapper sharedRefDatabase;
+
+  @Mock SharedRefEnforcement tmpRefEnforcement;
 
   @Before
   public void setup() throws Exception {
     super.setUp();
 
     gitRepoSetup();
-    zookeeperAndPolicyEnforcementSetup();
   }
 
   private void gitRepoSetup() throws Exception {
@@ -72,24 +80,6 @@
     B = repo.commit(repo.getRevWalk().parseCommit(A));
   }
 
-  private void zookeeperAndPolicyEnforcementSetup() {
-    zookeeperContainer = new ZookeeperTestContainerSupport(false);
-    int SLEEP_BETWEEN_RETRIES_MS = 30;
-    long TRANSACTION_LOCK_TIMEOUT = 1000l;
-    int NUMBER_OF_RETRIES = 5;
-
-    zkSharedRefDatabase =
-        new SharedRefDatabaseWrapper(
-            DynamicItem.itemOf(
-                SharedRefDatabase.class,
-                new ZkSharedRefDatabase(
-                    zookeeperContainer.getCurator(),
-                    new ZkConnectionConfig(
-                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                        TRANSACTION_LOCK_TIMEOUT))),
-            new DisabledSharedRefLogger());
-  }
-
   @Test
   public void immutableChangeShouldNotBeWrittenIntoZk() throws Exception {
     String AN_IMMUTABLE_REF = "refs/changes/01/1/1";
@@ -102,7 +92,8 @@
     BatchRefUpdateValidator.executeBatchUpdateWithValidation(
         batchRefUpdate, () -> execute(batchRefUpdate));
 
-    assertFalse(zkSharedRefDatabase.exists(A_TEST_PROJECT_NAME, AN_IMMUTABLE_REF));
+    verify(sharedRefDatabase, never())
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
   }
 
   @Test
@@ -116,7 +107,34 @@
     BatchRefUpdateValidator.executeBatchUpdateWithValidation(
         batchRefUpdate, () -> execute(batchRefUpdate));
 
-    assertFalse(zkSharedRefDatabase.exists(A_TEST_PROJECT_NAME, DRAFT_COMMENT));
+    verify(sharedRefDatabase, never())
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, newRef(DRAFT_COMMENT, A.getId()), B.getId());
+  }
+
+  @Test
+  public void validationShouldFailWhenLocalRefDbIsOutOfSync() throws Exception {
+    String AN_OUT_OF_SYNC_REF = "refs/changes/01/1/1";
+    BatchRefUpdate batchRefUpdate =
+        newBatchUpdate(
+            Collections.singletonList(new ReceiveCommand(A, B, AN_OUT_OF_SYNC_REF, UPDATE)));
+    BatchRefUpdateValidator batchRefUpdateValidator =
+        getRefValidatorForEnforcement(A_TEST_PROJECT_NAME, tmpRefEnforcement);
+
+    doReturn(SharedRefEnforcement.EnforcePolicy.REQUIRED)
+        .when(batchRefUpdateValidator.refEnforcement)
+        .getPolicy(A_TEST_PROJECT_NAME, AN_OUT_OF_SYNC_REF);
+    lenient()
+        .doReturn(false)
+        .when(sharedRefDatabase)
+        .isUpToDate(A_TEST_PROJECT_NAME_KEY, newRef(AN_OUT_OF_SYNC_REF, AN_OBJECT_ID_1));
+
+    batchRefUpdateValidator.executeBatchUpdateWithValidation(
+        batchRefUpdate, () -> execute(batchRefUpdate));
+
+    final List<ReceiveCommand> commands = batchRefUpdate.getCommands();
+    assertThat(commands.size()).isEqualTo(1);
+    commands.forEach(
+        (command) -> assertThat(command.getResult()).isEqualTo(ReceiveCommand.Result.LOCK_FAILURE));
   }
 
   private BatchRefUpdateValidator newDefaultValidator(String projectName) {
@@ -126,7 +144,7 @@
   private BatchRefUpdateValidator getRefValidatorForEnforcement(
       String projectName, SharedRefEnforcement sharedRefEnforcement) {
     return new BatchRefUpdateValidator(
-        zkSharedRefDatabase,
+        sharedRefDatabase,
         new ValidationMetrics(new DisabledMetricMaker()),
         sharedRefEnforcement,
         new DummyLockWrapper(),
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
index 37d5008..047a1c5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/DisabledSharedRefLogger.java
@@ -33,4 +33,7 @@
 
   @Override
   public void logLockRelease(String project, String refName) {}
+
+  @Override
+  public <T> void logRefUpdate(String project, String refName, T currRef, T newRefValue) {}
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
index f368d41..c6f0fc1 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/Log4jSharedRefLoggerTest.java
@@ -18,10 +18,10 @@
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.json.OutputFormat;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.Sequences;
 import com.google.gerrit.server.util.SystemLog;
 import com.google.gson.Gson;
 import com.googlesource.gerrit.plugins.multisite.Log4jSharedRefLogger;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
index 730e558..187f686 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteBatchRefUpdateTest.java
@@ -15,17 +15,18 @@
 package com.googlesource.gerrit.plugins.multisite.validation;
 
 import static java.util.Arrays.asList;
-import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import java.io.IOException;
 import java.util.Collections;
 import org.eclipse.jgit.lib.BatchRefUpdate;
@@ -50,6 +51,7 @@
 
   @Mock SharedRefDatabaseWrapper sharedRefDb;
   @Mock BatchRefUpdate batchRefUpdate;
+  @Mock BatchRefUpdateValidator batchRefUpdateValidator;
   @Mock RefDatabase refDatabase;
   @Mock RevWalk revWalk;
   @Mock ProgressMonitor progressMonitor;
@@ -110,30 +112,38 @@
     setMockRequiredReturnValues();
 
     // When compareAndPut against sharedDb succeeds
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(eq(A_TEST_PROJECT_NAME), refEquals(oldRef), eq(newRef.getObjectId()));
+        .compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
     multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
     verify(sharedRefDb)
-        .compareAndPut(eq(A_TEST_PROJECT_NAME), refEquals(oldRef), eq(newRef.getObjectId()));
+        .compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
   }
 
   private Ref refEquals(Ref oldRef) {
     return argThat(new RefMatcher(oldRef));
   }
 
-  @Test
+  @Test(expected = IOException.class)
   public void executeAndFailsWithExceptions() throws IOException {
+    multiSiteRefUpdate = getMultiSiteBatchRefUpdateWithMockedValidator();
+    doThrow(new IOException("IO Test Exception"))
+        .when(batchRefUpdateValidator)
+        .executeBatchUpdateWithValidation(any(), any());
+
+    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
+  }
+
+  @Test
+  public void executeSuccessfullyWithNoExceptionsWhenOutOfSync() throws IOException {
     setMockRequiredReturnValues();
-    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME, A_TEST_REF_NAME);
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
-    try {
-      multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
-      fail("Expecting an IOException to be thrown");
-    } catch (IOException e) {
-      verify(validationMetrics).incrementSplitBrainPrevention();
-    }
+    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME_KEY, A_TEST_REF_NAME);
+    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
+
+    multiSiteRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
+
+    verify(validationMetrics).incrementSplitBrainPrevention();
   }
 
   @Test
@@ -163,6 +173,17 @@
     return new MultiSiteBatchRefUpdate(batchRefValidatorFactory, A_TEST_PROJECT_NAME, refDatabase);
   }
 
+  private MultiSiteBatchRefUpdate getMultiSiteBatchRefUpdateWithMockedValidator() {
+    BatchRefUpdateValidator.Factory batchRefValidatorFactory =
+        new BatchRefUpdateValidator.Factory() {
+          @Override
+          public BatchRefUpdateValidator create(String projectName, RefDatabase refDb) {
+            return batchRefUpdateValidator;
+          }
+        };
+    return new MultiSiteBatchRefUpdate(batchRefValidatorFactory, A_TEST_PROJECT_NAME, refDatabase);
+  }
+
   protected static class RefMatcher implements ArgumentMatcher<Ref> {
     private Ref left;
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java
index 82476a1..491ced4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteGitRepositoryManagerTest.java
@@ -18,7 +18,7 @@
 import static org.mockito.Mockito.verify;
 
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import org.eclipse.jgit.lib.Repository;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java
index b41378b..41b83e5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefDatabaseTest.java
@@ -17,7 +17,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.junit.Rule;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
index a9d8e76..16cda4e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRefUpdateTest.java
@@ -24,8 +24,8 @@
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.RefUpdateValidator.Factory;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefUpdateStub;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefUpdateStub;
 import java.io.IOException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectIdRef;
@@ -65,10 +65,10 @@
   @Test
   public void newUpdateShouldValidateAndSucceed() throws Exception {
 
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, oldRef, newRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
 
@@ -82,7 +82,7 @@
   @Test(expected = Exception.class)
   public void newUpdateShouldValidateAndFailWithIOException() throws Exception {
 
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
 
@@ -94,7 +94,7 @@
   @Test
   public void newUpdateShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException {
 
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
 
@@ -113,10 +113,10 @@
   public void newUpdateShouldNotIncreaseSplitBrainPreventedCounterIfFailingSharedDbPostUpdate()
       throws IOException {
 
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, oldRef, newRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
 
@@ -135,10 +135,10 @@
   public void newUpdateShouldtIncreaseSplitBrainCounterIfFailingSharedDbPostUpdate()
       throws IOException {
 
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
     doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, oldRef, newRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, newRef.getObjectId());
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulUpdate(oldRef, newRef.getObjectId());
 
@@ -155,9 +155,11 @@
 
   @Test
   public void deleteShouldValidateAndSucceed() throws IOException {
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
-    doReturn(true).when(sharedRefDb).compareAndPut(A_TEST_PROJECT_NAME, oldRef, ObjectId.zeroId());
+    doReturn(true)
+        .when(sharedRefDb)
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, oldRef, ObjectId.zeroId());
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulDelete(oldRef);
 
@@ -171,7 +173,7 @@
   @Test
   public void deleteShouldIncreaseRefUpdateFailureCountWhenFailing() throws IOException {
 
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, oldRef);
+    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
 
     RefUpdate refUpdate = RefUpdateStub.forSuccessfulDelete(oldRef);
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java
index eb05b30..15c596f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/MultiSiteRepositoryTest.java
@@ -18,7 +18,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import java.io.IOException;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate.Result;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java
new file mode 100644
index 0000000..ba381a4
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectDeletedSharedDbCleanupTest.java
@@ -0,0 +1,45 @@
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectDeletedSharedDbCleanupTest implements RefFixture {
+  @Rule public TestName nameRule = new TestName();
+
+  @Mock ValidationMetrics mockValidationMetrics;
+  @Mock SharedRefDatabaseWrapper sharedRefDatabase;
+
+  @Test
+  public void aDeleteProjectEventShouldCleanupProjectFromZk() throws Exception {
+    String projectName = A_TEST_PROJECT_NAME;
+    ProjectDeletedSharedDbCleanup projectDeletedSharedDbCleanup =
+        new ProjectDeletedSharedDbCleanup(sharedRefDatabase, mockValidationMetrics);
+
+    ProjectDeletedListener.Event event =
+        new ProjectDeletedListener.Event() {
+          @Override
+          public String getProjectName() {
+            return projectName;
+          }
+
+          @Override
+          public NotifyHandling getNotify() {
+            return NotifyHandling.NONE;
+          }
+        };
+
+    projectDeletedSharedDbCleanup.onProjectDeleted(event);
+
+    Mockito.verify(sharedRefDatabase, Mockito.times(1)).remove(A_TEST_PROJECT_NAME_KEY);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
new file mode 100644
index 0000000..6360227
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/ProjectVersionRefUpdateTest.java
@@ -0,0 +1,267 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.multisite.validation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF;
+import static com.googlesource.gerrit.plugins.multisite.validation.ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.events.RefUpdatedEvent;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gerrit.testing.InMemoryTestEnvironment;
+import com.google.inject.Inject;
+import com.googlesource.gerrit.plugins.multisite.ProjectVersionLogger;
+import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
+import com.googlesource.gerrit.plugins.multisite.forwarder.Context;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ProjectVersionRefUpdateTest implements RefFixture {
+
+  @Rule public InMemoryTestEnvironment testEnvironment = new InMemoryTestEnvironment();
+
+  @Mock RefUpdatedEvent refUpdatedEvent;
+  @Mock SharedRefDatabaseWrapper sharedRefDb;
+  @Mock GitReferenceUpdated gitReferenceUpdated;
+  @Mock ProjectVersionLogger verLogger;
+
+  @Inject private ProjectConfig.Factory projectConfigFactory;
+  @Inject private InMemoryRepositoryManager repoManager;
+
+  private TestRepository<InMemoryRepository> repo;
+  private ProjectConfig project;
+  private RevCommit masterCommit;
+
+  @Before
+  public void setUp() throws Exception {
+    InMemoryRepository inMemoryRepo = repoManager.createRepository(A_TEST_PROJECT_NAME_KEY);
+    project = projectConfigFactory.create(A_TEST_PROJECT_NAME_KEY);
+    project.load(inMemoryRepo);
+    repo = new TestRepository<>(inMemoryRepo);
+    masterCommit = repo.branch("master").commit().create();
+  }
+
+  @After
+  public void tearDown() {
+    Context.unsetForwardedEvent();
+  }
+
+  @Test
+  public void producerShouldUpdateProjectVersionUponRefUpdatedEvent() throws IOException {
+    Context.setForwardedEvent(false);
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.of("26f7ee61bf0e470e8393c884526eec8a9b943a63"));
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.of("" + (masterCommit.getCommitTime() - 1)));
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
+        .thenReturn(true);
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any()))
+        .thenReturn(true);
+    when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
+    when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+
+    verify(sharedRefDb, atMost(1))
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
+
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
+  }
+
+  @Test
+  public void producerShouldUpdateProjectVersionUponForcedPushRefUpdatedEvent() throws Exception {
+    Context.setForwardedEvent(false);
+
+    Thread.sleep(1000L);
+    RevCommit masterPlusOneCommit = repo.branch("master").commit().create();
+
+    Thread.sleep(1000L);
+    repo.branch("master").update(masterCommit);
+
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.of("26f7ee61bf0e470e8393c884526eec8a9b943a63"));
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.of("" + (masterCommit.getCommitTime() - 1)));
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
+        .thenReturn(true);
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any()))
+        .thenReturn(true);
+    when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
+    when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+
+    verify(sharedRefDb, atMost(1))
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
+
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterPlusOneCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
+  }
+
+  @Test
+  public void producerShouldCreateNewProjectVersionWhenMissingUponRefUpdatedEvent()
+      throws IOException {
+    Context.setForwardedEvent(false);
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_REF,
+            String.class))
+        .thenReturn(Optional.empty());
+    when(sharedRefDb.get(
+            A_TEST_PROJECT_NAME_KEY,
+            ProjectVersionRefUpdate.MULTI_SITE_VERSIONING_VALUE_REF,
+            String.class))
+        .thenReturn(Optional.empty());
+
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class)))
+        .thenReturn(true);
+    when(sharedRefDb.compareAndPut(any(Project.NameKey.class), any(String.class), any(), any()))
+        .thenReturn(true);
+    when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
+    when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+
+    verify(sharedRefDb, atMost(1))
+        .compareAndPut(any(Project.NameKey.class), isNull(), any(ObjectId.class));
+
+    assertThat(ref).isNotNull();
+
+    ObjectLoader loader = repo.getRepository().open(ref.getObjectId());
+    long storedVersion =
+        Long.parseLong(IOUtils.toString(loader.openStream(), StandardCharsets.UTF_8.name()));
+    assertThat(storedVersion).isGreaterThan((long) masterCommit.getCommitTime());
+
+    verify(verLogger).log(A_TEST_PROJECT_NAME_KEY, storedVersion, 0);
+  }
+
+  @Test
+  public void producerShouldNotUpdateProjectVersionUponSequenceRefUpdatedEvent() throws Exception {
+    producerShouldNotUpdateProjectVersionUponMagicRefUpdatedEvent(RefNames.REFS_SEQUENCES);
+  }
+
+  @Test
+  public void producerShouldNotUpdateProjectVersionUponStarredChangesRefUpdatedEvent()
+      throws Exception {
+    producerShouldNotUpdateProjectVersionUponMagicRefUpdatedEvent(RefNames.REFS_STARRED_CHANGES);
+  }
+
+  private void producerShouldNotUpdateProjectVersionUponMagicRefUpdatedEvent(String magicRefPrefix)
+      throws Exception {
+    String magicRefName = magicRefPrefix + "/foo";
+    Context.setForwardedEvent(false);
+    when(refUpdatedEvent.getProjectNameKey()).thenReturn(A_TEST_PROJECT_NAME_KEY);
+    when(refUpdatedEvent.getRefName()).thenReturn(magicRefName);
+    repo.branch(magicRefName).commit().create();
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+    assertThat(ref).isNull();
+
+    verifyZeroInteractions(verLogger);
+  }
+
+  @Test
+  public void shouldNotUpdateProjectVersionWhenProjectDoesntExist() throws IOException {
+    Context.setForwardedEvent(false);
+    when(refUpdatedEvent.getProjectNameKey())
+        .thenReturn(new Project.NameKey("aNonExistentProject"));
+    when(refUpdatedEvent.getRefName()).thenReturn(A_TEST_REF_NAME);
+
+    new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+        .onEvent(refUpdatedEvent);
+
+    Ref ref = repo.getRepository().findRef(MULTI_SITE_VERSIONING_REF);
+    assertThat(ref).isNull();
+
+    verifyZeroInteractions(verLogger);
+  }
+
+  @Test
+  public void getRemoteProjectVersionShouldReturnCorrectValue() {
+    when(sharedRefDb.get(A_TEST_PROJECT_NAME_KEY, MULTI_SITE_VERSIONING_VALUE_REF, String.class))
+        .thenReturn(Optional.of("123"));
+
+    Optional<Long> version =
+        new ProjectVersionRefUpdate(repoManager, sharedRefDb, gitReferenceUpdated, verLogger)
+            .getProjectRemoteVersion(A_TEST_PROJECT_NAME);
+
+    assertThat(version.isPresent()).isTrue();
+    assertThat(version.get()).isEqualTo(123L);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
index f0677b8..a9968c4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/RefUpdateValidatorTest.java
@@ -16,20 +16,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import com.google.gerrit.reviewdb.client.Project;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.OutOfSyncException;
+import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.RefFixture;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedDbSplitBrainException;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.RefUpdate;
@@ -86,15 +83,18 @@
 
   @Test
   public void validationShouldSucceedWhenLocalRefDbIsUpToDate() throws Exception {
-    lenient().doReturn(false).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, localRef);
     lenient()
         .doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(anyString(), any(Ref.class), any(ObjectId.class));
+        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
+    lenient()
+        .doReturn(false)
+        .when(sharedRefDb)
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
     doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, localRef, newUpdateRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, newUpdateRef.getObjectId());
 
     Result result = refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW);
 
@@ -104,14 +104,14 @@
   @Test
   public void sharedRefDbShouldBeUpdatedWithRefDeleted() throws Exception {
     doReturn(ObjectId.zeroId()).when(refUpdate).getNewObjectId();
-    doReturn(true).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
+    doReturn(true).when(sharedRefDb).isUpToDate(any(Project.NameKey.class), any(Ref.class));
     lenient()
         .doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(anyString(), any(Ref.class), any(ObjectId.class));
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
     doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, localRef, ObjectId.zeroId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, ObjectId.zeroId());
     doReturn(localRef).doReturn(null).when(localRefDb).getRef(refName);
 
     Result result = refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.FORCED);
@@ -121,16 +121,16 @@
 
   @Test
   public void sharedRefDbShouldBeUpdatedWithNewRefCreated() throws Exception {
-    Ref localNullRef = SharedRefDatabase.nullRef(refName);
+    Ref localNullRef = nullRef(refName);
 
-    doReturn(true).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
+    doReturn(true).when(sharedRefDb).isUpToDate(any(Project.NameKey.class), any(Ref.class));
     lenient()
         .doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(anyString(), any(Ref.class), any(ObjectId.class));
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
     doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, localNullRef, newUpdateRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localNullRef, newUpdateRef.getObjectId());
     doReturn(localNullRef).doReturn(newUpdateRef).when(localRefDb).getRef(refName);
 
     Result result = refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW);
@@ -138,44 +138,52 @@
     assertThat(result).isEqualTo(RefUpdate.Result.NEW);
   }
 
-  @Test(expected = OutOfSyncException.class)
-  public void validationShouldFailWhenLocalRefDbIsNotUpToDate() throws Exception {
-    lenient().doReturn(true).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
-    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME, refName);
-    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, localRef);
+  @Test
+  public void validationShouldFailWhenLocalRefDbIsOutOfSync() throws Exception {
+    lenient()
+        .doReturn(true)
+        .when(sharedRefDb)
+        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
+    doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME_KEY, refName);
+    doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
 
-    refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW);
+    Result result = refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW);
+
+    assertThat(result).isEqualTo(Result.LOCK_FAILURE);
   }
 
   @Test(expected = SharedDbSplitBrainException.class)
   public void shouldTrowSplitBrainWhenLocalRefDbIsUpToDateButFinalCompareAndPutIsFailing()
       throws Exception {
-    lenient().doReturn(false).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, localRef);
+    lenient()
+        .doReturn(false)
+        .when(sharedRefDb)
+        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
     lenient()
         .doReturn(true)
         .when(sharedRefDb)
-        .compareAndPut(anyString(), any(Ref.class), any(ObjectId.class));
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
     doReturn(false)
         .when(sharedRefDb)
-        .compareAndPut(A_TEST_PROJECT_NAME, localRef, newUpdateRef.getObjectId());
+        .compareAndPut(A_TEST_PROJECT_NAME_KEY, localRef, newUpdateRef.getObjectId());
 
     refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.NEW);
   }
 
   @Test
   public void shouldNotUpdateSharedRefDbWhenFinalCompareAndPutIsFailing() throws Exception {
-    lenient().doReturn(false).when(sharedRefDb).isUpToDate(anyString(), any(Ref.class));
-    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME, localRef);
+    lenient()
+        .doReturn(false)
+        .when(sharedRefDb)
+        .isUpToDate(any(Project.NameKey.class), any(Ref.class));
+    doReturn(true).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, localRef);
 
     Result result =
         refUpdateValidator.executeRefUpdate(refUpdate, () -> RefUpdate.Result.LOCK_FAILURE);
 
-    verify(sharedRefDb, never()).compareAndPut(anyString(), any(Ref.class), any(ObjectId.class));
+    verify(sharedRefDb, never())
+        .compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
     assertThat(result).isEqualTo(RefUpdate.Result.LOCK_FAILURE);
   }
-
-  private Ref newRef(String refName, ObjectId objectId) {
-    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId);
-  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
similarity index 94%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
index d63436c..4ee3c85 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/CustomSharedRefEnforcementByProjectTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/CustomSharedRefEnforcementByProjectTest.java
@@ -12,15 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase.newRef;
 
 import com.googlesource.gerrit.plugins.multisite.Configuration;
 import com.googlesource.gerrit.plugins.multisite.Configuration.SharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.CustomSharedRefEnforcementByProject;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import java.util.Arrays;
 import org.eclipse.jgit.lib.Config;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
similarity index 89%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
index 83fcf52..b462408 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/DefaultSharedRefEnforcementTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/DefaultSharedRefEnforcementTest.java
@@ -12,13 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase.newRef;
 
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
 import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement.EnforcePolicy;
 import org.eclipse.jgit.lib.Ref;
 import org.junit.Test;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
index d6b92e4..fb8eb40 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/MultisiteReplicationPushFilterTest.java
@@ -21,19 +21,22 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
 import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
 import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
 import com.googlesource.gerrit.plugins.multisite.validation.MultisiteReplicationPushFilter;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@@ -60,6 +63,7 @@
   @Inject private InMemoryRepositoryManager gitRepositoryManager;
 
   String project = A_TEST_PROJECT_NAME;
+  Project.NameKey projectName = A_TEST_PROJECT_NAME_KEY;
 
   private TestRepository<InMemoryRepository> repo;
 
@@ -74,7 +78,7 @@
   public void shouldReturnAllRefUpdatesWhenAllUpToDate() throws Exception {
     List<RemoteRefUpdate> refUpdates =
         Arrays.asList(refUpdate("refs/heads/foo"), refUpdate("refs/heads/bar"));
-    doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(project), any());
+    doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
 
     MultisiteReplicationPushFilter pushFilter =
         new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
@@ -101,7 +105,7 @@
   public void shouldLoadLocalVersionAndNotFilter() throws Exception {
     RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated");
     List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated);
-    doReturn(false).doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(project), any());
+    doReturn(false).doReturn(true).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
 
     MultisiteReplicationPushFilter pushFilter =
         new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
@@ -116,7 +120,7 @@
     RemoteRefUpdate temporaryOutdated = refUpdate("refs/heads/temporaryOutdated");
     repo.branch("refs/heads/temporaryOutdated").commit().create();
     List<RemoteRefUpdate> refUpdates = Collections.singletonList(temporaryOutdated);
-    doReturn(false).doReturn(false).when(sharedRefDatabaseMock).isUpToDate(eq(project), any());
+    doReturn(false).doReturn(false).when(sharedRefDatabaseMock).isUpToDate(eq(projectName), any());
 
     MultisiteReplicationPushFilter pushFilter =
         new MultisiteReplicationPushFilter(sharedRefDatabaseMock, gitRepositoryManager);
@@ -147,35 +151,50 @@
     Set<String> rejectedSet = new HashSet<>();
     rejectedSet.addAll(Arrays.asList(rejectedRefs));
 
-    SharedRefDatabase sharedRefDatabase =
-        new SharedRefDatabase() {
+    GlobalRefDatabase sharedRefDatabase =
+        new GlobalRefDatabase() {
 
           @Override
-          public void removeProject(String project) throws IOException {}
-
-          @Override
-          public AutoCloseable lockRef(String project, String refName) throws SharedLockException {
-            return null;
-          }
-
-          @Override
-          public boolean isUpToDate(String project, Ref ref) throws SharedLockException {
+          public boolean isUpToDate(Project.NameKey project, Ref ref)
+              throws GlobalRefDbLockException {
             return !rejectedSet.contains(ref.getName());
           }
 
           @Override
-          public boolean exists(String project, String refName) {
+          public boolean exists(Project.NameKey project, String refName) {
             return true;
           }
 
           @Override
-          public boolean compareAndPut(String project, Ref currRef, ObjectId newRefValue)
-              throws IOException {
+          public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
+              throws GlobalRefDbSystemError {
             return false;
           }
+
+          @Override
+          public <T> boolean compareAndPut(
+              Project.NameKey project, String refName, T currValue, T newValue)
+              throws GlobalRefDbSystemError {
+            return false;
+          }
+
+          @Override
+          public AutoCloseable lockRef(Project.NameKey project, String refName)
+              throws GlobalRefDbLockException {
+            return null;
+          }
+
+          @Override
+          public void remove(Project.NameKey project) throws GlobalRefDbSystemError {}
+
+          @Override
+          public <T> Optional<T> get(Project.NameKey project, String refName, Class<T> clazz)
+              throws GlobalRefDbSystemError {
+            return Optional.empty();
+          }
         };
     return new SharedRefDatabaseWrapper(
-        DynamicItem.itemOf(SharedRefDatabase.class, sharedRefDatabase),
+        DynamicItem.itemOf(GlobalRefDatabase.class, sharedRefDatabase),
         new DisabledSharedRefLogger());
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java
new file mode 100644
index 0000000..c63530a
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/NoopSharedRefDatabaseTest.java
@@ -0,0 +1,28 @@
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Test;
+
+public class NoopSharedRefDatabaseTest implements RefFixture {
+
+  private Ref sampleRef = newRef(A_TEST_REF_NAME, AN_OBJECT_ID_1);
+  private NoopSharedRefDatabase objectUnderTest = new NoopSharedRefDatabase();
+
+  @Test
+  public void isUpToDateShouldAlwaysReturnTrue() {
+    assertThat(objectUnderTest.isUpToDate(A_TEST_PROJECT_NAME_KEY, sampleRef)).isTrue();
+  }
+
+  @Test
+  public void compareAndPutShouldAlwaysReturnTrue() {
+    assertThat(objectUnderTest.compareAndPut(A_TEST_PROJECT_NAME_KEY, sampleRef, AN_OBJECT_ID_2))
+        .isTrue();
+  }
+
+  @Test
+  public void existsShouldAlwaysReturnFalse() {
+    assertThat(objectUnderTest.exists(A_TEST_PROJECT_NAME_KEY, A_TEST_REF_NAME)).isFalse();
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefFixture.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefFixture.java
similarity index 84%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefFixture.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefFixture.java
index 72ea236..8ddbf5c 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefFixture.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefFixture.java
@@ -12,11 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
 
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
 import org.junit.Ignore;
 
 @Ignore
@@ -41,4 +43,12 @@
   default String testBranch() {
     return "aTestBranch";
   }
+
+  default Ref newRef(String refName, ObjectId objectId) {
+    return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId);
+  }
+
+  default Ref nullRef(String refName) {
+    return newRef(refName, ObjectId.zeroId());
+  }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java
index 57ab5e0..c9eeaa8 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefSharedDatabaseTest.java
@@ -16,8 +16,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.RefFixture;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Ref.Storage;
 import org.junit.Rule;
@@ -38,7 +38,7 @@
     ObjectId objectId = AN_OBJECT_ID_1;
     String refName = aBranchRef();
 
-    Ref aNewRef = SharedRefDatabase.newRef(refName, objectId);
+    Ref aNewRef = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId);
 
     assertThat(aNewRef.getName()).isEqualTo(refName);
     assertThat(aNewRef.getObjectId()).isEqualTo(objectId);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefUpdateStub.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java
similarity index 99%
rename from src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefUpdateStub.java
rename to src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java
index cec476e..7c1d7b4 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/RefUpdateStub.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/RefUpdateStub.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
+package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb;
 
 import java.io.IOException;
 import org.apache.commons.lang.NotImplementedException;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java
deleted file mode 100644
index e2bdfbc..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseIT.java
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.gerrit.metrics.DisabledMetricMaker;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.BatchRefUpdateValidator;
-import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.DummyLockWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.MultiSiteBatchRefUpdate;
-import com.googlesource.gerrit.plugins.multisite.validation.ValidationMetrics;
-import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import org.apache.curator.retry.RetryNTimes;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.RefDatabase;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
-import org.eclipse.jgit.transport.ReceiveCommand.Type;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-public class ZkSharedRefDatabaseIT extends AbstractDaemonTest implements RefFixture {
-  @Rule public TestName nameRule = new TestName();
-
-  ZookeeperTestContainerSupport zookeeperContainer;
-  SharedRefDatabaseWrapper zkSharedRefDatabase;
-  SharedRefEnforcement refEnforcement;
-
-  int SLEEP_BETWEEN_RETRIES_MS = 30;
-  long TRANSACTION_LOCK_TIMEOUT = 1000l;
-  int NUMBER_OF_RETRIES = 5;
-
-  @Before
-  public void setup() {
-    refEnforcement = new DefaultSharedRefEnforcement();
-    zookeeperContainer = new ZookeeperTestContainerSupport(false);
-    zkSharedRefDatabase =
-        new SharedRefDatabaseWrapper(
-            DynamicItem.itemOf(
-                SharedRefDatabase.class,
-                new ZkSharedRefDatabase(
-                    zookeeperContainer.getCurator(),
-                    new ZkConnectionConfig(
-                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                        TRANSACTION_LOCK_TIMEOUT))),
-            new DisabledSharedRefLogger());
-  }
-
-  @After
-  public void cleanup() {
-    zookeeperContainer.cleanup();
-  }
-
-  @Test
-  public void sequenceOfGitUpdatesWithARejectionCausesZKCheckToFail() throws Exception {
-    ObjectId commitObjectIdOne = commitBuilder().add("test_file.txt", "A").create().getId();
-    ObjectId commitObjectIdTwo = commitBuilder().add("test_file.txt", "B").create().getId();
-    ObjectId commitObjectIdThree = commitBuilder().add("test_file.txt", "A2").create().getId();
-
-    ReceiveCommand aRefCreation =
-        new ReceiveCommand(ObjectId.zeroId(), commitObjectIdOne, A_TEST_REF_NAME);
-
-    ReceiveCommand aCommandThatWillBeRejectedByJGit =
-        new ReceiveCommand(
-            commitObjectIdOne, commitObjectIdTwo, A_TEST_REF_NAME, Type.UPDATE_NONFASTFORWARD);
-
-    ReceiveCommand aCommandThatShouldSucceed =
-        new ReceiveCommand(commitObjectIdOne, commitObjectIdThree, A_TEST_REF_NAME, Type.UPDATE);
-
-    InMemoryRepository repository = testRepo.getRepository();
-    try (RevWalk rw = new RevWalk(repository)) {
-      newBatchRefUpdate(repository, aRefCreation).execute(rw, NullProgressMonitor.INSTANCE);
-
-      // The rejection of this command should not leave the shared DB into an inconsistent state
-      newBatchRefUpdate(repository, aCommandThatWillBeRejectedByJGit)
-          .execute(rw, NullProgressMonitor.INSTANCE);
-
-      // This command will succeed only if the previous one is not leaving any traces in the
-      // shared ref DB
-      newBatchRefUpdate(repository, aCommandThatShouldSucceed)
-          .execute(rw, NullProgressMonitor.INSTANCE);
-
-      assertThat(aRefCreation.getResult()).isEqualTo(Result.OK);
-      assertThat(aCommandThatWillBeRejectedByJGit.getResult())
-          .isEqualTo(Result.REJECTED_NONFASTFORWARD);
-      assertThat(aCommandThatShouldSucceed.getResult()).isEqualTo(Result.OK);
-    }
-  }
-
-  @Test
-  public void aBatchWithOneFailedCommandShouldFailAllOtherCommands() throws Exception {
-    ObjectId commitObjectIdOne = commitBuilder().add("test_file1.txt", "A").create().getId();
-    ObjectId commitObjectIdTwo = commitBuilder().add("test_file1.txt", "B").create().getId();
-    ObjectId commitObjectIdThree = commitBuilder().add("test_file2.txt", "C").create().getId();
-
-    ReceiveCommand firstCommand =
-        new ReceiveCommand(ObjectId.zeroId(), commitObjectIdOne, A_TEST_REF_NAME);
-
-    ReceiveCommand aNonFastForwardUpdate =
-        new ReceiveCommand(
-            commitObjectIdOne, commitObjectIdTwo, A_TEST_REF_NAME, Type.UPDATE_NONFASTFORWARD);
-
-    ReceiveCommand aNewCreate =
-        new ReceiveCommand(ObjectId.zeroId(), commitObjectIdThree, "refs/for/master2");
-
-    InMemoryRepository repository = testRepo.getRepository();
-    try (RevWalk rw = new RevWalk(repository)) {
-      newBatchRefUpdate(repository, firstCommand, aNonFastForwardUpdate, aNewCreate)
-          .execute(rw, NullProgressMonitor.INSTANCE);
-    }
-
-    // All commands in batch failed because of the second one
-    assertThat(firstCommand.getResult()).isEqualTo(Result.REJECTED_OTHER_REASON);
-    assertThat(aNonFastForwardUpdate.getResult()).isEqualTo(Result.REJECTED_NONFASTFORWARD);
-    assertThat(aNewCreate.getResult()).isEqualTo(Result.REJECTED_OTHER_REASON);
-
-    // Zookeeper has been left untouched
-    assertFalse(existsDataInZkForCommand(firstCommand));
-    assertFalse(existsDataInZkForCommand(aNonFastForwardUpdate));
-    assertFalse(existsDataInZkForCommand(aNewCreate));
-  }
-
-  @Test
-  public void shouldBeSuccessfulWhenRefIsRecreated() throws Exception {
-    ObjectId commitObjectIdOne = commitBuilder().add("test_file1.txt", "A").create().getId();
-    ObjectId commitObjectIdTwo = commitBuilder().add("test_file1.txt", "B").create().getId();
-
-    ReceiveCommand firstCommand =
-        new ReceiveCommand(ObjectId.zeroId(), commitObjectIdOne, A_TEST_REF_NAME);
-
-    ReceiveCommand deleteCommand =
-        new ReceiveCommand(commitObjectIdOne, ObjectId.zeroId(), A_TEST_REF_NAME, Type.DELETE);
-
-    ReceiveCommand secondCommand =
-        new ReceiveCommand(ObjectId.zeroId(), commitObjectIdTwo, A_TEST_REF_NAME);
-
-    InMemoryRepository repository = testRepo.getRepository();
-    try (RevWalk rw = new RevWalk(repository)) {
-
-      newBatchRefUpdate(repository, firstCommand).execute(rw, NullProgressMonitor.INSTANCE);
-      newBatchRefUpdate(repository, deleteCommand).execute(rw, NullProgressMonitor.INSTANCE);
-      newBatchRefUpdate(repository, secondCommand).execute(rw, NullProgressMonitor.INSTANCE);
-    }
-
-    assertTrue(existsDataInZkForCommand(secondCommand));
-  }
-
-  private boolean existsDataInZkForCommand(ReceiveCommand firstCommand) throws Exception {
-    return zkSharedRefDatabase.exists(A_TEST_PROJECT_NAME, firstCommand.getRefName());
-  }
-
-  private MultiSiteBatchRefUpdate newBatchRefUpdate(
-      Repository localGitRepo, ReceiveCommand... commands) {
-
-    BatchRefUpdateValidator.Factory batchRefValidatorFactory =
-        new BatchRefUpdateValidator.Factory() {
-          @Override
-          public BatchRefUpdateValidator create(String projectName, RefDatabase refDb) {
-            return new BatchRefUpdateValidator(
-                zkSharedRefDatabase,
-                new ValidationMetrics(new DisabledMetricMaker()),
-                new DefaultSharedRefEnforcement(),
-                new DummyLockWrapper(),
-                projectName,
-                refDb);
-          }
-        };
-
-    MultiSiteBatchRefUpdate result =
-        new MultiSiteBatchRefUpdate(
-            batchRefValidatorFactory, A_TEST_PROJECT_NAME, localGitRepo.getRefDatabase());
-
-    result.setAllowNonFastForwards(false);
-    for (ReceiveCommand command : commands) {
-      result.addCommand(command);
-    }
-    return result;
-  }
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
deleted file mode 100644
index bff41f1..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZkSharedRefDatabaseTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-
-import com.google.gerrit.extensions.api.changes.NotifyHandling;
-import com.google.gerrit.extensions.events.ProjectDeletedListener;
-import com.google.gerrit.extensions.registration.DynamicItem;
-import com.googlesource.gerrit.plugins.multisite.SharedRefDatabaseWrapper;
-import com.googlesource.gerrit.plugins.multisite.validation.DisabledSharedRefLogger;
-import com.googlesource.gerrit.plugins.multisite.validation.ProjectDeletedSharedDbCleanup;
-import com.googlesource.gerrit.plugins.multisite.validation.ValidationMetrics;
-import com.googlesource.gerrit.plugins.multisite.validation.ZkConnectionConfig;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.DefaultSharedRefEnforcement;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefDatabase;
-import com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.SharedRefEnforcement;
-import org.apache.curator.retry.RetryNTimes;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-public class ZkSharedRefDatabaseTest implements RefFixture {
-  @Rule public TestName nameRule = new TestName();
-
-  ZookeeperTestContainerSupport zookeeperContainer;
-  SharedRefDatabaseWrapper zkSharedRefDatabase;
-  SharedRefEnforcement refEnforcement;
-
-  ValidationMetrics mockValidationMetrics;
-
-  @Before
-  public void setup() {
-    refEnforcement = new DefaultSharedRefEnforcement();
-    zookeeperContainer = new ZookeeperTestContainerSupport(false);
-    int SLEEP_BETWEEN_RETRIES_MS = 30;
-    long TRANSACTION_LOCK_TIMEOUT = 1000l;
-    int NUMBER_OF_RETRIES = 5;
-
-    zkSharedRefDatabase =
-        new SharedRefDatabaseWrapper(
-            DynamicItem.itemOf(
-                SharedRefDatabase.class,
-                new ZkSharedRefDatabase(
-                    zookeeperContainer.getCurator(),
-                    new ZkConnectionConfig(
-                        new RetryNTimes(NUMBER_OF_RETRIES, SLEEP_BETWEEN_RETRIES_MS),
-                        TRANSACTION_LOCK_TIMEOUT))),
-            new DisabledSharedRefLogger());
-
-    mockValidationMetrics = mock(ValidationMetrics.class);
-  }
-
-  @After
-  public void cleanup() {
-    zookeeperContainer.cleanup();
-  }
-
-  @Test
-  public void shouldCompareAndPutSuccessfully() throws Exception {
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-    Ref newRef = refOf(AN_OBJECT_ID_2);
-    String projectName = A_TEST_PROJECT_NAME;
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndPut(projectName, oldRef, newRef.getObjectId()))
-        .isTrue();
-  }
-
-  @Test
-  public void shouldFetchLatestObjectIdInZk() throws Exception {
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-    Ref newRef = refOf(AN_OBJECT_ID_2);
-    String projectName = A_TEST_PROJECT_NAME;
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndPut(projectName, oldRef, newRef.getObjectId()))
-        .isTrue();
-
-    assertThat(zkSharedRefDatabase.isUpToDate(projectName, newRef)).isTrue();
-    assertThat(zkSharedRefDatabase.isUpToDate(projectName, oldRef)).isFalse();
-  }
-
-  @Test
-  public void shouldCompareAndPutWithNullOldRefSuccessfully() throws Exception {
-    Ref oldRef = refOf(null);
-    Ref newRef = refOf(AN_OBJECT_ID_2);
-    String projectName = A_TEST_PROJECT_NAME;
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndPut(projectName, oldRef, newRef.getObjectId()))
-        .isTrue();
-  }
-
-  @Test
-  public void compareAndPutShouldFailIfTheObjectionHasNotTheExpectedValue() throws Exception {
-    String projectName = A_TEST_PROJECT_NAME;
-
-    Ref oldRef = refOf(AN_OBJECT_ID_1);
-    Ref expectedRef = refOf(AN_OBJECT_ID_2);
-
-    zookeeperContainer.createRefInZk(projectName, oldRef);
-
-    assertThat(zkSharedRefDatabase.compareAndPut(projectName, expectedRef, AN_OBJECT_ID_3))
-        .isFalse();
-  }
-
-  private Ref refOf(ObjectId objectId) {
-    return SharedRefDatabase.newRef(aBranchRef(), objectId);
-  }
-
-  @Test
-  public void removeProjectShouldRemoveTheWholePathInZk() throws Exception {
-    String projectName = A_TEST_PROJECT_NAME;
-    Ref someRef = refOf(AN_OBJECT_ID_1);
-
-    zookeeperContainer.createRefInZk(projectName, someRef);
-
-    assertThat(zookeeperContainer.readRefValueFromZk(projectName, someRef))
-        .isEqualTo(AN_OBJECT_ID_1);
-
-    assertThat(getNumChildrenForPath("/")).isEqualTo(1);
-
-    zkSharedRefDatabase.removeProject(projectName);
-
-    assertThat(getNumChildrenForPath("/")).isEqualTo(0);
-  }
-
-  @Test
-  public void aDeleteProjectEventShouldCleanupProjectFromZk() throws Exception {
-    String projectName = A_TEST_PROJECT_NAME;
-    Ref someRef = refOf(AN_OBJECT_ID_1);
-    ProjectDeletedSharedDbCleanup projectDeletedSharedDbCleanup =
-        new ProjectDeletedSharedDbCleanup(zkSharedRefDatabase, mockValidationMetrics);
-
-    ProjectDeletedListener.Event event =
-        new ProjectDeletedListener.Event() {
-          @Override
-          public String getProjectName() {
-            return projectName;
-          }
-
-          @Override
-          public NotifyHandling getNotify() {
-            return NotifyHandling.NONE;
-          }
-        };
-
-    zookeeperContainer.createRefInZk(projectName, someRef);
-
-    assertThat(getNumChildrenForPath("/")).isEqualTo(1);
-
-    projectDeletedSharedDbCleanup.onProjectDeleted(event);
-
-    assertThat(getNumChildrenForPath("/")).isEqualTo(0);
-  }
-
-  @Override
-  public String testBranch() {
-    return "branch_" + nameRule.getMethodName();
-  }
-
-  private int getNumChildrenForPath(String path) throws Exception {
-    return zookeeperContainer
-        .getCurator()
-        .checkExists()
-        .forPath(String.format(path))
-        .getNumChildren();
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java b/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
deleted file mode 100644
index 3237e3a..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/multisite/validation/dfsrefdb/zookeeper/ZookeeperTestContainerSupport.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper;
-
-import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkSharedRefDatabase.pathFor;
-import static com.googlesource.gerrit.plugins.multisite.validation.dfsrefdb.zookeeper.ZkSharedRefDatabase.writeObjectId;
-
-import com.googlesource.gerrit.plugins.multisite.ZookeeperConfig;
-import org.apache.curator.framework.CuratorFramework;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.junit.Ignore;
-import org.testcontainers.containers.GenericContainer;
-import org.testcontainers.containers.wait.strategy.Wait;
-
-@Ignore
-public class ZookeeperTestContainerSupport {
-
-  static class ZookeeperContainer extends GenericContainer<ZookeeperContainer> {
-    public static String ZOOKEEPER_VERSION = "3.4.13";
-
-    public ZookeeperContainer() {
-      super("zookeeper:" + ZOOKEEPER_VERSION);
-    }
-  }
-
-  private ZookeeperContainer container;
-  private ZookeeperConfig configuration;
-  private CuratorFramework curator;
-
-  public CuratorFramework getCurator() {
-    return curator;
-  }
-
-  public ZookeeperContainer getContainer() {
-    return container;
-  }
-
-  @SuppressWarnings("resource")
-  public ZookeeperTestContainerSupport(boolean migrationMode) {
-    container = new ZookeeperContainer().withExposedPorts(2181).waitingFor(Wait.forListeningPort());
-    container.start();
-    Integer zkHostPort = container.getMappedPort(2181);
-    Config sharedRefDbConfig = new Config();
-    String connectString = container.getContainerIpAddress() + ":" + zkHostPort;
-    sharedRefDbConfig.setBoolean("ref-database", null, "enabled", true);
-    sharedRefDbConfig.setString("ref-database", "zookeeper", "connectString", connectString);
-    sharedRefDbConfig.setString(
-        "ref-database",
-        ZookeeperConfig.SUBSECTION,
-        ZookeeperConfig.KEY_CONNECT_STRING,
-        connectString);
-
-    configuration = new ZookeeperConfig(sharedRefDbConfig);
-    this.curator = configuration.buildCurator();
-  }
-
-  public void cleanup() {
-    this.curator.delete();
-    this.container.stop();
-  }
-
-  public ObjectId readRefValueFromZk(String projectName, Ref ref) throws Exception {
-    final byte[] bytes = curator.getData().forPath(pathFor(projectName, ref));
-    return ZkSharedRefDatabase.readObjectId(bytes);
-  }
-
-  public void createRefInZk(String projectName, Ref ref) throws Exception {
-    curator
-        .create()
-        .creatingParentContainersIfNeeded()
-        .forPath(pathFor(projectName, ref), writeObjectId(ref.getObjectId()));
-  }
-}