[Spring Boot] AmazonS3Exception 미발생 오류 해결하기

고리·2023년 11월 10일
0

Server

목록 보기
10/12
post-thumbnail

spring boot을 사용해 S3 bucket에 저장된 파일(폴더)를 다운로드 받는 기능을 테스트 하다가 테스트가 예상대로 흘러가지 않아서 꽤 많은 시간을 허비했다. 이번 포스팅은 AWS SDK for Java의 TransferManager ClassdownloadDirectory Method의 동작 방식을 이해해 문제를 해결해 나가는 포스팅이다.


❗ 문제 발생

@Value("${cloud.aws.s3.bucket}")
public String bucket;

public void downloadFolder(String dirName) {
    try {
        File localDirectory = new File(dirName);
        dirName = URLDecoder.decode(dirName, StandardCharsets.UTF_8);

        log.info("Download folder start");
        MultipleFileDownload downloadDirectory = transferManager.downloadDirectory(bucket, dirName, localDirectory);
        downloadDirectory.waitForCompletion();
        log.info("Download folder finish");
    } catch (InterruptedException |  AmazonS3Exception e) {
        log.error(e.getMessage());
        throw new AmazonS3Exception(e.getMessage());
    }
}

위의 코드는 S3-bucket에서 dirName과 일치하는 이름의 directory를 다운로드 하는 코드이다. 이걸 테스트 하기 위해서 테스트 코드를 아래와 같이 작성 했다.

@Autowired
S3Utils s3Utils; // downloadFolder method가 속한 class

@Test
void downloadFolder() {
    Assertions.assertDoesNotThrow(() -> s3Utils.downloadFolder(testDir));
    Assertions.assertThrows(AmazonS3Exception.class,
            () -> s3Utils.downloadFolder("null"));
}

테스트 코드를 실행한 결과 이상한 것을 발견할 수 있었다.

위의 사진에서 테스트 코드로 작성한 두번의 다운로드가 정상 종료된 것이다. 분명 "null" 은 s3 bucket에 존재하지 않기 때문에 AmazonS3Exception을 예상 했지만 Exception은 발생하지 않았고 테스트에 실패하게 되었다.


❓ 문제 원인

문제의 원인은 AWS S3가 객체 스토리지 임을 잠시 잊었기 때문이다. 객체 스토리지에서 디렉토리란 존재하지 않는다. 모든 것은 Key-Value 쌍으로 저장되기 때문에 각 객체가 unique한 Key를 갖는다. 이 Key가 파일 경로와 비슷하게 보일 수 있기 때문에 S3가 내부적으로 디렉토리 구조를 갖는다고 착각할 수 있는 것이다.

AWS S3는 객체 스토리지

myFolder/myFile.jpeg 라는 키를 가진 객체가 있다면, 이는 myFolder 란 디렉토리 내에 myFile.jpeg 파일이 있는 것처럼 보인다. AWS S3에서 myFolder/myFile.jpeg란 그 자체로 고유한 키이므로 디렉토리는 존재하지 않는다.

downloadDirectory 메서드의 동작 방식

AWSJavaSDK 문서에는 이렇게 적혀있다.

Downloads all objects in the virtual directory designated by the keyPrefix given to the destination directory given. All virtual subdirectories will be downloaded recursively.

TransferManager Class의 downloadDirectory 메서드는 파라미터 값으로 받은 S3 버킷에서 특정 접두사(prefix)를 가진 모든 객체를 찾아 주어진 destination(localDirectory)에 저장한다.

downloadDirectory 메서드의 구현체를 확인해보자 이 링크의 1383줄에 가면 구현체를 확인할 수 있는데 prefix에 대한 예외 처리가 명시되지 않는 것을 확인할 수 있다.

prefix를 가진 객체가 존재하는지 확인하는 기능 없이 prefix를 포함하는 모든 객체를 찾기 떄문에 prefix를 가진 객체가 없다면 아무것도 다운로드 되지 않지만, 이것은 예외 상황이 아니기 때문에 AmazonS3Exception이 발생하지 않는 것이다!


💡 해결 방법

다운로드 하려는 객체가 해당 S3 버킷에 존재하는지 여부를 확인하는 코드를 추가해 이 에러(사실은 조금 특별한 예외 케이스)를 해결할 수 있다.

그런데 이미 다운로드 과정에서 s3 bucket내 모든 객체를 돌면서 존재 여부를 확인 하는데 굳이 모든 객체를 검사하는 메서드가 필요할까?

그렇지는 않겠다는 생각이 들었다. 그래서 생각한 방법은 다운로드가 완료된 후 동일한 dirName의 폴더가 로컬에 생성되었는지 확인하는 것이다.

if (!Files.isDirectory(Paths.get(dirName))) {
	throw new AmazonS3Exception("'dirName' Object does not exist");
}

위의 코드를 download 후에 추가하자


🩹 결과

업로드중..

최종 코드

@Value("${cloud.aws.s3.bucket}")
public String bucket;

public void downloadFolder(String dirName) {
    try {
        dirName = URLDecoder.decode(dirName, StandardCharsets.UTF_8);
        File localDirectory = new File(dirName);
        log.info("Download folder start");
        MultipleFileDownload downloadDirectory = transferManager.downloadDirectory(bucket, dirName, localDirectory);
        downloadDirectory.waitForCompletion();
        log.info("Download folder finish");
        if (!Files.isDirectory(Paths.get(dirName))) {
            throw new AmazonS3Exception("'dirName' Object does not exist");
        }
    } catch (InterruptedException |  AmazonS3Exception e) {
        log.error(e.getMessage());
        throw new AmazonS3Exception(e.getMessage());
    }
}
profile
Back-End Developer

0개의 댓글