1. 배경
상품의 자식 엔티티로 연관관계가 맺어져 있는 이미지가 부모 엔티티와의 연결이 끊겼을때 s3에 저장을 하면서 비용이 계속 발생하게 되는
고아객체 현상을 어떻게 관리하면 적절할지를 고민해본 과정을 정리하는 목적으로 작성되었습니다.
2. 발생 원인
- 상황 A (업로드 후 중단): 사용자가 이미지를 업로드했으나, 최종 '저장' 버튼을 누르지 않고 브라우저를 닫음.
(파일은 남고, DB 기록은 없음 → 고아 객체 발생) - 상황 B (서버 오류): 이미지는 스토리지에 올라갔는데, DB에 저장하는 순간 에러가 발생하여 트랜잭션이 롤백되며 파일만 남음.
- 상황 C (삭제 실패): 게시글을 지워서 DB 데이터는 삭제되었는데, 스토리지의 파일을 지우는 로직이 실패함.
이미지 파일이 있다는 사실이 두 시스템에서 동일하게 유지되지 않는 정합성 불일치 문제가 발생합니다.
3. 문제점
당장 서비스 기능에는 문제가 없어 보일 수 있지만, 방치하면 다음과 같은 리스크가 쌓입니다.
- 비용 증가: 사용하지 않는 '쓰레기 데이터'가 누적되어 스토리지 유지 비용이 불필요하게 상승합니다.
- 운영 효율 저하: 데이터 백업, 마이그레이션, 인덱싱 시 불필요한 리소스를 소모하게 됩니다.
- 데이터 오염: 실제 존재하지 않는 리소스를 참조하려는 시도가 발생할 수 있어 디버깅이 어려워집니다.
특히 s3를 이용한 이미지 등록 방식을 사용하면서 서비스되지 않는 이미지로 원인이 파악되지 않은채 계속해서 비용이 발생한다는 점은 큰 문제라 인식되어 이를 해결해보고자 합니다.
4. 해결 방법 탐색
이 정합성을 다시 맞추기 위해 보통 다음과 같은 방법을 사용합니다.
1) Batch 처리
주기적으로 DB의 파일 리스트와 S3의 파일 리스트를 비교합니다.
- 방법: DB에 존재하지 않는 S3 파일들을 식별하여 일괄 삭제합니다.
- 장점: 로직이 단순하며 시스템 복잡도를 크게 높이지 않습니다.
2) 임시 경로 활용
파일의 상태를 임시/확정으로 분리하여 관리합니다.
- 방법: 최초 업로드 시 temp/ 경로에 저장하고, DB 저장이 완료되는 시점에 images/ 정식 경로로 이동(Move)시킵니다.
- 장점: temp/ 폴더만 주기적으로 비우면 되므로 관리가 매우 직관적입니다.
3) 트랜잭션 보장
이벤트 기반 아키텍처에서 주로 사용하며, 작업 실패 시 후속 조치를 보장합니다.
- 방법: DB 저장 실패 시 '파일 삭제 이벤트'를 발행하거나, 로컬 트랜잭션 종료 후 스토리지 작업을 수행하는 보상 트랜잭션을 설계합니다.
- 장점: 실시간에 가까운 정합성을 유지할 수 있습니다.
5. 결과
임시 경로를 활용하는 경우 DB에 등록된 파일만 존재하게 되므로 검증과 관리가 용이하지만,
파일을 이동시키는 과정에서 S3 API 호출 비용이 발생한다는 점에서 비용을 줄이기 위한 문제해결이 목적이므로 제외하였습니다.
트랜잭션 보장 방식의 경우 대용량 서비스에 적합하며 실시간 정합성을 유지한다는 장점이 강력하지만, 이벤트 발행 설계가 필요하다는 점에서 오버엔지니어링이라는 판단으로 제외하였습니다
@Scheduled(cron = "0 0 0 * * *") // 매일 새벽 12시 실행
public void cleanupOrphanedImages() {
log.error("파일 삭제 시작: ");
LocalDateTime deleteTime = LocalDateTime.now().minusDays(1); // 데이터베이스에는 삭제 요청이 되었으나 처리되지 않는 이미지
List<ProductImage> deletedImages = productImageRepository.findByDeletedAtBefore(deleteTime);
for (ProductImage img : deletedImages) {
try {
s3deleteFile(img.getUrl());
productImageRepository.delete(img);
} catch (Exception e) {
// S3 삭제 실패 시 로그를 남기고, 다음 배치 때 다시 시도
log.error("파일 삭제 실패: {}",img.getId());
}
}
}
'Spring Boot' 카테고리의 다른 글
| [Spring Boot] Spring Validation 흐름을 이해하고 Custom Validator를 설계해보기 (0) | 2026.01.26 |
|---|---|
| [Spring Boot] Spring Security와 JWT를 활용한 인증 시스템 구현기 (0) | 2025.12.25 |