멀티미디어 파일을 포함한 글 작성 요청을 처리하는 서버를 개발할 때, 파일 저장소로 AWS S3를 사용하고 있다고 가정하자. 만약 글 작성 요청을 처리하는 중에 서버에 장애가 발생하여 요청이 중단될 경우, 트랜잭션이 실행 결과를 롤백할 때 외부 서비스인 S3의 작업은 함께 롤백되지 않는다. 그렇다면 어떻게 S3의 작업을 롤백해야 할까? 고민하고 검색해서 여러 가지 방법을 찾아봤다.
내가 작성한 메서드의 초기 형태는 아래와 같은 형식이다. S3에 이미지 파일을 올리는게 비즈니스 로직 사이에 있다.
@Transactional
public PostResponseDto createPost(
MultipartFile[] images
) {
// 불필요한 코드 지움
Post savedPost = postRepository.save(postRepository.save(post));
if (images.length > 0) {
s3Uploader.uploadPostImages(savedPost.getId().toString(), images);
}
return new PostResponseDto(savedPost);
}
문제 발생
10번 줄의 new PostResponseDto(savedPost);에서 예외가 발생했다. createPost 메서드는 트랜잭션 내부에서 작동하므로 save는 취소됐다. 하지만 S3에 업로드한 이미지는 취소되지 않는다. 어떻게 해결할까?
비즈니스 로직 순서를 바꿔보자
그렇다면 서버의 비즈니스 로직이 완료된 후에 S3에 업로드 하도록 순서를 바꿨다.
void newCreatePost(){
createPost();
uploadImages();
}
위와 같이 글 작성과 이미지 등록을 분리했다. 이제 이미지 등록 메서드가 실행될 때, 서버의 비즈니스 로직은 문제없이 동작하는 것이 분명하므로 해결된걸까? 아니다. 만약 S3에 업로드 하는 중에 네트워크 환경이 불안정해서 요청이 정상적으로 전달되지 않았다면 예외가 발생한다. 5개의 이미지 중에서 3개만 S3에 저장한 상태에서 오류가 발생했다고 가정하자. 어떻게 해결할까?
롤백 메서드를 만들어보자
업로드 중에 실패한다면 이미 업로드 한 것들을 모두 취소해야 한다. 그러기 위해서 롤백 메서드를 만들어보자.
try {
upload();
} catch(Exception e) {
rollback();
}
그런데 다시 생각해보자. 만약 롤백 메서드를 실행하는 중에 예외가 발생하면 어떻게 처리해야 할까? 롤백에 대한 롤~~백을 아무리 많이 만들어도 결국 해결할 수 없다. 다음 방법으로 넘어가자.
배치 프로그램을 만들어보자
S3와 서버의 데이터 무결성을 즉각적으로 보장하는 것은 힘들었다. 그래서 데이터 무결성을 즉각적으로 보장해야 하는지 생각해보니 그럴 필요가 없었다. 데이터를 저장하고 있는 비용이 데이터를 추가/삭제하는 비용보다 훨씬 저렴하기 때문이다.
그러므로 한 달에 한 번 S3의 모든 파일 목록을 가져와서 서버에 존재하지 않는 파일을 삭제하는 배치 프로그램을 만들었다. 우와~ 드디어 문제를 해결했다?
@Scheduled(cron = "0 0 2 1 * *") // 매달 1일마다 2시에 데이터 무결성 체크
public void checkDataIntegrity() {
try {
// S3에서 파일 목록 가져오기
List<S3ObjectSummary> s3Objects = amazonS3Client.listObjects(
new ListObjectsRequest().withBucketName(bucket)).getObjectSummaries();
// S3에만 존재하는 파일 찾고 삭제하기
for (S3ObjectSummary s3Object : s3Objects) {
if (postImageRepository.findPostImageByImageUrl(s3Object.getKey()).isEmpty()) {
amazonS3Client.deleteObject(bucket, s3Object.getKey());
}
}
} catch (Exception e) {
log.error("예외 발생: {}", e.getMessage());
}
}
그러나 이 배치 프로그램은 S3의 모든 파일 목록을 탐색한다는 분명한 단점이 있다. S3에 저장된 파일 목록이 많아질수록, DB에 저장된 Image 데이터가 많아질수록 배치 프로그램이 실행되는데 걸리는 시간과 필요한 메모리가 늘어난다. 지금은 괜찮지만 서비스가 성장한다면 반드시 문제가 된다. 다른 좋은 방법을 찾아보자.
DB의 트랜잭션을 흉내내어 보자
찾아보니 DB의 트랜잭션처럼 로그를 만들면 탐색 범위를 줄일 수 있다. S3에 파일을 추가하는 메서드가 성공하면 DB에 저장 로그를 남긴다. 적당한 주기의 기간동안 생성된 로그를 순회하며 저장 로그와 실제 DB 데이터가 일치하는지 검사하고, 일치하지 않는다면 S3에서 삭제하는 방식이다. 탐색 범위를 원하는 기간으로 제한하고, S3의 데이터를 받을 필요도 없다.
참고
- https://d2.naver.com/helloworld/407507 DB가 트랜잭션을 관리하는 방법에 대해서 알기 쉽게 설명하고 있다.
'Tool > Spring' 카테고리의 다른 글
[Spring] Spring boot와 redis로 회원가입 이메일 인증기능 만들기 (0) | 2023.12.18 |
---|---|
[Spring] 커스텀 어노테이션으로 DTO 유효성 검사하기 (0) | 2023.12.14 |
[Spring boot] controller layer 테스트 코드 작성하기 (0) | 2023.12.04 |
[Spring boot] Service layer 테스트 코드 작성하기 (0) | 2023.12.04 |
[Spring boot] Dto 유효성 검사 테스트 코드 작성하기 (0) | 2023.12.03 |