본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

boto3로 R2 사용하기

원문 보기 위키 갱신

Python boto3로 R2를 사용하는 예시예요. S3 클라이언트를 구성하고 객체를 다루며, 멀티파트 업로드 성능 최적화 방법과 사전 서명 URL 생성까지 다뤄요.

출처: 문서

본문

boto3 ↗︎를 R2 엔드포인트를 사용하도록 구성해요:

import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY 환경 변수 ↗︎를 설정했다면 aws_access_key_id와 aws_secret_access_key는 생략할 수 있어요.

이 클라이언트를 사용한 일반적인 작업이에요:

# Get object metadata
s3.head_object(Bucket="my-bucket", Key="dog.png")

# Get object
response = s3.get_object(Bucket="my-bucket", Key="dog.png")

# Upload single file
with open("./dog.png", "rb") as f:
    s3.upload_fileobj(f, "my-bucket", "dog.png")

# Delete object
s3.delete_object(Bucket="my-bucket", Key="dog.png")

업로드 성능 최적화 (Optimizing upload performance)

여러 GB 크기의 파일(학습 데이터나 영상 등) 같은 큰 객체에서는 upload_fileobj가 처리량 병목이 될 수 있어요. 내부 스레드 풀은 Python의 GIL ↗︎에 제약을 받고, TransferConfig의 max_concurrency를 올려도 약 10개 스레드 이상에서는 효과가 줄어들어요.

대신 ThreadPoolExecutor와 함께 저수준 멀티파트 API를 사용해요:

import boto3
import math
import os
from concurrent.futures import ThreadPoolExecutor

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

bucket = "my-bucket"
key = "large-file.bin"
file_path = "./large-file.bin"
part_size = 16 * 1024 * 1024  # 16 MiB per part
max_workers = 10

# Step 1: Create the multipart upload
upload_id = None
mpu = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = mpu["UploadId"]

def upload_part(part_number, data):
    response = s3.upload_part(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        PartNumber=part_number,
        Body=data,
    )
    return {"PartNumber": part_number, "ETag": response["ETag"]}

try:
    file_size = os.path.getsize(file_path)
    part_count = math.ceil(file_size / part_size)

    # Step 2: Upload parts in parallel
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = []
        with open(file_path, "rb") as f:
            for i in range(part_count):
                data = f.read(part_size)
                futures.append(pool.submit(upload_part, i + 1, data))

        parts = [future.result() for future in futures]

    # Step 3: Complete the upload
    s3.complete_multipart_upload(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        MultipartUpload={"Parts": parts},
    )
    print("Multipart upload complete.")
except Exception:
    if upload_id:
        try:
            s3.abort_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id)
        except Exception:
            pass
    raise

멀티파트 업로드의 파트 크기 제한과 수명 주기 관리에 대해 더 자세히 알아보려면 객체 업로드를 참고하세요.

사전 서명 URL 생성 (Generate presigned URLs)

버킷에 대한 임시 공개 읽기 또는 쓰기 접근 권한을 공유하는 사전 서명 링크를 생성해요.

# Generate presigned URL for reading (GET)
get_url = s3.generate_presigned_url(
    "get_object",
    Params={"Bucket": "my-bucket", "Key": "dog.png"},
    ExpiresIn=3600,  # Valid for 1 hour
)

# Generate presigned URL for writing (PUT)
put_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "my-bucket",
        "Key": "dog.png",
        "ContentType": "image/png",
    },
    ExpiresIn=3600,
)
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature>
https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...&X-Amz-Date=<timestamp>&X-Amz-Expires=3600&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=<signature>

사전 서명 PUT URL로 업로드해요. ContentType이 포함된 사전 서명 URL을 사용할 때는 클라이언트가 일치하는 Content-Type 헤더를 꼭 포함해야 해요:

curl -X PUT "https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/dog.png?X-Amz-Algorithm=..." \
  -H "Content-Type: image/png" \
  --data-binary @dog.png

CORS와 Content-Type으로 업로드 제한하기 (Restrict uploads with CORS and Content-Type)

업로드용 사전 서명 URL을 생성할 때 다음 방법으로 남용과 오용을 제한할 수 있어요:

  • Content-Type 제한: 사전 서명 URL 매개변수에 허용된 content type을 지정해요. 클라이언트가 다른 Content-Type 헤더를 보내면 업로드는 실패해요.

  • CORS 구성: 버킷에 CORS 규칙을 설정해 어떤 오리진이 파일을 업로드할 수 있는지 제어해요. Cloudflare 대시보드 ↗︎에서 버킷 설정에 JSON 정책을 추가해 CORS를 구성할 수 있어요:

[
	{
		"AllowedOrigins": ["https://example.com"],
		"AllowedMethods": ["PUT"],
		"AllowedHeaders": ["Content-Type"],
		"ExposeHeaders": ["ETag"],
		"MaxAgeSeconds": 3600
	}
]

그다음 Content-Type 제한이 적용된 사전 서명 URL을 생성해요:

put_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "my-bucket",
        "Key": "dog.png",
        "ContentType": "image/png",
    },
    ExpiresIn=3600,
)

클라이언트가 이 사전 서명 URL을 사용할 때는 반드시 다음을 지켜야 해요:

  • 허용된 오리진에서 요청할 것 (CORS로 강제됨)

  • Content-Type: image/png 헤더를 포함할 것 (서명으로 강제됨)

더 알아보기 (Learn more)