Skip to content

Commit 85e2008

Browse files
committed
Refactor snapshot object path handling with Path APIs.
This change replaces string-based path parsing with Java Path operations for object snapshot logging, link creation, and log validation to improve cross-platform correctness and maintainability. Made-with: Cursor
1 parent 79d8fa7 commit 85e2008

File tree

5 files changed

+351
-0
lines changed

5 files changed

+351
-0
lines changed

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotFileSet.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.iotdb.db.storageengine.dataregion.modification.ModificationFile;
2323
import org.apache.iotdb.db.storageengine.dataregion.modification.v1.ModificationFileV1;
2424
import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
25+
import org.apache.iotdb.db.utils.ObjectTypeUtils;
2526

2627
import org.apache.tsfile.common.constant.TsFileConstant;
2728

@@ -37,6 +38,9 @@ public class SnapshotFileSet {
3738
TsFileResource.RESOURCE_SUFFIX.replace(".", ""),
3839
ModificationFileV1.FILE_SUFFIX.replace(".", ""),
3940
ModificationFile.FILE_SUFFIX.replace(".", ""),
41+
ObjectTypeUtils.OBJECT_FILE_SUFFIX.replace(".", ""),
42+
ObjectTypeUtils.OBJECT_TEMP_FILE_SUFFIX.replace(".", ""),
43+
ObjectTypeUtils.OBJECT_BACK_FILE_SUFFIX.replace(".", ""),
4044
};
4145

4246
private static final Set<String> DATA_FILE_SUFFIX_SET =

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
2727
import org.apache.iotdb.db.storageengine.dataregion.flush.CompressionRatio;
2828
import org.apache.iotdb.db.storageengine.rescon.disk.FolderManager;
29+
import org.apache.iotdb.db.storageengine.rescon.disk.TierManager;
2930
import org.apache.iotdb.db.storageengine.rescon.disk.strategy.DirectoryStrategyType;
31+
import org.apache.iotdb.db.utils.ObjectTypeUtils;
3032

3133
import org.apache.tsfile.external.commons.io.FileUtils;
3234
import org.slf4j.Logger;
@@ -38,6 +40,7 @@
3840
import java.nio.file.FileVisitor;
3941
import java.nio.file.Files;
4042
import java.nio.file.Path;
43+
import java.nio.file.SimpleFileVisitor;
4144
import java.nio.file.attribute.BasicFileAttributes;
4245
import java.util.ArrayList;
4346
import java.util.Arrays;
@@ -231,6 +234,17 @@ private void deleteAllFilesInDataDirs() throws IOException {
231234
timePartitions.addAll(Arrays.asList(files));
232235
}
233236
}
237+
238+
File objectRegionDir =
239+
new File(
240+
dataDirPath
241+
+ File.separator
242+
+ IoTDBConstant.OBJECT_FOLDER_NAME
243+
+ File.separator
244+
+ dataRegionId);
245+
if (objectRegionDir.exists()) {
246+
timePartitions.add(objectRegionDir);
247+
}
234248
}
235249

236250
try {
@@ -312,6 +326,91 @@ private void createLinksFromSnapshotDirToDataDirWithoutLog(File sourceDir)
312326
createLinksFromSnapshotToSourceDir(targetSuffix, files, folderManager);
313327
}
314328
}
329+
330+
File snapshotObjectDir = new File(sourceDir, IoTDBConstant.OBJECT_FOLDER_NAME);
331+
if (snapshotObjectDir.exists()) {
332+
FolderManager objectFolderManager =
333+
new FolderManager(
334+
TierManager.getInstance().getAllObjectFileFolders(),
335+
DirectoryStrategyType.SEQUENCE_STRATEGY);
336+
linkObjectTreeFromSnapshotToObjectDirs(snapshotObjectDir, objectFolderManager);
337+
}
338+
}
339+
340+
private void linkObjectTreeFromSnapshotToObjectDirs(
341+
File sourceObjectRoot, FolderManager folderManager)
342+
throws DiskSpaceInsufficientException, IOException {
343+
Map<String, String> fileTarget = new HashMap<>();
344+
List<File> files = new ArrayList<>();
345+
Files.walkFileTree(
346+
sourceObjectRoot.toPath(),
347+
new SimpleFileVisitor<Path>() {
348+
@Override
349+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
350+
if (isObjectSnapshotCandidate(file.getFileName().toString())) {
351+
files.add(file.toFile());
352+
}
353+
return FileVisitResult.CONTINUE;
354+
}
355+
});
356+
Path sourceRootPath = sourceObjectRoot.toPath();
357+
for (File file : files) {
358+
Path relativePath = sourceRootPath.relativize(file.toPath());
359+
String fileKey =
360+
relativePath.getNameCount() > 1
361+
? relativePath.getName(0).toString()
362+
: relativePath.toString();
363+
String dataDir = fileTarget.get(fileKey);
364+
final Path targetRelPath = relativePath;
365+
final String targetKey = fileKey;
366+
final String preferredDir = dataDir;
367+
final File sourceFile = file;
368+
369+
try {
370+
folderManager.getNextWithRetry(
371+
currentObjectDir -> {
372+
String effectiveDir = (preferredDir != null) ? preferredDir : currentObjectDir;
373+
File targetFile = new File(effectiveDir).toPath().resolve(targetRelPath).toFile();
374+
375+
try {
376+
if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) {
377+
throw new IOException(
378+
String.format(
379+
"Cannot create directory %s",
380+
targetFile.getParentFile().getAbsolutePath()));
381+
}
382+
383+
try {
384+
Files.createLink(targetFile.toPath(), sourceFile.toPath());
385+
LOGGER.debug("Created hard link from {} to {}", sourceFile, targetFile);
386+
fileTarget.put(targetKey, effectiveDir);
387+
return targetFile;
388+
} catch (IOException e) {
389+
LOGGER.info(
390+
"Cannot create link from {} to {}, fallback to copy", sourceFile, targetFile);
391+
}
392+
393+
Files.copy(sourceFile.toPath(), targetFile.toPath());
394+
fileTarget.put(targetKey, effectiveDir);
395+
return targetFile;
396+
} catch (Exception e) {
397+
LOGGER.warn(
398+
"Failed to process file {} in dir {}: {}",
399+
sourceFile.getName(),
400+
effectiveDir,
401+
e.getMessage(),
402+
e);
403+
throw e;
404+
}
405+
});
406+
} catch (Exception e) {
407+
throw new IOException(
408+
String.format(
409+
"Failed to process object file after retries. Source: %s",
410+
file.getAbsolutePath()),
411+
e);
412+
}
413+
}
315414
}
316415

317416
private void createLinksFromSnapshotToSourceDir(
@@ -470,6 +569,103 @@ private int takeHardLinksFromSnapshotToDataDir(
470569
}
471570
}
472571

572+
File objectSnapshotRoot =
573+
new File(snapshotFolder.getAbsolutePath() + File.separator + IoTDBConstant.OBJECT_FOLDER_NAME);
574+
if (objectSnapshotRoot.exists()) {
575+
cnt += linkObjectSnapshotTreeToDataDir(objectSnapshotRoot, fileInfoSet);
576+
}
577+
578+
return cnt;
579+
}
580+
581+
private int linkObjectSnapshotTreeToDataDir(File objectSnapshotRoot, Set<String> fileInfoSet)
582+
throws IOException {
583+
final FolderManager folderManager;
584+
try {
585+
folderManager =
586+
new FolderManager(
587+
TierManager.getInstance().getAllObjectFileFolders(),
588+
DirectoryStrategyType.SEQUENCE_STRATEGY);
589+
} catch (DiskSpaceInsufficientException e) {
590+
throw new IOException("Failed to initialize object folder manager", e);
591+
}
592+
Map<String, String> fileTarget = new HashMap<>();
593+
List<File> files = new ArrayList<>();
594+
Files.walkFileTree(
595+
objectSnapshotRoot.toPath(),
596+
new SimpleFileVisitor<Path>() {
597+
@Override
598+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
599+
if (isObjectSnapshotCandidate(file.getFileName().toString())) {
600+
files.add(file.toFile());
601+
}
602+
return FileVisitResult.CONTINUE;
603+
}
604+
});
605+
Path rootPath = objectSnapshotRoot.toPath();
606+
int cnt = 0;
607+
for (File file : files) {
608+
Path relativePath = rootPath.relativize(file.toPath());
609+
String fileKey =
610+
relativePath.getNameCount() > 1
611+
? relativePath.getName(0).toString()
612+
: relativePath.toString();
613+
String infoStr = getFileInfoString(file);
614+
if (!fileInfoSet.contains(infoStr)) {
615+
throw new IOException(
616+
String.format("File %s is not in the log file list", file.getAbsolutePath()));
617+
}
618+
final Path targetRelPath = relativePath;
619+
final String targetKey = fileKey;
620+
final File sourceFile = file;
621+
622+
try {
623+
folderManager.getNextWithRetry(
624+
currentObjectDir -> {
625+
String effectiveDir = fileTarget.getOrDefault(targetKey, currentObjectDir);
626+
File targetFile = new File(effectiveDir).toPath().resolve(targetRelPath).toFile();
627+
628+
try {
629+
if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) {
630+
throw new IOException(
631+
String.format(
632+
"Cannot create directory %s",
633+
targetFile.getParentFile().getAbsolutePath()));
634+
}
635+
636+
try {
637+
Files.createLink(targetFile.toPath(), sourceFile.toPath());
638+
LOGGER.debug("Created hard link from {} to {}", sourceFile, targetFile);
639+
fileTarget.putIfAbsent(targetKey, effectiveDir);
640+
return targetFile;
641+
} catch (IOException e) {
642+
LOGGER.info(
643+
"Cannot create link from {} to {}, fallback to copy", sourceFile, targetFile);
644+
}
645+
646+
Files.copy(sourceFile.toPath(), targetFile.toPath());
647+
fileTarget.putIfAbsent(targetKey, effectiveDir);
648+
return targetFile;
649+
} catch (Exception e) {
650+
LOGGER.warn(
651+
"Failed to process file {} in dir {}: {}",
652+
sourceFile.getName(),
653+
effectiveDir,
654+
e.getMessage(),
655+
e);
656+
throw e;
657+
}
658+
});
659+
} catch (Exception e) {
660+
throw new IOException(
661+
String.format(
662+
"Failed to process object snapshot file after retries. Source: %s",
663+
file.getAbsolutePath()),
664+
e);
665+
}
666+
cnt++;
667+
}
668+
473669
return cnt;
474670
}
475671

@@ -492,6 +688,33 @@ private void createLinksFromSourceToTarget(File targetDir, File[] files, Set<Str
492688
}
493689

494690
private String getFileInfoString(File file) {
691+
Path filePath = file.toPath();
692+
int objectDirIndex = -1;
693+
int nameCount = filePath.getNameCount();
694+
for (int i = 0; i < nameCount; i++) {
695+
if (IoTDBConstant.OBJECT_FOLDER_NAME.equals(filePath.getName(i).toString())) {
696+
objectDirIndex = i;
697+
break;
698+
}
699+
}
700+
if (objectDirIndex >= 0 && objectDirIndex < nameCount - 1) {
701+
Path relativeToObject = filePath.subpath(objectDirIndex + 1, nameCount);
702+
String fileName = relativeToObject.getFileName().toString();
703+
Path parentPath = relativeToObject.getParent();
704+
String middlePath = "";
705+
if (parentPath != null) {
706+
List<String> pathElements = new ArrayList<>();
707+
for (Path element : parentPath) {
708+
pathElements.add(element.toString());
709+
}
710+
middlePath = String.join("/", pathElements);
711+
}
712+
return fileName
713+
+ SnapshotLogger.SPLIT_CHAR
714+
+ middlePath
715+
+ SnapshotLogger.SPLIT_CHAR
716+
+ "object";
717+
}
495718
String[] splittedStr = file.getAbsolutePath().split(File.separator.equals("\\") ? "\\\\" : "/");
496719
int length = splittedStr.length;
497720
return splittedStr[length - SnapshotLogger.FILE_NAME_OFFSET]
@@ -501,6 +724,12 @@ private String getFileInfoString(File file) {
501724
+ splittedStr[length - SnapshotLogger.SEQUENCE_OFFSET];
502725
}
503726

727+
private boolean isObjectSnapshotCandidate(String fileName) {
728+
return fileName.endsWith(ObjectTypeUtils.OBJECT_FILE_SUFFIX)
729+
|| fileName.endsWith(ObjectTypeUtils.OBJECT_TEMP_FILE_SUFFIX)
730+
|| fileName.endsWith(ObjectTypeUtils.OBJECT_BACK_FILE_SUFFIX);
731+
}
732+
504733
public List<File> getSnapshotFileInfo() throws IOException {
505734
File snapshotLogFile = getSnapshotLogFile();
506735

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLogger.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import java.io.IOException;
2525
import java.nio.charset.StandardCharsets;
2626
import java.nio.file.Files;
27+
import java.nio.file.Path;
28+
import java.util.ArrayList;
29+
import java.util.List;
2730

2831
public class SnapshotLogger implements AutoCloseable {
2932
public static final String SNAPSHOT_LOG_NAME = "snapshot.log";
@@ -63,6 +66,26 @@ public void close() throws Exception {
6366
* @param sourceFile
6467
* @throws IOException
6568
*/
69+
public void logObjectRelativePath(Path relativePathFromObjectRoot) throws IOException {
70+
String fileName = relativePathFromObjectRoot.getFileName().toString();
71+
Path parentPath = relativePathFromObjectRoot.getParent();
72+
String middlePath = "";
73+
if (parentPath != null) {
74+
List<String> pathElements = new ArrayList<>();
75+
for (Path element : parentPath) {
76+
pathElements.add(element.toString());
77+
}
78+
middlePath = String.join("/", pathElements);
79+
}
80+
os.write(fileName.getBytes(StandardCharsets.UTF_8));
81+
os.write(SPLIT_CHAR.getBytes(StandardCharsets.UTF_8));
82+
os.write(middlePath.getBytes(StandardCharsets.UTF_8));
83+
os.write(SPLIT_CHAR.getBytes(StandardCharsets.UTF_8));
84+
os.write("object".getBytes(StandardCharsets.UTF_8));
85+
os.write("\n".getBytes(StandardCharsets.UTF_8));
86+
os.flush();
87+
}
88+
6689
public void logFile(File sourceFile) throws IOException {
6790
String[] splitInfo =
6891
sourceFile.getAbsolutePath().split(File.separator.equals("\\") ? "\\\\" : "/");

0 commit comments

Comments
 (0)