S3
정의
AWS S3(이하 S3)란 Simple Storage Service의 약자로, 클라우드 환경에서 일반적으로 사용되는 계층 없는 데이터 저장 방법이다. 다른 데이터 스토리지 방법과 달리 디렉터리 트리를 사용하지 않고 key-value 형태로 저장되는 것이 특징이다.
**온라인** 스토리지 서비스라고도 하는데, 그 이유는 데이터 조작에 HTTP/HTTPS를 통한 API가 사용되기 때문이다. 99.9999999999% (11 9’s) of durability)의 내구성을 가지고 있고, 용도에 따라 세부적인 접속 관리가 가능하기 때문에 높은 가용성, 신뢰성, 안정성을 자랑한다. 더 자세한 내용은 게시글의 Reference를 참고하길 바란다.
구조
예를 들어 /web/hello.html이라는 파일 안에 아래와 같은 내용이 작성되어 있다고 가정해보자.
<!-- /web/naver.html -->
<html>
<body>
<h1>Hello, world!</h1>
</body>
<html> 이 경우에 /web/naver.html이라는 파일명이 key이고, 위의 내용이 value로 관리되는 것이다. 이렇게 계층 구조가 아니기 때문에 개별 단위(오브젝트)가 스토리지 풀의 동일한 레벨에 있다는 것이 특징이다.
용도
가장 대중적으로는 이미지와 같은 정적 파일을 저장 및 조회할 때 사용되는 경우이다. S3에는 정적 파일 자체를 저장하고, 나머지 메타 데이터(파일 접근을 위한 주소, 파일 크기, 저장 일자 등)는 연관성있는 정보와 함께 관계형 데이터베이스에 저장하는 형식으로 많이 사용된다.
그 외에도, 정적 웹 사이트 호스팅 목적, 로그 저장소, AWS EBS 스냅샷 등 다양한 영역에 폭넓게 활용할 수 있다.
데이터베이스(RDBMS)에 이미지를 저장하지 않는 이유
만약 RDBMS로 MySQL을 사용한다고 했을 때 아마 이미지를 저장하기 위한 Column 타입은 BLOB(Binary Large Object)이 될 것이다. BLOB 타입은 기본적으로 binary string으로 취급되는데 이는 Column에 url만 저장하는 것보다 차지하는 용량이 커지게 될 것이고, 이는 백업 용량도 커지게 될 것이다. AWS RDS와 S3의 용량을 비교했을 때 S3가 GB 당 비용이 저렴하므로 비용적인 측면에서 유리하다.
또한, RDB에 저장할 경우 상대적으로 이미지 로딩 속도가 느리기 때문에 병목 현상이 발생할 수 있다는 점 등의 단점이 존재하기 때문에 일반적으로 S3에서 관리하는 케이스가 많다.
S3 - Presigned URL
정의
Presigned URL이란 AWS 인스턴스에 대한 접근 권한을 제공하기 위해서 사용되는, 이름 그대로 사전에 적절한 권한을 가진 자격증명에 의하여 Signed 된 URL을 의미한다.
S3 뿐만 아니라 AWS에서 제공하는 다양한 서비스에서 Presigned URL을 활용할 수 있지만, 우리는 S3의 케이스에 대해서만 다뤄보도록 하겠다.
이점
S3의 Presigned URL은 S3 버킷의 오브젝트에 대한 일시적인 액세스를 제공하는 URL이다. Presigned URL을 사용하게 됨으로써 얻게되는 이점들이 있는데 이 중 크게 보안과 서버 부하 감소가 있다. 이에 대해 살펴보자.
보안
기본적으로 S3 버킷과 그 안의 오브젝트 들은 Private으로 설정하는 것이 원칙이다. Presigned URL은 특정 시간 동안만 오브젝트에 접근할 수 있도록 허용하므로, 영구적인 권한을 부여하지 않고도 일시적으로 객체에 접근하게 할 수 있다. 이를 통해 보안상의 이점을 제공할 수 있고, 민감한 데이터에 대한 일시적인 접근을 허용하는 경우에 유용하다.
서버 부하 감소
클라이언트에서 오브젝트를 직접 업로드하거나 다운로드 할 수 있도록 Presigned URL을 제공하게 되면, 중간에 서버를 거치지 않고도 S3에 접근할 수 있기 때문에, 대역폭과 시간을 절약할 수 있다.
또한 서버 입장에서는 단순히 Presigned URL을 생성하고 제공하는 역할만 하게 되므로, 대규모의 데이터 전송 작업에 있어서 서버의 부하를 줄일 수 있다. 반대로 API 서버가 파일 업로드를 직접 대응하는 것은 확장성 측면에서 좋지 못하다고 할 수 있다.
추가적으로 큰 용량의 파일 업로드 요청이 있었을 때 스트림을 활용하여 중간에 차단할 수 있지만, 이 역시도 서버의 부하가 아예 없다고 할 수는 없으므로 Presigned URL을 사용하는 것이 더 좋은 방법이다.
이미지 업로드 전략
위에서 서술한 장점 덕분에 SW마에스트로에서 진행하고 잇는 우리 프로젝트에서도 Presinged URL을 통해 이미지를 업로드하기로 결정하였다.
기존 이미지 업로드 전략

이전 업로드 전략은 위 그림과 같다. Client가 Server(EC2)에 이미지를 전송하고, 이를 거쳐 S3에 저장되는 방식이다.
Presigned URL을 사용한 이미지 업로드 전략

우리는 위 방식으로 수정하기로 하였다.
Spring Boot - S3 Presigned-url 발급 받기
S3 버킷 정책 수정
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "2",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E3IJ8VKLEQEQV7"
},
"Action": [
"s3:Get*",
"s3:Put*"
],
"Resource": [
"arn:aws:s3:::dev.storage.gifthub.kr",
"arn:aws:s3:::dev.storage.gifthub.kr/privacy",
"arn:aws:s3:::dev.storage.gifthub.kr/brand/*",
"arn:aws:s3:::dev.storage.gifthub.kr/product/*",
"arn:aws:s3:::dev.storage.gifthub.kr/voucher/*.jpg",
"arn:aws:s3:::dev.storage.gifthub.kr/voucher/*.jpeg",
"arn:aws:s3:::dev.storage.gifthub.kr/voucher/*.gif",
"arn:aws:s3:::dev.storage.gifthub.kr/voucher/*.png"
]
}
]
} 우선 우리 팀은 이미 S3 버킷을 만들어 놓은 상태이므로 몇 가지 설정만 수정하였다.
우선 Action 설정을 통해 GET과 PUT 요청만 할 수 있도록 수정하였다. 이 게시글의 경우 이미지 등록을 위한 Presigned-url 발급이 목적이기 때문에 PUT 요청만 허용하여도 문제가 없다.
그리고 Resource 옵션을 통해 특정 디렉토리(사실은 key지만)에 한해서 확장자를 제한해두었다. 우리 서비스의 경우 Presigned-url로 PUT 요청을 할 때는 이미지만 받고 싶었기 때문에 .jpg, jpeg, gif, png만으로 제한해두었다.
Spring Boot 소스 코드
build.gradle
...
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'
testImplementation group: 'io.findify', name: 's3mock_2.12', version: '0.2.6'
...XXXService.java
public String getPresignedUrlForSaveVoucher(String dirName, String extension) {
String key = dirName + "/" + UUID.randomUUID().toString() + "." + extension;
GeneratePresignedUrlRequest generatePresignedUrlRequest =
new GeneratePresignedUrlRequest(bucketName, key, HttpMethod.PUT)
.withExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 5));
return amazonS3Client.generatePresignedUrl(generatePresignedUrlRequest).toString();
} 위와 같이 AWS S3를 위한 의존성을 추가하였고, GeneratePresignedUrlRequest 클래스를 통해 Presgined-url을 발급받을 수 있다.