You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zh...@apache.org on 2020/09/22 14:04:50 UTC

[hbase] 09/09: HBASE-25013 Avoid reset the backup master root cache every time when syncing (#2392)

This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-24950
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 1a859d84841a336517c6e2114b36ea0265e2b070
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Mon Sep 21 20:32:15 2020 +0800

    HBASE-25013 Avoid reset the backup master root cache every time when syncing (#2392)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../org/apache/hadoop/hbase/MetaTableAccessor.java |  21 +--
 .../hadoop/hbase/ClientMetaTableAccessor.java      |  23 ++++
 .../hadoop/hbase/client/ConnectionUtils.java       |  39 +-----
 .../apache/hadoop/hbase/client/MasterRegistry.java |  11 +-
 .../hadoop/hbase/client/ZKConnectionRegistry.java  |  25 +++-
 .../src/main/protobuf/server/master/Master.proto   |  14 ++
 .../hbase/client/AsyncClusterConnection.java       |   3 +-
 .../hbase/client/AsyncClusterConnectionImpl.java   |  52 +++++--
 .../hadoop/hbase/coprocessor/MasterObserver.java   |  20 +++
 .../org/apache/hadoop/hbase/master/HMaster.java    |  70 +++-------
 .../hadoop/hbase/master/MasterCoprocessorHost.java |  19 +++
 .../hadoop/hbase/master/MasterRpcServices.java     |  62 +++++++--
 .../hadoop/hbase/master/MetaLocationCache.java     |  45 ++++--
 .../hbase/master/assignment/AssignmentManager.java |  33 ++---
 .../hbase/master/assignment/RegionStateStore.java  |  44 ++----
 .../hadoop/hbase/master/region/MasterRegion.java   |   9 ++
 .../region/RegionScannerAsResultScanner.java       |  88 ++++++++++++
 .../hadoop/hbase/master/region/RootStore.java      | 153 +++++++++++++++++++++
 .../apache/hadoop/hbase/regionserver/HRegion.java  |   1 -
 .../hbase/client/DummyAsyncClusterConnection.java  |   5 +-
 .../hadoop/hbase/client/TestAsyncAdminBase.java    |   5 +-
 .../client/TestFailedMetaReplicaAssigment.java     |  10 +-
 .../hbase/master/TestBackupMasterSyncRoot.java     | 113 +++++++++++++++
 .../hbase/master/TestCloseAnOpeningRegion.java     |   6 +-
 .../hbase/master/TestClusterRestartFailover.java   |  10 +-
 .../hadoop/hbase/master/TestMetaLocationCache.java |  12 +-
 .../master/assignment/MockMasterServices.java      |   8 +-
 .../assignment/TestOpenRegionProcedureBackoff.java |  10 +-
 .../assignment/TestOpenRegionProcedureHang.java    |  10 +-
 .../assignment/TestRaceBetweenSCPAndDTP.java       |  10 +-
 .../assignment/TestRaceBetweenSCPAndTRSP.java      |  10 +-
 .../TestRegionAssignedToMultipleRegionServers.java |  10 +-
 .../assignment/TestReportOnlineRegionsRace.java    |  10 +-
 ...tReportRegionStateTransitionFromDeadServer.java |  10 +-
 .../TestReportRegionStateTransitionRetry.java      |  10 +-
 .../master/assignment/TestSCPGetRegionsRace.java   |  10 +-
 .../assignment/TestWakeUpUnexpectedProcedure.java  |  10 +-
 .../apache/hadoop/hbase/util/TestRegionMover2.java |  13 +-
 38 files changed, 737 insertions(+), 277 deletions(-)

diff --git a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
index 26d8b98..8c609c9 100644
--- a/hbase-balancer/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
+++ b/hbase-balancer/src/main/java/org/apache/hadoop/hbase/MetaTableAccessor.java
@@ -468,8 +468,7 @@ public final class MetaTableAccessor {
   public static void scanMeta(Connection connection, @Nullable final byte[] startRow,
     @Nullable final byte[] stopRow, QueryType type, @Nullable Filter filter, int maxRows,
     final ClientMetaTableAccessor.Visitor visitor) throws IOException {
-    int rowUpperLimit = maxRows > 0 ? maxRows : Integer.MAX_VALUE;
-    Scan scan = getMetaScan(connection, rowUpperLimit);
+    Scan scan = getMetaScan(connection, maxRows);
 
     for (byte[] family : type.getFamilies()) {
       scan.addFamily(family);
@@ -486,26 +485,12 @@ public final class MetaTableAccessor {
 
     if (LOG.isTraceEnabled()) {
       LOG.trace("Scanning META" + " starting at row=" + Bytes.toStringBinary(startRow) +
-        " stopping at row=" + Bytes.toStringBinary(stopRow) + " for max=" + rowUpperLimit +
+        " stopping at row=" + Bytes.toStringBinary(stopRow) + " for max=" + maxRows +
         " with caching=" + scan.getCaching());
     }
-
-    int currentRow = 0;
     try (Table metaTable = getMetaHTable(connection)) {
       try (ResultScanner scanner = metaTable.getScanner(scan)) {
-        Result data;
-        while ((data = scanner.next()) != null) {
-          if (data.isEmpty()) {
-            continue;
-          }
-          // Break if visit returns false.
-          if (!visitor.visit(data)) {
-            break;
-          }
-          if (++currentRow >= rowUpperLimit) {
-            break;
-          }
-        }
+        ClientMetaTableAccessor.visit(scanner, visitor, maxRows);
       }
     }
   }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClientMetaTableAccessor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClientMetaTableAccessor.java
index ed0d9b4..f19c913 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/ClientMetaTableAccessor.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/ClientMetaTableAccessor.java
@@ -32,6 +32,7 @@ import org.apache.hadoop.hbase.client.Consistency;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.Scan.ReadType;
 import org.apache.hadoop.hbase.client.TableState;
@@ -486,4 +487,26 @@ public final class ClientMetaTableAccessor {
     }
     return stopRow;
   }
+
+  /**
+   * Visit all the result of the given {@code scanner}.
+   * <p/>
+   * It is the caller's duty to close the {@code scanner}.
+   * @param maxRows maximum rows to visit, or -1 means unlimited.
+   */
+  public static void visit(ResultScanner scanner, Visitor visitor, int maxRows) throws IOException {
+    for (int rows = 0;;) {
+      Result result = scanner.next();
+      if (result == null) {
+        return;
+      }
+      if (!visitor.visit(result)) {
+        return;
+      }
+      rows++;
+      if (maxRows > 0 && rows >= maxRows) {
+        return;
+      }
+    }
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java
index 8f298e6..4be7546 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ConnectionUtils.java
@@ -38,7 +38,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.CellComparator;
@@ -53,7 +52,6 @@ import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
 import org.apache.hadoop.hbase.exceptions.ClientExceptionsUtil;
 import org.apache.hadoop.hbase.ipc.HBaseRpcController;
 import org.apache.hadoop.hbase.ipc.RpcClient;
-import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
 import org.apache.hadoop.hbase.ipc.ServerNotRunningYetException;
 import org.apache.hadoop.hbase.ipc.ServerRpcController;
 import org.apache.hadoop.hbase.security.User;
@@ -77,8 +75,6 @@ import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ClientService;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.ScanResponse;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ClientMetaService;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetAllMetaRegionLocationsRequest;
 
 /**
  * Utility used by client connections.
@@ -649,8 +645,8 @@ public final class ConnectionUtils {
     }
   }
 
-  public static void tryClearMasterStubCache(IOException error,
-    ClientMetaService.Interface currentStub, AtomicReference<ClientMetaService.Interface> stub) {
+  public static <T> void tryClearMasterStubCache(IOException error,
+    T currentStub, AtomicReference<T> stub) {
     if (ClientExceptionsUtil.isConnectionException(error) ||
       error instanceof ServerNotRunningYetException) {
       stub.compareAndSet(currentStub, null);
@@ -725,35 +721,4 @@ public final class ConnectionUtils {
       }
     }
   }
-
-  public static CompletableFuture<List<HRegionLocation>> getAllMetaRegionLocations(
-    boolean excludeOfflinedSplitParents,
-    CompletableFuture<ClientMetaService.Interface> getStubFuture,
-    AtomicReference<ClientMetaService.Interface> stubRef,
-    RpcControllerFactory rpcControllerFactory, int callTimeoutMs) {
-    CompletableFuture<List<HRegionLocation>> future = new CompletableFuture<>();
-    addListener(getStubFuture, (stub, error) -> {
-      if (error != null) {
-        future.completeExceptionally(error);
-        return;
-      }
-      HBaseRpcController controller = rpcControllerFactory.newController();
-      if (callTimeoutMs > 0) {
-        controller.setCallTimeout(callTimeoutMs);
-      }
-      stub.getAllMetaRegionLocations(controller, GetAllMetaRegionLocationsRequest.newBuilder()
-        .setExcludeOfflinedSplitParents(excludeOfflinedSplitParents).build(), resp -> {
-          if (controller.failed()) {
-            IOException ex = controller.getFailed();
-            tryClearMasterStubCache(ex, stub, stubRef);
-            future.completeExceptionally(ex);
-            return;
-          }
-          List<HRegionLocation> locs = resp.getMetaLocationsList().stream()
-            .map(ProtobufUtil::toRegionLocation).collect(Collectors.toList());
-          future.complete(locs);
-        });
-    });
-    return future;
-  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MasterRegistry.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MasterRegistry.java
index d760cf0..d07e133 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MasterRegistry.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/MasterRegistry.java
@@ -361,8 +361,12 @@ public class MasterRegistry implements ConnectionRegistry {
     LocateMetaRegionRequest request =
       LocateMetaRegionRequest.newBuilder().setRow(ByteString.copyFrom(row))
         .setLocateType(ProtobufUtil.toProtoRegionLocateType(locateType)).build();
-    return this.<LocateMetaRegionResponse> call((c, s, d) -> s.locateMetaRegion(c, request, d),
-      r -> true, "locateMeta()").thenApply(this::transformRegionLocations);
+    return this
+      .<LocateMetaRegionResponse> call((c, s, d) -> s.locateMetaRegion(c, request, d),
+        r -> r.getMetaLocationsList().stream()
+          .anyMatch(l -> l.hasRegionInfo() && l.hasServerName()),
+        "locateMeta()")
+      .thenApply(this::transformRegionLocations);
   }
 
   private List<HRegionLocation>
@@ -378,7 +382,8 @@ public class MasterRegistry implements ConnectionRegistry {
       .setExcludeOfflinedSplitParents(excludeOfflinedSplitParents).build();
     return this
       .<GetAllMetaRegionLocationsResponse> call(
-        (c, s, d) -> s.getAllMetaRegionLocations(c, request, d), r -> true,
+        (c, s, d) -> s.getAllMetaRegionLocations(c, request, d),
+        r -> r.getMetaLocationsCount() > 0,
         "getAllMetaRegionLocations(" + excludeOfflinedSplitParents + ")")
       .thenApply(this::transformRegionLocationList);
   }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZKConnectionRegistry.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZKConnectionRegistry.java
index a10ef18..d66ac1a 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZKConnectionRegistry.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/ZKConnectionRegistry.java
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.client;
 import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_RPC_TIMEOUT;
 import static org.apache.hadoop.hbase.HConstants.HBASE_RPC_READ_TIMEOUT_KEY;
 import static org.apache.hadoop.hbase.HConstants.HBASE_RPC_TIMEOUT_KEY;
+import static org.apache.hadoop.hbase.client.ConnectionUtils.tryClearMasterStubCache;
 import static org.apache.hadoop.hbase.client.RegionInfo.DEFAULT_REPLICA_ID;
 import static org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil.lengthOfPBMagic;
 import static org.apache.hadoop.hbase.util.FutureUtils.addListener;
@@ -58,6 +59,7 @@ import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ClientMetaService;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetAllMetaRegionLocationsRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.LocateMetaRegionRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ZooKeeperProtos;
 
@@ -298,8 +300,27 @@ class ZKConnectionRegistry implements ConnectionRegistry {
   @Override
   public CompletableFuture<List<HRegionLocation>>
     getAllMetaRegionLocations(boolean excludeOfflinedSplitParents) {
-    return ConnectionUtils.getAllMetaRegionLocations(excludeOfflinedSplitParents, getStub(),
-      cachedStub, rpcControllerFactory, -1);
+    CompletableFuture<List<HRegionLocation>> future = new CompletableFuture<>();
+    addListener(getStub(), (stub, error) -> {
+      if (error != null) {
+        future.completeExceptionally(error);
+        return;
+      }
+      HBaseRpcController controller = rpcControllerFactory.newController();
+      stub.getAllMetaRegionLocations(controller, GetAllMetaRegionLocationsRequest.newBuilder()
+        .setExcludeOfflinedSplitParents(excludeOfflinedSplitParents).build(), resp -> {
+          if (controller.failed()) {
+            IOException ex = controller.getFailed();
+            tryClearMasterStubCache(ex, stub, cachedStub);
+            future.completeExceptionally(ex);
+            return;
+          }
+          List<HRegionLocation> locs = resp.getMetaLocationsList().stream()
+            .map(ProtobufUtil::toRegionLocation).collect(Collectors.toList());
+          future.complete(locs);
+        });
+    });
+    return future;
   }
 
   @Override
diff --git a/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto b/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto
index 01a8941..2bb35c2 100644
--- a/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/server/master/Master.proto
@@ -1371,3 +1371,17 @@ service ClientMetaService {
   rpc GetAllMetaRegionLocations(GetAllMetaRegionLocationsRequest)
     returns(GetAllMetaRegionLocationsResponse);
 }
+
+message SyncRootRequest {
+  required int64 lastSyncSeqId = 1;
+}
+
+message SyncRootResponse {
+  required int64 lastModifiedSeqId = 1;
+  repeated RegionLocation meta_locations = 2;
+}
+
+service RootSyncService {
+  rpc SyncRoot(SyncRootRequest)
+    returns(SyncRootResponse);
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnection.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnection.java
index 8e64b4b..8b0f0a2 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnection.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnection.java
@@ -103,5 +103,6 @@ public interface AsyncClusterConnection extends AsyncConnection {
   /**
    * Fetch all meta region locations from active master, used by backup masters for caching.
    */
-  CompletableFuture<List<HRegionLocation>> getAllMetaRegionLocations(int callTimeoutMs);
+  CompletableFuture<Pair<Long, List<HRegionLocation>>> syncRoot(long lastSyncSeqId,
+    int callTimeoutMs);
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnectionImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnectionImpl.java
index cfe62db..8fdd439 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnectionImpl.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/client/AsyncClusterConnectionImpl.java
@@ -17,17 +17,23 @@
  */
 package org.apache.hadoop.hbase.client;
 
+import static org.apache.hadoop.hbase.client.ConnectionUtils.tryClearMasterStubCache;
+import static org.apache.hadoop.hbase.util.FutureUtils.addListener;
+
+import java.io.IOException;
 import java.net.SocketAddress;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.RegionLocations;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.ipc.HBaseRpcController;
 import org.apache.hadoop.hbase.ipc.RpcClient;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.util.Pair;
@@ -46,7 +52,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.PrepareBul
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.PrepareBulkLoadResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ClientMetaService;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RootSyncService;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SyncRootRequest;
 
 /**
  * The implementation of AsyncClusterConnection.
@@ -54,11 +61,11 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ClientMeta
 @InterfaceAudience.Private
 class AsyncClusterConnectionImpl extends AsyncConnectionImpl implements AsyncClusterConnection {
 
-  private final AtomicReference<ClientMetaService.Interface> cachedClientMetaStub =
+  private final AtomicReference<RootSyncService.Interface> cachedRootSyncStub =
     new AtomicReference<>();
 
-  private final AtomicReference<CompletableFuture<ClientMetaService.Interface>>
-    clientMetaStubMakeFuture = new AtomicReference<>();
+  private final AtomicReference<CompletableFuture<RootSyncService.Interface>>
+    rootSyncStubMakeFuture = new AtomicReference<>();
 
   public AsyncClusterConnectionImpl(Configuration conf, ConnectionRegistry registry,
     String clusterId, SocketAddress localAddress, User user) {
@@ -143,15 +150,38 @@ class AsyncClusterConnectionImpl extends AsyncConnectionImpl implements AsyncClu
       .call();
   }
 
-  private CompletableFuture<ClientMetaService.Interface> getClientMetaStub() {
-    return ConnectionUtils.getMasterStub(registry, cachedClientMetaStub, clientMetaStubMakeFuture,
-      rpcClient, user, rpcTimeout, TimeUnit.MILLISECONDS, ClientMetaService::newStub,
-      "ClientMetaService");
+  private CompletableFuture<RootSyncService.Interface> getRootSyncStub() {
+    return ConnectionUtils.getMasterStub(registry, cachedRootSyncStub, rootSyncStubMakeFuture,
+      rpcClient, user, rpcTimeout, TimeUnit.MILLISECONDS, RootSyncService::newStub,
+      "RootSyncService");
   }
 
   @Override
-  public CompletableFuture<List<HRegionLocation>> getAllMetaRegionLocations(int callTimeoutMs) {
-    return ConnectionUtils.getAllMetaRegionLocations(false, getClientMetaStub(),
-      cachedClientMetaStub, rpcControllerFactory, callTimeoutMs);
+  public CompletableFuture<Pair<Long, List<HRegionLocation>>> syncRoot(long lastSyncSeqId,
+    int callTimeoutMs) {
+    CompletableFuture<Pair<Long, List<HRegionLocation>>> future = new CompletableFuture<>();
+    addListener(getRootSyncStub(), (stub, error) -> {
+      if (error != null) {
+        future.completeExceptionally(error);
+        return;
+      }
+      HBaseRpcController controller = rpcControllerFactory.newController();
+      if (callTimeoutMs > 0) {
+        controller.setCallTimeout(callTimeoutMs);
+      }
+      stub.syncRoot(controller,
+        SyncRootRequest.newBuilder().setLastSyncSeqId(lastSyncSeqId).build(), resp -> {
+          if (controller.failed()) {
+            IOException ex = controller.getFailed();
+            tryClearMasterStubCache(ex, stub, cachedRootSyncStub);
+            future.completeExceptionally(ex);
+            return;
+          }
+          List<HRegionLocation> locs = resp.getMetaLocationsList().stream()
+            .map(ProtobufUtil::toRegionLocation).collect(Collectors.toList());
+          future.complete(Pair.newPair(resp.getLastModifiedSeqId(), locs));
+        });
+    });
+    return future;
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
index 8ca8972..e6197ba 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java
@@ -1824,4 +1824,24 @@ public interface MasterObserver {
   default void postGetAllMetaRegionLocations(ObserverContext<MasterCoprocessorEnvironment> ctx,
     boolean excludeOfflinedSplitParents, List<HRegionLocation> locs) {
   }
+
+  /**
+   * Called before syncing root
+   * @param ctx ctx the coprocessor instance's environment
+   * @param lastSyncSeqId the sequence id when we call sync root last time
+   */
+  default void preSyncRoot(ObserverContext<MasterCoprocessorEnvironment> ctx, long lastSyncSeqId) {
+  }
+
+  /**
+   * Called before syncing root
+   * @param ctx ctx the coprocessor instance's environment
+   * @param lastSyncSeqId the sequence id when we call sync root last time
+   * @param lastModifiedSeqId the sequence id for this sync operation, it could be less than or
+   *          equal to {@code lastSyncSeqId}, then it usually means we do not sync anything.
+   * @param locs the locations of all meta regions, including meta replicas if any.
+   */
+  default void postSyncRoot(ObserverContext<MasterCoprocessorEnvironment> ctx, long lastSyncSeqId,
+    long lastModifiedSeqId, List<HRegionLocation> locs) {
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index b9523bf..fcb71d8 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -96,6 +96,7 @@ import org.apache.hadoop.hbase.client.RegionLocateType;
 import org.apache.hadoop.hbase.client.RegionReplicaUtil;
 import org.apache.hadoop.hbase.client.RegionStatesCount;
 import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
@@ -150,6 +151,7 @@ import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
 import org.apache.hadoop.hbase.master.procedure.TruncateTableProcedure;
 import org.apache.hadoop.hbase.master.region.MasterRegion;
 import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.master.replication.AbstractPeerProcedure;
 import org.apache.hadoop.hbase.master.replication.AddPeerProcedure;
 import org.apache.hadoop.hbase.master.replication.DisablePeerProcedure;
@@ -470,7 +472,11 @@ public class HMaster extends HRegionServer implements MasterServices {
   private ProcedureStore procedureStore;
 
   // the master local storage to store procedure data, root table, etc.
-  private MasterRegion masterRegion;
+  @VisibleForTesting
+  MasterRegion masterRegion;
+
+  // a wrapper of MasterRegion to provide root table storage
+  private RootStore rootStore;
 
   // handle table states
   private TableStateManager tableStateManager;
@@ -902,8 +908,8 @@ public class HMaster extends HRegionServer implements MasterServices {
   // Will be overriden in test to inject customized AssignmentManager
   @VisibleForTesting
   protected AssignmentManager createAssignmentManager(MasterServices master,
-    MasterRegion masterRegion) {
-    return new AssignmentManager(master, masterRegion);
+    RootStore rootStore) {
+    return new AssignmentManager(master, rootStore);
   }
 
   /**
@@ -1066,6 +1072,7 @@ public class HMaster extends HRegionServer implements MasterServices {
 
     // initialize master local region
     masterRegion = MasterRegionFactory.create(this);
+    rootStore = new RootStore(masterRegion);
 
     tryMigrateRootTableFromZooKeeper();
 
@@ -1079,7 +1086,7 @@ public class HMaster extends HRegionServer implements MasterServices {
         .collect(Collectors.groupingBy(p -> p.getClass()));
 
     // Create Assignment Manager
-    this.assignmentManager = createAssignmentManager(this, masterRegion);
+    this.assignmentManager = createAssignmentManager(this, rootStore);
     this.assignmentManager.start();
     // TODO: TRSP can perform as the sub procedure for other procedures, so even if it is marked as
     // completed, it could still be in the procedure list. This is a bit strange but is another
@@ -4005,16 +4012,12 @@ public class HMaster extends HRegionServer implements MasterServices {
     }
     Scan scan =
       CatalogFamilyFormat.createRegionLocateScan(TableName.META_TABLE_NAME, row, locateType, 1);
-    try (RegionScanner scanner = masterRegion.getScanner(scan)) {
-      boolean moreRows;
-      List<Cell> cells = new ArrayList<>();
-      do {
-        moreRows = scanner.next(cells);
-        if (cells.isEmpty()) {
-          continue;
+    try (ResultScanner scanner = rootStore.getScanner(scan)) {
+      for (;;) {
+        Result result = scanner.next();
+        if (result == null) {
+          break;
         }
-        Result result = Result.create(cells);
-        cells.clear();
         RegionLocations locs = CatalogFamilyFormat.getRegionLocations(result);
         if (locs == null || locs.getDefaultRegionLocation() == null) {
           LOG.warn("No location found when locating meta region with row='{}', locateType={}",
@@ -4032,7 +4035,7 @@ public class HMaster extends HRegionServer implements MasterServices {
           continue;
         }
         return locs;
-      } while (moreRows);
+      }
       LOG.warn("No location available when locating meta region with row='{}', locateType={}",
         Bytes.toStringBinary(row), locateType);
       return null;
@@ -4041,39 +4044,10 @@ public class HMaster extends HRegionServer implements MasterServices {
 
   public List<RegionLocations> getAllMetaRegionLocations(boolean excludeOfflinedSplitParents)
     throws IOException {
-    Scan scan = new Scan().addFamily(HConstants.CATALOG_FAMILY);
-    List<RegionLocations> list = new ArrayList<>();
-    try (RegionScanner scanner = masterRegion.getScanner(scan)) {
-      boolean moreRows;
-      List<Cell> cells = new ArrayList<>();
-      do {
-        moreRows = scanner.next(cells);
-        if (cells.isEmpty()) {
-          continue;
-        }
-        Result result = Result.create(cells);
-        cells.clear();
-        RegionLocations locs = CatalogFamilyFormat.getRegionLocations(result);
-        if (locs == null) {
-          LOG.warn("No locations in {}", result);
-          continue;
-        }
-        HRegionLocation loc = locs.getRegionLocation();
-        if (loc == null) {
-          LOG.warn("No non null location in {}", result);
-          continue;
-        }
-        RegionInfo info = loc.getRegion();
-        if (info == null) {
-          LOG.warn("No serialized RegionInfo in {}", result);
-          continue;
-        }
-        if (excludeOfflinedSplitParents && info.isSplitParent()) {
-          continue;
-        }
-        list.add(locs);
-      } while (moreRows);
-    }
-    return list;
+    return rootStore.getAllMetaRegionLocations(excludeOfflinedSplitParents);
+  }
+
+  public Pair<Long, List<RegionLocations>> syncRoot(long lastSyncSeqId) throws IOException {
+    return rootStore.sync(lastSyncSeqId);
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
index 728da5c..3289c9d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java
@@ -2078,4 +2078,23 @@ public class MasterCoprocessorHost
       }
     });
   }
+
+  public void preSyncRoot(long lastSyncSeqId) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.preSyncRoot(this, lastSyncSeqId);
+      }
+    });
+  }
+
+  public void postSyncRoot(long lastSyncSeqId, long lastModifiedSeqId, List<HRegionLocation> locs)
+    throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      public void call(MasterObserver observer) throws IOException {
+        observer.postSyncRoot(this, lastSyncSeqId, lastModifiedSeqId, locs);
+      }
+    });
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index fbda246..0593df9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -293,6 +293,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.Recommissi
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RegionSpecifierAndState;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RootSyncService;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalogScanRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalogScanResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCleanerChoreRequest;
@@ -328,6 +329,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExce
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchExceedThrottleQuotaResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SwitchRpcThrottleResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SyncRootRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SyncRootResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.TruncateTableResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.UnassignRegionRequest;
@@ -410,9 +413,10 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.
  */
 @InterfaceAudience.Private
 @SuppressWarnings("deprecation")
-public class MasterRpcServices extends RSRpcServices implements MasterService.BlockingInterface,
-  RegionServerStatusService.BlockingInterface, LockService.BlockingInterface,
-  HbckService.BlockingInterface, ClientMetaService.BlockingInterface {
+public class MasterRpcServices extends RSRpcServices
+  implements MasterService.BlockingInterface, RegionServerStatusService.BlockingInterface,
+  LockService.BlockingInterface, HbckService.BlockingInterface, ClientMetaService.BlockingInterface,
+  RootSyncService.BlockingInterface {
 
   private static final Logger LOG = LoggerFactory.getLogger(MasterRpcServices.class.getName());
   private static final Logger AUDITLOG =
@@ -546,7 +550,7 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
    */
   @Override
   protected List<BlockingServiceAndInterface> getServices() {
-    List<BlockingServiceAndInterface> bssi = new ArrayList<>(5);
+    List<BlockingServiceAndInterface> bssi = new ArrayList<>();
     bssi.add(new BlockingServiceAndInterface(MasterService.newReflectiveBlockingService(this),
       MasterService.BlockingInterface.class));
     bssi.add(
@@ -558,6 +562,8 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
       HbckService.BlockingInterface.class));
     bssi.add(new BlockingServiceAndInterface(ClientMetaService.newReflectiveBlockingService(this),
       ClientMetaService.BlockingInterface.class));
+    bssi.add(new BlockingServiceAndInterface(RootSyncService.newReflectiveBlockingService(this),
+      RootSyncService.BlockingInterface.class));
     bssi.addAll(super.getServices());
     return bssi;
   }
@@ -3417,6 +3423,18 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
     }
   }
 
+  private static List<HRegionLocation> locs2Loc(List<RegionLocations> locs) {
+    List<HRegionLocation> list = new ArrayList<>();
+    for (RegionLocations ls : locs) {
+      for (HRegionLocation loc : ls) {
+        if (loc != null) {
+          list.add(loc);
+        }
+      }
+    }
+    return list;
+  }
+
   @Override
   public GetAllMetaRegionLocationsResponse getAllMetaRegionLocations(RpcController controller,
     GetAllMetaRegionLocationsRequest request) throws ServiceException {
@@ -3431,16 +3449,7 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
         list = cache.getAllMetaRegionLocations(excludeOfflinedSplitParents);
       } else {
         List<RegionLocations> locs = master.getAllMetaRegionLocations(excludeOfflinedSplitParents);
-        list = new ArrayList<>();
-        if (locs != null) {
-          for (RegionLocations ls : locs) {
-            for (HRegionLocation loc : ls) {
-              if (loc != null) {
-                list.add(loc);
-              }
-            }
-          }
-        }
+        list = locs2Loc(locs);
       }
       GetAllMetaRegionLocationsResponse.Builder builder =
         GetAllMetaRegionLocationsResponse.newBuilder();
@@ -3456,4 +3465,29 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
       throw new ServiceException(e);
     }
   }
+
+  @Override
+  public SyncRootResponse syncRoot(RpcController controller, SyncRootRequest request)
+    throws ServiceException {
+    long lastSyncSeqId = request.getLastSyncSeqId();
+    try {
+      master.checkServiceStarted();
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preSyncRoot(lastSyncSeqId);
+      }
+      Pair<Long, List<RegionLocations>> pair = master.syncRoot(lastSyncSeqId);
+      List<HRegionLocation> locs = locs2Loc(pair.getSecond());
+      SyncRootResponse.Builder builder = SyncRootResponse.newBuilder();
+      builder.setLastModifiedSeqId(pair.getFirst());
+      for (HRegionLocation loc : locs) {
+        builder.addMetaLocations(ProtobufUtil.toRegionLocation(loc));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postSyncRoot(lastSyncSeqId, pair.getFirst(), locs);
+      }
+      return builder.build();
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MetaLocationCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MetaLocationCache.java
index 0f5cf11..cc234f6 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MetaLocationCache.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MetaLocationCache.java
@@ -30,7 +30,9 @@ import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
+import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.MetaCellComparator;
 import org.apache.hadoop.hbase.RegionLocations;
@@ -50,7 +52,7 @@ import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesti
  * A cache of meta region locations.
  */
 @InterfaceAudience.Private
-class MetaLocationCache implements Stoppable {
+public class MetaLocationCache implements Stoppable {
 
   private static final Logger LOG = LoggerFactory.getLogger(MetaLocationCache.class);
 
@@ -68,13 +70,17 @@ class MetaLocationCache implements Stoppable {
   // default timeout 1 second
   private static final int DEFAULT_FETCH_TIMEOUT_MS = 1000;
 
-  private static final class CacheHolder {
+  @VisibleForTesting
+  static final class CacheHolder {
+
+    final long lastSyncSeqId;
 
     final NavigableMap<byte[], RegionLocations> cache;
 
     final List<HRegionLocation> all;
 
-    CacheHolder(List<HRegionLocation> all) {
+    CacheHolder(long lastSyncSeqId, List<HRegionLocation> all) {
+      this.lastSyncSeqId = lastSyncSeqId;
       this.all = Collections.unmodifiableList(all);
       NavigableMap<byte[], SortedSet<HRegionLocation>> startKeyToLocs =
         new TreeMap<>(MetaCellComparator.ROW_COMPARATOR);
@@ -93,7 +99,10 @@ class MetaLocationCache implements Stoppable {
     }
   }
 
-  private volatile CacheHolder holder;
+  @VisibleForTesting
+  final AtomicReference<CacheHolder> holder = new AtomicReference<>();
+
+  private final ScheduledChore refreshChore;
 
   private volatile boolean stopped = false;
 
@@ -102,34 +111,43 @@ class MetaLocationCache implements Stoppable {
       master.getConfiguration().getInt(SYNC_INTERVAL_SECONDS, DEFAULT_SYNC_INTERVAL_SECONDS);
     int fetchTimeoutMs =
       master.getConfiguration().getInt(FETCH_TIMEOUT_MS, DEFAULT_FETCH_TIMEOUT_MS);
-    master.getChoreService().scheduleChore(new ScheduledChore(
-      getClass().getSimpleName() + "-Sync-Chore", this, syncIntervalSeconds, 0, TimeUnit.SECONDS) {
+    refreshChore = new ScheduledChore(getClass().getSimpleName() + "-Sync-Chore", this,
+      syncIntervalSeconds, 0, TimeUnit.SECONDS) {
 
       @Override
       protected void chore() {
         AsyncClusterConnection conn = master.getAsyncClusterConnection();
         if (conn != null) {
-          addListener(conn.getAllMetaRegionLocations(fetchTimeoutMs), (locs, error) -> {
+          final CacheHolder ch = holder.get();
+          long lastSyncSeqId = ch != null ? ch.lastSyncSeqId : HConstants.NO_SEQNUM;
+          addListener(conn.syncRoot(lastSyncSeqId, fetchTimeoutMs), (resp, error) -> {
             if (error != null) {
-              LOG.warn("Failed to fetch all meta region locations from active master", error);
+              LOG.warn("Failed to sync root data from active master", error);
               return;
             }
-            holder = new CacheHolder(locs);
+            long lastModifiedSeqId = resp.getFirst().longValue();
+            if (ch == null || lastModifiedSeqId > ch.lastSyncSeqId && holder.get() == ch) {
+              // since we may trigger cache refresh when locating, here we use CAS to avoid race
+              holder.compareAndSet(ch, new CacheHolder(lastModifiedSeqId, resp.getSecond()));
+            }
           });
         }
       }
-    });
+    };
+    master.getChoreService().scheduleChore(refreshChore);
   }
 
-  RegionLocations locateMeta(byte[] row, RegionLocateType locateType) {
+  @VisibleForTesting
+  public RegionLocations locateMeta(byte[] row, RegionLocateType locateType) {
     if (locateType == RegionLocateType.AFTER) {
       // as we know the exact row after us, so we can just create the new row, and use the same
       // algorithm to locate it.
       row = Arrays.copyOf(row, row.length + 1);
       locateType = RegionLocateType.CURRENT;
     }
-    CacheHolder holder = this.holder;
+    CacheHolder holder = this.holder.get();
     if (holder == null) {
+      refreshChore.triggerNow();
       return null;
     }
     return locateType.equals(RegionLocateType.BEFORE) ?
@@ -138,8 +156,9 @@ class MetaLocationCache implements Stoppable {
   }
 
   List<HRegionLocation> getAllMetaRegionLocations(boolean excludeOfflinedSplitParents) {
-    CacheHolder holder = this.holder;
+    CacheHolder holder = this.holder.get();
     if (holder == null) {
+      refreshChore.triggerNow();
       return Collections.emptyList();
     }
     if (!excludeOfflinedSplitParents) {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
index fc621ff..8d789c2 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
@@ -34,7 +34,6 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.CatalogFamilyFormat;
-import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.HConstants;
@@ -47,6 +46,7 @@ import org.apache.hadoop.hbase.client.MasterSwitchType;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.RegionStatesCount;
 import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableState;
@@ -67,13 +67,12 @@ import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureScheduler;
 import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.procedure2.ProcedureEvent;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.procedure2.ProcedureInMemoryChore;
 import org.apache.hadoop.hbase.procedure2.util.StringUtils;
-import org.apache.hadoop.hbase.regionserver.RegionScanner;
 import org.apache.hadoop.hbase.regionserver.SequenceId;
 import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -172,22 +171,22 @@ public class AssignmentManager {
   private final int assignMaxAttempts;
   private final int assignRetryImmediatelyMaxAttempts;
 
-  private final MasterRegion masterRegion;
+  private final RootStore rootStore;
 
   private final Object checkIfShouldMoveSystemRegionLock = new Object();
 
   private Thread assignThread;
 
-  public AssignmentManager(MasterServices master, MasterRegion masterRegion) {
-    this(master, masterRegion, new RegionStateStore(master, masterRegion));
+  public AssignmentManager(MasterServices master, RootStore rootStore) {
+    this(master, rootStore, new RegionStateStore(master, rootStore));
   }
 
   @VisibleForTesting
-  AssignmentManager(MasterServices master, MasterRegion masterRegion, RegionStateStore stateStore) {
+  AssignmentManager(MasterServices master, RootStore rootStore, RegionStateStore stateStore) {
     this.master = master;
     this.regionStateStore = stateStore;
     this.metrics = new MetricsAssignmentManager();
-    this.masterRegion = masterRegion;
+    this.rootStore = rootStore;
 
     final Configuration conf = master.getConfiguration();
 
@@ -231,17 +230,13 @@ public class AssignmentManager {
     // load meta region states.
     // notice that, here we will load all replicas, and in MasterMetaBootstrap we may assign new
     // replicas, or remove excess replicas.
-    try (RegionScanner scanner =
-      masterRegion.getScanner(new Scan().addFamily(HConstants.CATALOG_FAMILY))) {
-      List<Cell> cells = new ArrayList<>();
-      boolean moreRows;
-      do {
-        moreRows = scanner.next(cells);
-        if (cells.isEmpty()) {
-          continue;
+    try (ResultScanner scanner =
+      rootStore.getScanner(new Scan().addFamily(HConstants.CATALOG_FAMILY))) {
+      for(;;) {
+        Result result = scanner.next();
+        if (result == null) {
+          break;
         }
-        Result result = Result.create(cells);
-        cells.clear();
         RegionStateStore
           .visitMetaEntry((r, regionInfo, state, regionLocation, lastHost, openSeqNum) -> {
             RegionStateNode regionNode = regionStates.getOrCreateRegionStateNode(regionInfo);
@@ -265,7 +260,7 @@ public class AssignmentManager {
               }
             }
           }, result);
-      } while (moreRows);
+      }
     }
   }
 
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java
index 4e7d271..5fe93f9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/RegionStateStore.java
@@ -48,6 +48,7 @@ import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.RegionInfoBuilder;
 import org.apache.hadoop.hbase.client.RegionReplicaUtil;
 import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.client.TableDescriptor;
@@ -55,10 +56,9 @@ import org.apache.hadoop.hbase.master.MasterFileSystem;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.master.RegionState.State;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.procedure2.util.StringUtils;
-import org.apache.hadoop.hbase.regionserver.RegionScanner;
 import org.apache.hadoop.hbase.replication.ReplicationBarrierFamilyFormat;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
@@ -92,11 +92,11 @@ public class RegionStateStore {
 
   private final MasterServices master;
 
-  private final MasterRegion masterRegion;
+  private final RootStore rootStore;
 
-  public RegionStateStore(MasterServices master, MasterRegion masterRegion) {
+  public RegionStateStore(MasterServices master, RootStore rootStore) {
     this.master = master;
-    this.masterRegion = masterRegion;
+    this.rootStore = rootStore;
   }
 
   @FunctionalInterface
@@ -238,21 +238,9 @@ public class RegionStateStore {
     // scan meta first
     MetaTableAccessor.fullScanRegions(master.getConnection(), visitor);
     // scan root
-    try (RegionScanner scanner =
-      masterRegion.getScanner(new Scan().addFamily(HConstants.CATALOG_FAMILY))) {
-      boolean moreRows;
-      List<Cell> cells = new ArrayList<>();
-      do {
-        moreRows = scanner.next(cells);
-        if (cells.isEmpty()) {
-          continue;
-        }
-        Result result = Result.create(cells);
-        cells.clear();
-        if (!visitor.visit(result)) {
-          break;
-        }
-      } while (moreRows);
+    try (ResultScanner scanner =
+      rootStore.getScanner(new Scan().addFamily(HConstants.CATALOG_FAMILY))) {
+      ClientMetaTableAccessor.visit(scanner, visitor, -1);
     }
   }
 
@@ -270,7 +258,7 @@ public class RegionStateStore {
     throws IOException {
     try {
       if (regionInfo.isMetaRegion()) {
-        masterRegion.update(r -> r.put(put));
+        rootStore.put(put);
       } else {
         try (Table table = master.getConnection().getTable(TableName.META_TABLE_NAME)) {
           table.put(put);
@@ -301,11 +289,7 @@ public class RegionStateStore {
   private void multiMutate(RegionInfo ri, List<Mutation> mutations) throws IOException {
     debugLogMutations(mutations);
     if (ri.isMetaRegion()) {
-      masterRegion.update(region -> {
-        List<byte[]> rowsToLock =
-          mutations.stream().map(Mutation::getRow).collect(Collectors.toList());
-        region.mutateRowsWithLocks(mutations, rowsToLock, HConstants.NO_NONCE, HConstants.NO_NONCE);
-      });
+      rootStore.multiMutate(mutations);
     } else {
       byte[] row =
         Bytes.toBytes(RegionReplicaUtil.getRegionInfoForDefaultReplica(ri).getRegionNameAsString() +
@@ -342,7 +326,7 @@ public class RegionStateStore {
     Get get =
       new Get(CatalogFamilyFormat.getMetaKeyForRegion(region)).addFamily(HConstants.CATALOG_FAMILY);
     if (region.isMetaRegion()) {
-      return masterRegion.get(get);
+      return rootStore.get(get);
     } else {
       try (Table table = getMetaTable()) {
         return table.get(get);
@@ -505,7 +489,7 @@ public class RegionStateStore {
     }
     debugLogMutation(delete);
     if (mergeRegion.isMetaRegion()) {
-      masterRegion.update(r -> r.delete(delete));
+      rootStore.delete(delete);
     } else {
       try (Table table = getMetaTable()) {
         table.delete(delete);
@@ -576,9 +560,7 @@ public class RegionStateStore {
     if (!metaRegions.isEmpty()) {
       List<Delete> deletes = makeDeleteRegionInfos(metaRegions, ts);
       debugLogMutations(deletes);
-      for (Delete d : deletes) {
-        masterRegion.update(r -> r.delete(d));
-      }
+      rootStore.delete(deletes);
       LOG.info("Deleted {} regions from ROOT", metaRegions.size());
       LOG.debug("Deleted regions: {}", metaRegions);
     }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
index cbfc2a3..5bc23c9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegion.java
@@ -142,6 +142,15 @@ public final class MasterRegion {
     return region.getScanner(scan);
   }
 
+  WAL getWAL() {
+    return region.getWAL();
+  }
+
+  @VisibleForTesting
+  public long getReadPoint() {
+    return region.getMVCC().getReadPoint();
+  }
+
   @VisibleForTesting
   public FlushResult flush(boolean force) throws IOException {
     return region.flush(force);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RegionScannerAsResultScanner.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RegionScannerAsResultScanner.java
new file mode 100644
index 0000000..f7ac315
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RegionScannerAsResultScanner.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hbase.master.region;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
+import org.apache.hadoop.hbase.regionserver.RegionScanner;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrap a {@link RegionScanner} as a {@link ResultScanner}.
+ */
+@InterfaceAudience.Private
+class RegionScannerAsResultScanner implements ResultScanner {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RootStore.class);
+
+  private final RegionScanner scanner;
+
+  private boolean moreRows = true;
+
+  private final List<Cell> cells = new ArrayList<>();
+
+  public RegionScannerAsResultScanner(RegionScanner scanner) {
+    this.scanner = scanner;
+  }
+
+  @Override
+  public boolean renewLease() {
+    return true;
+  }
+
+  @Override
+  public Result next() throws IOException {
+    if (!moreRows) {
+      return null;
+    }
+    for (;;) {
+      moreRows = scanner.next(cells);
+      if (cells.isEmpty()) {
+        if (!moreRows) {
+          return null;
+        } else {
+          continue;
+        }
+      }
+      Result result = Result.create(cells);
+      cells.clear();
+      return result;
+    }
+  }
+
+  @Override
+  public ScanMetrics getScanMetrics() {
+    return null;
+  }
+
+  @Override
+  public void close() {
+    try {
+      scanner.close();
+    } catch (IOException e) {
+      LOG.warn("Failed to close scanner", e);
+    }
+  }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RootStore.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RootStore.java
new file mode 100644
index 0000000..0607f9e
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/RootStore.java
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hbase.master.region;
+
+import static org.apache.hadoop.hbase.HConstants.NO_NONCE;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import org.apache.hadoop.hbase.CatalogFamilyFormat;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionLocation;
+import org.apache.hadoop.hbase.RegionLocations;
+import org.apache.hadoop.hbase.client.Delete;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.Mutation;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
+import org.apache.hadoop.hbase.util.AtomicUtils;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
+import org.apache.hadoop.hbase.wal.WALEdit;
+import org.apache.hadoop.hbase.wal.WALKey;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A wrapper of {@link MasterRegion} to support root table storage.
+ */
+@InterfaceAudience.Private
+public class RootStore {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RootStore.class);
+
+  private final MasterRegion region;
+
+  private final AtomicLong lastModifiedSeqId = new AtomicLong(HConstants.NO_SEQNUM);
+
+  public RootStore(MasterRegion region) {
+    this.region = region;
+    lastModifiedSeqId.set(region.getReadPoint());
+    region.getWAL().registerWALActionsListener(new WALActionsListener() {
+
+      @Override
+      public void postAppend(long entryLen, long elapsedTimeMillis, WALKey logKey, WALEdit logEdit)
+        throws IOException {
+        for (byte[] family : logEdit.getFamilies()) {
+          // we only care about catalog family
+          if (!Bytes.equals(family, HConstants.CATALOG_FAMILY)) {
+            return;
+          }
+        }
+        AtomicUtils.updateMax(lastModifiedSeqId, logKey.getSequenceId());
+      }
+    });
+  }
+
+  public ResultScanner getScanner(Scan scan) throws IOException {
+    return new RegionScannerAsResultScanner(region.getScanner(scan));
+  }
+
+  public Result get(Get get) throws IOException {
+    return region.get(get);
+  }
+
+  public void put(Put put) throws IOException {
+    region.update(r -> r.put(put));
+  }
+
+  public void delete(Delete delete) throws IOException {
+    region.update(r -> r.delete(delete));
+  }
+
+  public void delete(List<Delete> deletes) throws IOException {
+    region.update(r -> {
+      for (Delete delete : deletes) {
+        r.delete(delete);
+      }
+    });
+  }
+
+  public void multiMutate(List<Mutation> mutations) throws IOException {
+    region.update(r -> {
+      List<byte[]> rowsToLock =
+        mutations.stream().map(Mutation::getRow).collect(Collectors.toList());
+      r.mutateRowsWithLocks(mutations, rowsToLock, NO_NONCE, NO_NONCE);
+    });
+  }
+
+  public List<RegionLocations> getAllMetaRegionLocations(boolean excludeOfflinedSplitParents)
+    throws IOException {
+    List<RegionLocations> list = new ArrayList<>();
+    try (ResultScanner scanner = getScanner(new Scan().addFamily(HConstants.CATALOG_FAMILY))) {
+      for (;;) {
+        Result result = scanner.next();
+        if (result == null) {
+          break;
+        }
+        RegionLocations locs = CatalogFamilyFormat.getRegionLocations(result);
+        if (locs == null) {
+          LOG.warn("No locations in {}", result);
+          continue;
+        }
+        HRegionLocation loc = locs.getRegionLocation();
+        if (loc == null) {
+          LOG.warn("No non null location in {}", result);
+          continue;
+        }
+        RegionInfo info = loc.getRegion();
+        if (info == null) {
+          LOG.warn("No serialized RegionInfo in {}", result);
+          continue;
+        }
+        if (excludeOfflinedSplitParents && info.isSplitParent()) {
+          continue;
+        }
+        list.add(locs);
+      }
+    }
+    return list;
+  }
+
+  public Pair<Long, List<RegionLocations>> sync(long lastSyncSeqId) throws IOException {
+    long lastModSeqId = Math.min(lastModifiedSeqId.get(), region.getReadPoint());
+    if (lastModSeqId <= lastSyncSeqId) {
+      return Pair.newPair(lastSyncSeqId, Collections.emptyList());
+    }
+    return Pair.newPair(lastModSeqId, getAllMetaRegionLocations(false));
+  }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
index ed0d2f5..ec27717 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
@@ -1518,7 +1518,6 @@ public class HRegion implements HeapSize, PropagatingConfigurationObserver, Regi
     }
   }
 
-  @VisibleForTesting
   public MultiVersionConcurrencyControl getMVCC() {
     return mvcc;
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/DummyAsyncClusterConnection.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/DummyAsyncClusterConnection.java
index 4385a5a..93e8aa5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/DummyAsyncClusterConnection.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/DummyAsyncClusterConnection.java
@@ -155,12 +155,13 @@ public class DummyAsyncClusterConnection implements AsyncClusterConnection {
   }
 
   @Override
-  public Connection toConnection() {
+  public CompletableFuture<Pair<Long, List<HRegionLocation>>> syncRoot(long lastSyncSeqId,
+    int callTimeoutMs) {
     return null;
   }
 
   @Override
-  public CompletableFuture<List<HRegionLocation>> getAllMetaRegionLocations(int callTimeoutMs) {
+  public Connection toConnection() {
     return null;
   }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncAdminBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncAdminBase.java
index 70cffd8..e895f16 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncAdminBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncAdminBase.java
@@ -26,7 +26,6 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ForkJoinPool;
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
-import org.apache.commons.io.IOUtils;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.StartMiniClusterOption;
@@ -43,6 +42,8 @@ import org.junit.runners.Parameterized.Parameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
+
 /**
  * Class to test AsyncAdmin.
  */
@@ -92,7 +93,7 @@ public abstract class TestAsyncAdminBase extends AbstractTestUpdateConfiguration
 
   @AfterClass
   public static void tearDownAfterClass() throws Exception {
-    IOUtils.closeQuietly(ASYNC_CONN);
+    Closeables.close(ASYNC_CONN, true);
     TEST_UTIL.shutdownMiniCluster();
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFailedMetaReplicaAssigment.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFailedMetaReplicaAssigment.java
index d242932..1fd8b96 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFailedMetaReplicaAssigment.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestFailedMetaReplicaAssigment.java
@@ -35,7 +35,7 @@ import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
 import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
 import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.testclassification.MiscTests;
 import org.junit.AfterClass;
@@ -113,8 +113,8 @@ public class TestFailedMetaReplicaAssigment {
 
     @Override
     public AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new BrokenMasterMetaAssignmentManager(master, masterRegion);
+      RootStore rootStore) {
+      return new BrokenMasterMetaAssignmentManager(master, rootStore);
     }
   }
 
@@ -122,8 +122,8 @@ public class TestFailedMetaReplicaAssigment {
     MasterServices master;
 
     public BrokenMasterMetaAssignmentManager(final MasterServices master,
-      MasterRegion masterRegion) {
-      super(master, masterRegion);
+      RootStore rootStore) {
+      super(master, rootStore);
       this.master = master;
     }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBackupMasterSyncRoot.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBackupMasterSyncRoot.java
new file mode 100644
index 0000000..be85bd8
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestBackupMasterSyncRoot.java
@@ -0,0 +1,113 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.hadoop.hbase.master;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.RegionLocations;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.StartMiniClusterOption;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.RegionLocateType;
+import org.apache.hadoop.hbase.master.MetaLocationCache.CacheHolder;
+import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ MasterTests.class, MediumTests.class })
+public class TestBackupMasterSyncRoot {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestBackupMasterSyncRoot.class);
+
+  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    UTIL.getConfiguration().setInt(MetaLocationCache.SYNC_INTERVAL_SECONDS, 1);
+    StartMiniClusterOption option =
+      StartMiniClusterOption.builder().numMasters(2).numRegionServers(3).build();
+    UTIL.startMiniCluster(option);
+    UTIL.getAdmin().balancerSwitch(false, true);
+  }
+
+  @AfterClass
+  public static void tearDown() throws IOException {
+    UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testSync() throws Exception {
+    HMaster active = UTIL.getHBaseCluster().getMaster();
+    AssignmentManager activeAM = active.getAssignmentManager();
+    RegionInfo meta =
+      activeAM.getRegionStates().getRegionsOfTable(TableName.META_TABLE_NAME).get(0);
+    ServerName expected = activeAM.getRegionStates().getRegionStateNode(meta).getRegionLocation();
+    HMaster backup = UTIL.getHBaseCluster().getMasterThreads().stream().map(t -> t.getMaster())
+      .filter(h -> h != active).findFirst().get();
+    MetaLocationCache cache = backup.getMetaLocationCache();
+    UTIL.waitFor(10000, () -> {
+      RegionLocations loc = cache.locateMeta(HConstants.EMPTY_START_ROW, RegionLocateType.CURRENT);
+      return loc != null && loc.getRegionLocation().getServerName().equals(expected);
+    });
+    CacheHolder currentHolder = cache.holder.get();
+    assertNotNull(currentHolder);
+    long lastSyncSeqId = currentHolder.lastSyncSeqId;
+    long currentMVCC = active.masterRegion.getReadPoint();
+    assertTrue(lastSyncSeqId <= currentMVCC);
+    TableName table = TableName.valueOf("test");
+    UTIL.createTable(table, Bytes.toBytes("f"));
+    UTIL.waitTableAvailable(table);
+    long newMVCC = active.masterRegion.getReadPoint();
+    // we have created several new procedures so the read point should be advanced
+    assertTrue(newMVCC > currentMVCC);
+    Thread.sleep(3000);
+    // should not change since the root family is not changed
+    assertSame(currentHolder, cache.holder.get());
+
+    ServerName newExpected =
+      UTIL.getAdmin().getRegionServers().stream().filter(s -> !s.equals(expected)).findAny().get();
+    active.getAssignmentManager().moveAsync(new RegionPlan(meta, expected, newExpected)).get();
+    assertEquals(newExpected,
+      activeAM.getRegionStates().getRegionStateNode(meta).getRegionLocation());
+    UTIL.waitFor(10000, () -> {
+      RegionLocations loc = cache.locateMeta(HConstants.EMPTY_START_ROW, RegionLocateType.CURRENT);
+      return loc != null && loc.getRegionLocation().getServerName().equals(newExpected);
+    });
+    CacheHolder newHolder = cache.holder.get();
+    // this time the cache should be changed
+    assertNotSame(currentHolder, newHolder);
+    assertTrue(newHolder.lastSyncSeqId > newMVCC);
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestCloseAnOpeningRegion.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestCloseAnOpeningRegion.java
index ecc5796..957eba5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestCloseAnOpeningRegion.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestCloseAnOpeningRegion.java
@@ -31,7 +31,7 @@ import org.apache.hadoop.hbase.client.Put;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -71,8 +71,8 @@ public class TestCloseAnOpeningRegion {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManager(master, masterRegion) {
+      RootStore rootStore) {
+      return new AssignmentManager(master, rootStore) {
 
         @Override
         public ReportRegionStateTransitionResponse reportRegionStateTransition(
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
index 7ec4c46..5f092e4 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestartFailover.java
@@ -39,7 +39,7 @@ import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
 import org.apache.hadoop.hbase.master.assignment.ServerState;
 import org.apache.hadoop.hbase.master.assignment.ServerStateNode;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.test.MetricsAssertHelper;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
@@ -167,15 +167,15 @@ public class TestClusterRestartFailover extends AbstractTestRestartCluster {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMetaLocationCache.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMetaLocationCache.java
index 306767e..2e4d3af 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMetaLocationCache.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMetaLocationCache.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -45,6 +46,7 @@ import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.FutureUtils;
+import org.apache.hadoop.hbase.util.Pair;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
@@ -99,7 +101,7 @@ public class TestMetaLocationCache {
   @Test
   public void testError() throws InterruptedException {
     AsyncClusterConnection conn = mock(AsyncClusterConnection.class);
-    when(conn.getAllMetaRegionLocations(anyInt()))
+    when(conn.syncRoot(anyLong(), anyInt()))
       .thenReturn(FutureUtils.failedFuture(new RuntimeException("inject error")));
     when(master.getAsyncClusterConnection()).thenReturn(conn);
     Thread.sleep(2000);
@@ -109,8 +111,8 @@ public class TestMetaLocationCache {
     HRegionLocation loc =
       new HRegionLocation(RegionInfoBuilder.newBuilder(TableName.META_TABLE_NAME).build(),
         ServerName.valueOf("localhost", 12345, System.currentTimeMillis()));
-    when(conn.getAllMetaRegionLocations(anyInt()))
-      .thenReturn(CompletableFuture.completedFuture(Arrays.asList(loc)));
+    when(conn.syncRoot(anyLong(), anyInt()))
+      .thenReturn(CompletableFuture.completedFuture(Pair.newPair(1L, Arrays.asList(loc))));
     Thread.sleep(2000);
     List<HRegionLocation> list = cache.getAllMetaRegionLocations(false);
     assertEquals(1, list.size());
@@ -131,8 +133,8 @@ public class TestMetaLocationCache {
       ServerName.valueOf("127.0.0.2", 12345, System.currentTimeMillis()));
     HRegionLocation daughter2Loc = new HRegionLocation(daughter2,
       ServerName.valueOf("127.0.0.3", 12345, System.currentTimeMillis()));
-    when(conn.getAllMetaRegionLocations(anyInt())).thenReturn(
-      CompletableFuture.completedFuture(Arrays.asList(parentLoc, daughter1Loc, daughter2Loc)));
+    when(conn.syncRoot(anyLong(), anyInt())).thenReturn(CompletableFuture
+      .completedFuture(Pair.newPair(1L, Arrays.asList(parentLoc, daughter1Loc, daughter2Loc))));
     when(master.getAsyncClusterConnection()).thenReturn(conn);
     Thread.sleep(2000);
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/MockMasterServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/MockMasterServices.java
index 4bcecf1..345a6d2 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/MockMasterServices.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/MockMasterServices.java
@@ -52,6 +52,7 @@ import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
 import org.apache.hadoop.hbase.master.procedure.RSProcedureDispatcher;
 import org.apache.hadoop.hbase.master.region.MasterRegion;
 import org.apache.hadoop.hbase.master.region.MasterRegionFactory;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.ProcedureEvent;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
@@ -110,9 +111,10 @@ public class MockMasterServices extends MockNoopMasterServices {
       conf.getBoolean(HBASE_SPLIT_WAL_COORDINATED_BY_ZK, DEFAULT_HBASE_SPLIT_COORDINATED_BY_ZK)?
         null: new SplitWALManager(this);
     this.masterRegion = MasterRegionFactory.create(this);
+    RootStore rootStore = new RootStore(masterRegion);
     // Mock an AM.
     this.assignmentManager =
-      new AssignmentManager(this, masterRegion, new MockRegionStateStore(this, masterRegion)) {
+      new AssignmentManager(this, rootStore, new MockRegionStateStore(this, rootStore)) {
 
         @Override
         public boolean isTableEnabled(final TableName tableName) {
@@ -302,8 +304,8 @@ public class MockMasterServices extends MockNoopMasterServices {
   }
 
   private static class MockRegionStateStore extends RegionStateStore {
-    public MockRegionStateStore(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public MockRegionStateStore(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureBackoff.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureBackoff.java
index 227872a..8f0a5a1 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureBackoff.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureBackoff.java
@@ -32,7 +32,7 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -56,8 +56,8 @@ public class TestOpenRegionProcedureBackoff {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -77,8 +77,8 @@ public class TestOpenRegionProcedureBackoff {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureHang.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureHang.java
index 57ce3a3..530f06e 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureHang.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestOpenRegionProcedureHang.java
@@ -32,7 +32,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
@@ -75,8 +75,8 @@ public class TestOpenRegionProcedureHang {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master,MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -112,8 +112,8 @@ public class TestOpenRegionProcedureHang {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndDTP.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndDTP.java
index 6576eb7..107cf82 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndDTP.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndDTP.java
@@ -31,7 +31,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.procedure.DisableTableProcedure;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
@@ -70,8 +70,8 @@ public class TestRaceBetweenSCPAndDTP {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master,masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -97,8 +97,8 @@ public class TestRaceBetweenSCPAndDTP {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndTRSP.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndTRSP.java
index 65cef99..42d0c34 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndTRSP.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRaceBetweenSCPAndTRSP.java
@@ -32,7 +32,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
@@ -70,8 +70,8 @@ public class TestRaceBetweenSCPAndTRSP {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -110,8 +110,8 @@ public class TestRaceBetweenSCPAndTRSP {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionAssignedToMultipleRegionServers.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionAssignedToMultipleRegionServers.java
index dac471e..56caeec 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionAssignedToMultipleRegionServers.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestRegionAssignedToMultipleRegionServers.java
@@ -37,7 +37,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.ServerManager;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -81,8 +81,8 @@ public class TestRegionAssignedToMultipleRegionServers {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -116,8 +116,8 @@ public class TestRegionAssignedToMultipleRegionServers {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportOnlineRegionsRace.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportOnlineRegionsRace.java
index 2d0128a..217bde6 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportOnlineRegionsRace.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportOnlineRegionsRace.java
@@ -39,7 +39,7 @@ import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -69,8 +69,8 @@ public class TestReportOnlineRegionsRace {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -112,8 +112,8 @@ public class TestReportOnlineRegionsRace {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionFromDeadServer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionFromDeadServer.java
index 41e884d..db9f45f 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionFromDeadServer.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionFromDeadServer.java
@@ -40,7 +40,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.ServerManager;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -83,8 +83,8 @@ public class TestReportRegionStateTransitionFromDeadServer {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -123,8 +123,8 @@ public class TestReportRegionStateTransitionFromDeadServer {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionRetry.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionRetry.java
index 0943298..632801e 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionRetry.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestReportRegionStateTransitionRetry.java
@@ -36,7 +36,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -61,8 +61,8 @@ public class TestReportRegionStateTransitionRetry {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -90,8 +90,8 @@ public class TestReportRegionStateTransitionRetry {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestSCPGetRegionsRace.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestSCPGetRegionsRace.java
index ffca796..d4f1cf4 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestSCPGetRegionsRace.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestSCPGetRegionsRace.java
@@ -38,7 +38,7 @@ import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.ServerManager;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -90,8 +90,8 @@ public class TestSCPGetRegionsRace {
 
   private static final class AssignmentManagerForTest extends AssignmentManager {
 
-    public AssignmentManagerForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AssignmentManagerForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -136,8 +136,8 @@ public class TestSCPGetRegionsRace {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AssignmentManagerForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AssignmentManagerForTest(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestWakeUpUnexpectedProcedure.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestWakeUpUnexpectedProcedure.java
index fb0e460..943d51e 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestWakeUpUnexpectedProcedure.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/assignment/TestWakeUpUnexpectedProcedure.java
@@ -38,7 +38,7 @@ import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.RegionPlan;
 import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.master.ServerManager;
-import org.apache.hadoop.hbase.master.region.MasterRegion;
+import org.apache.hadoop.hbase.master.region.RootStore;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.regionserver.RSRpcServices;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
@@ -136,8 +136,8 @@ public class TestWakeUpUnexpectedProcedure {
 
   private static final class AMForTest extends AssignmentManager {
 
-    public AMForTest(MasterServices master, MasterRegion masterRegion) {
-      super(master, masterRegion);
+    public AMForTest(MasterServices master, RootStore rootStore) {
+      super(master, rootStore);
     }
 
     @Override
@@ -204,8 +204,8 @@ public class TestWakeUpUnexpectedProcedure {
 
     @Override
     protected AssignmentManager createAssignmentManager(MasterServices master,
-      MasterRegion masterRegion) {
-      return new AMForTest(master, masterRegion);
+      RootStore rootStore) {
+      return new AMForTest(master, rootStore);
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover2.java
index 6007fd7..4092929 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover2.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover2.java
@@ -67,6 +67,11 @@ public class TestRegionMover2 {
 
   private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
 
+  // when moving region, we first need to get the location of meta so it will call master inside the
+  // master rpc handler thread, which may cause dead lock if we have more than 3 threads here since
+  // we only have 3 rpc handlers for master in UT.
+  private static final int MAX_THREADS = 2;
+
   @BeforeClass
   public static void setUpBeforeClass() throws Exception {
     TEST_UTIL.startMiniCluster(3);
@@ -116,7 +121,7 @@ public class TestRegionMover2 {
       .collect(Collectors.toList());
     RegionMover.RegionMoverBuilder rmBuilder =
       new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
-        .maxthreads(8);
+        .maxthreads(MAX_THREADS);
     try (RegionMover rm = rmBuilder.build()) {
       LOG.debug("Unloading {}", regionServer.getServerName());
       rm.unload();
@@ -153,7 +158,7 @@ public class TestRegionMover2 {
 
     RegionMover.RegionMoverBuilder rmBuilder =
       new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
-        .maxthreads(8);
+        .maxthreads(MAX_THREADS);
     try (RegionMover rm = rmBuilder.build()) {
       LOG.debug("Unloading {}", regionServer.getServerName());
       rm.unload();
@@ -194,13 +199,13 @@ public class TestRegionMover2 {
     admin.flush(tableName);
     HRegionServer regionServer = cluster.getRegionServer(0);
     String rsName = regionServer.getServerName().getAddress().toString();
-    int numRegions = regionServer.getNumberOfOnlineRegions();
+    regionServer.getNumberOfOnlineRegions();
     List<HRegion> hRegions = regionServer.getRegions().stream()
       .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
       .collect(Collectors.toList());
     RegionMover.RegionMoverBuilder rmBuilder =
       new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
-        .maxthreads(8);
+        .maxthreads(MAX_THREADS);
     try (RegionMover rm = rmBuilder.build()) {
       LOG.debug("Unloading {}", regionServer.getServerName());
       rm.unload();