2626import org .apache .iotdb .db .storageengine .dataregion .DataRegion ;
2727import org .apache .iotdb .db .storageengine .dataregion .flush .CompressionRatio ;
2828import org .apache .iotdb .db .storageengine .rescon .disk .FolderManager ;
29+ import org .apache .iotdb .db .storageengine .rescon .disk .TierManager ;
2930import org .apache .iotdb .db .storageengine .rescon .disk .strategy .DirectoryStrategyType ;
31+ import org .apache .iotdb .db .utils .ObjectTypeUtils ;
3032
3133import org .apache .tsfile .external .commons .io .FileUtils ;
3234import org .slf4j .Logger ;
3840import java .nio .file .FileVisitor ;
3941import java .nio .file .Files ;
4042import java .nio .file .Path ;
43+ import java .nio .file .SimpleFileVisitor ;
4144import java .nio .file .attribute .BasicFileAttributes ;
4245import java .util .ArrayList ;
4346import 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
0 commit comments