Skip to main content

Command Palette

Search for a command to run...

PreSigned-Url로 서버 오버헤드 줄이기

spring-cloud-starter-aws 를 통해 Presigned URL사용해보자

Updated
PreSigned-Url로 서버 오버헤드 줄이기
K

backend developer interested in technical problem

2024년3월11일 현재 최신버전인 Spring 3.1.9버전과 spring-cloud-aws 3.0.0버전의 공식문서를 읽고 구현했습니다. Spring cloud aws 2.x 버전과 다른 부분이 여럿 있었습니다.(ex)application.yml에 설정할 값 등)
이전 버전을 사용하시는 경우 버전에 맞는 글을 참고해주셔야 합니다!

[참고](Spring Cloud AWS 문서)

🟢Presigned URL?

인증이 끝난 사용자에게 제공하는 S3에 접근하는URL 입니다. 만료시간을 설정할 수 있습니다.

큰흐름은 클라이언트가 서버로 요청시 인증절차를 마치고 이후 URL을 만료시간을 설정해 발급합니다. 이 URL을 클라이언트에 넘겨주면 클라이언트는 서버를 거치지 않고 S3에서 직접 파일을 받아옵니다.

프로젝트에서 OTA서버에 제공할 업데이트 파일을 전송하는 기능을 제작해야 했습니다. 파일 전송시에 Presigned URL을 이용해 S3→서버→클라이언트가 아닌 S3→클라이언트로 직접 전송을 해 오버헤드를 줄이는 동시에 시간이 지나면 만료되는 URL을 통해 보안적인 문제도 해결할 수 있습니다.

1️⃣의존성 설치

implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:{프로젝트에-맞는-버전}'

최신 버전 정보는 Spring Cloud문서 항목을 찾아보면 알 수 있습니다. 2024년 3월 11일 기준 3.0.0이 최신 버전이네요.


2️⃣S3 Client Component 만들기

공식문서에는 이런식으로 S3 Client를 만들 수 있다고 하지만 application.yml의 설정값을 보고 자동주입되어서 따로 만들필요는 없습니다. S3Client의 기본적인 기능만 사용한다면 말입니다. 따로 커스텀 설정이 필요한 경우 만들어서 사용해주시면됩니다.

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;

import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

@Component
class S3ClientSample {
    private final S3Client s3Client;

    S3ClientSample(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    void readFile() throws IOException {
        ResponseInputStream<GetObjectResponse> response = s3Client.getObject(
            request -> request.bucket("bucket-name").key("file-name.txt"));

        String fileContent = StreamUtils.copyToString(response, StandardCharsets.UTF_8);

        System.out.println(fileContent);
    }
}

3️⃣S3 Template 사용

참고: S3 Object와 S3 Template

low level의 S3 리소스를 사용하려면 S3 Spring Cloud 문서의 S3 Objects as Spring Resources를 참고해주세요.

일반적인 작업에는 S3 Template를 사용하면 편합니다. 참고로

기능S3 ObjectS3Template
기본 클래스없음S3Client (내부적으로 사용)
용도S3 버킷의 개별 객체를 나타냄S3 작업을 간편하게 수행하기 위한 유틸리티 클래스
파일 업로드직접적으로 지원하지 않음upload 메서드 제공 (입력 스트림과 버킷/키 지정)
파일 다운로드직접적으로 지원하지 않음download 메서드 제공 (버킷/키 지정)
서명 URL 생성직접적으로 지원하지 않음createSignedGetUrl, createSignedPutUrl 메서드 제공
객체 저장/로드지원하지 않음store, read 메서드 제공 (Java 객체 직렬화/역직렬화)

코드 면에서도 S3 Template가 가독성높고 간결한 코드를 작성할 수 있습니다.

S3Client s3Client = new S3Client(awsCredentialsProvider);

// 파일 업로드 (메타데이터 없음)
s3Client.putObject("bucket-name", "file.txt", new FileInputStream("file.txt"));

// 파일 업로드 (메타데이터 포함)
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType("text/plain");
s3Client.putObject("bucket-name", "file.txt", new FileInputStream("file.txt"), objectMetadata);
@Autowired
private S3Template s3Template;

// 파일 업로드 (메타데이터 없음)
InputStream inputStream = ...;
s3Template.upload("bucket-name", "file.txt", inputStream);

// 파일 업로드 (메타데이터 포함)
s3Template.upload("bucket-name", "file.txt", inputStream, ObjectMetadata.builder().contentType("text/plain").build());

S3Template 예시

@Autowired
private S3Template s3Template;

InputStream is = ...
// uploading file without metadata
s3Template.upload(BUCKET, "file.txt", is);

// uploading file with metadata
s3Template.upload(BUCKET, "file.txt", is, ObjectMetadata.builder().contentType("text/plain").build());

//Signed URL사용
URL signedGetUrl = s3Template.createSignedGetUrl("bucket_name", "file.txt", Duration.ofMinutes(5));

//애플리케이션안에서 객체로 저장할 때 
Person p = new Person("John", "Doe");
s3Template.store(BUCKET, "person.json", p);

//애플리케이션안에서 객체로 읽어들일 때
Person loadedPerson = s3Template.read(BUCKET, "person.json", Person.class);

4️⃣Presigned URL로 파일 다운로드해보기

@RefreshScope
@Component
@Getter
public class ConfigurationValue {
    public static int DURATION_MINUTE = 5; //만료시간

    @Value("${properties.message}")
    private String message;

    @Value("${properties.sqm.bucketName}")
    private String bucketName; //펌웨어가 저장된 bucket이름

    @Value("${properties.sqm.latestVersion}")
    private String latestVersion; //최신버전. applictaion.yml에서 property값을 읽어온다. 

    @Value("${properties.sqm.key}")
    private String key; //bucket에서 접근할 파일이름.마찬가지로 applictaion.yml에서 property값을 읽어온다. 

}

OTA서버에 최신버전을 요청하고 기기는 자신의 버전과 비교해 버전이 다르면 최신릴리즈를 받을 수 있는 preSignedUrl을 통해 다운받습니다.

쿠버네티스환경에 배포하는데, 만약 새로운 업데이트가 생기면 애플리케이션을 재배포할 필요없이 configMap을 수정하면 애플리케이션이 이를 감지하고 프로퍼티값을 동적으로 리로드합니다. 해당기능에 관한 글은 추후에 자세히 작성하기로….

다운로드는 간단합니다. 요청이 오면 URL을 만들어서 반환하는 간단한 기능은 아래와 같습니다.

@GetMapping("/presigned-url")
    public URL getPresigendURL(){
            //설정값을 읽어오는 Bean에 할당된 값을 파라미터로 넣어주면 끝. 
            //S3Template를 사용하면 편리하다.
            //BucketName, key, Duration 순으로 파라미터 입력
        return s3Template.createSignedGetURL(propertyValue.getBucketName(), propertyValue.getKey(), Duration.ofMinutes(DURATION_MINUTE));
    }

🟣설정값

아래는 필수 설정값입니다. spring-cloud-aws 2.x 버전에서는 s3하위에 버킷명을 적어줬지만 이제는 호출시 bucketName을 넣어줍니다. 참고로 spring.cloud.aws.s3.region으로 지역을 설정해도 되지만 aws.region.static필드에 설정해도됩니다. 전자는 s3의 region을 설정하는거고 후자는 aws전체 라이브러리가 접근할 리전을 설정하는 겁니다.

또한 S3에 접근 가능한 IAM사용자를 만들어 access-keysecret-key를 설정해주셔야합니다.

# 리전을 설정하는 두 가지 방법. 전자는 aws라이브러리 전체 리전설정이고 후자는 s3의 리전
    aws:
      region:
        static: ap-northeast-2 
      s3:
        region: ap-northeast-2
  spring:
        cloud:
        aws:
          credentials:
            access-key: ${YOUR-ACCESS-KEY} #IAM
            secret-key: ${YOUR-SECRET-KEY}
          s3:
            region: ${YOUR-REGION} # ap-northeast-2

위 내용이 기본적으로 필요한 설정값들이고, 더 자세한 설정도 가능합니다.

  • application.yml에 넣을 수 있는 property값

    | Name | Description | Required | Default value | | --- | --- | --- | --- | | spring.cloud.aws.s3.enabled | S3 통합을 활성화합니다. | No | true | | spring.cloud.aws.s3.endpoint | S3Client가 사용하는 엔드포인트를 설정합니다. | No | http://localhost:4566/ | | spring.cloud.aws.s3.region | S3Client가 사용하는 리전을 설정합니다. | No | eu-west-1 | | spring.cloud.aws.s3.accelerate-mode-enabled | S3에 접근할 때 가속 엔드포인트를 사용하도록 설정하는 옵션입니다. 가속 엔드포인트는 아마존 CloudFront의 전 세계적으로 분산된 엣지 로케이션을 사용하여 객체의 전송 속도를 빠르게 합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.checksum-validation-enabled | S3에 저장된 객체의 체크섬을 검증하는 것을 비활성화하는 옵션입니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.chunked-encoding-enabled | PutObjectRequest와 UploadPartRequest에 대한 요청 페이로드를 서명할 때 청크 인코딩을 사용하도록 설정하는 옵션입니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.path-style-access-enabled | S3 객체에 접근할 때 DNS 스타일 접근 대신 패스 스타일 접근을 사용하도록 설정하는 옵션입니다. S3에 접근할 때 더 나은 로드 밸런싱을 위해 DNS 스타일 접근이 선호됩니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.use-arn-region-enabled | S3 작업의 대상으로 전달된 S3 리소스 ARN이 클라이언트가 구성된 리전과 다른 리전에 있을 경우, 이 플래그를 'true'로 설정해야 합니다. 그렇지 않으면 예외가 발생합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.crt.minimum-part-size-in-bytes | 전송 파트의 최소 파트 크기를 설정합니다. 최소 파트 크기를 줄이면 멀티파트 전송이 더 많은 수의 작은 파트로 분할됩니다. 이 값이 너무 낮으면 전송 속도에 부정적인 영향을 미쳐 각 파트에 대한 추가 지연 및 네트워크 통신이 발생합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.crt.initial-read-buffer-size-in-bytes | S3에서 다운로드한 파트를 버퍼링하는 데 클라이언트가 사용할 시작 버퍼 크기를 구성합니다. 높은 다운로드 처리량을 유지하기 위해 더 큰 윈도우를 유지합니다; 여러 파트를 동시에 다운로드할 수 없는 한 윈도우가 여러 파트를 수용할 만큼 충분히 큽니다. 메모리에 버퍼링되는 데이터의 양을 제한하기 위해 작은 윈도우를 유지합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.crt.target-throughput-in-gbps | 전송 요청의 목표 처리량입니다. 값이 높을수록 더 많은 S3 연결이 열립니다. 전송 관리자가 구성된 목표 처리량을 달성할 수 있는지 여부는 환경의 네트워크 대역폭 및 구성된 최대 동시성과 같은 여러 요인에 따라 달라집니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.crt.max-concurrency | 전송 중에 설정해야 하는 S3 연결의 최대 수를 지정합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.transfer-manager.max-depth | S3TransferManager#uploadDirectory 작업에서 방문할 디렉토리의 최대 수준을 지정합니다. | No | null (falls back to SDK default) | | spring.cloud.aws.s3.transfer-manager.follow-symbolic-links | S3TransferManager#uploadDirectory 작업에서 파일 트리를 탐색할 때 심볼릭 링크를 따라갈지 지정합니다. | No | null (falls back to SDK default) |


🔵참고 . S3TransferManager 및 CRT 기반 S3 클라이언트

  • AWS에서는 S3 파일 전송을 쉽게 하기 위해 S3TransferManager 를 제공합니다. S3TransferManager 는 S3 객체를 관리하고 전송 과정을 자동화하기 때문에 작업 부담을 줄여줍니다.

  • 최적의 성능을 위해 S3TransferManager 는 CRT 기반 S3 클라이언트와 함께 사용하는 것이 좋습니다. CRT(AWS Common Runtime) 기반 클라이언트는 최신 기술을 사용하여 S3와의 통신 속도를 향상시킵니다.

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3-transfer-manager</artifactId>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk.crt</groupId>
  <artifactId>aws-crt</artifactId>
</dependency>
  • S3TransferManager 를 사용하면 S3 파일 전송 코드를 간소화하고 관리를 용이하게 할 수 있습니다.

  • CRT 기반 S3 클라이언트를 사용하면 S3와의 통신 속도를 향상시켜 빠르게 파일을 전송할 수 있습니다.

More from this blog

rfc 959 읽어보면서 ftp이해하기

들어가며 프로젝트에서 FTP를 통해 파일을 전송하는 클라이언트를 개발했을 때 이야기입니다. 프로토콜에 대한 이해 없이 개발을 진행하다 보니 불필요하게 시간을 소모한 경험이 있어서 RFC 원문을 읽어보게 되었습니다. 읽게 된 계기 당시 FTP 프로토콜에 대한 이해가 부족한 상태로 개발을 시작했습니다. '제어를 위한 포트와 데이터 전송을 위한 포트가 분리되어 있다' 정도만 알고 있었고 Active 모드와 Passive 모드의 차이를 제대로 이해...

Dec 5, 20256
rfc 959 읽어보면서 ftp이해하기

mapstruct 로 보일러플레이트 코드 줄이기

배경 사내에서 교통 모니터링용 레이더 디바이스를 관리하는 API를 개발했을때 이야기입니다. 반복되는 코드에서 느꼈던 피로를 개선하고자 mapstruct를 적용해봤습니다. 실황 우선 일부 필드만 추출한 Device에 대해 간단히 이야기해야할 것 같습니다. 교통 모니터링용 레이더 디바이스로 신호등이나 가로등에 설치하며 설치된 좌표를 기록하는 위도, 경도와 레이더가 바라보는 방향을 의미하는 heading_angle필드 등이 있습니다. public...

Nov 13, 20259
mapstruct 로 보일러플레이트 코드 줄이기
T

takoyakisoba

38 posts