학습결과 파일 베스트 에폭 제외 삭제
This commit is contained in:
@@ -286,8 +286,7 @@ public class DatasetApiController {
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@PostMapping("/deliveries")
|
||||
public ApiResponseDto<ResponseObj> insertDeliveriesDataset(@RequestBody AddDeliveriesReq req)
|
||||
throws IOException {
|
||||
public ApiResponseDto<ResponseObj> insertDeliveriesDataset(@RequestBody AddDeliveriesReq req) {
|
||||
return ApiResponseDto.createOK(datasetService.insertDeliveriesDataset(req));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,10 +541,19 @@ public class DatasetDto {
|
||||
@Schema(description = "경로", example = "/")
|
||||
private String filePath;
|
||||
|
||||
@Schema(description = "제목", example = "")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "메모", example = "")
|
||||
private String memo;
|
||||
|
||||
@Schema(description = "비교년도", example = "")
|
||||
private Integer compareYyyy;
|
||||
|
||||
@Schema(description = "기준년도", example = "")
|
||||
private Integer targetYyyy;
|
||||
|
||||
@Schema(description = "회차", example = "")
|
||||
private Long roundNo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelTrainMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelValidationMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.TransferDetailDto;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.CleanupResult;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.ModelProgressStepDto;
|
||||
import com.kamco.cd.training.model.service.ModelTrainDetailService;
|
||||
import com.kamco.cd.training.model.service.ModelTrainMngService;
|
||||
@@ -34,6 +35,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.apache.coyote.BadRequestException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -326,4 +328,25 @@ public class ModelTrainDetailApiController {
|
||||
UUID uuid) {
|
||||
return ApiResponseDto.ok(modelTrainDetailService.findModelTrainProgressInfo(uuid));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "모델관리 > 모델 상세 > best epoch 제외 삭제",
|
||||
description = "best epoch 제외 pth 파일 삭제 API")
|
||||
@ApiResponses(
|
||||
value = {
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "삭제 성공",
|
||||
content =
|
||||
@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = CleanupResult.class))),
|
||||
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
|
||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||
})
|
||||
@DeleteMapping("/{uuid}/cleanup")
|
||||
public ApiResponseDto<CleanupResult> cleanup(
|
||||
@Parameter(description = "모델 uuid") @PathVariable UUID uuid) {
|
||||
return ApiResponseDto.ok(modelTrainDetailService.cleanup(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public class ModelTrainMngDto {
|
||||
private ZonedDateTime packingEndDttm;
|
||||
|
||||
private Long beforeModelId;
|
||||
private Integer bestEpoch;
|
||||
|
||||
public String getStatusName() {
|
||||
if (this.statusCd == null || this.statusCd.isBlank()) return null;
|
||||
@@ -327,4 +328,23 @@ public class ModelTrainMngDto {
|
||||
@JsonFormatDttm private ZonedDateTime endTime;
|
||||
private boolean isError;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class CleanupResult {
|
||||
// cleanup 대상 전체 파일 수 (삭제 대상 + 유지 파일 포함)
|
||||
private int totalCount;
|
||||
|
||||
// 실제로 삭제된 파일 개수
|
||||
private int deletedCount;
|
||||
|
||||
// 삭제 실패한 파일 개수
|
||||
private int failedCount;
|
||||
|
||||
// 삭제 실패한 파일명 목록
|
||||
private List<String> failedFiles;
|
||||
|
||||
// 유지된 파일명 (best epoch 기준)
|
||||
private String keptFile;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kamco.cd.training.model.service;
|
||||
|
||||
import com.kamco.cd.training.common.enums.ModelType;
|
||||
import com.kamco.cd.training.common.exception.CustomApiException;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.DatasetReq;
|
||||
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectTransferDataSet;
|
||||
import com.kamco.cd.training.model.dto.ModelConfigDto;
|
||||
@@ -14,15 +15,24 @@ import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelTrainMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.ModelValidationMetrics;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.TransferDetailDto;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainDetailDto.TransferHyperSummary;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.CleanupResult;
|
||||
import com.kamco.cd.training.model.dto.ModelTrainMngDto.ModelProgressStepDto;
|
||||
import com.kamco.cd.training.postgres.core.ModelTrainDetailCoreService;
|
||||
import com.kamco.cd.training.postgres.core.ModelTrainMngCoreService;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -35,6 +45,9 @@ public class ModelTrainDetailService {
|
||||
private final ModelTrainDetailCoreService modelTrainDetailCoreService;
|
||||
private final ModelTrainMngCoreService mngCoreService;
|
||||
|
||||
@Value("${train.docker.responseDir}")
|
||||
private String responseDir;
|
||||
|
||||
/**
|
||||
* 모델 상세정보 요약
|
||||
*
|
||||
@@ -152,4 +165,136 @@ public class ModelTrainDetailService {
|
||||
public List<ModelProgressStepDto> findModelTrainProgressInfo(UUID uuid) {
|
||||
return modelTrainDetailCoreService.findModelTrainProgressInfo(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 베스트 에폭 제외 *.pth 파일 삭제
|
||||
*
|
||||
* @param uuid 학습 모델 uuid
|
||||
*/
|
||||
public CleanupResult cleanup(UUID uuid) {
|
||||
|
||||
CleanupResult result = new CleanupResult();
|
||||
|
||||
// 학습 정보 조회
|
||||
ModelTrainMngDto.Basic model = modelTrainDetailCoreService.findByModelByUUID(uuid);
|
||||
|
||||
if (model == null) {
|
||||
throw new CustomApiException(
|
||||
"NOT_FOUND_DATA", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다. UUID: " + uuid);
|
||||
}
|
||||
|
||||
// 학습 결과 폴더 경로
|
||||
Path dir = Paths.get(responseDir, model.getUuid().toString());
|
||||
|
||||
if (!Files.exists(dir) || !Files.isDirectory(dir)) {
|
||||
log.info("디렉토리가 없습니다.: {}", dir);
|
||||
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND, "디렉토리가 없습니다.");
|
||||
}
|
||||
|
||||
if (!Files.isReadable(dir)) {
|
||||
throw new CustomApiException("FORBIDDEN", HttpStatus.FORBIDDEN, "디렉토리 읽기 권한이 없습니다.");
|
||||
}
|
||||
|
||||
if (!Files.isWritable(dir)) {
|
||||
throw new CustomApiException("FORBIDDEN", HttpStatus.FORBIDDEN, "디렉토리 삭제 권한이 없습니다.");
|
||||
}
|
||||
|
||||
// 저장된 best epoch
|
||||
int bestEpoch = model.getBestEpoch();
|
||||
|
||||
if (bestEpoch <= 0) {
|
||||
throw new CustomApiException(
|
||||
"BAD_REQUEST", HttpStatus.BAD_REQUEST, "잘못된 bestEpoch 값 입니다. : " + bestEpoch);
|
||||
}
|
||||
|
||||
log.info("cleanup 시작. dir={}, bestEpoch={}", dir, bestEpoch);
|
||||
|
||||
try (Stream<Path> stream = Files.list(dir)) {
|
||||
|
||||
List<Path> pthFiles =
|
||||
stream.filter(p -> p.getFileName().toString().endsWith(".pth")).toList();
|
||||
|
||||
if (pthFiles.isEmpty()) {
|
||||
log.info("pth 파일이 없습니다.");
|
||||
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND, "pth 파일이 없습니다.");
|
||||
}
|
||||
|
||||
// ===== keep 파일 찾기 =====
|
||||
Path keep = null;
|
||||
|
||||
// 우선순위 1: best_*_epoch_{bestEpoch}.pth
|
||||
for (Path p : pthFiles) {
|
||||
String name = p.getFileName().toString();
|
||||
if (name.startsWith("best_") && name.contains("epoch_" + bestEpoch + ".pth")) {
|
||||
keep = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 우선순위 2: epoch_{bestEpoch}.pth
|
||||
if (keep == null) {
|
||||
for (Path p : pthFiles) {
|
||||
if (p.getFileName().toString().equals("epoch_" + bestEpoch + ".pth")) {
|
||||
keep = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keep == null) {
|
||||
log.info("bestEpoch에 해당하는 파일이 없습니다. epoch={}", bestEpoch);
|
||||
throw new CustomApiException(
|
||||
"NOT_FOUND_DATA", HttpStatus.NOT_FOUND, "bestEpoch에 해당하는 파일이 없습니다. : " + bestEpoch);
|
||||
}
|
||||
|
||||
log.info("유지할 파일: {}", keep.getFileName());
|
||||
|
||||
// ===== 결과 세팅 =====
|
||||
result.setTotalCount(pthFiles.size());
|
||||
result.setKeptFile(keep.getFileName().toString());
|
||||
|
||||
int deletedCount = 0;
|
||||
List<String> failed = new ArrayList<>();
|
||||
|
||||
// ===== 삭제 처리 =====
|
||||
for (Path p : pthFiles) {
|
||||
|
||||
if (!p.toAbsolutePath().normalize().equals(keep.toAbsolutePath().normalize())) {
|
||||
|
||||
try {
|
||||
boolean deleted = Files.deleteIfExists(p);
|
||||
|
||||
if (deleted) {
|
||||
deletedCount++;
|
||||
log.info("삭제됨: {}", p.getFileName());
|
||||
} else {
|
||||
log.info("이미 없음 (skip): {}", p.getFileName());
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
failed.add(p.getFileName().toString());
|
||||
log.error("삭제 실패: {}", p.getFileName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 결과 저장 =====
|
||||
result.setDeletedCount(deletedCount);
|
||||
result.setFailedCount(failed.size());
|
||||
result.setFailedFiles(failed);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("파일 목록 조회 실패: {}", dir, e);
|
||||
throw new CustomApiException(
|
||||
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "파일 목록 조회 실패");
|
||||
}
|
||||
|
||||
log.info(
|
||||
"cleanup 완료. total={}, deleted={}, failed={}",
|
||||
result.getTotalCount(),
|
||||
result.getDeletedCount(),
|
||||
result.getFailedCount());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ public class ModelMasterEntity {
|
||||
this.packingState,
|
||||
this.packingStrtDttm,
|
||||
this.packingEndDttm,
|
||||
this.beforeModelId);
|
||||
this.beforeModelId,
|
||||
this.bestEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user