DynamoDB 와 ElastiCache (키 설계, 용량 모드, Valkey 캐시)
DynamoDB 의 파티션 키와 정렬 키 설계, 단일 테이블 설계, 온디맨드와 프로비저닝 용량, GSI, 글로벌 테이블(2025-06 GA 다중 리전 강한 일관성), 그리고 ElastiCache 의 Valkey 엔진과 서버리스, 캐시 패턴과 흔한 장애를 정리합니다.
DynamoDB 의 핵심 개념
DynamoDB 는 서버리스 키-값, 문서 데이터베이스입니다. 테이블 크기에 상관없이 한 자릿수 ms 응답을 내는 대신, 어떻게 읽을지를 먼저 정하고 키를 설계해야 합니다. 관계형처럼 "일단 넣고 나중에 아무렇게나 질의" 하는 방식은 맞지 않습니다.
| 개념 | 뜻 |
|---|---|
| 파티션 키 (PK) | 데이터를 파티션에 나누는 해시 키. 값이 고르게 퍼져야 함 |
| 정렬 키 (SK) | 같은 PK 안에서 정렬. 범위 질의(begins_with, between) 가능 |
| 항목 | 한 행. 최대 400 KB |
| GSI | 다른 키로 읽기 위한 전역 보조 인덱스. 비동기로 갱신, 테이블당 기본 20 개 |
| LSI | 같은 PK, 다른 SK. 테이블 만들 때만 생성, 최대 5 개 |
접근 패턴부터 쓴다
주문 서비스라면 먼저 "고객의 최근 주문 20 개", "주문 번호로 주문 상세", "상태가 배송 중인 주문" 같은 접근 패턴을 적고, 그에 맞게 키를 고릅니다.
| PK | SK | 속성 |
|---|---|---|
CUST#c-1001 | ORDER#2026-10-20T09:12:00#o-778 | status, amount |
ORDER#o-778 | ITEM#1 | sku, qty |
ORDER#o-778 | META | customerId, createdAt |
import boto3
from boto3.dynamodb.conditions import Key
table = boto3.resource("dynamodb", region_name="ap-northeast-2").Table("orders")
resp = table.query(
KeyConditionExpression=Key("PK").eq("CUST#c-1001") & Key("SK").begins_with("ORDER#"),
ScanIndexForward=False, Limit=20,
)Scan 은 테이블 전체를 읽어서 느리고 비쌉니다. 운영 코드에서 Scan 이 보이면 키 설계를 다시 봅니다.
핫 파티션
파티션 하나는 초당 3,000 RCU, 1,000 WCU 가 한도입니다. 특정 키(인기 상품, 오늘 날짜)에 쓰기가 몰리면 테이블 전체 용량이 남아도 ProvisionedThroughputExceededException 이나 throttling 이 납니다. 키에 접미사를 붙여 분산(write sharding)하거나, CloudWatch Contributor Insights 로 어떤 키가 뜨거운지 먼저 찾습니다.
용량 모드
| 모드 | 과금 | 맞는 곳 |
|---|---|---|
| 온디맨드 | 읽기, 쓰기 요청 단위 | 예측이 어렵거나 새 서비스. 대부분의 기본값 |
| 프로비저닝 + 오토 스케일링 | 초당 용량(RCU, WCU)을 시간당 | 꾸준하고 예측 가능한 대량 트래픽. 예약 용량 할인 |
2024-11 에 온디맨드 처리량 단가가 절반으로 내려가면서 "기본은 온디맨드" 가 더 확실해졌습니다. 온디맨드에도 테이블별 최대 처리량을 걸 수 있어서 버그로 인한 비용 폭주를 막을 수 있습니다. 2025-12 의 Database Savings Plans 는 DynamoDB 온디맨드 처리량에 최대 18%, 프로비저닝 용량에 최대 12% 할인을 줍니다.
일관성, 트랜잭션, 글로벌 테이블
- 읽기는 기본이 최종 일관성이고,
ConsistentRead=True로 강한 일관성 읽기를 할 수 있습니다(RCU 두 배, GSI 는 불가). TransactWriteItems로 최대 100 개 항목을 원자적으로 씁니다.- 글로벌 테이블은 여러 리전에 같은 테이블을 두고 양방향으로 복제합니다. 기본은 비동기 복제(마지막 쓰기 우선)이고, 2025-06 에 다중 리전 강한 일관성(MRSC) 이 GA 되어 세 리전 구성에서 어느 리전에서 읽어도 최신 값을 보장하는 모드를 고를 수 있습니다. MRSC 는 쓰기 지연이 늘어나니 꼭 필요한 테이블에만 씁니다.
운영 기능
- TTL: 만료 시각 속성을 정하면 무료로 지워 줍니다(즉시가 아니라 보통 며칠 안).
- Streams: 변경 이벤트를 Lambda 로 받아 이벤트 기반 처리, 집계 테이블 갱신에 씁니다.
- 특정 시점 복구(PITR): 최근 최대 35 일 안의 초 단위 시점으로 새 테이블을 복원합니다. 복구 기간을 1 일부터 35 일 사이로 줄일 수 있습니다.
- S3 내보내기, 제로 ETL: 분석은 운영 테이블을 스캔하지 말고 S3 내보내기나 Redshift, OpenSearch 제로 ETL 통합으로 합니다.
- 삭제 보호를 운영 테이블에 켭니다.
ElastiCache
ElastiCache 는 관리형 인메모리 캐시입니다. 엔진은 Valkey, Redis OSS, Memcached 중에서 고릅니다.
Valkey
Valkey 는 Redis 의 라이선스 변경 뒤 Linux Foundation 아래에서 이어진 오픈 소스 포크입니다. ElastiCache 는 2024-10 부터 Valkey 를 지원하고, Valkey 가격을 서버리스는 Redis OSS 보다 33%, 노드 기반은 최대 20% 낮게 매깁니다. 명령과 프로토콜이 호환되므로 대부분의 Redis 클라이언트를 그대로 쓰고, 기존 Redis OSS 클러스터는 다운타임 없이 Valkey 로 엔진을 바꿀 수 있습니다. 새로 만든다면 Valkey 가 기본 선택입니다.
서버리스와 노드 기반
| 방식 | 특징 |
|---|---|
| ElastiCache Serverless | 용량 계획 없음. 저장 GB-시간과 처리 단위(ECPU)로 과금. Valkey 는 최소 100 MB 부터, 월 6 USD 수준에서 시작 |
| 노드 기반 클러스터 | 노드 유형과 샤드, 복제본 수를 직접 정함. 꾸준한 대량 트래픽에 단가가 유리, 예약 노드와 Savings Plans 적용 |
내구성이 필요한 기본 저장소로 쓰려면 ElastiCache 대신 MemoryDB 를 봅니다. 트랜잭션 로그를 여러 AZ 에 남깁니다.
캐시 패턴
def get_product(pid):
key = f"product:{pid}"
v = cache.get(key)
if v is not None:
return json.loads(v)
row = db.fetch_product(pid) # 캐시 미스면 DB 에서
cache.set(key, json.dumps(row), ex=300 + random.randint(0, 60)) # TTL 에 흔들림을 더해 동시 만료 방지
return row- 지연 로딩(cache-aside) 이 기본입니다. 쓸 때는 DB 를 고친 뒤 캐시 키를 지웁니다.
- TTL 에 무작위 값을 더해 한꺼번에 만료되며 DB 에 몰리는 캐시 스탬피드를 막습니다.
- 큰 값(수 MB)과 거대한 키 하나에 몰리는 패턴은 지연과 네트워크 대역폭 문제를 부릅니다.
증상, 원인, 해결
| 증상 | 원인 | 해결 |
|---|---|---|
지연 급증, EngineCPUUtilization 높음 | KEYS *, 큰 SMEMBERS 같은 O(N) 명령 | SCAN 계열로 바꾸고 느린 로그 확인 |
| 키가 사라짐 | 메모리 가득 차서 축출(eviction) | Evictions 지표, maxmemory-policy, 노드 확장 |
| 연결 실패 | 보안 그룹, 전송 중 암호화 설정 불일치 | 클라이언트 TLS 설정, 6379 포트 허용 |
| 장애 조치 뒤 쓰기 실패 | 클라이언트가 옛 주 노드 IP 를 캐시 | 구성 엔드포인트 사용, 클러스터 모드 지원 클라이언트 |
자주 하는 실수
- DynamoDB 를 관계형처럼 설계: 테이블을 엔터티마다 쪼개고 조인을 애플리케이션에서 하면 요청 수와 비용이 폭증합니다.
- GSI 쓰기 용량 부족: GSI 가 throttling 되면 본 테이블 쓰기도 막힙니다.
- 캐시를 유일한 저장소로 씀: ElastiCache 는 재시작과 축출로 데이터를 잃을 수 있습니다.
- 캐시 무효화 없이 TTL 만 김: 오래된 값이 길게 보입니다. 쓰기 경로에서 키를 지웁니다.