Amazon S3 심화 정리 6편. 사물함 비유로 풀어 — S3 Select 서버 측 SQL, Batch Operations 대량 메타 작업, 이벤트 알림(SNS·SQS·Lambda·EventBridge), Object Lock(WORM·Governance vs Compliance·Legal Hold), Presigned URL 심화, Object Lambda 변환, S3 Access Points·Multi-Region Access Points까지 친절하게 정리.
이 글은 Amazon S3 심화 정리 시리즈의 여섯 번째 편입니다. 1편에서 객체 스토리지의 기본기를, 2편에서 보안, 3편에서 성능, 4편에서 복제, 5편에서 라이프사이클까지 풀어 왔어요. 이번 6편은 살짝 결이 다릅니다. 지금까지가 "S3를 잘 쓰는 법"이었다면, 6편은 "S3가 사실 이런 것까지 된다" 는 고급 기능 모음이에요. 그중 가장 묵직한 두 축이 Object Lock(봉인)과 Access Points(출입구) 입니다.
S3 Select·Batch Operations·이벤트 알림·Object Lock·Presigned URL 심화·Object Lambda·Access Points·Multi-Region Access Points — 이름만 봐도 묵직하죠. 한 번에 다 외우려고 하면 머리가 터집니다. 비유로 흐름만 잡고, 함정 포인트만 짚고 넘어가는 게 목표예요. 공식 문서는 S3 사용자 가이드 의 Object Lock·Access Points 챕터를 옆에 띄워 두면 좋습니다.
Object Lock·Access Points 단원이 처음엔 왜 어렵게 느껴질까요
이유는 두 가지예요.
첫째, 고급 기능들이 서로 관련 없는 것처럼 보입니다. Select는 SQL 쿼리고, Batch Operations는 대량 작업이고, Object Lock은 봉인이고, Access Points는 엔드포인트 — 도대체 뭐가 묶이는 건지 감이 안 와요. "이걸 한 챕터에 묶을 이유가 있나?" 싶은 거죠.
둘째, 이름이 비슷한 게 너무 많습니다. Object Lock·Legal Hold·Retention·Governance Mode·Compliance Mode — Lock 계열만 다섯 개 단어가 줄지어 나옵니다. Access Points는 또 Multi-Region Access Points와 헷갈리고요. 처음 보면 "이게 다 다른 거야?" 하고 멍해집니다.
해결법은 이렇게 잡으면 됩니다. 사물함 비유 를 끝까지 끌고 가는 거예요. 사물함에서 자료 꺼낼 때 서버가 필요한 줄만 골라 보내 주는 게 Select고, 사물함 안 모든 봉투에 한 번에 도장 찍는 게 Batch Operations고, 사물함이 바뀔 때마다 메일 보내 주는 게 이벤트 알림이고, 한 번 넣으면 못 꺼내는 봉인이 Object Lock입니다. 비유로 한 번 묶어 놓으면 갑자기 흐름이 정리돼요.
S3 Select — 봉투 통째로 꺼내지 말고 필요한 줄만 골라 보내라
무엇이고 왜 쓰나요
S3에 1GB짜리 CSV 파일이 있는데, 거기서 revenue가 50000 넘는 줄만 100건 정도 필요하다고 해 봅시다. 평소 같으면 어떻게 하죠? 1GB 파일 전체를 다운로드해서, 클라이언트에서 필터링해서, 100건만 추려야 합니다. 999.99MB는 그냥 버리는 셈이에요. 네트워크도 시간도 비용도 다 낭비.
회사 비유로 풀면 이래요. 사물함에서 두꺼운 자료 봉투를 통째로 꺼내 사무실로 가져오고, 책상 위에서 필요한 줄만 골라 보고, 나머지는 다시 봉투에 넣어 사물함에 돌려 놓는 거죠. 비효율적이에요.
S3 Select 는 이걸 뒤집습니다. 사물함 안에 있는 서버가 봉투를 펼쳐 보고, 필요한 줄만 골라서 보내 줘요. 봉투를 통째로 꺼내지 않아도 됩니다. 클라이언트는 100건만 받아요. 네트워크도 빠르고 비용도 적게 듭니다.
기술적으로는 — S3 객체 전체를 다운로드하지 않고 SQL 쿼리로 필요한 데이터만 추출 하는 기능입니다. 서버 측에서 데이터 필터링이 일어나고, 클라이언트로는 결과만 반환돼요.
지원 형식
CSV·JSON·Apache Parquet 세 가지를 지원합니다.
| 형식 | 비고 |
|---|---|
| CSV | 헤더 있음/없음 모두 가능, 가장 일반적 |
| JSON (Lines) | 줄별 JSON, 중첩 구조 쿼리 가능 |
| Apache Parquet | 열 기반 형식, 분석에 최적 |
압축도 지원해요. CSV·JSON은 GZIP·BZIP2, Parquet은 ZSTD까지 됩니다.
일반 다운로드와의 차이
| 항목 | 일반 다운로드 | S3 Select |
|---|---|---|
| 데이터 전송 | 전체 파일 | 쿼리 결과만 |
| 처리 위치 | 클라이언트 | S3 서버 |
| 비용 | 전체 데이터 전송 | 스캔 데이터 비용 |
| 속도 | 파일 크기에 비례 | 쿼리 결과 크기에 비례 |
여기서 시험 함정이 하나 있어요. S3 Select가 무조건 싸다고 말하면 함정에 빠집니다. 스캔하는 바이트 수에 따라 비용이 발생해요. 1GB 파일을 통으로 스캔해서 1KB만 뽑아도, 스캔 비용은 1GB만큼 청구됩니다. 그래서 Parquet 같은 열 기반 형식이 유리해요. 필요한 컬럼만 스캔하니까요.
CLI 예시
# CSV 파일에서 조건 쿼리
aws s3api select-object-content \
--bucket my-bucket \
--key data/sales-2024.csv \
--expression "SELECT * FROM S3Object s WHERE s.revenue > 50000 AND s.region = 'APAC'" \
--expression-type SQL \
--input-serialization '{"CSV": {"FileHeaderInfo": "USE", "RecordDelimiter": "\n", "FieldDelimiter": ","}}' \
--output-serialization '{"CSV": {}}' \
output.csv
# JSON 중첩 구조 쿼리
aws s3api select-object-content \
--bucket my-bucket \
--key data/orders.json \
--expression "SELECT s.customer.name, s.total FROM S3Object s WHERE s.status = 'COMPLETED'" \
--expression-type SQL \
--input-serialization '{"JSON": {"Type": "LINES"}}' \
--output-serialization '{"JSON": {}}' \
output.json
Python (boto3) 예시
import boto3
import json
s3_client = boto3.client('s3')
response = s3_client.select_object_content(
Bucket='my-bucket',
Key='data/analytics.parquet',
ExpressionType='SQL',
Expression="""
SELECT user_id, event_type, COUNT(*) as event_count
FROM S3Object
WHERE event_date >= '2024-01-01'
GROUP BY user_id, event_type
""",
InputSerialization={'Parquet': {}, 'CompressionType': 'NONE'},
OutputSerialization={'JSON': {}}
)
# 결과는 스트리밍으로 옴
for event in response['Payload']:
if 'Records' in event:
data = event['Records']['Payload'].decode('utf-8')
# 한 줄씩 파싱
elif 'Stats' in event:
stats = event['Stats']['Details']
print(f"Bytes scanned: {stats['BytesScanned']}")
print(f"Bytes returned: {stats['BytesReturned']}")
여기서 한 가지 더 — 결과가 스트리밍으로 옵니다. Records 이벤트와 Stats 이벤트가 섞여 들어와요. Stats에 보면 스캔/처리/반환 바이트가 다 찍혀서, 비용 추적할 때 유용합니다.
> 한 줄 정리 — S3 Select는 사물함 안 서버가 봉투를 펼쳐 필요한 줄만 골라 보내는 기능. SQL로 CSV·JSON·Parquet 쿼리. 스캔 바이트가 비용 기준이라 Parquet이 유리.
S3 Batch Operations — 한 번에 도장 찍기 (Object Lock 일괄 적용도 가능)
무엇이고 왜 쓰나요
버킷 안에 객체가 1억 개 있다고 해 봅시다. 이걸 다 Glacier로 옮기고 싶어요. 한 객체씩 aws s3 cp 명령을 1억 번 돌리면? 며칠도 모자라고 비용도 폭발합니다. 중간에 끊기면 어디까지 했는지도 모르고요.
회사 비유로 — 사물함 안에 봉투가 1만 개 있는데, 모든 봉투에 "2026년 보관" 도장을 찍고 싶어요. 한 봉투씩 꺼내서 도장 찍는 게 아니라, "전 봉투에 도장 찍어 줘" 라고 한 번에 시키는 게 효율적이죠.
S3 Batch Operations 가 정확히 그 자리입니다. 수백만~수억 개의 S3 객체에 대한 대규모 메타 작업을 단일 요청 으로 처리해요. 진행 상황 추적, 완료 알림, 실패 목록 보고서까지 자동으로 제공됩니다.
지원 작업 유형
| 작업 | 설명 |
|---|---|
S3PutObjectCopy | 객체 복사 + 스토리지 클래스 변경 |
S3PutObjectTagging | 객체 태그 추가/변경 |
S3DeleteObjectTagging | 객체 태그 삭제 |
S3PutObjectAcl | 객체 ACL 변경 |
S3RestoreObject | Glacier 객체 복원 |
S3PutObjectLegalHold | 법적 보류 설정 |
S3PutObjectRetention | 보존 기간 설정 |
LambdaInvoke | Lambda 함수 실행 (커스텀 처리) |
마지막 LambdaInvoke 가 강력해요. 위 목록에 없는 작업을 하고 싶으면 Lambda 함수를 작성해서 객체별로 실행시킬 수 있습니다. 사실상 모든 작업이 가능 해요.
매니페스트 — 어떤 객체에 작업할지 명단
Batch Operations에는 매니페스트(Manifest) 가 필요합니다. "어떤 객체들에 작업할 건지"의 명단이에요. 두 가지 방식 중 고를 수 있어요.
- S3 인벤토리 리포트 — 버킷 전체 객체 목록을 자동 생성해 둔 것
- CSV 파일 —
bucket,key,version_id형식으로 직접 작성
CSV 예시는 이렇게 단순합니다.
source-bucket,path/to/file1.txt
source-bucket,path/to/file2.txt,version-id-123
source-bucket,images/photo1.jpg
대량 스토리지 클래스 변환 예시
# 배치 작업 생성 (STANDARD → GLACIER)
aws s3control create-job \
--account-id 123456789012 \
--operation '{
"S3PutObjectCopy": {
"TargetResource": "arn:aws:s3:::source-bucket",
"StorageClass": "GLACIER",
"MetadataDirective": "COPY",
"TaggingDirective": "COPY"
}
}' \
--manifest '{
"Spec": {
"Format": "S3BatchOperations_CSV_20180820",
"Fields": ["Bucket", "Key"]
},
"Location": {
"ObjectArn": "arn:aws:s3:::manifest-bucket/manifest.csv",
"ETag": "etag-value"
}
}' \
--report '{
"Bucket": "arn:aws:s3:::reports-bucket",
"Format": "Report_CSV_20180820",
"Enabled": true,
"Prefix": "batch-reports/",
"ReportScope": "AllTasks"
}' \
--priority 10 \
--role-arn arn:aws:iam::123456789012:role/S3BatchOpsRole
# 작업 상태 모니터링
aws s3control describe-job \
--account-id 123456789012 \
--job-id JOB_ID
Lambda 호출 형 — 커스텀 처리
def lambda_handler(event, context):
s3_client = boto3.client('s3')
invocation_id = event['invocationId']
invocation_schema_version = event['invocationSchemaVersion']
results = []
for task in event['tasks']:
task_id = task['taskId']
bucket = task['s3BucketArn'].split(':::')[1]
key = task['s3Key']
try:
# 객체별 처리 로직
s3_client.put_object_tagging(
Bucket=bucket, Key=key,
Tagging={'TagSet': [
{'Key': 'ProcessedBy', 'Value': 'BatchJob'},
{'Key': 'ProcessedDate', 'Value': '2026-05-02'}
]}
)
results.append({
'taskId': task_id,
'resultCode': 'Succeeded',
'resultString': f'OK: {key}'
})
except Exception as e:
results.append({
'taskId': task_id,
'resultCode': 'PermanentFailure',
'resultString': str(e)
})
return {
'invocationSchemaVersion': invocation_schema_version,
'treatMissingKeysAs': 'PermanentFailure',
'invocationId': invocation_id,
'results': results
}
여기서 시험 함정이 하나 있어요. Batch Operations는 객체 데이터 자체를 변환하지 않습니다. 어디까지나 메타 작업 — 복사·태그·ACL·보존 설정·Glacier 복원·Lambda 호출 — 만 합니다. "객체 안 내용을 일괄 수정"하고 싶으면 Lambda 호출 모드를 써야 해요.
> 한 줄 정리 — Batch Operations는 사물함 안 모든 봉투에 한 번에 도장 찍기. 매니페스트로 대상 명단 주면 진행/실패 보고서까지 자동.
S3 이벤트 알림 — 사물함 변동 시 자동으로 메일 발송하는 알리미
무엇이고 왜 쓰나요
버킷에 새 이미지가 업로드될 때마다 자동으로 썸네일을 만들고 싶다고 해 봅시다. 평소처럼 하면 — 매 5분마다 "새 파일 있나?" 폴링하는 cron 잡을 짜야 해요. 비효율적이죠.
회사 비유로 — 사물함에 새 봉투가 들어올 때마다 누군가 5분에 한 번 사물함 문을 열어 보면서 "새 거 있나?" 확인하는 식입니다. 차라리 사물함이 봉투 들어올 때 알림을 주면 좋겠죠. 그게 이벤트 알림입니다.
S3 이벤트 알림(Event Notifications) 은 버킷에서 발생하는 특정 이벤트에 반응해 다른 AWS 서비스로 알림을 보내요. 이벤트 기반 워크플로의 시작점입니다.
어떤 이벤트를 잡을 수 있나
| 카테고리 | 이벤트 |
|---|---|
| 객체 생성 | s3:ObjectCreated:*, :Put, :Copy, :MultipartUpload |
| 객체 삭제 | s3:ObjectRemoved:*, :Delete, :DeleteMarkerCreated |
| 복원 | s3:ObjectRestore:Post, :Completed, :Delete |
| 복제 | s3:Replication:* |
| 라이프사이클 | s3:LifecycleTransition, s3:LifecycleExpiration:* |
| 태깅 | s3:ObjectTagging:Put, :Delete |
| ACL | s3:ObjectAcl:Put |
* 와일드카드로 카테고리 전체를 잡을 수도 있고, 세부 이벤트만 콕 집을 수도 있어요.
알림 대상 — 4가지
| 대상 | 자리 |
|---|---|
| Amazon SNS Topic | 이메일·SMS·HTTP 알림으로 분배 |
| Amazon SQS Queue | 메시지 큐 — 컨슈머가 천천히 처리 |
| AWS Lambda Function | 서버리스 함수 즉시 실행 |
| Amazon EventBridge | 모든 이벤트를 EventBridge로 — 라우팅 규칙으로 다양한 타깃 분기 |
여기서 시험 함정이 하나 있어요. SNS·SQS·Lambda는 각자 1:1 대상입니다. "여러 람다에 같은 이벤트 보내고 싶다"면 — SNS Topic을 두고 그 SNS에서 여러 람다로 팬아웃하거나, EventBridge 를 쓰면 됩니다. EventBridge는 한 이벤트를 여러 룰 로 라우팅할 수 있어 가장 유연해요.
설정 예시
# SNS Topic으로 알림
aws s3api put-bucket-notification-configuration \
--bucket my-bucket \
--notification-configuration '{
"TopicConfigurations": [
{
"Id": "NewObjectNotification",
"TopicArn": "'$SNS_TOPIC_ARN'",
"Events": ["s3:ObjectCreated:*"],
"Filter": {
"Key": {
"FilterRules": [
{"Name": "prefix", "Value": "uploads/"},
{"Name": "suffix", "Value": ".jpg"}
]
}
}
}
]
}'
# Lambda 트리거
aws s3api put-bucket-notification-configuration \
--bucket my-bucket \
--notification-configuration '{
"LambdaFunctionConfigurations": [
{
"Id": "ImageProcessor",
"LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:ImageProcessor",
"Events": ["s3:ObjectCreated:*"]
}
]
}'
# EventBridge 모든 이벤트 전달
aws s3api put-bucket-notification-configuration \
--bucket my-bucket \
--notification-configuration '{
"EventBridgeConfiguration": {}
}'
Filter 가 핵심이에요. prefix·suffix 로 특정 폴더·확장자만 잡을 수 있습니다. 위 예시는 uploads/ 접두사 + .jpg 접미사 — 즉 업로드 폴더의 JPEG 파일만 트리거.
Lambda 처리 예시
import boto3
import urllib.parse
def lambda_handler(event, context):
s3_client = boto3.client('s3')
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = urllib.parse.unquote_plus(record['s3']['object']['key'])
event_name = record['eventName']
if event_name.startswith('ObjectCreated'):
response = s3_client.head_object(Bucket=bucket, Key=key)
content_type = response['ContentType']
if content_type.startswith('image/'):
# 썸네일 생성 등
pass
return {'statusCode': 200}
여기서 시험 함정이 하나 있어요. 이벤트 알림은 한 객체당 보통 1번만 도착하지만, 드물게 중복 됩니다. SQS도 Lambda도 "at least once" 모델이에요. 중복 처리를 가정 하고 멱등성 있게 짜야 합니다. 같은 이벤트가 두 번 와도 같은 결과가 나와야 해요.
> 한 줄 정리 — 이벤트 알림은 사물함 변동 시 자동 메일 발송. SNS·SQS·Lambda·EventBridge 4종 대상. 멱등성 가정.
Object Lock — 한 번 넣으면 못 꺼내는 봉인
무엇이고 왜 쓰나요
금융·의료·법률 같은 규정 준수 요구가 강한 데이터 가 있어요. 7년 보관해야 하고, 그 안엔 누구도 — 심지어 본인도 — 수정·삭제하면 안 됩니다. 이걸 WORM(Write Once Read Many) 모델이라고 해요. 한 번 쓰고 여러 번 읽기만.
회사 비유로 — 사물함에 봉투를 넣되, 봉투 입구를 봉인 하는 거예요. 봉인된 봉투는 못 꺼내고 못 바꿉니다. 보관 기간이 지나야 비로소 봉인이 풀려요.
Object Lock 이 그 자리입니다. 새 버킷 생성 시점에만 활성화 가능 — 1편에서 미리 깐 복선이 여기서 나옵니다.
두 가지 모드 — Governance vs Compliance
여기서 가장 헷갈리는 부분이에요. 보존 모드가 두 가지입니다.
| 항목 | Governance Mode | Compliance Mode |
|---|---|---|
| 설명 | 특별 권한으로 해제 가능 | 누구도 해제 불가 |
| 루트 계정 | 해제 가능 | 해제 불가 |
| 사용 사례 | 내부 정책 | 외부 규정 (SEC, FINRA 등) |
| 유연성 | 높음 | 없음 |
회사 비유로 풀면 — Governance Mode = 관리자만 풀 수 있는 봉인. 평소엔 잠겨 있지만, 특별 권한 (s3:BypassGovernanceRetention) 가진 사람이 --bypass-governance-retention 플래그로 해제 가능. 내부 정책으로 "원칙적으로 못 만지지만 비상 시 풀 수 있게" 하는 자리.
Compliance Mode = 누구도 풀 수 없는 봉인. 루트 계정도 못 풉니다. AWS 본사도 못 풀어요. 진짜로 누구도 풀 수 없습니다. 외부 규정 — 미국 SEC 17a-4(f) 같은 — 을 충족시켜야 할 때 쓰는 자리. 한 번 켜면 보존 기간이 끝나기 전엔 절대 못 만져요.
여기서 시험 함정이 하나 있어요. "Compliance Mode를 실수로 켰다" 는 보기는 함정입니다. 실수로 켰어도 보존 기간이 끝날 때까지 그 객체를 못 지웁니다. 실수로 100년 보존을 걸었다? 100년 동안 그 객체에 묶여 있고 저장 비용이 청구돼요. 켜기 전에 정말 신중해야 하는 모드입니다.
Legal Hold — 기간 무관 별도 봉인
또 헷갈리는 게 Legal Hold(법적 보류) 와 Retention Period(보존 기간) 입니다.
| 항목 | Legal Hold | Retention Period |
|---|---|---|
| 기간 | 무기한 | 특정 기간 지정 |
| 해제 | s3:PutObjectLegalHold 권한으로 언제든지 | 기간 만료 후 자동 (또는 Governance에서 수동) |
| 목적 | 소송·감사 | 규정 준수 보관 |
회사 비유로 — Retention Period는 "이 봉투는 2030년 12월 31일까지 봉인" 처럼 종료 시점이 정해져 있어요. 그날이 오면 자동으로 봉인 풀림.
Legal Hold는 다릅니다. "기한 무관, 명시적으로 떼지 않는 한 영구 봉인" 이에요. 소송이 진행 중이라 증거를 보존해야 할 때, 언제 끝날지 모르니 무기한으로 잠가 두는 거죠. 소송이 끝나면 명시적으로 Legal Hold를 OFF로 바꿉니다.
여기서 흥미로운 점 — 두 봉인은 독립적 입니다. 한 객체에 Retention과 Legal Hold가 동시에 걸릴 수 있어요. 둘 중 하나라도 살아 있으면 객체는 보호됨. 둘 다 풀려야 비로소 자유로워집니다.
설정 예시
# Object Lock 활성화 버킷 생성 (생성 시점에만!)
aws s3api create-bucket \
--bucket compliance-bucket \
--object-lock-enabled-for-bucket
# 기본 보존 정책 — 모든 신규 객체에 자동 적용
aws s3api put-object-lock-configuration \
--bucket compliance-bucket \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "COMPLIANCE",
"Years": 7
}
}
}'
# 개별 객체 보존 기간 설정
aws s3api put-object-retention \
--bucket compliance-bucket \
--key financial-record.pdf \
--retention '{
"Mode": "GOVERNANCE",
"RetainUntilDate": "2030-12-31T23:59:59Z"
}'
# 법적 보류 설정 (기간 없음)
aws s3api put-object-legal-hold \
--bucket compliance-bucket \
--key lawsuit-document.pdf \
--legal-hold Status=ON
# Governance 모드 해제 (특별 권한 필요)
aws s3api put-object-retention \
--bucket compliance-bucket \
--key file.pdf \
--retention '{}' \
--bypass-governance-retention
> 한 줄 정리 — Object Lock = 봉인된 봉투. Governance(관리자만 풀 수 있음) vs Compliance(누구도 못 풂, 루트 X). Legal Hold는 기간 무관 별도 봉인. 버킷 생성 시점에만 켤 수 있음.
Presigned URL 심화 — 임시 출입증 더 깊이 보기
2편 복습 — 짧게
2편에서 Presigned URL을 회사 비유로 — "제3자에게 임시 출입증 발급" 이라고 풀었어요. AWS 자격 증명 없는 외부 사용자도 정해진 시간 동안 정해진 객체에 접근하게 해 주는 서명된 URL입니다.
이번 편에서는 그 심화 — 만료·서명·SDK 사용 패턴 을 좀 더 깊게 다룹니다.
만료 시간
만료 시간(ExpiresIn)은 초 단위로 지정해요. 기본 3600초(1시간), 최대값은 서명 방식에 따라 달라집니다.
- IAM 사용자 자격 증명으로 서명 — 최대 7일
- 임시 자격 증명(STS) 으로 서명 — 임시 자격 증명 자체의 만료 시점까지
여기서 시험 함정이 하나 있어요. 임시 자격 증명으로 서명하면 URL 만료 시간을 7일로 줘도 임시 자격 증명이 1시간 뒤 만료되면 같이 만료됩니다. URL의 ExpiresIn은 "최대 이 시간까지 유효"의 의미일 뿐, 서명한 자격 증명의 만료를 못 넘어요. Lambda·EC2 인스턴스 프로파일 같은 임시 자격 증명을 쓸 때 자주 만나는 함정입니다.
Presigned POST — 업로드 제한 조건
generate_presigned_url 은 보통 GET 용입니다. 외부에서 업로드 시키는 자리에는 generate_presigned_post 가 더 강력해요. 업로드 시 강제 조건을 걸 수 있거든요.
import boto3
def generate_presigned_post(bucket, key, expiration=3600, max_file_size=10*1024*1024):
s3_client = boto3.client('s3')
response = s3_client.generate_presigned_post(
bucket,
key,
Fields={
'Content-Type': 'image/jpeg',
'x-amz-server-side-encryption': 'AES256'
},
Conditions=[
{'Content-Type': 'image/jpeg'},
['content-length-range', 1024, max_file_size],
{'x-amz-server-side-encryption': 'AES256'}
],
ExpiresIn=expiration
)
return response
# 5MB 제한, JPEG만, AES256 암호화 강제
presigned = generate_presigned_post(
'my-bucket',
'uploads/user-photo.jpg',
max_file_size=5*1024*1024
)
조건의 의미를 풀면 — JPEG만 업로드 가능, 1KB ~ 5MB 사이 크기, 서버 측 암호화 AES256 강제. 이 조건을 어기면 업로드가 거부됩니다. 서버에서 추가 검증할 필요 없이 S3가 알아서 막아 줘요.
다운로드 URL — 파일명 강제
ResponseContentDisposition 으로 다운로드 시 파일명을 지정할 수 있어요.
def create_download_url(bucket, key, expiry_minutes=60, filename=None):
s3_client = boto3.client('s3')
params = {'Bucket': bucket, 'Key': key}
if filename:
params['ResponseContentDisposition'] = f'attachment; filename="{filename}"'
return s3_client.generate_presigned_url(
'get_object',
Params=params,
ExpiresIn=expiry_minutes * 60
)
url = create_download_url(
'my-bucket',
'reports/2024-annual-report.pdf',
expiry_minutes=30,
filename='Annual_Report_2024.pdf'
)
S3 키는 reports/2024-annual-report.pdf 인데 사용자가 다운받을 때 파일명은 Annual_Report_2024.pdf 로 떨어집니다. 사용자에게 보여주는 파일명과 내부 키 이름을 분리할 수 있어요.
> 한 줄 정리 — Presigned URL 만료는 서명한 자격 증명의 만료를 못 넘는다. 업로드 강제 조건은 generate_presigned_post. 다운로드 파일명은 ResponseContentDisposition.
S3 Object Lambda — 사물함에서 자료 꺼낼 때 즉석 변환
무엇이고 왜 쓰나요
같은 객체를 호출자에 따라 다르게 보여주고 싶을 때 가 있어요. 예를 들어 — 같은 고객 데이터 JSON 인데, 일반 직원에게는 SSN·신용카드 마스킹된 버전, 관리자에게는 원본을 보여주고 싶다면? 객체를 두 벌 만들어 두는 게 아니라, GET 요청이 올 때 즉석에서 변환 하면 깔끔하죠.
회사 비유로 — 사물함에서 자료를 꺼낼 때, 자료실 직원이 요청자 신분에 따라 즉석에서 일부 줄을 가리고 줍니다. 원본은 그대로, 꺼내 가는 사본만 변형되는 거예요.
S3 Object Lambda 가 그 자리입니다. GET 요청을 가로채서 Lambda 함수로 변환한 결과를 반환 해요. 원본 객체는 수정되지 않습니다.
사용 사례
- 이미지 자동 리사이즈 — 같은 원본에서 썸네일·고해상도 동적 생성
- 데이터 마스킹 — 권한별로 민감 정보 가려서 반환
- 포맷 변환 — XML 원본을 GET 시 JSON으로 변환
- 데이터 필터링 — 사용자별로 보이는 행/열 제한
작동 원리
Object Lambda는 Object Lambda Access Point 를 통해 작동합니다. 클라이언트는 일반 S3 GET처럼 호출하지만, 그 GET 요청이 Lambda를 거쳐서 변환된 결과로 돌아와요.
import boto3, json
s3_client = boto3.client('s3')
def lambda_handler(event, context):
object_context = event['getObjectContext']
request_route = object_context['outputRoute']
request_token = object_context['outputToken']
s3_url = object_context['inputS3Url']
# 원본 객체 가져오기
response = s3_client.get_object(Bucket='source-bucket', Key='data.json')
original_data = json.loads(response['Body'].read())
# 마스킹 변환
masked_data = mask_sensitive_data(original_data)
# 변환된 데이터를 호출자에게 반환
s3_client.write_get_object_response(
Body=json.dumps(masked_data),
RequestRoute=request_route,
RequestToken=request_token,
ContentType='application/json'
)
return {'status_code': 200}
def mask_sensitive_data(data):
if isinstance(data, dict):
return {
k: '***MASKED***' if k in ['ssn', 'credit_card', 'password']
else mask_sensitive_data(v)
for k, v in data.items()
}
elif isinstance(data, list):
return [mask_sensitive_data(i) for i in data]
return data
핵심은 write_get_object_response 호출이에요. 이게 변환된 응답을 호출자에게 돌려주는 API입니다.
여기서 시험 함정이 하나 있어요. Object Lambda는 GET만 가로챕니다. PUT·DELETE·HEAD는 그대로 통과해요. "업로드 시 자동 변환"이 필요하면 — 그건 이벤트 알림 + Lambda 패턴으로 해야 합니다 (앞에서 본 그 패턴).
> 한 줄 정리 — Object Lambda = 사물함에서 꺼낼 때 즉석 변환. GET 요청만 가로챔. 원본은 불변. Object Lambda Access Point 경유.
S3 Access Points — 사물함마다 별도의 출입구
무엇이고 왜 쓰나요
큰 회사에서 한 버킷을 여러 팀이 공유 한다고 해 봅시다. Finance 팀은 finance/ 접두사만 봐야 하고, Marketing 팀은 marketing/ 접두사만 봐야 하고, Analytics 팀은 전체 읽기만 가능하고… 이걸 다 버킷 정책 한 장에 넣으려고 하면 — Statement가 길어지고, 충돌나고, 디버깅 지옥에 빠집니다.
회사 비유로 — 사물함은 하나인데, 각 팀이 별도의 출입구 로 들어옵니다. Finance 출입구를 통해 들어오면 finance 칸만 보이고, Marketing 출입구를 통해 들어오면 marketing 칸만 보이는 식이에요. 출입구마다 권한을 따로 설정.
S3 Access Points 가 그 자리입니다. 한 버킷에 대해 여러 개의 네임드 네트워크 엔드포인트 를 만들어, 각 엔드포인트마다 독립적인 정책을 설정할 수 있어요.
자리
- 여러 팀이 같은 버킷 — 다른 권한
- VPC 전용 접근 — 인터넷에서는 못 들어옴, 특정 VPC에서만
- 특정 접두사만 노출
설정 예시
# 일반 액세스 포인트 생성
aws s3control create-access-point \
--account-id 123456789012 \
--name finance-access-point \
--bucket my-shared-bucket
# VPC 전용 액세스 포인트
aws s3control create-access-point \
--account-id 123456789012 \
--name vpc-only-access-point \
--bucket my-secure-bucket \
--vpc-configuration VpcId=vpc-xxxxxxxx
# 액세스 포인트 정책 — finance 접두사만 허용
aws s3control put-access-point-policy \
--account-id 123456789012 \
--name finance-access-point \
--policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/FinanceRole"},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:us-east-1:123456789012:accesspoint/finance-access-point/object/finance/*"
}]
}'
# 액세스 포인트로 접근
ACCESS_POINT_ARN="arn:aws:s3:us-east-1:123456789012:accesspoint/finance-access-point"
aws s3api get-object \
--bucket $ACCESS_POINT_ARN \
--key finance/report.pdf \
report.pdf
여기서 시험 함정이 하나 있어요. 액세스 포인트 정책은 버킷 정책을 대체하지 않습니다. 둘이 함께 평가돼요. 둘 다에서 허용 되어야 접근 가능합니다 (정확히는 — 한쪽이 허용하고 다른 쪽이 거부하지 않으면 OK 인 표준 IAM 평가 모델). 그래서 보통 버킷 정책을 단순화 — "이 버킷의 액세스는 액세스 포인트 통해서만" — 해 두고, 세부 권한은 액세스 포인트 별로 잘게 쪼갭니다.
> 한 줄 정리 — Access Points = 사물함에 별도의 출입구를 여러 개. 각 출입구별 정책 분리. VPC 전용도 가능.
Multi-Region Access Points — 전 세계 어디서도 가장 가까운 사물함으로
무엇이고 왜 쓰나요
글로벌 서비스를 운영합니다. 미국·유럽·아시아에 같은 데이터를 복제해 두고 (4편에서 본 CRR), 사용자는 자기 지역에서 가장 가까운 버킷을 읽고 싶어요. 평소 같으면 — 클라이언트가 자기 위치에 따라 us-bucket, eu-bucket, ap-bucket 중 하나를 골라 호출해야 합니다. 클라이언트 로직이 복잡해져요.
회사 비유로 — 전 세계 도시마다 같은 자료가 들어 있는 사물함이 있어요. 서울 사용자가 "내 자료 꺼내 줘" 하면 서울 사물함에서, 런던 사용자가 같은 요청을 하면 런던 사물함에서 자동으로 꺼내 주면 좋겠죠. 클라이언트는 도시 이름을 몰라도 됩니다. 글로벌 단일 엔드포인트 만 알면 돼요.
Multi-Region Access Points (MRAP) 가 그 자리입니다. 여러 리전에 걸친 버킷들 위에 하나의 글로벌 엔드포인트 를 두고, 요청은 AWS 글로벌 네트워크가 자동으로 가장 가까운 리전으로 라우팅 합니다.
자리
- 글로벌 사용자에게 낮은 지연
- 리전 장애 시 자동 페일오버
- 클라이언트 측 라우팅 로직 제거
작동 방식 — 핵심 두 가지
1. 자동 라우팅 — 클라이언트 IP 기반으로 가장 가까운 리전의 버킷에 요청을 보냄. AWS Global Accelerator 비슷한 원리.
2. 활성/패시브 페일오버 — 한 리전이 장애나면 자동으로 다른 리전으로 트래픽 이동. RTO가 매우 짧아요.
여기서 시험 함정이 하나 있어요. MRAP 자체가 데이터 복제를 해 주는 게 아닙니다. MRAP는 이미 복제된 버킷들 위에 라우팅 레이어를 까는 거예요. CRR(Cross-Region Replication)이 먼저 설정 돼 있어야 의미가 있습니다 — 그 위에 MRAP가 글로벌 엔드포인트를 제공하는 구조.
또 한 가지 — MRAP는 추가 비용이 발생합니다. 라우팅·데이터 전송 비용이 일반 S3 호출보다 약간 더 비싸요. 글로벌 분산이 진짜 필요할 때만 쓰는 게 좋습니다. 단일 리전 트래픽이면 오버킬이에요.
> 한 줄 정리 — MRAP = 전 세계 어디서 접근해도 가장 가까운 사물함으로 자동 라우팅. CRR 위에 깔리는 글로벌 엔드포인트. 페일오버 자동.
보너스 — Requester Pays · S3 Analytics
마지막으로 짧게 두 개만 더.
Requester Pays — 데이터 받는 쪽이 비용 부담
기본은 — 버킷 소유자가 저장 비용·요청 비용·전송 비용을 다 냅니다. Requester Pays 를 켜면 — 데이터를 요청하는 쪽이 요청·전송 비용을 부담 해요. 저장 비용만 소유자 몫.
자리는 명확합니다. 대규모 공개 데이터셋 을 호스팅하는데 다운로드 폭증으로 비용이 부담된다면, 받는 쪽에 비용을 떠넘기는 거죠. 학술 데이터·정부 공개 데이터 같은 데서 자주 봅니다.
# Requester Pays 활성화
aws s3api put-bucket-request-payment \
--bucket public-dataset-bucket \
--request-payment-configuration PaymentRequested=Requester
# 접근 시 헤더 필수
aws s3api get-object \
--bucket public-dataset-bucket \
--key data/large-dataset.csv \
--request-payer requester \
output.csv
여기서 시험 함정이 하나 있어요. 요청자는 --request-payer requester 헤더를 꼭 붙여야 합니다. 이걸 빠뜨리면 요청이 거부돼요. "내가 비용 부담함을 인지함"이라는 명시적 의사 표시예요.
S3 Analytics — 클래스 전환 시점 추천
5편의 라이프사이클 정책을 짤 때 — "Standard에서 Standard-IA로 며칠 뒤 옮기지?" 가 항상 고민이에요. 30일? 60일? 90일? 워크로드마다 다릅니다.
S3 Analytics (스토리지 클래스 분석) 이 그 답을 줍니다. 객체의 접근 패턴을 분석해서 Standard → Standard-IA 전환 최적 시점 을 추천해 줘요.
aws s3api put-bucket-analytics-configuration \
--bucket my-bucket \
--id AnalyzeAll \
--analytics-configuration '{
"Id": "AnalyzeAll",
"StorageClassAnalysis": {
"DataExport": {
"OutputSchemaVersion": "V_1",
"Destination": {
"S3BucketDestination": {
"Format": "CSV",
"Bucket": "arn:aws:s3:::analytics-output-bucket",
"Prefix": "analytics/"
}
}
}
}
}'
데이터가 24~48시간 뒤 생성 되고, 이후 점차 정확해집니다. 한 달 정도 돌려 두면 신뢰할 만한 추천이 나와요.
시리즈 다른 편
같은 시리즈의 다른 글들도 같은 친절 톤으로 묶어 정리되어 있어요.
- 1편 — 기초
- 2편 — 보안
- 3편 — 성능 최적화
- 4편 — 복제
- 5편 — 라이프사이클
- 6편 — 고급 기능 (현재 글)
- 7편 — AWS 통합 (완)
시험 직전 한 번 더 — Object Lock 포함 압축 노트
여기까지가 S3 6편(고급 기능)의 핵심입니다. 종류가 많은 단원이라 압축 노트를 좀 길게 잡았어요.
- S3 Select — 서버 측 SQL, CSV·JSON·Parquet 지원. 봉투 통째로 안 꺼내고 필요한 줄만
- S3 Select 비용은 스캔 바이트 기준 — Parquet이 유리 (필요 컬럼만 스캔)
- Batch Operations — 수억 객체 메타 작업. 매니페스트로 대상 명단
- Batch Operations 작업 종류 — Copy·Tag·ACL·Restore·Lock·LambdaInvoke
- Batch Operations는 데이터 자체를 변환 안 함 — 메타 작업만. 데이터 변환은 LambdaInvoke 모드로
- 이벤트 알림 4가지 대상 — SNS·SQS·Lambda·EventBridge
- EventBridge가 가장 유연 — 한 이벤트를 여러 룰로 라우팅
- 이벤트 알림은 at least once — 중복 가정, 멱등성 필수
- Object Lock — WORM, 버킷 생성 시점에만 활성화 가능
- Governance Mode — 특별 권한(
s3:BypassGovernanceRetention)으로 해제 가능 - Compliance Mode — 누구도 해제 불가, 루트도 X
- Legal Hold — 기간 무관, 명시적 해제까지 영구. Retention과 독립
- 한 객체에 Retention + Legal Hold 동시 가능 — 둘 다 풀려야 자유
- Presigned URL 만료 — 서명한 자격 증명의 만료를 못 넘음 (임시 자격 증명 함정)
generate_presigned_post— 업로드 강제 조건 (크기·타입·암호화)- 다운로드 파일명 변경 —
ResponseContentDisposition - Object Lambda — GET 요청만 가로챔. 원본 불변. Object Lambda Access Point 경유
- Object Lambda는 PUT·DELETE·HEAD는 그대로 통과 — 업로드 변환은 이벤트 알림 + Lambda 패턴
- Access Points — 한 버킷에 여러 출입구, 출입구별 정책. VPC 전용도 가능
- Access Point 정책 + 버킷 정책 둘 다에서 허용 돼야 접근 가능
- Multi-Region Access Points (MRAP) — 전 세계 단일 글로벌 엔드포인트
- MRAP는 CRR이 먼저 깔려 있어야 의미 — 라우팅 레이어일 뿐, 복제는 별개
- Requester Pays — 받는 쪽이 비용 부담.
--request-payer requester헤더 필수 - S3 Analytics — Standard → Standard-IA 전환 최적 시점 추천. 24~48h 뒤 데이터 시작
다음 글(7편)에서는 S3 정적 웹사이트 호스팅 + CloudFront 통합 을 풀어 갑니다. S3로 웹사이트 호스팅하기, CloudFront로 글로벌 캐싱·HTTPS 적용, OAC(Origin Access Control)로 버킷 직접 접근 차단까지 — 시리즈를 마무리하는 7편으로 이어집니다.