A. Question

AWS S3 데이터를 S3TransferManagerCopyObject를 사용해서 마이그레이션하는 방법은?

B. Answer

1. 배경 및 목적

데이터를 보내고 싶은 출발지 source와 데이터를 받고 싶은 목적지 target이 아래와 같다고 가정한다.

SOURCE_BUCKET = "testbucket-julee"
TARGET_BUCKET = "testbucket-staccato"
SOURCE_FOLDER = "testfolder"
TARGET_FOLDER = "animals"

aws

SOURCE_BUCKETSOURCE_FOLDER에 있는 모든 객체를 TARGET_BUCKETTARGET_FOLDER에 옮기는 것이 목적이다.

aws

2. TARGET 버킷에 권한 추가

2.1. TARGET 버킷에 들어가서 권한 클릭

aws

2.2. 편집 클릭

aws

2.3. 정책 작성 후, 변경 사항 저장 클릭

aws

  • 이는 source_account_id 계정의 사용자가 객체 업로드객체 ACL 설정target_bucket 버킷에서 수행할 수 있도록 허용한 정책이다.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowSourceAccountPutObject",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::{source_account_id}:root"
            },
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::{target_bucket}/*"
        }
    ]
}
  • 계정 ID는 아래 사진처럼 오른쪽 상단을 누르면 알 수 있다.

aws

2.4. 완료

aws

3. 코드 작성

코드는 아래의 사이트들을 참고했다.

3.1. dependency 추가

dependencies {
    implementation 'software.amazon.awssdk:s3-transfer-manager'
    implementation 'software.amazon.awssdk.crt:aws-crt:0.33.2'
    implementation 'io.github.cdimascio:dotenv-java:3.0.0'
    // ...
}
  • s3-transfer-manager: AWS S3에서 파일 업로드 및 다운로드를 효율적으로 처리하기 위해 사용
  • aws-crt
    • S3TransferManager가 내부적으로 사용하는 라이브러리
    • 네이티브 라이브러리를 활용하여 암호화 및 네트워크 작업 성능을 최적화
    • 대용량 데이터 전송을 고성능으로 처리
  • dotenv
    • .env 파일의 환경 변수를 읽어오는 라이브러리로, 자격 증명이나 설정 값을 관리하는 데 사용

3.2. S3Client 정의

public class Main {
    public static void main(String[] args) {
        Dotenv dotenv = Dotenv.load();
        String accessKey = dotenv.get("AWS_ACCESS_KEY");
        String secretKey = dotenv.get("AWS_SECRET_ACCESS_KEY");
    }
    // ...
}

public class S3FileCopier {
    private final S3Client s3Client;

    public S3FileCopier(String accessKey, String secretKey, Region region) {
        AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(accessKey, secretKey);
        this.s3Client = S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
                .region(region)
                .build();
    }
    // ...
}
  • .env 파일에 AWS_ACCESS_KEYAWS_SECRET_ACCESS_KEY를 저장하고, Dotenv 라이브러리를 사용해 이를 불러온다.
  • 불러온 자격 증명을 기반으로 StaticCredentialsProvider를 통해 인증 정보를 설정하고, 이를 사용해 S3Client를 생성한다.

3.3. copyObject()

private void copyObject(String sourceKey, String targetKey) {
    CopyObjectRequest copyRequest = CopyObjectRequest.builder()
            .sourceBucket(SOURCE_BUCKET)
            .sourceKey(sourceKey)
            .destinationBucket(TARGET_BUCKET)
            .destinationKey(targetKey)
            .build();

    s3Client.copyObject(copyRequest);
}
  • 하나의 S3 객체를 SOURCE_BUCKET/SOURCE_KEY에서 TARGET_BUCKET/TARGET_KEY로 복사한다.
  • S3 객체 스토리지로, URL을 통해 객체를 관리하며 실제 폴더 구조를 가지지 않는다. 따라서 복사 대상 객체의 경로를 더 깊게 설정하고 싶다면, sourceKey 또는 targetKey/를 포함하여 경로를 구성하면 된다.

3.4. copyFolderBetweenBuckets()

public void copyFolderBetweenBuckets() {
    String continuationToken = null;

    do {
        ListObjectsV2Request listRequest = ListObjectsV2Request.builder()
                .bucket(SOURCE_BUCKET)
                .prefix(SOURCE_FOLDER)
                .continuationToken(continuationToken)
                .build();

        ListObjectsV2Response listResponse = s3Client.listObjectsV2(listRequest);
        List<S3Object> objects = listResponse.contents();

        for (S3Object object : objects) {
            String sourceKey = object.key();
            String targetKey = TARGET_FOLDER + sourceKey.substring(SOURCE_FOLDER.length());
            copyObject(sourceKey, targetKey);
        }

        continuationToken = listResponse.nextContinuationToken();
    } while (continuationToken != null);
}
  • 메서드 흐름
    • ListObjectsV2 API를 사용해 SOURCE_BUCKETSOURCE_FOLDER에 포함된 객체 목록을 가져온다.
    • 각 객체의 키를 기반으로 TARGET_BUCKET 내의 TARGET_FOLDER로 복사한다.
  • 대량 데이터 처리
    • S3에서 반환되는 객체 목록은 최대 1000개의 객체로 제한된다.
    • 1000개 이상의 객체를 처리할 수 있도록 연속 토큰 continuationToken을 사용하여 여러 페이지를 처리한다.
  • 폴더 구조 유지
    • S3는 실제 폴더 구조를 가지지 않지만, sourceKeytargetKey를 조작하여 폴더 구조를 유지한다.

3.5. 복사한 객체를 확인하는 로직

private void copyObject(String sourceKey, String targetKey) {
    if (doesObjectExist(targetKey)) {
        return;
    }
    // ...
}


private boolean doesObjectExist(String key) {
    try {
        s3Client.headObject(b -> b.bucket(TARGET_BUCKET).key(key));
        return true;
    } catch (Exception e) {
        return false;
    }
}
  • 복사 도중 작업이 중단되거나 실패한 상황에서 코드를 재실행하더라도, 이미 복사된 객체는 다시 복사하지 않도록 하기 위해 이 로직을 작성했다.
  • 이 로직은 TARGET_BUCKET에 객체가 이미 존재하는지 확인한 후, 존재할 경우 복사를 건너뛰도록 동작한다. 이를 통해 중복 복사로 인한 오버헤드를 방지하고, 실패한 작업을 효율적으로 복구할 수 있도록 한다.

4. 코드 실행

aws

5. 완료

  • SOURCE 버킷의 모든 객체가 TARGET 버킷으로 잘 복사가 된 것을 확인할 수 있다.

aws

C. Reference

태그:

카테고리:

업데이트:

댓글남기기