S3 Http Connection Time Error 해결하기 (Try-With-Resources)
에러 로그
2023-09-15 10:38:51.890 WARN 1 --- [nio-8092-exec-7] c.a.s.s.internal.S3AbortableInputStream : Not all bytes were read from the S3ObjectInputStream, aborting HTTP connection. This is likely an error and may result in sub-optimal behavior. Request only the bytes you need via a ranged GET or drain the input stream after use.
2023-09-15 10:38:51.891 WARN 1 --- [nio-8092-exec-7] c.a.s.s.internal.S3AbortableInputStream : Not all bytes were read from the S3ObjectInputStream, aborting HTTP connection. This is likely an error and may result in sub-optimal behavior. Request only the bytes you need via a ranged GET or drain the input stream after use.
2023-09-15 10:38:51.891 WARN 1 --- [nio-8092-exec-7] c.a.s.s.internal.S3AbortableInputStream : Not all bytes were read from the S3ObjectInputStream, aborting HTTP connection. This is likely an error and may result in sub-optimal behavior. Request only the bytes you need via a ranged GET or drain the input stream after use.
2023-09-15 10:38:51.891 ERROR 1 --- [nio-8092-exec-7] c.m.a.common.service.S3FileService : error : com.amazonaws.SdkClientException: Unable to execute HTTP request: Timeout waiting for connection from pool
2023-09-15 10:37:57.378 INFO 1 --- [nio-8092-exec-7] c.m.aidelete.common.log.LogInterceptor : API url : /aidelete/download/297524e29520230915103732
S3에 파일을 대용량으로 업로드/다운로드 테스트를 하던 중에 발생한 에러다. (대략 900개)
테스트는 세명이서 동시에 진행했다.
기존 코드는 파일 구조를 DB에 저장하는 구조여서 폴더 내부에 있는 파일을 다운로드 받으려면
DB에서 해당 폴더의 파일들을 모두 찾은 뒤, S3에 접속하도록 구성이 되어있다.
확인해보니, 900장을 다운로드할 경우, API 1개가 작동하는 동안 S3에 900번 연결을 시도하는데
이 횟수가 너무 방대해서 연결이 끊긴듯 싶었다.
코드를 살펴보니 아래 부분에서 상당한 문제가 있었다.
AmazonS3 amazonS3 = s3Config.amazonS3();
S3Object s3Object = amazonS3.getObject(bucket, currentPath);
- s3에서는 getObject를 통해 S3Object라는 파일 객체를 조회한다.
- S3Object는 HTTP Connection으로부터 데이터를 스트리밍할 수 있는 S3ObjectInputStream을 가진다.
- getObject()를 실행하면 HTTP Connection이 계속해서 열려있기 때문에 스트림 조회가 끝나면, HTTP Connection 종료를 위해 닫아주어야 된다.
즉 스트림을 닫아주지 않았다.
그래서 나는 close()를 이용해서 스트림을 닫아주었다.
하지만 그래도 에러는 지속되었다. 왜지?! 닫았다고 생각했지만 사실 안닫혀있었을수도?
이런 문제들은 try-with-resources를 통해서 해결할 수 있었다.
try-with-resources 구문이란? 🤔
자바 7에서 소개되었고 사용 후에 닫혀야하는 자원을 처리할 때 사용하는 구문이다.
특징으로는 리소스 관리가 간소화되며 자원이 올바르게 닫히도록 보장해준다.
이를 통해 리소스 누수 가능성을 줄일 수 있다.
getObject()로 인해 닫혀야하는 스트림 자원이 있는 지금 같은 경우 사용 가능한 구문이다.
기본 코드는 다음과 같다.
try (리소스유형 리소스1 = 초기화; 리소스유형 리소스2 = 초기화; ...) {
// 리소스를 사용하는 코드
} catch (예외유형 e) {
// 예외 처리 코드
}
- try 다음에 괄호 내에서 사용할 리소스를 선언하면 된다.
- 이후 예외가 발생하면 각 리소스는 자동으로 닫힌다. (누수의 위험이 적음)
S3Object뿐만 아니라, ZipFile이나 FileReader에서 유용하게 사용할 수 있는 듯 하다.
FileReader를 사용한 예제다.
static String readFirstLineFromFile(String path) throws IOException {
try (FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr)) {
return br.readLine();
}
}
기존에는 리소스를 닫기위해서는 finally 구문을 사용해야했는데, try-with-resources 구문을 통해 이런 불편함을 해소할 수 있게 되었다.
그리고 finally 구문 사용시 자원 누수의 가능성이 있기 때문에, 자원을 해제하는 것은 가비지 컬렉터에(GC)에 의존하지 않고 프로그램이 직접 처리해야된다.
try-with-resources 특징 정리 😇
- 자원을 자동으로 닫는데 사용되는 편리한 구문이다!
- try 키워드 뒤에 괄호 내부에 리소스를 여러개를 동시에 선언할 수 있다.
- 괄호 내부에 리소스 선언시 자동으로 닫히는 것을 보장해준다.
- 리소스는 'AutoCloseable' 인터페이스를 구현한 클래스여야 사용 가능하다! (ex: ZipFile, FileReader, S3Object)
- 예외 발생시 try-catch와 마찬가지로 catch 블록에서 처리된다. 다른점은 catch 블록이 실행된 이후에도 자원의 닫힘은 보장된다.
참고