You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2022/08/18 08:53:00 UTC

[iotdb] branch master updated: [IOTDB-3771] Fix cannot take snapshot when the data dir and snapshot dir is on different disk (#6782)

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

qiaojialin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 66e53e1338 [IOTDB-3771] Fix cannot take snapshot when the data dir and snapshot dir is on different disk (#6782)
66e53e1338 is described below

commit 66e53e1338205b1d1548405cebab7658dca14f19
Author: Liu Xuxin <37...@users.noreply.github.com>
AuthorDate: Thu Aug 18 16:52:53 2022 +0800

    [IOTDB-3771] Fix cannot take snapshot when the data dir and snapshot dir is on different disk (#6782)
---
 .../org/apache/iotdb/commons/utils/FileUtils.java  |  17 ++
 server/pom.xml                                     |   6 +
 .../java/org/apache/iotdb/db/conf/IoTDBConfig.java |   2 +-
 .../db/conf/directories/DirectoryManager.java      |  17 ++
 .../statemachine/DataRegionStateMachine.java       |  21 +-
 .../org/apache/iotdb/db/engine/StorageEngine.java  |   2 +-
 .../apache/iotdb/db/engine/StorageEngineV2.java    |   8 +-
 .../iotdb/db/engine/snapshot/SnapshotFileSet.java  |  44 ++++
 .../iotdb/db/engine/snapshot/SnapshotLoader.java   | 144 ++++++++++--
 .../db/engine/snapshot/SnapshotLogAnalyzer.java    |  79 +++++++
 .../iotdb/db/engine/snapshot/SnapshotLogger.java   |  58 +++++
 .../iotdb/db/engine/snapshot/SnapshotTaker.java    | 243 ++++++++++++---------
 .../exception/DirectoryNotLegalException.java      |   7 +-
 .../iotdb/db/engine/storagegroup/DataRegion.java   | 123 +++++------
 .../engine/storagegroup/TsFileProcessorInfo.java   |   6 +-
 .../dataregion/StorageGroupManager.java            |   4 +-
 .../org/apache/iotdb/db/rescon/SystemInfo.java     |  10 +-
 .../db/engine/snapshot/IoTDBSnapshotTest.java      | 199 +++++++++++++++++
 .../apache/iotdb/db/utils/EnvironmentUtils.java    |  17 ++
 .../iotdb/tsfile/utils/TsFileGeneratorUtils.java   | 100 +++++----
 20 files changed, 847 insertions(+), 260 deletions(-)

diff --git a/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java b/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
index a641e11381..b7a2b8b9be 100644
--- a/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
+++ b/node-commons/src/main/java/org/apache/iotdb/commons/utils/FileUtils.java
@@ -136,4 +136,21 @@ public class FileUtils {
     }
     return sum;
   }
+
+  public static void recursiveDeleteFolder(String path) throws IOException {
+    File file = new File(path);
+    if (file.isDirectory()) {
+      File[] files = file.listFiles();
+      if (files == null || files.length == 0) {
+        org.apache.commons.io.FileUtils.deleteDirectory(file);
+      } else {
+        for (File f : files) {
+          recursiveDeleteFolder(f.getAbsolutePath());
+        }
+        org.apache.commons.io.FileUtils.deleteDirectory(file);
+      }
+    } else {
+      org.apache.commons.io.FileUtils.delete(file);
+    }
+  }
 }
diff --git a/server/pom.xml b/server/pom.xml
index fed4669024..347268084a 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -223,6 +223,12 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.iotdb</groupId>
+            <artifactId>node-commons</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
index 416a2a300e..3ee6e92b35 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
@@ -1156,7 +1156,7 @@ public class IoTDBConfig {
     return dataDirs;
   }
 
-  void setDataDirs(String[] dataDirs) {
+  public void setDataDirs(String[] dataDirs) {
     this.dataDirs = dataDirs;
   }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/directories/DirectoryManager.java b/server/src/main/java/org/apache/iotdb/db/conf/directories/DirectoryManager.java
index 7b3a1478b9..5233337cf0 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/directories/DirectoryManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/directories/DirectoryManager.java
@@ -19,6 +19,7 @@
 package org.apache.iotdb.db.conf.directories;
 
 import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.utils.TestOnly;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.conf.SystemStatus;
 import org.apache.iotdb.db.conf.directories.strategy.DirectoryStrategy;
@@ -175,6 +176,22 @@ public class DirectoryManager {
     return folders;
   }
 
+  @TestOnly
+  public void resetFolders() {
+    sequenceFileFolders =
+        new ArrayList<>(Arrays.asList(IoTDBDescriptor.getInstance().getConfig().getDataDirs()));
+    for (int i = 0; i < sequenceFileFolders.size(); i++) {
+      sequenceFileFolders.set(
+          i, sequenceFileFolders.get(i) + File.separator + IoTDBConstant.SEQUENCE_FLODER_NAME);
+    }
+    unsequenceFileFolders =
+        new ArrayList<>(Arrays.asList(IoTDBDescriptor.getInstance().getConfig().getDataDirs()));
+    for (int i = 0; i < unsequenceFileFolders.size(); i++) {
+      unsequenceFileFolders.set(
+          i, unsequenceFileFolders.get(i) + File.separator + IoTDBConstant.UNSEQUENCE_FLODER_NAME);
+    }
+  }
+
   private static class DirectoriesHolder {
     private static final DirectoryManager INSTANCE = new DirectoryManager();
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/consensus/statemachine/DataRegionStateMachine.java b/server/src/main/java/org/apache/iotdb/db/consensus/statemachine/DataRegionStateMachine.java
index 721f3c33af..6c52fa73c3 100644
--- a/server/src/main/java/org/apache/iotdb/db/consensus/statemachine/DataRegionStateMachine.java
+++ b/server/src/main/java/org/apache/iotdb/db/consensus/statemachine/DataRegionStateMachine.java
@@ -48,6 +48,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
@@ -97,7 +98,7 @@ public class DataRegionStateMachine extends BaseStateMachine {
     } catch (Exception e) {
       logger.error(
           "Exception occurs when taking snapshot for {}-{} in {}",
-          region.getLogicalStorageGroupName(),
+          region.getStorageGroupName(),
           region.getDataRegionId(),
           snapshotDir,
           e);
@@ -110,7 +111,7 @@ public class DataRegionStateMachine extends BaseStateMachine {
     DataRegion newRegion =
         new SnapshotLoader(
                 latestSnapshotRootDir.getAbsolutePath(),
-                region.getLogicalStorageGroupName(),
+                region.getStorageGroupName(),
                 region.getDataRegionId())
             .loadSnapshotForStateMachine();
     if (newRegion == null) {
@@ -258,8 +259,20 @@ public class DataRegionStateMachine extends BaseStateMachine {
 
   @Override
   public List<File> getSnapshotFiles(File latestSnapshotRootDir) {
-    // TODO: implement this method
-    return super.getSnapshotFiles(latestSnapshotRootDir);
+    try {
+      return new SnapshotLoader(
+              latestSnapshotRootDir.getAbsolutePath(),
+              region.getStorageGroupName(),
+              region.getDataRegionId())
+          .getSnapshotFileInfo();
+    } catch (IOException e) {
+      logger.error(
+          "Meets error when getting snapshot files for {}-{}",
+          region.getStorageGroupName(),
+          region.getDataRegionId(),
+          e);
+      return null;
+    }
   }
 
   @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
index ab461f6b8c..60e0fecefa 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngine.java
@@ -1113,7 +1113,7 @@ public class StorageEngine implements IService {
   public String getStorageGroupPath(PartialPath path) throws StorageEngineException {
     PartialPath deviceId = path.getDevicePath();
     DataRegion storageGroupProcessor = getProcessor(deviceId);
-    return storageGroupProcessor.getLogicalStorageGroupName()
+    return storageGroupProcessor.getStorageGroupName()
         + File.separator
         + storageGroupProcessor.getDataRegionId();
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngineV2.java b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngineV2.java
index 2cef4a19a0..f1a5f3ab21 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/StorageEngineV2.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/StorageEngineV2.java
@@ -540,7 +540,7 @@ public class StorageEngineV2 implements IService {
 
   public void closeStorageGroupProcessor(String storageGroupPath, boolean isSeq) {
     for (DataRegion dataRegion : dataRegionMap.values()) {
-      if (dataRegion.getLogicalStorageGroupName().equals(storageGroupPath)) {
+      if (dataRegion.getStorageGroupName().equals(storageGroupPath)) {
         if (isSeq) {
           for (TsFileProcessor tsFileProcessor : dataRegion.getWorkSequenceTsFileProcessors()) {
             dataRegion.syncCloseOneTsFileProcessor(isSeq, tsFileProcessor);
@@ -662,14 +662,12 @@ public class StorageEngineV2 implements IService {
                 .equals(ConsensusFactory.MultiLeaderConsensus)) {
           WALManager.getInstance()
               .deleteWALNode(
-                  region.getLogicalStorageGroupName()
-                      + FILE_NAME_SEPARATOR
-                      + region.getDataRegionId());
+                  region.getStorageGroupName() + FILE_NAME_SEPARATOR + region.getDataRegionId());
         }
       } catch (Exception e) {
         logger.error(
             "Error occurs when deleting data region {}-{}",
-            region.getLogicalStorageGroupName(),
+            region.getStorageGroupName(),
             region.getDataRegionId(),
             e);
       } finally {
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotFileSet.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotFileSet.java
new file mode 100644
index 0000000000..ee5aeb6db7
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotFileSet.java
@@ -0,0 +1,44 @@
+/*
+ * 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.iotdb.db.engine.snapshot;
+
+import org.apache.iotdb.db.engine.modification.ModificationFile;
+import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
+import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SnapshotFileSet {
+  public static final String[] DATA_FILE_SUFFIX =
+      new String[] {
+        TsFileConstant.TSFILE_SUFFIX, TsFileResource.RESOURCE_SUFFIX, ModificationFile.FILE_SUFFIX,
+      };
+
+  private static final Set<String> DATA_FILE_SUFFIX_SET =
+      new HashSet<>(Arrays.asList(DATA_FILE_SUFFIX));
+
+  public static boolean isDataFile(File file) {
+    String[] fileName = file.getName().split("\\.");
+    return DATA_FILE_SUFFIX_SET.contains(fileName[fileName.length - 1]);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLoader.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLoader.java
index 8067da84d4..cff5886f38 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLoader.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLoader.java
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.iotdb.db.engine.snapshot;
 
 import org.apache.iotdb.commons.conf.IoTDBConstant;
@@ -24,6 +25,7 @@ import org.apache.iotdb.db.conf.directories.DirectoryManager;
 import org.apache.iotdb.db.engine.StorageEngineV2;
 import org.apache.iotdb.db.engine.storagegroup.DataRegion;
 import org.apache.iotdb.db.exception.DiskSpaceInsufficientException;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
@@ -31,17 +33,21 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.FileSystemException;
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 
 public class SnapshotLoader {
   private Logger LOGGER = LoggerFactory.getLogger(SnapshotLoader.class);
   private String storageGroupName;
   private String snapshotPath;
   private String dataRegionId;
+  private SnapshotLogAnalyzer logAnalyzer;
 
   public SnapshotLoader(String snapshotPath, String storageGroupName, String dataRegionId) {
     this.snapshotPath = snapshotPath;
@@ -66,6 +72,25 @@ public class SnapshotLoader {
     }
   }
 
+  private File getSnapshotLogFile() {
+    File sourceDataDir = new File(snapshotPath);
+
+    if (sourceDataDir.exists()) {
+      File[] files =
+          sourceDataDir.listFiles((dir, name) -> name.equals(SnapshotLogger.SNAPSHOT_LOG_NAME));
+      if (files == null || files.length == 0) {
+        LOGGER.warn("Failed to find snapshot log file, cannot recover it");
+      } else if (files.length > 1) {
+        LOGGER.warn(
+            "Found more than one snapshot log file, cannot recover it. {}", Arrays.toString(files));
+      } else {
+        LOGGER.info("Reading snapshot log file {}", files[0]);
+        return files[0];
+      }
+    }
+    return null;
+  }
+
   /**
    * 1. Clear origin data 2. Move snapshot data to data dir 3. Load data region
    *
@@ -77,27 +102,49 @@ public class SnapshotLoader {
         storageGroupName,
         dataRegionId,
         snapshotPath);
+
+    File snapshotLogFile = getSnapshotLogFile();
+
+    if (snapshotLogFile == null) {
+      return loadSnapshotWithoutLog();
+    } else {
+      return loadSnapshotWithLog(snapshotLogFile);
+    }
+  }
+
+  private DataRegion loadSnapshotWithoutLog() {
     try {
-      deleteAllFilesInDataDirs();
-      LOGGER.info("Remove all data files in original data dir");
-    } catch (IOException e) {
+      LOGGER.info("Moving snapshot file to data dirs");
+      createLinksFromSnapshotDirToDataDirWithoutLog(new File(snapshotPath));
+      return loadSnapshot();
+    } catch (IOException | DiskSpaceInsufficientException e) {
+      LOGGER.error(
+          "Exception occurs when loading snapshot for {}-{}", storageGroupName, dataRegionId, e);
       return null;
     }
+  }
 
-    // move the snapshot data to data dir
-    File sourceDataDir = new File(snapshotPath);
-    if (sourceDataDir.exists()) {
+  private DataRegion loadSnapshotWithLog(File logFile) {
+    try {
+      logAnalyzer = new SnapshotLogAnalyzer(logFile);
+    } catch (Exception e) {
+      LOGGER.error("Exception occurs when reading snapshot file", e);
+      return null;
+    }
+
+    try {
       try {
-        createLinksFromSnapshotDirToDataDir(sourceDataDir);
-        LOGGER.info("Move data files from snapshot to data directory");
-      } catch (IOException | DiskSpaceInsufficientException e) {
-        LOGGER.error(
-            "Exception occurs when creating links from snapshot directory to data directory", e);
+        deleteAllFilesInDataDirs();
+        LOGGER.info("Remove all data files in original data dir");
+      } catch (IOException e) {
         return null;
       }
-    }
 
-    return loadSnapshot();
+      createLinksFromSnapshotDirToDataDirWithLog();
+      return loadSnapshot();
+    } finally {
+      logAnalyzer.close();
+    }
   }
 
   private void deleteAllFilesInDataDirs() throws IOException {
@@ -154,7 +201,7 @@ public class SnapshotLoader {
     }
   }
 
-  private void createLinksFromSnapshotDirToDataDir(File sourceDir)
+  private void createLinksFromSnapshotDirToDataDirWithoutLog(File sourceDir)
       throws IOException, DiskSpaceInsufficientException {
     File seqFileDir = new File(sourceDir, "sequence" + File.separator + storageGroupName);
     File unseqFileDir = new File(sourceDir, "unsequence" + File.separator + storageGroupName);
@@ -202,7 +249,13 @@ public class SnapshotLoader {
                   throw new IOException(
                       String.format("Failed to create dir %s", targetFile.getParent()));
                 }
-                Files.createLink(targetFile.toPath(), file.toPath());
+                try {
+                  Files.createLink(targetFile.toPath(), file.toPath());
+                } catch (FileSystemException e) {
+                  // cannot create link between two directories in different fs
+                  // copy it
+                  Files.copy(file.toPath(), targetFile.toPath());
+                }
               }
             }
           }
@@ -247,7 +300,11 @@ public class SnapshotLoader {
                   throw new IOException(
                       String.format("Failed to create dir %s", targetFile.getParent()));
                 }
-                Files.createLink(targetFile.toPath(), file.toPath());
+                try {
+                  Files.createLink(targetFile.toPath(), file.toPath());
+                } catch (FileSystemException e) {
+                  Files.copy(file.toPath(), targetFile.toPath());
+                }
               }
             }
           }
@@ -255,4 +312,59 @@ public class SnapshotLoader {
       }
     }
   }
+
+  private void createLinksFromSnapshotDirToDataDirWithLog() {
+    while (logAnalyzer.hasNext()) {
+      Pair<String, String> filesPath = logAnalyzer.getNextPairs();
+      File sourceFile = new File(filesPath.left);
+      File linkedFile = new File(filesPath.right);
+      if (!linkedFile.exists()) {
+        LOGGER.warn("Snapshot file {} does not exist, skip it", linkedFile);
+        continue;
+      }
+      if (!sourceFile.getParentFile().exists() && !sourceFile.getParentFile().mkdirs()) {
+        LOGGER.error("Failed to create folder {}", sourceFile.getParentFile());
+        continue;
+      }
+      try {
+        Files.createLink(sourceFile.toPath(), linkedFile.toPath());
+      } catch (IOException e) {
+        LOGGER.error("Failed to create link from {} to {}", linkedFile, sourceFile, e);
+      }
+    }
+  }
+
+  public List<File> getSnapshotFileInfo() throws IOException {
+    File snapshotLogFile = getSnapshotLogFile();
+
+    if (snapshotLogFile == null) {
+      return getSnapshotFileWithoutLog();
+    } else {
+      return getSnapshotFileWithLog(snapshotLogFile);
+    }
+  }
+
+  private List<File> getSnapshotFileWithLog(File logFile) throws IOException {
+    SnapshotLogAnalyzer analyzer = new SnapshotLogAnalyzer(logFile);
+    try {
+
+      List<File> fileList = new LinkedList<>();
+
+      while (analyzer.hasNext()) {
+        fileList.add(new File(analyzer.getNextPairs().right));
+      }
+
+      return fileList;
+    } finally {
+      analyzer.close();
+    }
+  }
+
+  private List<File> getSnapshotFileWithoutLog() {
+    return new LinkedList<>(
+        Arrays.asList(
+            Objects.requireNonNull(
+                new File(snapshotPath)
+                    .listFiles((dir, name) -> SnapshotFileSet.isDataFile(new File(dir, name))))));
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogAnalyzer.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogAnalyzer.java
new file mode 100644
index 0000000000..9291a99e3d
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogAnalyzer.java
@@ -0,0 +1,79 @@
+/*
+ * 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.iotdb.db.engine.snapshot;
+
+import org.apache.iotdb.tsfile.utils.Pair;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class SnapshotLogAnalyzer {
+  private static final Logger LOGGER = LoggerFactory.getLogger(SnapshotLogAnalyzer.class);
+  private File snapshotLogFile;
+  private BufferedReader reader;
+
+  public SnapshotLogAnalyzer(File snapshotLogFile) throws FileNotFoundException {
+    this.snapshotLogFile = snapshotLogFile;
+    this.reader = new BufferedReader(new FileReader(snapshotLogFile));
+  }
+
+  public void close() {
+    try {
+      reader.close();
+    } catch (IOException e) {
+      LOGGER.error("Exception occurs when closing log analyzer", e);
+    }
+  }
+
+  public boolean hasNext() {
+    try {
+      return reader != null && reader.ready();
+    } catch (Exception e) {
+      return false;
+    }
+  }
+
+  /**
+   * @return The next pair of files recorded in the log. The left one is the path of source file,
+   *     the right one is the path of target file
+   */
+  public Pair<String, String> getNextPairs() {
+    if (reader == null) {
+      return null;
+    }
+    try {
+      String fileInfo = reader.readLine();
+      String[] filesPath = fileInfo.split(SnapshotLogger.SPLIT_CHAR);
+      if (filesPath.length != 2) {
+        LOGGER.warn("Illegal file info: {} in snapshot log", fileInfo);
+        return null;
+      }
+      return new Pair<>(filesPath[0], filesPath[1]);
+    } catch (IOException e) {
+      LOGGER.error("Exception occurs when analyzing snapshot log", e);
+      return null;
+    }
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogger.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogger.java
new file mode 100644
index 0000000000..fe1937fbcf
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotLogger.java
@@ -0,0 +1,58 @@
+/*
+ * 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.iotdb.db.engine.snapshot;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+public class SnapshotLogger implements AutoCloseable {
+  public static final String SNAPSHOT_LOG_NAME = "snapshot.log";
+  public static final String SPLIT_CHAR = "#";
+
+  private File logFile;
+  private BufferedOutputStream os;
+
+  public SnapshotLogger(File logFile) throws FileNotFoundException {
+    this.logFile = logFile;
+    os = new BufferedOutputStream(new FileOutputStream(logFile));
+  }
+
+  @Override
+  public void close() throws Exception {
+    os.close();
+  }
+
+  public void logFile(String sourceFile, String linkFile) throws IOException {
+    os.write(sourceFile.getBytes(StandardCharsets.UTF_8));
+    os.write(SPLIT_CHAR.getBytes(StandardCharsets.UTF_8));
+    os.write(linkFile.getBytes(StandardCharsets.UTF_8));
+    os.write("\n".getBytes(StandardCharsets.UTF_8));
+    os.flush();
+  }
+
+  public void cleanUpWhenFailed() throws IOException {
+    os.close();
+    Files.delete(logFile.toPath());
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotTaker.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotTaker.java
index 4796b66ac8..004793bfa2 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotTaker.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/SnapshotTaker.java
@@ -16,11 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.iotdb.db.engine.snapshot;
 
-import org.apache.iotdb.commons.conf.IoTDBConstant;
+import org.apache.iotdb.commons.utils.FileUtils;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
-import org.apache.iotdb.db.engine.compaction.log.CompactionLogger;
 import org.apache.iotdb.db.engine.modification.ModificationFile;
 import org.apache.iotdb.db.engine.snapshot.exception.DirectoryNotLegalException;
 import org.apache.iotdb.db.engine.storagegroup.DataRegion;
@@ -33,7 +33,6 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 
@@ -46,9 +45,9 @@ import java.util.Objects;
 public class SnapshotTaker {
   private static final Logger LOGGER = LoggerFactory.getLogger(SnapshotTaker.class);
   private final DataRegion dataRegion;
-  public static String SNAPSHOT_FILE_INFO_SEP_STR = "_";
-  private File seqBaseDir;
-  private File unseqBaseDir;
+  private SnapshotLogger snapshotLogger;
+  private List<TsFileResource> seqFiles;
+  private List<TsFileResource> unseqFiles;
 
   public SnapshotTaker(DataRegion dataRegion) {
     this.dataRegion = dataRegion;
@@ -64,22 +63,6 @@ public class SnapshotTaker {
       throw new DirectoryNotLegalException(
           String.format("%s already exists and is not empty", snapshotDirPath));
     }
-    seqBaseDir =
-        new File(
-            snapshotDir,
-            "sequence"
-                + File.separator
-                + dataRegion.getLogicalStorageGroupName()
-                + File.separator
-                + dataRegion.getDataRegionId());
-    unseqBaseDir =
-        new File(
-            snapshotDir,
-            "unsequence"
-                + File.separator
-                + dataRegion.getLogicalStorageGroupName()
-                + File.separator
-                + dataRegion.getDataRegionId());
 
     if (!snapshotDir.exists() && !snapshotDir.mkdirs()) {
       throw new IOException(String.format("Failed to create directory %s", snapshotDir));
@@ -89,109 +72,157 @@ public class SnapshotTaker {
       dataRegion.syncCloseAllWorkingTsFileProcessors();
     }
 
-    List<Long> timePartitions = dataRegion.getTimePartitions();
-    TsFileManager manager = dataRegion.getTsFileManager();
-    manager.readLock();
+    File snapshotLog = new File(snapshotDir, SnapshotLogger.SNAPSHOT_LOG_NAME);
     try {
-      for (Long timePartition : timePartitions) {
-        List<String> seqDataDirs = getAllDataDirOfOnePartition(true, timePartition);
+      snapshotLogger = new SnapshotLogger(snapshotLog);
+      boolean success = true;
+
+      readLockTheFile();
+      try {
+        success = createSnapshot(seqFiles, snapshotDir.getName());
+        success = createSnapshot(unseqFiles, snapshotDir.getName()) && success;
+      } finally {
+        readUnlockTheFile();
+      }
 
-        try {
-          createFileSnapshot(seqDataDirs, true, timePartition);
-        } catch (IOException e) {
-          LOGGER.error("Fail to create snapshot", e);
-          cleanUpWhenFail(snapshotDir);
-          return false;
-        }
+      if (!success) {
+        LOGGER.warn(
+            "Failed to take snapshot for {}-{}, clean up",
+            dataRegion.getStorageGroupName(),
+            dataRegion.getDataRegionId());
+        cleanUpWhenFail(snapshotDir.getName());
+      } else {
+        LOGGER.info(
+            "Successfully take snapshot for {}-{}, snapshot directory is {}",
+            dataRegion.getStorageGroupName(),
+            dataRegion.getDataRegionId(),
+            snapshotDirPath);
+      }
 
-        List<String> unseqDataDirs = getAllDataDirOfOnePartition(false, timePartition);
+      return success;
+    } catch (Exception e) {
+      LOGGER.error(
+          "Exception occurs when taking snapshot for {}-{}",
+          dataRegion.getStorageGroupName(),
+          dataRegion.getDataRegionId(),
+          e);
+      return false;
+    } finally {
+      try {
+        snapshotLogger.close();
+      } catch (Exception e) {
+        LOGGER.error("Failed to close snapshot logger", e);
+      }
+    }
+  }
 
-        try {
-          createFileSnapshot(unseqDataDirs, false, timePartition);
-        } catch (IOException e) {
-          LOGGER.error("Fail to create snapshot", e);
-          cleanUpWhenFail(snapshotDir);
-          return false;
-        }
+  private void readLockTheFile() {
+    TsFileManager manager = dataRegion.getTsFileManager();
+    manager.readLock();
+    try {
+      seqFiles = manager.getTsFileList(true);
+      unseqFiles = manager.getTsFileList(false);
+      for (TsFileResource resource : seqFiles) {
+        resource.readLock();
+      }
+      for (TsFileResource resource : unseqFiles) {
+        resource.readLock();
       }
     } finally {
       manager.readUnlock();
     }
-
-    LOGGER.info(
-        "Successfully take snapshot for {}-{}, snapshot directory is {}",
-        dataRegion.getLogicalStorageGroupName(),
-        dataRegion.getDataRegionId(),
-        snapshotDirPath);
-
-    return true;
   }
 
-  private List<String> getAllDataDirOfOnePartition(boolean sequence, long timePartition) {
-    String[] dataDirs = IoTDBDescriptor.getInstance().getConfig().getDataDirs();
-    List<String> resultDirs = new LinkedList<>();
-
-    for (String dataDir : dataDirs) {
-      resultDirs.add(
-          dataDir
-              + File.separator
-              + (sequence
-                  ? IoTDBConstant.SEQUENCE_FLODER_NAME
-                  : IoTDBConstant.UNSEQUENCE_FLODER_NAME)
-              + File.separator
-              + dataRegion.getLogicalStorageGroupName()
-              + File.separator
-              + dataRegion.getDataRegionId()
-              + File.separator
-              + timePartition
-              + File.separator);
+  private void readUnlockTheFile() {
+    for (TsFileResource resource : seqFiles) {
+      resource.readUnlock();
+    }
+    for (TsFileResource resource : unseqFiles) {
+      resource.readUnlock();
     }
-    return resultDirs;
   }
 
-  private void createFileSnapshot(List<String> sourceDirPaths, boolean sequence, long timePartition)
-      throws IOException {
-    File timePartitionDir =
-        new File(sequence ? seqBaseDir : unseqBaseDir, String.valueOf(timePartition));
-    if (!timePartitionDir.exists() && !timePartitionDir.mkdirs()) {
-      throw new IOException(
-          String.format("%s not exists and cannot create it", timePartitionDir.getAbsolutePath()));
+  private boolean createSnapshot(List<TsFileResource> resources, String snapshotId) {
+    try {
+      for (TsFileResource resource : resources) {
+        File tsFile = resource.getTsFile();
+        File snapshotTsFile = getSnapshotFilePathForTsFile(tsFile, snapshotId);
+        // create hard link for tsfile, resource, mods
+        createHardLink(snapshotTsFile, tsFile);
+        createHardLink(
+            new File(snapshotTsFile.getAbsolutePath() + TsFileResource.RESOURCE_SUFFIX),
+            new File(tsFile.getAbsolutePath() + TsFileResource.RESOURCE_SUFFIX));
+        if (resource.getModFile().exists()) {
+          createHardLink(
+              new File(snapshotTsFile.getAbsolutePath() + ModificationFile.FILE_SUFFIX),
+              new File(tsFile.getAbsolutePath() + ModificationFile.FILE_SUFFIX));
+        }
+      }
+      return true;
+    } catch (IOException e) {
+      LOGGER.error("Catch IOException when creating snapshot", e);
+      return false;
     }
+  }
 
-    for (String sourceDirPath : sourceDirPaths) {
-      File sourceDir = new File(sourceDirPath);
-      if (!sourceDir.exists()) {
-        continue;
-      }
-      // Collect TsFile, TsFileResource, Mods, CompactionMods
-      File[] files =
-          sourceDir.listFiles(
-              (dir, name) ->
-                  name.endsWith(".tsfile")
-                      || name.endsWith(TsFileResource.RESOURCE_SUFFIX)
-                      || name.endsWith(ModificationFile.FILE_SUFFIX)
-                      || name.endsWith(ModificationFile.COMPACTION_FILE_SUFFIX)
-                      || name.endsWith(CompactionLogger.INNER_COMPACTION_LOG_NAME_SUFFIX)
-                      || name.endsWith(CompactionLogger.CROSS_COMPACTION_LOG_NAME_SUFFIX)
-                      || name.endsWith(IoTDBConstant.INNER_COMPACTION_TMP_FILE_SUFFIX)
-                      || name.endsWith(IoTDBConstant.CROSS_COMPACTION_TMP_FILE_SUFFIX));
-      if (files == null || files.length == 0) {
-        continue;
-      }
+  private void createHardLink(File target, File source) throws IOException {
+    Files.createLink(target.toPath(), source.toPath());
+    snapshotLogger.logFile(source.getAbsolutePath(), target.getAbsolutePath());
+  }
 
-      for (File file : files) {
-        File linkFile = new File(timePartitionDir, file.getName());
-        Files.createLink(linkFile.toPath(), file.toPath());
-      }
+  /**
+   * Construct the snapshot file path for a given tsfile, and will create the dir. Eg, given a
+   * tsfile in /data/iotdb/data/sequence/root.testsg/1/0/1-1-0-0.tsfile, with snapshotId "sm123",
+   * the snapshot location will be /data/iotdb/data/snapshot/sm123/root.testsg/1/0/1-1-0-0.tsfile
+   *
+   * @param tsFile tsfile to be taken a snapshot
+   * @param snapshotId the id for current snapshot
+   * @return the File object of the snapshot file, and its parent directory will be created
+   * @throws IOException
+   */
+  public File getSnapshotFilePathForTsFile(File tsFile, String snapshotId) throws IOException {
+    // ... data (un)sequence sgName dataRegionId timePartition tsFileName
+    String[] splittedPath =
+        tsFile.getAbsolutePath().split(File.separator.equals("\\") ? "\\\\" : File.separator);
+    // snapshot dir will be like
+    // ... data snapshot snapshotId (un)sequence sgName dataRegionId timePartition
+    StringBuilder stringBuilder = new StringBuilder();
+    int i = 0;
+    // build the prefix part of data dir
+    for (; i < splittedPath.length - 5; ++i) {
+      stringBuilder.append(splittedPath[i]);
+      stringBuilder.append(File.separator);
     }
+    stringBuilder.append("snapshot");
+    stringBuilder.append(File.separator);
+    stringBuilder.append(snapshotId);
+    stringBuilder.append(File.separator);
+    // the content in here will be
+    // ... data snapshot snapshotId
+
+    // build the rest part for the dir
+    for (; i < splittedPath.length - 1; ++i) {
+      stringBuilder.append(splittedPath[i]);
+      stringBuilder.append(File.separator);
+    }
+    File dir = new File(stringBuilder.toString());
+    if (!dir.exists() && !dir.mkdirs()) {
+      throw new IOException("Cannot create directory " + dir.getAbsolutePath());
+    }
+    return new File(dir, tsFile.getName());
   }
 
-  private void cleanUpWhenFail(File snapshotDir) {
-    File[] files = snapshotDir.listFiles();
-    if (files != null) {
-      for (File file : files) {
-        if (!file.delete()) {
-          LOGGER.error("Failed to delete link file {} after failing to create snapshot", file);
+  private void cleanUpWhenFail(String snapshotId) {
+    for (String dataDir : IoTDBDescriptor.getInstance().getConfig().getDataDirs()) {
+      File dataDirForThisSnapshot =
+          new File(dataDir + File.separator + "snapshot" + File.separator + snapshotId);
+      if (dataDirForThisSnapshot.exists()) {
+        try {
+          FileUtils.recursiveDeleteFolder(dataDirForThisSnapshot.getAbsolutePath());
+        } catch (IOException e) {
+          LOGGER.error(
+              "Failed to delete folder {} when cleaning up",
+              dataDirForThisSnapshot.getAbsolutePath());
         }
       }
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/exception/DirectoryNotLegalException.java b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/exception/DirectoryNotLegalException.java
index bd4742d9e5..96eb9650dd 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/snapshot/exception/DirectoryNotLegalException.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/snapshot/exception/DirectoryNotLegalException.java
@@ -18,11 +18,8 @@
  */
 package org.apache.iotdb.db.engine.snapshot.exception;
 
-import org.apache.iotdb.commons.exception.IoTDBException;
-import org.apache.iotdb.rpc.TSStatusCode;
-
-public class DirectoryNotLegalException extends IoTDBException {
+public class DirectoryNotLegalException extends Exception {
   public DirectoryNotLegalException(String message) {
-    super(message, TSStatusCode.SNAPSHOT_DIR_NOT_LEGAL.getStatusCode());
+    super(message);
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/DataRegion.java b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/DataRegion.java
index e98ce60de4..6169901c29 100755
--- a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/DataRegion.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/DataRegion.java
@@ -218,7 +218,7 @@ public class DataRegion {
   /** data region id */
   private String dataRegionId;
   /** logical storage group name */
-  private String logicalStorageGroupName;
+  private String storageGroupName;
   /** storage group system directory */
   private File storageGroupSysDir;
   /** manage seqFileList and unSeqFileList */
@@ -281,21 +281,21 @@ public class DataRegion {
    * @param systemDir system dir path
    * @param dataRegionId data region id e.g. 1
    * @param fileFlushPolicy file flush policy
-   * @param logicalStorageGroupName logical storage group name e.g. root.sg1
+   * @param storageGroupName logical storage group name e.g. root.sg1
    */
   public DataRegion(
       String systemDir,
       String dataRegionId,
       TsFileFlushPolicy fileFlushPolicy,
-      String logicalStorageGroupName)
+      String storageGroupName)
       throws DataRegionException {
     this.dataRegionId = dataRegionId;
-    this.logicalStorageGroupName = logicalStorageGroupName;
+    this.storageGroupName = storageGroupName;
     this.fileFlushPolicy = fileFlushPolicy;
 
     storageGroupSysDir = SystemFileFactory.INSTANCE.getFile(systemDir, dataRegionId);
     this.tsFileManager =
-        new TsFileManager(logicalStorageGroupName, dataRegionId, storageGroupSysDir.getPath());
+        new TsFileManager(storageGroupName, dataRegionId, storageGroupSysDir.getPath());
     if (storageGroupSysDir.mkdirs()) {
       logger.info(
           "Storage Group system Directory {} doesn't exist, create it",
@@ -306,7 +306,7 @@ public class DataRegion {
 
     // if use id table, we use id table flush time manager
     if (config.isEnableIDTable()) {
-      idTable = IDTableManager.getInstance().getIDTableDirectly(logicalStorageGroupName);
+      idTable = IDTableManager.getInstance().getIDTableDirectly(storageGroupName);
       lastFlushTimeManager = new IDTableFlushTimeManager(idTable);
     } else {
       lastFlushTimeManager = new LastFlushTimeManager();
@@ -318,11 +318,11 @@ public class DataRegion {
         && !StorageEngineV2.getInstance().isAllSgReady()) {
       logger.debug(
           "Skip recovering data region {}[{}] when consensus protocol is ratis and storage engine is not ready.",
-          logicalStorageGroupName,
+          storageGroupName,
           dataRegionId);
       for (String fileFolder : DirectoryManager.getInstance().getAllFilesFolders()) {
         File dataRegionFolder =
-            fsFactory.getFile(fileFolder, logicalStorageGroupName + File.separator + dataRegionId);
+            fsFactory.getFile(fileFolder, storageGroupName + File.separator + dataRegionId);
         if (dataRegionFolder.exists()) {
           File[] timePartitions = dataRegionFolder.listFiles();
           if (timePartitions != null) {
@@ -333,7 +333,7 @@ public class DataRegion {
                 logger.error(
                     "Exception occurs when deleting time partition directory {} for {}-{}",
                     timePartitions,
-                    logicalStorageGroupName,
+                    storageGroupName,
                     dataRegionId,
                     e);
               }
@@ -352,11 +352,20 @@ public class DataRegion {
             storageGroupInfo,
             StorageGroupInfo::getMemCost,
             Tag.NAME.toString(),
-            "storageGroup_" + getLogicalStorageGroupName());
+            "storageGroup_" + getStorageGroupName());
   }
 
-  public String getLogicalStorageGroupName() {
-    return logicalStorageGroupName;
+  @TestOnly
+  public DataRegion(String storageGroupName, String id) {
+    this.storageGroupName = storageGroupName;
+    this.dataRegionId = id;
+    this.tsFileManager = new TsFileManager(storageGroupName, id, "");
+    this.partitionMaxFileVersions = new HashMap<>();
+    partitionMaxFileVersions.put(0L, 0L);
+  }
+
+  public String getStorageGroupName() {
+    return storageGroupName;
   }
 
   public boolean isReady() {
@@ -414,7 +423,7 @@ public class DataRegion {
         if (lastLogTime + config.getRecoveryLogIntervalInMs() < System.currentTimeMillis()) {
           logger.info(
               "The data region {}[{}] has recovered {}%, please wait a moment.",
-              logicalStorageGroupName, dataRegionId, recoveredFilesNum * 1.0 / numOfFilesToRecover);
+              storageGroupName, dataRegionId, recoveredFilesNum * 1.0 / numOfFilesToRecover);
           lastLogTime = System.currentTimeMillis();
         }
       }
@@ -547,13 +556,10 @@ public class DataRegion {
     if (config.isMppMode()
         ? StorageEngineV2.getInstance().isAllSgReady()
         : StorageEngine.getInstance().isAllSgReady()) {
-      logger.info(
-          "The data region {}[{}] is created successfully", logicalStorageGroupName, dataRegionId);
+      logger.info("The data region {}[{}] is created successfully", storageGroupName, dataRegionId);
     } else {
       logger.info(
-          "The data region {}[{}] is recovered successfully",
-          logicalStorageGroupName,
-          dataRegionId);
+          "The data region {}[{}] is recovered successfully", storageGroupName, dataRegionId);
     }
   }
 
@@ -565,11 +571,7 @@ public class DataRegion {
     }
     timedCompactionScheduleTask =
         IoTDBThreadPoolFactory.newSingleThreadScheduledExecutor(
-            ThreadName.COMPACTION_SCHEDULE.getName()
-                + "-"
-                + logicalStorageGroupName
-                + "-"
-                + dataRegionId);
+            ThreadName.COMPACTION_SCHEDULE.getName() + "-" + storageGroupName + "-" + dataRegionId);
     ScheduledExecutorUtil.safelyScheduleWithFixedDelay(
         timedCompactionScheduleTask,
         this::executeCompaction,
@@ -580,7 +582,7 @@ public class DataRegion {
 
   private void recoverCompaction() {
     CompactionRecoverManager compactionRecoverManager =
-        new CompactionRecoverManager(tsFileManager, logicalStorageGroupName, dataRegionId);
+        new CompactionRecoverManager(tsFileManager, storageGroupName, dataRegionId);
     compactionRecoverManager.recoverInnerSpaceCompaction(true);
     compactionRecoverManager.recoverInnerSpaceCompaction(false);
     compactionRecoverManager.recoverCrossSpaceCompaction();
@@ -643,7 +645,7 @@ public class DataRegion {
     List<File> upgradeFiles = new ArrayList<>();
     for (String baseDir : folders) {
       File fileFolder =
-          fsFactory.getFile(baseDir + File.separator + logicalStorageGroupName, dataRegionId);
+          fsFactory.getFile(baseDir + File.separator + storageGroupName, dataRegionId);
       if (!fileFolder.exists()) {
         continue;
       }
@@ -723,11 +725,7 @@ public class DataRegion {
           String.format(
               "data region %s[%s] is down, because the time of tsfile %s is larger than system current time, "
                   + "file time is %d while system current time is %d, please check it.",
-              logicalStorageGroupName,
-              dataRegionId,
-              tsFile.getAbsolutePath(),
-              fileTime,
-              currentTime));
+              storageGroupName, dataRegionId, tsFile.getAbsolutePath(), fileTime, currentTime));
     }
   }
 
@@ -1525,7 +1523,7 @@ public class DataRegion {
     String filePath =
         TsFileNameGenerator.generateNewTsFilePathWithMkdir(
             sequence,
-            logicalStorageGroupName,
+            storageGroupName,
             dataRegionId,
             timePartitionId,
             System.currentTimeMillis(),
@@ -1542,7 +1540,7 @@ public class DataRegion {
     if (sequence) {
       tsFileProcessor =
           new TsFileProcessor(
-              logicalStorageGroupName + FILE_NAME_SEPARATOR + dataRegionId,
+              storageGroupName + FILE_NAME_SEPARATOR + dataRegionId,
               fsFactory.getFileWithParent(filePath),
               storageGroupInfo,
               this::closeUnsealedTsFileProcessorCallBack,
@@ -1551,7 +1549,7 @@ public class DataRegion {
     } else {
       tsFileProcessor =
           new TsFileProcessor(
-              logicalStorageGroupName + FILE_NAME_SEPARATOR + dataRegionId,
+              storageGroupName + FILE_NAME_SEPARATOR + dataRegionId,
               fsFactory.getFileWithParent(filePath),
               storageGroupInfo,
               this::closeUnsealedTsFileProcessorCallBack,
@@ -1604,7 +1602,7 @@ public class DataRegion {
           if (System.currentTimeMillis() - startTime > 60_000) {
             logger.warn(
                 "{} has spent {}s to wait for closing one tsfile.",
-                logicalStorageGroupName + "-" + this.dataRegionId,
+                storageGroupName + "-" + this.dataRegionId,
                 (System.currentTimeMillis() - startTime) / 1000);
           }
         }
@@ -1613,7 +1611,7 @@ public class DataRegion {
         logger.error(
             "syncCloseOneTsFileProcessor error occurs while waiting for closing the storage "
                 + "group {}",
-            logicalStorageGroupName + "-" + dataRegionId,
+            storageGroupName + "-" + dataRegionId,
             e);
       }
     }
@@ -1647,8 +1645,7 @@ public class DataRegion {
       if (!workUnsequenceTsFileProcessors.containsKey(tsFileProcessor.getTimeRangeId())) {
         timePartitionIdVersionControllerMap.remove(tsFileProcessor.getTimeRangeId());
       }
-      logger.info(
-          "close a sequence tsfile processor {}", logicalStorageGroupName + "-" + dataRegionId);
+      logger.info("close a sequence tsfile processor {}", storageGroupName + "-" + dataRegionId);
     } else {
       closingUnSequenceTsFileProcessor.add(tsFileProcessor);
       tsFileProcessor.asyncClose();
@@ -1670,13 +1667,13 @@ public class DataRegion {
   public void deleteFolder(String systemDir) {
     logger.info(
         "{} will close all files for deleting data folder {}",
-        logicalStorageGroupName + "-" + dataRegionId,
+        storageGroupName + "-" + dataRegionId,
         systemDir);
     writeLock("deleteFolder");
     try {
       File dataRegionSystemFolder =
           SystemFileFactory.INSTANCE.getFile(
-              systemDir + File.separator + logicalStorageGroupName, dataRegionId);
+              systemDir + File.separator + storageGroupName, dataRegionId);
       org.apache.iotdb.commons.utils.FileUtils.deleteDirectoryAndEmptyParent(
           dataRegionSystemFolder);
     } finally {
@@ -1705,8 +1702,7 @@ public class DataRegion {
   /** delete tsfile */
   public void syncDeleteDataFiles() {
     logger.info(
-        "{} will close all files for deleting data files",
-        logicalStorageGroupName + "-" + dataRegionId);
+        "{} will close all files for deleting data files", storageGroupName + "-" + dataRegionId);
     writeLock("syncDeleteDataFiles");
     try {
 
@@ -1730,7 +1726,7 @@ public class DataRegion {
   private void deleteAllSGFolders(List<String> folder) {
     for (String tsfilePath : folder) {
       File dataRegionDataFolder =
-          fsFactory.getFile(tsfilePath, logicalStorageGroupName + File.separator + dataRegionId);
+          fsFactory.getFile(tsfilePath, storageGroupName + File.separator + dataRegionId);
       if (dataRegionDataFolder.exists()) {
         org.apache.iotdb.commons.utils.FileUtils.deleteDirectoryAndEmptyParent(
             dataRegionDataFolder);
@@ -1741,14 +1737,13 @@ public class DataRegion {
   /** Iterate each TsFile and try to lock and remove those out of TTL. */
   public synchronized void checkFilesTTL() {
     if (dataTTL == Long.MAX_VALUE) {
-      logger.debug(
-          "{}: TTL not set, ignore the check", logicalStorageGroupName + "-" + dataRegionId);
+      logger.debug("{}: TTL not set, ignore the check", storageGroupName + "-" + dataRegionId);
       return;
     }
     long ttlLowerBound = System.currentTimeMillis() - dataTTL;
     logger.debug(
         "{}: TTL removing files before {}",
-        logicalStorageGroupName + "-" + dataRegionId,
+        storageGroupName + "-" + dataRegionId,
         new Date(ttlLowerBound));
 
     // copy to avoid concurrent modification of deletion
@@ -1798,7 +1793,7 @@ public class DataRegion {
           logger.info(
               "Exceed sequence memtable flush interval, so flush working memtable of time partition {} in storage group {}[{}]",
               tsFileProcessor.getTimeRangeId(),
-              logicalStorageGroupName,
+              storageGroupName,
               dataRegionId);
           fileFlushPolicy.apply(this, tsFileProcessor, tsFileProcessor.isSequence());
         }
@@ -1821,7 +1816,7 @@ public class DataRegion {
           logger.info(
               "Exceed unsequence memtable flush interval, so flush working memtable of time partition {} in storage group {}[{}]",
               tsFileProcessor.getTimeRangeId(),
-              logicalStorageGroupName,
+              storageGroupName,
               dataRegionId);
           fileFlushPolicy.apply(this, tsFileProcessor, tsFileProcessor.isSequence());
         }
@@ -1843,7 +1838,7 @@ public class DataRegion {
           if (System.currentTimeMillis() - startTime > 60_000) {
             logger.warn(
                 "{} has spent {}s to wait for closing all TsFiles.",
-                logicalStorageGroupName + "-" + this.dataRegionId,
+                storageGroupName + "-" + this.dataRegionId,
                 (System.currentTimeMillis() - startTime) / 1000);
           }
         }
@@ -1851,7 +1846,7 @@ public class DataRegion {
         logger.error(
             "CloseFileNodeCondition error occurs while waiting for closing the storage "
                 + "group {}",
-            logicalStorageGroupName + "-" + dataRegionId,
+            storageGroupName + "-" + dataRegionId,
             e);
         Thread.currentThread().interrupt();
       }
@@ -1864,7 +1859,7 @@ public class DataRegion {
     try {
       logger.info(
           "async force close all files in storage group: {}",
-          logicalStorageGroupName + "-" + dataRegionId);
+          storageGroupName + "-" + dataRegionId);
       // to avoid concurrent modification problem, we need a new array list
       for (TsFileProcessor tsFileProcessor :
           new ArrayList<>(workSequenceTsFileProcessors.values())) {
@@ -1885,8 +1880,7 @@ public class DataRegion {
     writeLock("forceCloseAllWorkingTsFileProcessors");
     try {
       logger.info(
-          "force close all processors in storage group: {}",
-          logicalStorageGroupName + "-" + dataRegionId);
+          "force close all processors in storage group: {}", storageGroupName + "-" + dataRegionId);
       // to avoid concurrent modification problem, we need a new array list
       for (TsFileProcessor tsFileProcessor :
           new ArrayList<>(workSequenceTsFileProcessors.values())) {
@@ -2277,7 +2271,7 @@ public class DataRegion {
       if (timePartitionStartId <= entry.getKey()
           && entry.getKey() <= timePartitionEndId
           && (timePartitionFilter == null
-              || timePartitionFilter.satisfy(logicalStorageGroupName, entry.getKey()))) {
+              || timePartitionFilter.satisfy(storageGroupName, entry.getKey()))) {
         WALFlushListener walFlushListener = entry.getValue().logDeleteInWAL(deletionPlan);
         walFlushListeners.add(walFlushListener);
       }
@@ -2286,7 +2280,7 @@ public class DataRegion {
       if (timePartitionStartId <= entry.getKey()
           && entry.getKey() <= timePartitionEndId
           && (timePartitionFilter == null
-              || timePartitionFilter.satisfy(logicalStorageGroupName, entry.getKey()))) {
+              || timePartitionFilter.satisfy(storageGroupName, entry.getKey()))) {
         WALFlushListener walFlushListener = entry.getValue().logDeleteInWAL(deletionPlan);
         walFlushListeners.add(walFlushListener);
       }
@@ -2301,8 +2295,7 @@ public class DataRegion {
       long deleteEnd,
       TimePartitionFilter timePartitionFilter) {
     if (timePartitionFilter != null
-        && !timePartitionFilter.satisfy(
-            logicalStorageGroupName, tsFileResource.getTimePartition())) {
+        && !timePartitionFilter.satisfy(storageGroupName, tsFileResource.getTimePartition())) {
       return true;
     }
 
@@ -2479,8 +2472,7 @@ public class DataRegion {
       closeStorageGroupCondition.notifyAll();
     }
     logger.info(
-        "signal closing storage group condition in {}",
-        logicalStorageGroupName + "-" + dataRegionId);
+        "signal closing storage group condition in {}", storageGroupName + "-" + dataRegionId);
   }
 
   private void executeCompaction() {
@@ -2992,7 +2984,7 @@ public class DataRegion {
         targetFile =
             fsFactory.getFile(
                 DirectoryManager.getInstance().getNextFolderForUnSequenceFile(),
-                logicalStorageGroupName
+                storageGroupName
                     + File.separatorChar
                     + dataRegionId
                     + File.separatorChar
@@ -3014,7 +3006,7 @@ public class DataRegion {
         targetFile =
             fsFactory.getFile(
                 DirectoryManager.getInstance().getNextFolderForSequenceFile(),
-                logicalStorageGroupName
+                storageGroupName
                     + File.separatorChar
                     + dataRegionId
                     + File.separatorChar
@@ -3270,7 +3262,7 @@ public class DataRegion {
    * @return data region path, like root.sg1/0
    */
   public String getStorageGroupPath() {
-    return logicalStorageGroupName + File.separator + dataRegionId;
+    return storageGroupName + File.separator + dataRegionId;
   }
 
   public StorageGroupInfo getStorageGroupInfo() {
@@ -3366,8 +3358,7 @@ public class DataRegion {
   public void abortCompaction() {
     tsFileManager.setAllowCompaction(false);
     List<AbstractCompactionTask> runningTasks =
-        CompactionTaskManager.getInstance()
-            .abortCompaction(logicalStorageGroupName + "-" + dataRegionId);
+        CompactionTaskManager.getInstance().abortCompaction(storageGroupName + "-" + dataRegionId);
     while (CompactionTaskManager.getInstance().isAnyTaskInListStillRunning(runningTasks)) {
       try {
         TimeUnit.MILLISECONDS.sleep(10);
@@ -3388,7 +3379,7 @@ public class DataRegion {
       Entry<Long, TsFileProcessor> longTsFileProcessorEntry = iterator.next();
       long partitionId = longTsFileProcessorEntry.getKey();
       TsFileProcessor processor = longTsFileProcessorEntry.getValue();
-      if (filter.satisfy(logicalStorageGroupName, partitionId)) {
+      if (filter.satisfy(storageGroupName, partitionId)) {
         processor.syncClose();
         iterator.remove();
         processor.getTsFileResource().remove();
@@ -3406,7 +3397,7 @@ public class DataRegion {
       TimePartitionFilter filter, Iterator<TsFileResource> iterator, boolean sequence) {
     while (iterator.hasNext()) {
       TsFileResource tsFileResource = iterator.next();
-      if (filter.satisfy(logicalStorageGroupName, tsFileResource.getTimePartition())) {
+      if (filter.satisfy(storageGroupName, tsFileResource.getTimePartition())) {
         tsFileResource.remove();
         tsFileManager.remove(tsFileResource, sequence);
         updateLatestFlushTimeToPartition(tsFileResource.getTimePartition(), Long.MIN_VALUE);
@@ -3734,7 +3725,7 @@ public class DataRegion {
     }
     // identifier should be same with getTsFileProcessor method
     return WALManager.getInstance()
-        .applyForWALNode(logicalStorageGroupName + FILE_NAME_SEPARATOR + dataRegionId);
+        .applyForWALNode(storageGroupName + FILE_NAME_SEPARATOR + dataRegionId);
   }
 
   /** Wait for this data region successfully deleted */
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/TsFileProcessorInfo.java b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/TsFileProcessorInfo.java
index 4eb8442697..4aafb030c8 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/TsFileProcessorInfo.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/TsFileProcessorInfo.java
@@ -47,7 +47,7 @@ public class TsFileProcessorInfo {
               Metric.MEM.toString(),
               MetricLevel.IMPORTANT,
               Tag.NAME.toString(),
-              "chunkMetaData_" + storageGroupInfo.getDataRegion().getLogicalStorageGroupName())
+              "chunkMetaData_" + storageGroupInfo.getDataRegion().getStorageGroupName())
           .incr(cost);
     }
   }
@@ -61,7 +61,7 @@ public class TsFileProcessorInfo {
             Metric.MEM.toString(),
             MetricLevel.IMPORTANT,
             Tag.NAME.toString(),
-            "chunkMetaData_" + storageGroupInfo.getDataRegion().getLogicalStorageGroupName())
+            "chunkMetaData_" + storageGroupInfo.getDataRegion().getStorageGroupName())
         .decr(cost);
   }
 
@@ -73,7 +73,7 @@ public class TsFileProcessorInfo {
             Metric.MEM.toString(),
             MetricLevel.IMPORTANT,
             Tag.NAME.toString(),
-            "chunkMetaData_" + storageGroupInfo.getDataRegion().getLogicalStorageGroupName())
+            "chunkMetaData_" + storageGroupInfo.getDataRegion().getStorageGroupName())
         .decr(memCost);
     memCost = 0L;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/dataregion/StorageGroupManager.java b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/dataregion/StorageGroupManager.java
index 345c995332..375ae1f9a0 100644
--- a/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/dataregion/StorageGroupManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/engine/storagegroup/dataregion/StorageGroupManager.java
@@ -254,7 +254,7 @@ public class StorageGroupManager {
         logger.info(
             "{} closing sg processor is called for closing {}, seq = {}",
             isSync ? "sync" : "async",
-            processor.getDataRegionId() + "-" + processor.getLogicalStorageGroupName(),
+            processor.getDataRegionId() + "-" + processor.getStorageGroupName(),
             isSeq);
       }
 
@@ -293,7 +293,7 @@ public class StorageGroupManager {
       if (processor != null) {
         logger.info(
             "async closing sg processor is called for closing {}, seq = {}, partitionId = {}",
-            processor.getDataRegionId() + "-" + processor.getLogicalStorageGroupName(),
+            processor.getDataRegionId() + "-" + processor.getStorageGroupName(),
             isSeq,
             partitionId);
         processor.writeLock("VirtualCloseStorageGroupProcessor-242");
diff --git a/server/src/main/java/org/apache/iotdb/db/rescon/SystemInfo.java b/server/src/main/java/org/apache/iotdb/db/rescon/SystemInfo.java
index 09e432bec2..88b639a82a 100644
--- a/server/src/main/java/org/apache/iotdb/db/rescon/SystemInfo.java
+++ b/server/src/main/java/org/apache/iotdb/db/rescon/SystemInfo.java
@@ -91,7 +91,7 @@ public class SystemInfo {
     } else {
       logger.info(
           "Change system to reject status. Triggered by: logical SG ({}), mem cost delta ({}), totalSgMemCost ({}).",
-          storageGroupInfo.getDataRegion().getLogicalStorageGroupName(),
+          storageGroupInfo.getDataRegion().getStorageGroupName(),
           delta,
           totalStorageGroupMemCost);
       rejected = true;
@@ -131,13 +131,13 @@ public class SystemInfo {
         && totalStorageGroupMemCost < REJECT_THERSHOLD) {
       logger.debug(
           "SG ({}) released memory (delta: {}) but still exceeding flush proportion (totalSgMemCost: {}), call flush.",
-          storageGroupInfo.getDataRegion().getLogicalStorageGroupName(),
+          storageGroupInfo.getDataRegion().getStorageGroupName(),
           delta,
           totalStorageGroupMemCost);
       if (rejected) {
         logger.info(
             "SG ({}) released memory (delta: {}), set system to normal status (totalSgMemCost: {}).",
-            storageGroupInfo.getDataRegion().getLogicalStorageGroupName(),
+            storageGroupInfo.getDataRegion().getStorageGroupName(),
             delta,
             totalStorageGroupMemCost);
       }
@@ -146,7 +146,7 @@ public class SystemInfo {
     } else if (totalStorageGroupMemCost >= REJECT_THERSHOLD) {
       logger.warn(
           "SG ({}) released memory (delta: {}), but system is still in reject status (totalSgMemCost: {}).",
-          storageGroupInfo.getDataRegion().getLogicalStorageGroupName(),
+          storageGroupInfo.getDataRegion().getStorageGroupName(),
           delta,
           totalStorageGroupMemCost);
       logCurrentTotalSGMemory();
@@ -154,7 +154,7 @@ public class SystemInfo {
     } else {
       logger.debug(
           "SG ({}) released memory (delta: {}), system is in normal status (totalSgMemCost: {}).",
-          storageGroupInfo.getDataRegion().getLogicalStorageGroupName(),
+          storageGroupInfo.getDataRegion().getStorageGroupName(),
           delta,
           totalStorageGroupMemCost);
       logCurrentTotalSGMemory();
diff --git a/server/src/test/java/org/apache/iotdb/db/engine/snapshot/IoTDBSnapshotTest.java b/server/src/test/java/org/apache/iotdb/db/engine/snapshot/IoTDBSnapshotTest.java
new file mode 100644
index 0000000000..804ed6ef94
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/engine/snapshot/IoTDBSnapshotTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.iotdb.db.engine.snapshot;
+
+import org.apache.iotdb.commons.utils.FileUtils;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.conf.directories.DirectoryManager;
+import org.apache.iotdb.db.engine.snapshot.exception.DirectoryNotLegalException;
+import org.apache.iotdb.db.engine.storagegroup.DataRegion;
+import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
+import org.apache.iotdb.db.engine.storagegroup.TsFileResourceStatus;
+import org.apache.iotdb.db.exception.DataRegionException;
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.tsfile.exception.write.WriteProcessException;
+import org.apache.iotdb.tsfile.utils.TsFileGeneratorUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.iotdb.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR;
+
+public class IoTDBSnapshotTest {
+  private String[] testDataDirs =
+      new String[] {"target/data/data1", "target/data/data2", "target/data/data3"};
+  private String testSgName = "root.testsg";
+
+  @Before
+  public void setUp() throws Exception {
+    EnvironmentUtils.envSetUp();
+  }
+
+  @After
+  public void tearDown() throws IOException, StorageEngineException {
+    FileUtils.recursiveDeleteFolder("target" + File.separator + "data");
+    EnvironmentUtils.cleanEnv();
+    FileUtils.recursiveDeleteFolder("target" + File.separator + "tmp");
+  }
+
+  private List<TsFileResource> writeTsFiles() throws IOException, WriteProcessException {
+    List<TsFileResource> resources = new ArrayList<>();
+    for (int i = 0; i < 100; i++) {
+      String filePath =
+          testDataDirs[i % 3]
+              + File.separator
+              + "sequence"
+              + File.separator
+              + testSgName
+              + File.separator
+              + "0"
+              + File.separator
+              + "0"
+              + File.separator
+              + String.format("%d-%d-0-0.tsfile", i + 1, i + 1);
+      TsFileGeneratorUtils.generateMixTsFile(filePath, 5, 5, 10, i * 100, (i + 1) * 100, 10, 10);
+      TsFileResource resource = new TsFileResource(new File(filePath));
+      resources.add(resource);
+      for (int idx = 0; idx < 5; idx++) {
+        resource.updateStartTime(testSgName + PATH_SEPARATOR + "d" + i, i * 100);
+        resource.updateEndTime(testSgName + PATH_SEPARATOR + "d" + i, (i + 1) * 100);
+      }
+      resource.updatePlanIndexes(i);
+      resource.setStatus(TsFileResourceStatus.CLOSED);
+      resource.serialize();
+    }
+    return resources;
+  }
+
+  @Test
+  public void testCreateSnapshot()
+      throws IOException, WriteProcessException, DataRegionException, DirectoryNotLegalException {
+    String[] originDataDirs = IoTDBDescriptor.getInstance().getConfig().getDataDirs();
+    IoTDBDescriptor.getInstance().getConfig().setDataDirs(testDataDirs);
+    DirectoryManager.getInstance().resetFolders();
+    try {
+      List<TsFileResource> resources = writeTsFiles();
+      DataRegion region = new DataRegion(testSgName, "0");
+      region.getTsFileManager().addAll(resources, true);
+      File snapshotDir = new File("target" + File.separator + "snapshot");
+      Assert.assertTrue(snapshotDir.exists() || snapshotDir.mkdirs());
+      try {
+        new SnapshotTaker(region).takeFullSnapshot(snapshotDir.getAbsolutePath(), true);
+        File[] files =
+            snapshotDir.listFiles((dir, name) -> name.equals(SnapshotLogger.SNAPSHOT_LOG_NAME));
+        Assert.assertEquals(1, files.length);
+        SnapshotLogAnalyzer analyzer = new SnapshotLogAnalyzer(files[0]);
+        int cnt = 0;
+        while (analyzer.hasNext()) {
+          analyzer.getNextPairs();
+          cnt++;
+        }
+        analyzer.close();
+        Assert.assertEquals(200, cnt);
+        for (TsFileResource resource : resources) {
+          Assert.assertTrue(resource.tryWriteLock());
+        }
+      } finally {
+        FileUtils.recursiveDeleteFolder(snapshotDir.getAbsolutePath());
+      }
+    } finally {
+      IoTDBDescriptor.getInstance().getConfig().setDataDirs(originDataDirs);
+      DirectoryManager.getInstance().resetFolders();
+    }
+  }
+
+  @Test
+  public void testLoadSnapshot()
+      throws IOException, WriteProcessException, DataRegionException, DirectoryNotLegalException {
+    String[] originDataDirs = IoTDBDescriptor.getInstance().getConfig().getDataDirs();
+    IoTDBDescriptor.getInstance().getConfig().setDataDirs(testDataDirs);
+    DirectoryManager.getInstance().resetFolders();
+    try {
+      List<TsFileResource> resources = writeTsFiles();
+      DataRegion region = new DataRegion(testSgName, "0");
+      region.getTsFileManager().addAll(resources, true);
+      File snapshotDir = new File("target" + File.separator + "snapshot");
+      Assert.assertTrue(snapshotDir.exists() || snapshotDir.mkdirs());
+      try {
+        Assert.assertTrue(
+            new SnapshotTaker(region).takeFullSnapshot(snapshotDir.getAbsolutePath(), true));
+        DataRegion dataRegion =
+            new SnapshotLoader(snapshotDir.getAbsolutePath(), testSgName, "0")
+                .loadSnapshotForStateMachine();
+        Assert.assertNotNull(dataRegion);
+        List<TsFileResource> resource = dataRegion.getTsFileManager().getTsFileList(true);
+        Assert.assertEquals(100, resource.size());
+      } finally {
+        FileUtils.recursiveDeleteFolder(snapshotDir.getAbsolutePath());
+      }
+    } finally {
+      IoTDBDescriptor.getInstance().getConfig().setDataDirs(originDataDirs);
+      DirectoryManager.getInstance().resetFolders();
+    }
+  }
+
+  @Test
+  public void testGetSnapshotFile() throws IOException {
+    File tsFile =
+        new File(
+            IoTDBDescriptor.getInstance().getConfig().getDataDirs()[0]
+                + File.separator
+                + "sequence"
+                + File.separator
+                + "root.test"
+                + File.separator
+                + "0"
+                + File.separator
+                + "0"
+                + File.separator
+                + "1-1-0-0.tsfile");
+    File snapshotFile =
+        new SnapshotTaker(null).getSnapshotFilePathForTsFile(tsFile, "test-snapshotId");
+    Assert.assertEquals(
+        new File(
+                IoTDBDescriptor.getInstance().getConfig().getDataDirs()[0]
+                    + File.separator
+                    + "snapshot"
+                    + File.separator
+                    + "test-snapshotId"
+                    + File.separator
+                    + "sequence"
+                    + File.separator
+                    + "root.test"
+                    + File.separator
+                    + "0"
+                    + File.separator
+                    + "0"
+                    + File.separator
+                    + "1-1-0-0.tsfile")
+            .getAbsolutePath(),
+        snapshotFile.getAbsolutePath());
+
+    Assert.assertTrue(snapshotFile.getParentFile().exists());
+  }
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/utils/EnvironmentUtils.java b/server/src/test/java/org/apache/iotdb/db/utils/EnvironmentUtils.java
index e1b7afac89..df70dcd56b 100644
--- a/server/src/test/java/org/apache/iotdb/db/utils/EnvironmentUtils.java
+++ b/server/src/test/java/org/apache/iotdb/db/utils/EnvironmentUtils.java
@@ -395,4 +395,21 @@ public class EnvironmentUtils {
     File file = new File(dir);
     file.mkdirs();
   }
+
+  public static void recursiveDeleteFolder(String path) throws IOException {
+    File file = new File(path);
+    if (file.isDirectory()) {
+      File[] files = file.listFiles();
+      if (files == null || files.length == 0) {
+        FileUtils.deleteDirectory(file);
+      } else {
+        for (File f : files) {
+          recursiveDeleteFolder(f.getAbsolutePath());
+        }
+        FileUtils.deleteDirectory(file);
+      }
+    } else {
+      FileUtils.delete(file);
+    }
+  }
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TsFileGeneratorUtils.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TsFileGeneratorUtils.java
index 7669cece20..919851ef76 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TsFileGeneratorUtils.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/utils/TsFileGeneratorUtils.java
@@ -128,57 +128,65 @@ public class TsFileGeneratorUtils {
     if (file.exists()) {
       file.delete();
     }
-    if (chunkGroupSize > 0)
-      TSFileDescriptor.getInstance().getConfig().setGroupSizeInByte(chunkGroupSize);
-    if (pageSize > 0)
-      TSFileDescriptor.getInstance().getConfig().setMaxNumberOfPointsInPage(pageSize);
-    try (TsFileWriter tsFileWriter = new TsFileWriter(file)) {
-      // register align timeseries
-      List<MeasurementSchema> alignedMeasurementSchemas = new ArrayList<>();
-      for (int i = 0; i < measurementNum; i++) {
-        alignedMeasurementSchemas.add(
-            new MeasurementSchema("s" + i, TSDataType.INT64, TSEncoding.PLAIN));
-      }
-      for (int i = alignDeviceOffset; i < alignDeviceOffset + deviceNum; i++) {
-        tsFileWriter.registerAlignedTimeseries(
-            new Path(testStorageGroup + PATH_SEPARATOR + "d" + i), alignedMeasurementSchemas);
-      }
+    int originGroupSize = TSFileDescriptor.getInstance().getConfig().getGroupSizeInByte();
+    int originPageSize = TSFileDescriptor.getInstance().getConfig().getMaxNumberOfPointsInPage();
+    try {
+      if (chunkGroupSize > 0)
+        TSFileDescriptor.getInstance().getConfig().setGroupSizeInByte(chunkGroupSize);
+      if (pageSize > 0)
+        TSFileDescriptor.getInstance().getConfig().setMaxNumberOfPointsInPage(pageSize);
+      try (TsFileWriter tsFileWriter = new TsFileWriter(file)) {
+        // register align timeseries
+        List<MeasurementSchema> alignedMeasurementSchemas = new ArrayList<>();
+        for (int i = 0; i < measurementNum; i++) {
+          alignedMeasurementSchemas.add(
+              new MeasurementSchema("s" + i, TSDataType.INT64, TSEncoding.PLAIN));
+        }
+        for (int i = alignDeviceOffset; i < alignDeviceOffset + deviceNum; i++) {
+          tsFileWriter.registerAlignedTimeseries(
+              new Path(testStorageGroup + PATH_SEPARATOR + "d" + i), alignedMeasurementSchemas);
+        }
 
-      // write with record
-      for (int i = alignDeviceOffset; i < alignDeviceOffset + deviceNum; i++) {
-        writeWithTsRecord(
-            tsFileWriter,
-            testStorageGroup + PATH_SEPARATOR + "d" + i,
-            alignedMeasurementSchemas,
-            pointNum,
-            startTime,
-            startValue,
-            true);
-      }
+        // write with record
+        for (int i = alignDeviceOffset; i < alignDeviceOffset + deviceNum; i++) {
+          writeWithTsRecord(
+              tsFileWriter,
+              testStorageGroup + PATH_SEPARATOR + "d" + i,
+              alignedMeasurementSchemas,
+              pointNum,
+              startTime,
+              startValue,
+              true);
+        }
 
-      // register nonAlign timeseries
-      List<MeasurementSchema> measurementSchemas = new ArrayList<>();
-      for (int i = 0; i < measurementNum; i++) {
-        measurementSchemas.add(new MeasurementSchema("s" + i, TSDataType.INT64, TSEncoding.PLAIN));
-      }
-      for (int i = 0; i < deviceNum; i++) {
-        tsFileWriter.registerTimeseries(
-            new Path(testStorageGroup + PATH_SEPARATOR + "d" + i), measurementSchemas);
-      }
+        // register nonAlign timeseries
+        List<MeasurementSchema> measurementSchemas = new ArrayList<>();
+        for (int i = 0; i < measurementNum; i++) {
+          measurementSchemas.add(
+              new MeasurementSchema("s" + i, TSDataType.INT64, TSEncoding.PLAIN));
+        }
+        for (int i = 0; i < deviceNum; i++) {
+          tsFileWriter.registerTimeseries(
+              new Path(testStorageGroup + PATH_SEPARATOR + "d" + i), measurementSchemas);
+        }
 
-      // write with record
-      for (int i = 0; i < deviceNum; i++) {
-        writeWithTsRecord(
-            tsFileWriter,
-            testStorageGroup + PATH_SEPARATOR + "d" + i,
-            measurementSchemas,
-            pointNum,
-            startTime,
-            startValue,
-            false);
+        // write with record
+        for (int i = 0; i < deviceNum; i++) {
+          writeWithTsRecord(
+              tsFileWriter,
+              testStorageGroup + PATH_SEPARATOR + "d" + i,
+              measurementSchemas,
+              pointNum,
+              startTime,
+              startValue,
+              false);
+        }
       }
+      return file;
+    } finally {
+      TSFileDescriptor.getInstance().getConfig().setGroupSizeInByte(originGroupSize);
+      TSFileDescriptor.getInstance().getConfig().setMaxNumberOfPointsInPage(originPageSize);
     }
-    return file;
   }
 
   public static File generateAlignedTsFile(