S3
S3는 AWS에서 가장 처음 런칭한 프로젝트라고 한다. 이미지 저장소로 잘 알고 있는 이 S3, Simple Storage Service에 이미지 저장소를 만들어보자.
S3 Object의 구성요소
- Key - 파일의 고유 식별자로, bucket/test/example.png와 같은 고유한 파일 경로를 의미한다.
- Value - 파일의 내용(예: 이미지 바이너리 데이터, 텍스트 데이터)
- Version ID - 파일의 버전 아이디로, 같은 파일명이나 다른 버전으로 올리고 싶을 때 사용한다. 이를 통해 롤백 가능!
- Metadata - 데이터의 데이터라고도 불리는, 파일의 정보를 담은 데이터를 의미한다.(예. 최종 수정일, 파일 타입)
- CORS(Cross Origin Resource Sharing) - 한 버킷의 파일을 (지역과 상관없이) 다른 버킷에서도 접근 가능하도록 해주는 기능
S3 Data Consistency Model
1. Read after Write Consistency(PUT) - 우리가 S3 버킷에 파일을 올릴 때 PUT으로 업로드한다. 업로드하면 바로 확인(READ)할 수 있다.
2. Eventual Consistency(UPDATE, DELETE) - PUT과는 달리 변경/삭제 할 시 그 결과가 바로 나타나지 않는다. 일정 시간 내에 모든 복제 노드 간에 데이터가 동일하게 되기까지 S3 내부에서 작업이 필요하다.
이는 분산시스템 구성 CAP 이론에 따라 일관성과 가용성 중 하나를 포기해야 하는 상황에 가용성(유저의 요청을 blocking하지 않는)을 챙기는 것을 의미한다.
S3 요금 책정 방식
- 매 GB당 돈을 지불한다.
- PUT, GET, COPY 요청 횟수당
- 데이터 다운로드 시 / 다른 리소스로 전송 시
- 어떤 Metadata인지 / 어떤 용도(object tag)로 사용되는지
AWS SDK for JAVA
AWS SDK for JAVA에 따르면 SDK를 사용하여 S3, EC2, DynamoDB 등을 기반으로 애플리케이션을 빌드할 수 있다. 이 때 아래 두 개의 방식 중에 선택하여 구현할 수 있다.
AWS SDK for Java 1.x → com.amazonaws
AWS SDK for Java v2 → software.amazon.awssdk
awsdocs/aws-doc-sdk-example 깃허브 레포 에서 위 두 방식을 통해 S3를 사용할 수 있는 방법이 친절하게 설명되어있다.
AWS Docs에는 Java v2로 구현하길 권장했으나 Java 1.x 로 구현해도 문제가 없고, Java 1.x가 코드가 좀 더 수월하다고 느껴 com.amazonaws 의존성을 사용하여 구현하게 되었다.
//AWS SDK for Java 1.x
implementation platform('com.amazonaws:aws-java-sdk-bom:1.12.529')
implementation 'com.amazonaws:aws-java-sdk-s3'
presigned URL 로 이미지 업로드
presigned URL이란 일정 기간 동안 객체 다운로드를 허가하는 미리 서명된 URL로, ”S3버킷으로의 접근”을 일정시간 동안 허용해준다.
presigned URL의 가장 큰 장점은 Server를 거치지 않고 S3 버킷으로 곧바로 이미지를 push 할 수 있다는 점이다. 아래와 같이 PUT 명령어를 통해 presignedURL에 바로 이미지를 업로드하면
바로 S3에 잘 업로드되는 것을 볼 수 있다.
작동방식
코드는 s3 sdk example github 레포를 활용하여 구현하였다.
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.aws_access_key}")
private String accessKey;
@Value("${cloud.aws.credentials.aws_secret_key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3() {
//자격 증명
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.withRegion(region)
.build();
}
}
@Component
public class S3Uploader {
@Autowired
private S3Config s3Config;
@Value("${cloud.aws.bucket}")
private String bucket;
@Value("${cloud.aws.cloudfront.path}")
private String cloudfrontPath;
private final int PRESIGNED_URL_EXPIRATION = 60 * 1000; // 1분
public String getPresignedUrl(String fileName) {
return generatePresignedUrl(fileName).toString();
}
// Upload file to S3 using presigned url
private GeneratePresignedUrlRequest getGeneratePresignedUrlRequest(String fileName) {
return new GeneratePresignedUrlRequest(bucket, "test/" + fileName)
.withMethod(HttpMethod.PUT)
.withExpiration(setExpiration());
}
private URL generatePresignedUrl(String fileName) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePresignedUrlRequest(fileName);
URL url = s3Config.amazonS3().generatePresignedUrl(generatePresignedUrlRequest);
return url;
}
private Date setExpiration(){
Date expiration = new Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += PRESIGNED_URL_EXPIRATION;
expiration.setTime(expTimeMillis);
return expiration;
}
private String getUniqueFilename(String fileName) {
String uuid = UUID.randomUUID().toString();
return "test/" + fileName + uuid;
}
//get file from cloudfront not using presigned url
private String getCloudfrontFilePath(String fileName) {
return cloudfrontPath + fileName;
}
}
위에서 사용되는 환경변수(@Value)의 경우에는 EB의 환경변수로 아래와 같이 간편하게,안전하게 기입해주면 배포될 때 자동으로 적용된다😋
추가로, S3 Bucket의 경우에는 IAM 의 AmazonS3FullAccess 를 할당해주었고, 버킷 정책에서 s3:PutObject 를 통해 s3에 이미지를 put하는 명령어를 수행할 수 있도록 변경하였다.
CloudFront로 이미지 가져오기
CloudFront 는 CDN 서비스로, 간단히 말하면 캐싱을 통해 사용자에게 더 빠른 서비스를 제공하는 것을 목적으로 한다. 오리진 서버와 엣지 서버로 구성되어 있으며 오리진은 원본 서버이고, 엣지 서버는 컨텐츠들이 캐시에 보관되어지는 장소이다.
아래 그림과 같이 동작하게 된다.
CloudFront와 S3를 간단히 연결한 이후 CloudFront를 통해 S3 이미지를 가져올 수 있도록 버킷 정책을 바꾸어주면, 아래와 같이 내가 원하는 사진이 바로 나오는 것을 볼 수 있게 된다 !
이 때 GET 과정에서는 굳이 presigned URL을 사용하지 않았는데, 우선 GET 요청의 경우에는 이미지/파일을 가져오는 속도가 매우 중요하고, multi file GET 요청 시 presigned URL이 매우 불리하며, presigned URL 특성 상 만료 기한이 있기 때문에 몇 초 내에 여러 번 이미지를 불러올 경우 presigned URL 경로 자체를 프론트 내 caching 해 놓는 것 또한 불리하다고 판단하여 사용하지 않았다.
'BackEnd > JAVA\SPRING' 카테고리의 다른 글
[Java|Spring|AWS] OpenSearch Java API로 검색 기능 구현 (2) | 2024.07.21 |
---|---|
[Spring Security] Spring Security 이용하여 헤더로 간단히 User 구분하기 (0) | 2024.07.10 |
[Java/Spring] 템플릿 콜백 패턴으로 JDBC 템플릿 구현해보기 (0) | 2024.04.02 |
[SpringSecurity] Koala 프로젝트 간단한 인증처리 (0) | 2024.02.13 |
[Java|Spring] Koala 프로젝트 구현 과정 중 이슈 정리 (0) | 2024.02.01 |