AWS Certified Developer - Associate (DVA-C02) #4 Domain 1-3 AWS 서비스로 개발: DynamoDB 개발
#3 API Gateway에서 관문을 정리했으니, 이번에는 그 뒤에 오는 데이터입니다. 서버리스 애플리케이션의 기본 데이터베이스는 DynamoDB입니다. SAA가 “언제 DynamoDB를 고르는가"를 물었다면, DVA는 한 단계 더 들어가 **“키를 어떻게 설계하고, 쓰기 충돌을 어떻게 막고, 변경을 어떻게 흘려보내는가”**를 코드 수준으로 묻습니다.
키 설계: 파티션 키와 정렬 키 #
DynamoDB의 모든 테이블은 **기본 키(Primary Key)**로 항목을 식별합니다.
| 키 구성 | 의미 |
|---|---|
| 파티션 키만 | 파티션 키 값이 곧 항목의 고유 식별자 |
| 파티션 키 + 정렬 키 | 같은 파티션 키 안에서 정렬 키로 여러 항목 정렬,범위 조회 |
파티션 키는 데이터가 물리적으로 분산되는 기준입니다. 특정 키에 트래픽이 몰리면(핫 파티션) 그 파티션만 스로틀링됩니다. 그래서 시험은 “고르게 분산되는 파티션 키"를 좋은 설계로 봅니다. 카디널리티가 높고 접근이 고른 속성을 파티션 키로 골라야 합니다.
Query vs Scan #
- Query. 파티션 키로 항목을 찾고, 정렬 키로 범위를 좁힙니다. 효율적입니다.
- Scan. 테이블 전체를 읽습니다. 느리고 비쌉니다.
시험 원칙: Scan은 피하고 Query를 쓴다. “특정 속성으로 자주 조회해야 하는데 그게 키가 아니다"라면 답은 Scan이 아니라 **인덱스(GSI)**입니다.
LSI vs GSI #
키가 아닌 속성으로 조회하려면 보조 인덱스가 필요합니다. 두 종류의 차이는 시험 단골입니다.
| 구분 | LSI(로컬 보조 인덱스) | GSI(글로벌 보조 인덱스) |
|---|---|---|
| 파티션 키 | 테이블과 동일 | 다른 속성 가능 |
| 정렬 키 | 다른 속성 | 다른 속성 |
| 생성 시점 | 테이블 생성 시에만 | 언제든 추가,삭제 |
| 일관성 | 강력한 일관성 가능 | 최종 일관성만 |
| 용량 | 테이블 용량 공유 | 자체 용량 |
핵심 한 줄: LSI는 테이블 만들 때만, 같은 파티션 키. GSI는 언제든, 다른 파티션 키. 대부분의 “다른 속성으로 조회” 요구는 유연한 GSI가 답입니다. LSI는 같은 파티션 키 안에서 다른 정렬 기준이 필요하고 강력한 일관성이 필요할 때만 고려합니다.
읽기 일관성 #
DynamoDB 읽기는 두 가지 일관성을 제공합니다.
- 최종 일관성 읽기(기본). 가장 최근 쓰기가 아직 반영되지 않았을 수 있음. 비용이 절반.
- 강력한 일관성 읽기. 항상 최신 값. 비용 2배, GSI에서는 불가.
용량 모드와 스로틀링 #
| 모드 | 적합한 경우 |
|---|---|
| 온디맨드(On-Demand) | 트래픽이 예측 불가,간헐적. 용량 관리 불필요 |
| 프로비저닝(Provisioned) | 트래픽이 예측 가능. RCU/WCU를 미리 지정, Auto Scaling 가능 |
프로비저닝 모드에서 용량을 초과하면 ProvisionedThroughputExceededException(스로틀링)이 납니다. SDK는 기본적으로 지수 백오프로 자동 재시도합니다. 짧은 순간의 버스트는 적응형 용량(adaptive capacity)이 일부 흡수합니다. “스로틀링이 난다"의 답은 보통 용량 상향, 키 분산 개선, 지수 백오프 재시도입니다.
조건부 쓰기와 낙관적 잠금 #
DVA에서 가장 중요한 개발 패턴입니다. **조건부 쓰기(Conditional Write)**는 조건이 참일 때만 쓰기를 수행합니다.
- 항목이 없을 때만 생성.
attribute_not_exists(PK)조건으로 중복 생성을 막습니다(멱등성에 활용). - 낙관적 잠금(Optimistic Locking). 항목에
version속성을 두고, “내가 읽은 버전과 같을 때만 갱신"이라는 조건으로 씁니다. 두 클라이언트가 동시에 같은 항목을 고치면 한쪽만 성공하고 다른 쪽은 **ConditionalCheckFailedException**을 받아 재시도합니다.
UpdateItem
SET stock = stock - 1, version = version + 1
ConditionExpression: version = :expectedVersion- 원자적 카운터(Atomic Counter).
SET views = views + 1처럼 조건 없이 증가시킵니다. 충돌 검증이 필요 없는 단순 카운트에 적합합니다.
낙관적 잠금 = 조건부 쓰기 + 버전 속성. 동시 수정 충돌을 다루는 문항의 정답입니다.
DynamoDB Streams #
테이블의 항목 변경(생성,수정,삭제)을 시간 순서로 캡처하는 변경 로그입니다.
- Lambda를 이벤트 소스 매핑으로 연결해 변경에 반응합니다(예: 새 주문이 들어오면 알림 발송).
- 스트림 레코드에는 변경 전/후 이미지를 담을 수 있습니다(
StreamViewType). - 글로벌 테이블(다중 리전 복제)의 기반이기도 합니다.
“DynamoDB에 항목이 추가되면 자동으로 후속 처리를 하라"의 답은 Streams + Lambda입니다.
TTL #
항목에 만료 시각(epoch) 속성을 지정하면, DynamoDB가 만료된 항목을 자동 삭제합니다. 세션,임시 데이터 정리에 쓰며, 삭제는 즉시가 아니라 백그라운드에서 이뤄집니다(보통 48시간 이내). TTL 삭제도 Streams로 캡처됩니다.
DAX #
**DynamoDB Accelerator(DAX)**는 DynamoDB 전용 인메모리 캐시로, 읽기 응답을 마이크로초 수준으로 낮춥니다.
- 읽기가 매우 많고 지연에 민감한 워크로드에 적합합니다.
- 애플리케이션 코드 변경이 거의 없습니다(DAX 클라이언트로 교체).
- 일반 캐시(ElastiCache)와의 구분이 함정입니다. “DynamoDB 읽기를 마이크로초로"는 DAX, “범용 인메모리 캐시"는 ElastiCache입니다.
시험 출제 패턴 #
- “키가 아닌 속성으로 자주 조회해야 한다.” → GSI(Scan 아님).
- “테이블 생성 후에 인덱스를 추가하려 한다.” → GSI(LSI는 생성 시에만).
- “두 사용자가 동시에 같은 항목을 수정한다. 충돌을 막으려면?” → 조건부 쓰기 + version(낙관적 잠금).
- “같은 주문이 두 번 들어와도 한 번만 생성하려면?” →
attribute_not_exists조건부 쓰기. - “항목이 추가되면 자동으로 후속 작업.” → DynamoDB Streams + Lambda.
- “
ProvisionedThroughputExceededException이 난다.” → 용량 상향,키 분산,지수 백오프. - “DynamoDB 읽기 지연을 마이크로초로.” → DAX.
- “세션 데이터를 일정 시간 후 자동 삭제.” → TTL.
정리 #
이번 글에서 잡은 것:
- 파티션 키는 분산 기준. 고른 분산 = 좋은 키. 조회는 Scan 아닌 Query
- LSI(생성 시,같은 파티션 키,강력한 일관성) vs GSI(언제든,다른 파티션 키,자체 용량,최종 일관성)
- 조건부 쓰기 + version = 낙관적 잠금,
attribute_not_exists는 멱등 생성 - Streams + Lambda로 변경에 반응, TTL로 자동 만료
- 스로틀링은 용량,키 분산,지수 백오프로, 읽기 지연은 DAX로
다음: Domain 1-4 메시징과 이벤트 #
서버리스는 컴포넌트를 느슨하게 잇는 비동기 메시징으로 확장됩니다. #5 메시징과 이벤트에서는 SQS(표준 vs FIFO), SNS, 둘을 결합한 팬아웃, EventBridge의 이벤트 라우팅, 그리고 Step Functions의 워크플로 오케스트레이션을 정리하겠습니다.