임시파일생성 소프트링크에서 하드링크로 변경

This commit is contained in:
2026-02-12 18:18:44 +09:00
parent 16e156b5b4
commit 6c865d26fd

View File

@@ -18,7 +18,7 @@ public class TmpDatasetService {
public String buildTmpDatasetSymlink(String uid, List<String> datasetUids) throws IOException { public String buildTmpDatasetSymlink(String uid, List<String> datasetUids) throws IOException {
log.info("========== buildTmpDatasetSymlink START =========="); log.info("========== buildTmpDatasetHardlink START ==========");
log.info("uid={}", uid); log.info("uid={}", uid);
log.info("datasetUids={}", datasetUids); log.info("datasetUids={}", datasetUids);
log.info("requestDir(raw)={}", requestDir); log.info("requestDir(raw)={}", requestDir);
@@ -30,8 +30,9 @@ public class TmpDatasetService {
log.info("BASE exists? {}", Files.isDirectory(BASE)); log.info("BASE exists? {}", Files.isDirectory(BASE));
log.info("tmp={}", tmp); log.info("tmp={}", tmp);
long noDir = 0, scannedDirs = 0, regularFiles = 0, symlinksMade = 0; long noDir = 0, scannedDirs = 0, regularFiles = 0, hardlinksMade = 0;
// tmp 디렉토리 준비
for (String type : List.of("train", "val")) { for (String type : List.of("train", "val")) {
for (String part : List.of("input1", "input2", "label")) { for (String part : List.of("input1", "input2", "label")) {
Path dir = tmp.resolve(type).resolve(part); Path dir = tmp.resolve(type).resolve(part);
@@ -40,6 +41,27 @@ public class TmpDatasetService {
} }
} }
// 하드링크는 "같은 파일시스템"에서만 가능하므로 BASE/tmp가 같은 FS인지 미리 확인(권장)
try {
var baseStore = Files.getFileStore(BASE);
var tmpStore = Files.getFileStore(tmp.getParent()); // BASE/tmp
if (!baseStore.name().equals(tmpStore.name()) || !baseStore.type().equals(tmpStore.type())) {
throw new IOException(
"Hardlink requires same filesystem. baseStore="
+ baseStore.name()
+ "("
+ baseStore.type()
+ "), tmpStore="
+ tmpStore.name()
+ "("
+ tmpStore.type()
+ ")");
}
} catch (Exception e) {
// FileStore 비교가 환경마다 애매할 수 있어서, 여기서는 경고만 주고 실제 createLink에서 최종 판단하게 둘 수도 있음.
log.warn("FileStore check skipped/failed (will rely on createLink): {}", e.toString());
}
for (String id : datasetUids) { for (String id : datasetUids) {
Path srcRoot = BASE.resolve(id); Path srcRoot = BASE.resolve(id);
log.info("---- dataset id={} srcRoot={} exists? {}", id, srcRoot, Files.isDirectory(srcRoot)); log.info("---- dataset id={} srcRoot={} exists? {}", id, srcRoot, Files.isDirectory(srcRoot));
@@ -70,18 +92,21 @@ public class TmpDatasetService {
String dstName = id + "__" + f.getFileName(); String dstName = id + "__" + f.getFileName();
Path dst = tmp.resolve(type).resolve(part).resolve(dstName); Path dst = tmp.resolve(type).resolve(part).resolve(dstName);
// dst가 남아있으면 삭제(심볼릭링크든 파일이든)
if (Files.exists(dst) || Files.isSymbolicLink(dst)) {
Files.delete(dst);
log.debug("deleted existing: {}", dst);
}
try { try {
if (Files.exists(dst) || Files.isSymbolicLink(dst)) { // 하드링크 생성 (dst가 새 파일로 생기지만 inode는 f와 동일)
Files.delete(dst); Files.createLink(dst, f);
log.debug("deleted existing: {}", dst); hardlinksMade++;
} log.debug("created hardlink: {} => {}", dst, f);
} catch (IOException e) {
Files.createSymbolicLink(dst, f.toAbsolutePath()); // 여기서 바로 실패시키면 “tmp는 만들었는데 내용은 0개” 같은 상태를 방지할 수 있음
symlinksMade++; log.error("FAILED create hardlink: {} => {}", dst, f, e);
log.debug("created symlink: {} -> {}", dst, f.toAbsolutePath()); throw e;
} catch (Exception e) {
log.error("FAILED create symlink: {} -> {}", dst, f.toAbsolutePath(), e);
} }
} }
} }
@@ -89,13 +114,23 @@ public class TmpDatasetService {
} }
} }
if (hardlinksMade == 0) {
throw new IOException(
"No hardlinks created. regularFiles="
+ regularFiles
+ ", scannedDirs="
+ scannedDirs
+ ", noDir="
+ noDir);
}
log.info("tmp dataset created: {}", tmp); log.info("tmp dataset created: {}", tmp);
log.info( log.info(
"summary: scannedDirs={}, noDir={}, regularFiles={}, symlinksMade={}", "summary: scannedDirs={}, noDir={}, regularFiles={}, hardlinksMade={}",
scannedDirs, scannedDirs,
noDir, noDir,
regularFiles, regularFiles,
symlinksMade); hardlinksMade);
return uid; return uid;
} }