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 2022/02/24 03:10:38 UTC

[hbase] branch branch-2 updated: HBASE-26640 Reimplement master local region initialization to better work with SFT (#4111)

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

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


The following commit(s) were added to refs/heads/branch-2 by this push:
     new 4cdb380  HBASE-26640 Reimplement master local region initialization to better work with SFT (#4111)
4cdb380 is described below

commit 4cdb380ccc8a456a94a6a65c4f9781aac381b317
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Thu Feb 24 10:10:02 2022 +0800

    HBASE-26640 Reimplement master local region initialization to better work with SFT (#4111)
    
    Signed-off-by: Josh Elser <el...@apache.org>
    Signed-off-by: Wellington Chevreuil <wc...@apache.org>
---
 .../org/apache/hadoop/hbase/master/HMaster.java    |   4 +-
 .../hadoop/hbase/master/region/MasterRegion.java   | 141 +++++++++++++++++----
 .../hbase/master/region/MasterRegionFactory.java   |  22 +++-
 .../storefiletracker/StoreFileTrackerFactory.java  |   6 +-
 .../hadoop/hbase/util/FSTableDescriptors.java      |   7 +
 .../hbase/master/region/MasterRegionTestBase.java  |  16 ++-
 .../region/TestChangeSFTForMasterRegion.java       |  86 +++++++++++++
 .../master/region/TestMasterRegionInitialize.java  | 117 +++++++++++++++++
 .../region/TestMasterRegionOnTwoFileSystems.java   |   9 +-
 9 files changed, 371 insertions(+), 37 deletions(-)

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 cfa6ecb..fb9c6d9 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
@@ -772,7 +772,7 @@ public class HMaster extends HRegionServer implements MasterServices {
         // done with in a one row put, which means if we have data in catalog family then we can
         // make sure that the migration is done.
         LOG.info("The {} family in master local region already has data in it, skip migrating...",
-          HConstants.CATALOG_FAMILY);
+          HConstants.CATALOG_FAMILY_STR);
         return;
       }
     }
@@ -4072,7 +4072,7 @@ public class HMaster extends HRegionServer implements MasterServices {
 
   @RestrictedApi(explanation = "Should only be called in tests", link = "",
     allowedOnPath = ".*/src/test/.*")
-  MasterRegion getMasterRegion() {
+  public MasterRegion getMasterRegion() {
     return masterRegion;
   }
 
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 71950bd..a0b8686 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
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.master.region;
 import static org.apache.hadoop.hbase.HConstants.HREGION_LOGDIR_NAME;
 
 import java.io.IOException;
+import java.util.List;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
@@ -27,6 +28,7 @@ import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.Server;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.RegionInfoBuilder;
@@ -34,13 +36,18 @@ 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;
 import org.apache.hadoop.hbase.regionserver.HRegion;
 import org.apache.hadoop.hbase.regionserver.HRegion.FlushResult;
 import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
 import org.apache.hadoop.hbase.regionserver.RegionScanner;
+import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
 import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.CommonFSUtils;
+import org.apache.hadoop.hbase.util.FSTableDescriptors;
 import org.apache.hadoop.hbase.util.FSUtils;
 import org.apache.hadoop.hbase.util.HFileArchiveUtil;
 import org.apache.hadoop.hbase.util.RecoverLeaseFSUtils;
@@ -92,6 +99,10 @@ public final class MasterRegion {
 
   private static final String DEAD_WAL_DIR_SUFFIX = "-dead";
 
+  static final String INITIALIZING_FLAG = ".initializing";
+
+  static final String INITIALIZED_FLAG = ".initialized";
+
   private static final int REGION_ID = 1;
 
   private final WALFactory walFactory;
@@ -196,32 +207,39 @@ public final class MasterRegion {
 
   private static HRegion bootstrap(Configuration conf, TableDescriptor td, FileSystem fs,
     Path rootDir, FileSystem walFs, Path walRootDir, WALFactory walFactory,
-    MasterRegionWALRoller walRoller, String serverName) throws IOException {
+    MasterRegionWALRoller walRoller, String serverName, boolean touchInitializingFlag)
+    throws IOException {
     TableName tn = td.getTableName();
     RegionInfo regionInfo = RegionInfoBuilder.newBuilder(tn).setRegionId(REGION_ID).build();
-    Path tmpTableDir = CommonFSUtils.getTableDir(rootDir,
-      TableName.valueOf(tn.getNamespaceAsString(), tn.getQualifierAsString() + "-tmp"));
-    if (fs.exists(tmpTableDir) && !fs.delete(tmpTableDir, true)) {
-      throw new IOException("Can not delete partial created proc region " + tmpTableDir);
-    }
-    HRegion.createHRegion(conf, regionInfo, fs, tmpTableDir, td).close();
     Path tableDir = CommonFSUtils.getTableDir(rootDir, tn);
-    if (!fs.rename(tmpTableDir, tableDir)) {
-      throw new IOException("Can not rename " + tmpTableDir + " to " + tableDir);
+    // persist table descriptor
+    FSTableDescriptors.createTableDescriptorForTableDirectory(fs, tableDir, td, true);
+    HRegion.createHRegion(conf, regionInfo, fs, tableDir, td).close();
+    Path initializedFlag = new Path(tableDir, INITIALIZED_FLAG);
+    if (!fs.mkdirs(initializedFlag)) {
+      throw new IOException("Can not touch initialized flag: " + initializedFlag);
+    }
+    Path initializingFlag = new Path(tableDir, INITIALIZING_FLAG);
+    if (!fs.delete(initializingFlag, true)) {
+      LOG.warn("failed to clean up initializing flag: " + initializingFlag);
     }
     WAL wal = createWAL(walFactory, walRoller, serverName, walFs, walRootDir, regionInfo);
     return HRegion.openHRegionFromTableDir(conf, fs, tableDir, regionInfo, td, wal, null, null);
   }
 
-  private static HRegion open(Configuration conf, TableDescriptor td, FileSystem fs, Path rootDir,
-    FileSystem walFs, Path walRootDir, WALFactory walFactory, MasterRegionWALRoller walRoller,
-    String serverName) throws IOException {
-    Path tableDir = CommonFSUtils.getTableDir(rootDir, td.getTableName());
-    Path regionDir =
-      fs.listStatus(tableDir, p -> RegionInfo.isEncodedRegionName(Bytes.toBytes(p.getName())))[0]
-        .getPath();
-    RegionInfo regionInfo = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir);
+  private static RegionInfo loadRegionInfo(FileSystem fs, Path tableDir) throws IOException {
+    // on branch-2, the RegionInfo.isEncodedRegionName will returns true for .initializing and
+    // .initialized, see HBASE-25368. Since RegionInfo is IA.Public, changing the implementation may
+    // raise compatibility concerns, so here we just skip them by our own.
+    Path regionDir = fs.listStatus(tableDir, p -> !p.getName().startsWith(".")
+      && RegionInfo.isEncodedRegionName(Bytes.toBytes(p.getName())))[0].getPath();
+    return HRegionFileSystem.loadRegionInfoFileContent(fs, regionDir);
+  }
 
+  private static HRegion open(Configuration conf, TableDescriptor td, RegionInfo regionInfo,
+    FileSystem fs, Path rootDir, FileSystem walFs, Path walRootDir, WALFactory walFactory,
+    MasterRegionWALRoller walRoller, String serverName) throws IOException {
+    Path tableDir = CommonFSUtils.getTableDir(rootDir, td.getTableName());
     Path walRegionDir = FSUtils.getRegionDirFromRootDir(walRootDir, regionInfo);
     Path replayEditsDir = new Path(walRegionDir, REPLAY_EDITS_DIR);
     if (!walFs.exists(replayEditsDir) && !walFs.mkdirs(replayEditsDir)) {
@@ -287,6 +305,39 @@ public final class MasterRegion {
     }
   }
 
+  private static void tryMigrate(Configuration conf, FileSystem fs, Path tableDir,
+    RegionInfo regionInfo, TableDescriptor oldTd, TableDescriptor newTd) throws IOException {
+    Class<? extends StoreFileTracker> oldSft =
+      StoreFileTrackerFactory.getTrackerClass(oldTd.getValue(StoreFileTrackerFactory.TRACKER_IMPL));
+    Class<? extends StoreFileTracker> newSft =
+      StoreFileTrackerFactory.getTrackerClass(newTd.getValue(StoreFileTrackerFactory.TRACKER_IMPL));
+    if (oldSft.equals(newSft)) {
+      LOG.debug("old store file tracker {} is the same with new store file tracker, skip migration",
+        StoreFileTrackerFactory.getStoreFileTrackerName(oldSft));
+      if (!oldTd.equals(newTd)) {
+        // we may change other things such as adding a new family, so here we still need to persist
+        // the new table descriptor
+        LOG.info("Update table descriptor from {} to {}", oldTd, newTd);
+        FSTableDescriptors.createTableDescriptorForTableDirectory(fs, tableDir, newTd, true);
+      }
+      return;
+    }
+    LOG.info("Migrate store file tracker from {} to {}", oldSft.getSimpleName(),
+      newSft.getSimpleName());
+    HRegionFileSystem hfs =
+      HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, regionInfo, false);
+    for (ColumnFamilyDescriptor oldCfd : oldTd.getColumnFamilies()) {
+      StoreFileTracker oldTracker = StoreFileTrackerFactory.create(conf, oldTd, oldCfd, hfs);
+      StoreFileTracker newTracker = StoreFileTrackerFactory.create(conf, oldTd, oldCfd, hfs);
+      List<StoreFileInfo> files = oldTracker.load();
+      LOG.debug("Store file list for {}: {}", oldCfd.getNameAsString(), files);
+      newTracker.set(oldTracker.load());
+    }
+    // persist the new table descriptor after migration
+    LOG.info("Update table descriptor from {} to {}", oldTd, newTd);
+    FSTableDescriptors.createTableDescriptorForTableDirectory(fs, tableDir, newTd, true);
+  }
+
   public static MasterRegion create(MasterRegionParams params) throws IOException {
     TableDescriptor td = params.tableDescriptor();
     LOG.info("Create or load local region for table " + td);
@@ -321,16 +372,58 @@ public final class MasterRegion {
 
     WALFactory walFactory = new WALFactory(conf, server.getServerName().toString());
     Path tableDir = CommonFSUtils.getTableDir(rootDir, td.getTableName());
+    Path initializingFlag = new Path(tableDir, INITIALIZING_FLAG);
+    Path initializedFlag = new Path(tableDir, INITIALIZED_FLAG);
     HRegion region;
-    if (fs.exists(tableDir)) {
-      // load the existing region.
-      region = open(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
-        server.getServerName().toString());
-    } else {
-      // bootstrapping...
+    if (!fs.exists(tableDir)) {
+      // bootstrap, no doubt
+      if (!fs.mkdirs(initializedFlag)) {
+        throw new IOException("Can not touch initialized flag");
+      }
       region = bootstrap(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
-        server.getServerName().toString());
+        server.getServerName().toString(), true);
+    } else {
+      if (!fs.exists(initializedFlag)) {
+        if (!fs.exists(initializingFlag)) {
+          // should be old style, where we do not have the initializing or initialized file, persist
+          // the table descriptor, touch the initialized flag and then open the region.
+          // the store file tracker must be DEFAULT
+          LOG.info("No {} or {} file, try upgrading", INITIALIZING_FLAG, INITIALIZED_FLAG);
+          TableDescriptor oldTd =
+            TableDescriptorBuilder.newBuilder(td).setValue(StoreFileTrackerFactory.TRACKER_IMPL,
+              StoreFileTrackerFactory.Trackers.DEFAULT.name()).build();
+          FSTableDescriptors.createTableDescriptorForTableDirectory(fs, tableDir, oldTd, true);
+          if (!fs.mkdirs(initializedFlag)) {
+            throw new IOException("Can not touch initialized flag: " + initializedFlag);
+          }
+          RegionInfo regionInfo = loadRegionInfo(fs, tableDir);
+          tryMigrate(conf, fs, tableDir, regionInfo, oldTd, td);
+          region = open(conf, td, regionInfo, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
+            server.getServerName().toString());
+        } else {
+          // delete all contents besides the initializing flag, here we can make sure tableDir
+          // exists(unless someone delete it manually...), so we do not do null check here.
+          for (FileStatus status : fs.listStatus(tableDir)) {
+            if (!status.getPath().getName().equals(INITIALIZING_FLAG)) {
+              fs.delete(status.getPath(), true);
+            }
+          }
+          region = bootstrap(conf, td, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
+            server.getServerName().toString(), false);
+        }
+      } else {
+        if (fs.exists(initializingFlag) && !fs.delete(initializingFlag, true)) {
+          LOG.warn("failed to clean up initializing flag: " + initializingFlag);
+        }
+        // open it, make sure to load the table descriptor from fs
+        TableDescriptor oldTd = FSTableDescriptors.getTableDescriptorFromFs(fs, tableDir);
+        RegionInfo regionInfo = loadRegionInfo(fs, tableDir);
+        tryMigrate(conf, fs, tableDir, regionInfo, oldTd, td);
+        region = open(conf, td, regionInfo, fs, rootDir, walFs, walRootDir, walFactory, walRoller,
+          server.getServerName().toString());
+      }
     }
+
     Path globalArchiveDir = HFileArchiveUtil.getArchivePath(baseConf);
     MasterRegionFlusherAndCompactor flusherAndCompactor = new MasterRegionFlusherAndCompactor(conf,
       server, region, params.flushSize(), params.flushPerChanges(), params.flushIntervalMs(),
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
index d70aef6..3ef6224 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/region/MasterRegionFactory.java
@@ -28,7 +28,10 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
 import org.apache.hadoop.hbase.regionserver.BloomType;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.ReflectionUtils;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -78,6 +81,8 @@ public final class MasterRegionFactory {
 
   private static final int DEFAULT_RING_BUFFER_SLOT_COUNT = 128;
 
+  public static final String TRACKER_IMPL = "hbase.master.store.region.file-tracker.impl";
+
   public static final TableName TABLE_NAME = TableName.valueOf("master:store");
 
   public static final byte[] PROC_FAMILY = Bytes.toBytes("proc");
@@ -89,10 +94,23 @@ public final class MasterRegionFactory {
       .setDataBlockEncoding(DataBlockEncoding.ROW_INDEX_V1).build())
     .setColumnFamily(ColumnFamilyDescriptorBuilder.of(PROC_FAMILY)).build();
 
+  private static TableDescriptor withTrackerConfigs(Configuration conf) {
+    String trackerImpl = conf.get(TRACKER_IMPL, conf.get(StoreFileTrackerFactory.TRACKER_IMPL,
+      StoreFileTrackerFactory.Trackers.DEFAULT.name()));
+    Class<? extends StoreFileTracker> trackerClass =
+      StoreFileTrackerFactory.getTrackerClass(trackerImpl);
+    if (StoreFileTrackerFactory.isMigration(trackerClass)) {
+      throw new IllegalArgumentException("Should not set store file tracker to " +
+        StoreFileTrackerFactory.Trackers.MIGRATION.name() + " for master local region");
+    }
+    StoreFileTracker tracker = ReflectionUtils.newInstance(trackerClass, conf, true, null);
+    return tracker.updateWithTrackerConfigs(TableDescriptorBuilder.newBuilder(TABLE_DESC)).build();
+  }
+
   public static MasterRegion create(Server server) throws IOException {
-    MasterRegionParams params = new MasterRegionParams().server(server)
-      .regionDirName(MASTER_STORE_DIR).tableDescriptor(TABLE_DESC);
     Configuration conf = server.getConfiguration();
+    MasterRegionParams params = new MasterRegionParams().server(server)
+      .regionDirName(MASTER_STORE_DIR).tableDescriptor(withTrackerConfigs(conf));
     long flushSize = conf.getLong(FLUSH_SIZE_KEY, DEFAULT_FLUSH_SIZE);
     long flushPerChanges = conf.getLong(FLUSH_PER_CHANGES_KEY, DEFAULT_FLUSH_PER_CHANGES);
     long flushIntervalMs = conf.getLong(FLUSH_INTERVAL_MS_KEY, DEFAULT_FLUSH_INTERVAL_MS);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerFactory.java
index 7f545e4..85c5ee2 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerFactory.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/storefiletracker/StoreFileTrackerFactory.java
@@ -84,7 +84,7 @@ public final class StoreFileTrackerFactory {
     return conf.get(TRACKER_IMPL, Trackers.DEFAULT.name());
   }
 
-  static String getStoreFileTrackerName(Class<? extends StoreFileTracker> clazz) {
+  public static String getStoreFileTrackerName(Class<? extends StoreFileTracker> clazz) {
     Trackers name = CLASS_TO_ENUM.get(clazz);
     return name != null ? name.name() : clazz.getName();
   }
@@ -184,4 +184,8 @@ public final class StoreFileTrackerFactory {
     }
     return descriptor;
   }
+
+  public static boolean isMigration(Class<?> clazz) {
+    return MigrationStoreFileTracker.class.isAssignableFrom(clazz);
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/FSTableDescriptors.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/FSTableDescriptors.java
index fe48368..57a91d1 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/FSTableDescriptors.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/FSTableDescriptors.java
@@ -511,6 +511,13 @@ public class FSTableDescriptors implements TableDescriptors {
     return td != null ? Optional.of(Pair.newPair(descFile, td)) : Optional.empty();
   }
 
+  @RestrictedApi(explanation = "Should only be called in tests", link = "",
+    allowedOnPath = ".*/src/test/.*")
+  public static void deleteTableDescriptors(FileSystem fs, Path tableDir) throws IOException {
+    Path tableInfoDir = new Path(tableDir, TABLEINFO_DIR);
+    deleteTableDescriptorFiles(fs, tableInfoDir, Integer.MAX_VALUE);
+  }
+
   /**
    * Deletes files matching the table info file pattern within the given directory whose sequenceId
    * is at most the given max sequenceId.
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
index 27a4b7a..0d35fdd 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/MasterRegionTestBase.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
 import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.CommonFSUtils;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
@@ -85,19 +86,24 @@ public class MasterRegionTestBase {
   /**
    * Creates a new MasterRegion using an existing {@code htu} on this class.
    */
-  protected void createMasterRegion() throws IOException {
-    configure(htu.getConfiguration());
+  protected final void createMasterRegion() throws IOException {
+    Configuration conf = htu.getConfiguration();
+    configure(conf);
     choreService = new ChoreService(getClass().getSimpleName());
     cleanerPool = new DirScanPool(htu.getConfiguration());
     Server server = mock(Server.class);
-    when(server.getConfiguration()).thenReturn(htu.getConfiguration());
+    when(server.getConfiguration()).thenReturn(conf);
     when(server.getServerName())
       .thenReturn(ServerName.valueOf("localhost", 12345, EnvironmentEdgeManager.currentTime()));
     when(server.getChoreService()).thenReturn(choreService);
     Path testDir = htu.getDataTestDir();
-    CommonFSUtils.setRootDir(htu.getConfiguration(), testDir);
+    CommonFSUtils.setRootDir(conf, testDir);
     MasterRegionParams params = new MasterRegionParams();
-    params.server(server).regionDirName(REGION_DIR_NAME).tableDescriptor(TD)
+    TableDescriptor td = TableDescriptorBuilder
+      .newBuilder(TD).setValue(StoreFileTrackerFactory.TRACKER_IMPL, conf
+        .get(StoreFileTrackerFactory.TRACKER_IMPL, StoreFileTrackerFactory.Trackers.DEFAULT.name()))
+      .build();
+    params.server(server).regionDirName(REGION_DIR_NAME).tableDescriptor(td)
       .flushSize(TableDescriptorBuilder.DEFAULT_MEMSTORE_FLUSH_SIZE).flushPerChanges(1_000_000)
       .flushIntervalMs(TimeUnit.MINUTES.toMillis(15)).compactMin(4).maxWals(32).useHsync(false)
       .ringBufferSlotCount(16).rollPeriodMs(TimeUnit.MINUTES.toMillis(15))
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestChangeSFTForMasterRegion.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestChangeSFTForMasterRegion.java
new file mode 100644
index 0000000..3f7c7a8
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestChangeSFTForMasterRegion.java
@@ -0,0 +1,86 @@
+/**
+ * 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.junit.Assert.assertEquals;
+
+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.TableName;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
+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;
+
+/**
+ * Make sure we do not loss data after changing SFT implementation
+ */
+@Category({ MasterTests.class, MediumTests.class })
+public class TestChangeSFTForMasterRegion {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestChangeSFTForMasterRegion.class);
+
+  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+
+  private static TableName NAME = TableName.valueOf("test");
+
+  private static byte[] FAMILY = Bytes.toBytes("family");
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    UTIL.getConfiguration().set(MasterRegionFactory.TRACKER_IMPL,
+      StoreFileTrackerFactory.Trackers.DEFAULT.name());
+    // use zk connection registry, as we will shutdown the only master instance which will likely to
+    // lead to dead loop
+    UTIL.getConfiguration().set(HConstants.CLIENT_CONNECTION_REGISTRY_IMPL_CONF_KEY,
+      HConstants.ZK_CONNECTION_REGISTRY_CLASS);
+    UTIL.startMiniCluster(1);
+    UTIL.createTable(NAME, FAMILY).close();
+  }
+
+  @AfterClass
+  public static void tearDown() throws IOException {
+    UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void test() throws Exception {
+    // shutdown master
+    UTIL.getMiniHBaseCluster().stopMaster(0).join();
+    UTIL.getMiniHBaseCluster().getConf().set(MasterRegionFactory.TRACKER_IMPL,
+      StoreFileTrackerFactory.Trackers.FILE.name());
+    UTIL.getMiniHBaseCluster().startMaster();
+    // make sure that the table still exists
+    UTIL.waitTableAvailable(NAME);
+    // confirm that we have changed the SFT to FILE
+    TableDescriptor td =
+      UTIL.getMiniHBaseCluster().getMaster().getMasterRegion().region.getTableDescriptor();
+    assertEquals(StoreFileTrackerFactory.Trackers.FILE.name(),
+      td.getValue(StoreFileTrackerFactory.TRACKER_IMPL));
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionInitialize.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionInitialize.java
new file mode 100644
index 0000000..5ebde72
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionInitialize.java
@@ -0,0 +1,117 @@
+/**
+ * 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.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.regionserver.HRegion.FlushResult;
+import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
+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.CommonFSUtils;
+import org.apache.hadoop.hbase.util.FSTableDescriptors;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category({ MasterTests.class, MediumTests.class })
+public class TestMasterRegionInitialize extends MasterRegionTestBase {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMasterRegionInitialize.class);
+
+  @Test
+  public void testUpgrade() throws IOException {
+    Path rootDir = new Path(htu.getDataTestDir(), REGION_DIR_NAME);
+    Path tableDir =
+      CommonFSUtils.getTableDir(rootDir, region.region.getTableDescriptor().getTableName());
+    Path initializingFlag = new Path(tableDir, MasterRegion.INITIALIZING_FLAG);
+    Path initializedFlag = new Path(tableDir, MasterRegion.INITIALIZED_FLAG);
+    HRegionFileSystem hfs = region.region.getRegionFileSystem();
+    assertFalse(hfs.getFileSystem().exists(initializingFlag));
+    assertTrue(hfs.getFileSystem().exists(initializedFlag));
+    byte[] row = Bytes.toBytes("row");
+    byte[] cf = CF1;
+    byte[] cq = Bytes.toBytes("qual");
+    byte[] value = Bytes.toBytes("value");
+    region.update(r -> r.put(new Put(row).addColumn(cf, cq, value)));
+    assertEquals(FlushResult.Result.FLUSHED_NO_COMPACTION_NEEDED, region.flush(true).getResult());
+    // delete initialized flag to simulate old implementation
+    hfs.getFileSystem().delete(initializedFlag, true);
+    FSTableDescriptors.deleteTableDescriptors(hfs.getFileSystem(), tableDir);
+    assertNull(FSTableDescriptors.getTableDescriptorFromFs(hfs.getFileSystem(), tableDir));
+    // reopen, with new file tracker
+    region.close(false);
+    htu.getConfiguration().set(StoreFileTrackerFactory.TRACKER_IMPL,
+      StoreFileTrackerFactory.Trackers.FILE.name());
+    createMasterRegion();
+
+    // make sure we successfully upgrade to new implementation without data loss
+    hfs = region.region.getRegionFileSystem();
+    assertFalse(hfs.getFileSystem().exists(initializingFlag));
+    assertTrue(hfs.getFileSystem().exists(initializedFlag));
+    TableDescriptor td = FSTableDescriptors.getTableDescriptorFromFs(hfs.getFileSystem(), tableDir);
+    assertEquals(StoreFileTrackerFactory.Trackers.FILE.name(),
+      td.getValue(StoreFileTrackerFactory.TRACKER_IMPL));
+    assertArrayEquals(value, region.get(new Get(row)).getValue(cf, cq));
+  }
+
+  @Test
+  public void testInitializingCleanup() throws IOException {
+    Path rootDir = new Path(htu.getDataTestDir(), REGION_DIR_NAME);
+    Path tableDir =
+      CommonFSUtils.getTableDir(rootDir, region.region.getTableDescriptor().getTableName());
+    Path initializingFlag = new Path(tableDir, MasterRegion.INITIALIZING_FLAG);
+    Path initializedFlag = new Path(tableDir, MasterRegion.INITIALIZED_FLAG);
+    HRegionFileSystem hfs = region.region.getRegionFileSystem();
+    assertFalse(hfs.getFileSystem().exists(initializingFlag));
+    assertTrue(hfs.getFileSystem().exists(initializedFlag));
+    byte[] row = Bytes.toBytes("row");
+    byte[] cf = CF1;
+    byte[] cq = Bytes.toBytes("qual");
+    byte[] value = Bytes.toBytes("value");
+    region.update(r -> r.put(new Put(row).addColumn(cf, cq, value)));
+    // delete initialized flag and touch a initializing flag, to simulate initializing in progress
+    hfs.getFileSystem().delete(initializedFlag, true);
+    if (!hfs.getFileSystem().mkdirs(initializingFlag)) {
+      throw new IOException("can not touch " + initializedFlag);
+    }
+
+    region.close(false);
+    createMasterRegion();
+    hfs = region.region.getRegionFileSystem();
+    assertFalse(hfs.getFileSystem().exists(initializingFlag));
+    assertTrue(hfs.getFileSystem().exists(initializedFlag));
+
+    // but the data should have been cleaned up
+    assertTrue(region.get(new Get(row)).isEmpty());
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
index bf157d6..60966e5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/region/TestMasterRegionOnTwoFileSystems.java
@@ -50,6 +50,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
 import org.apache.hadoop.hbase.regionserver.RegionScanner;
+import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
 import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -85,9 +86,11 @@ public class TestMasterRegionOnTwoFileSystems {
 
   private static byte[] CQ = Bytes.toBytes("q");
 
-  private static TableDescriptor TD =
-    TableDescriptorBuilder.newBuilder(TableName.valueOf("test:local"))
-      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build();
+  private static TableDescriptor TD = TableDescriptorBuilder
+    .newBuilder(TableName.valueOf("test:local"))
+    .setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF))
+    .setValue(StoreFileTrackerFactory.TRACKER_IMPL, StoreFileTrackerFactory.Trackers.DEFAULT.name())
+    .build();
 
   private static int COMPACT_MIN = 4;