🔥 Partition Key와 Sort Key: 데이터 분산의 원리
강의 목차

처음 사이드 프로젝트에서 나는 DynamoDB 테이블을 하나 만들었다. partition key를 country로 잡고 나라별 사용자 활동 로그를 적었다. 한국 사용자 비율이 다른 나라를 합친 것보다 압도적으로 높았는데, 평소에는 문제가 없다가 이벤트성 트래픽이 한 번 들어왔을 때 ProvisionedThroughputExceededException이 떴다. 테이블 전체 RCU는 한참 남아 있었다. 콘솔의 그래프도 한도 라인 아래쪽에서 놀고 있었다. 처음에는 그게 왜 그렇게 보이는지 알지 못했다.
이유는 partition 한 곳에 트래픽이 몰렸기 때문이었다. 테이블 전체에 capacity를 더 줘도, 그 capacity는 partition 사이에서 균등하게 나뉘기 때문에 한 partition이 받는 몫은 한정돼 있다. partition key를 country로 잡은 그 한 줄이, 한국 트래픽을 한 partition으로 모은 셈이었다. 그제야 partition key가 항목을 식별하는 역할만 하는 게 아니라, 항목이 어느 partition으로 갈지를 정하는 입력값이라는 걸 알게 됐다.
partition key는 두 가지 일을 같이 한다
partition key는 항목을 식별하는 값이다. GetItem(table, key)에 들어가는 그 key다. 그리고 동시에, DynamoDB가 항목을 어느 partition에 둘지를 정하는 hash 함수의 입력값이다. 같은 partition key 값을 가진 항목들은 항상 같은 partition으로 간다. DynamoDB는 다른 partition key 값을 hash로 계산한 뒤 여러 partition에 나눠 담는다.
DynamoDB란 무엇인가: 키-값 스토어의 관점에서 본 라우팅 계층이 이 hash 결과를 사용한다. 클라이언트가 GetItem을 호출하면 request router가 partition key를 hash해서 metadata subsystem에 그 hash가 어느 storage node 범위에 들어가는지 묻고, 그 노드로 요청을 보낸다. partition key 한 값에 대한 모든 요청은 같은 storage node 한 곳으로 도착한다. 이 사실이 partition 한도와 hot partition 현상을 동시에 설명한다.
partition key 외에 sort key를 더해서 composite primary key를 만들 수도 있다. 이때는 partition key + sort key 조합이 항목을 식별하고, partition 라우팅에는 partition key만 사용한다. sort key는 partition 안에서 항목들을 정렬해 두는 두 번째 축이다.
partition마다 throughput 한도가 따로 있다
DynamoDB의 한 partition이 받을 수 있는 throughput은 초당 1,000 WCU(write capacity units), 3,000 RCU(read capacity units)다. 이 숫자는 AWS DynamoDB Developer Guide의 "Best practices for designing and using partition keys effectively"에 그대로 적혀 있고, 2026-05-01 기준 변경 없다. on-demand 모드여도 DynamoDB는 partition마다 같은 한도를 둔다.
이 뜻은 숫자를 직접 넣어 계산하면 바로 드러난다. 테이블에 partition이 5개 있고 각 partition이 3,000 RCU를 낼 수 있으면, 테이블 전체 read capacity는 15,000 RCU다. 콘솔이 보여주는 숫자가 이거다. 그런데 트래픽이 한 partition key 값에 9,000 RPS 몰리면 그 partition은 3,000 RCU까지만 받는다. 나머지 6,000 RPS는 throttle된다. 다른 partition 4개는 거의 노는 상태인데도 그렇다. 콘솔 그래프와 실제 throttling이 어긋나 보이는 첫 번째 이유다.
partition 한도가 partition 단위라는 건, 테이블 전체 capacity 계획만으로는 throttling을 막을 수 없다는 뜻이다. 트래픽을 partition들 사이로 어떻게 분산시키느냐가 partition key 설계의 일이 된다.

sort key는 같은 partition 안에서 항목을 정렬해 둔다
sort key는 partition 안에서 항목들을 사전식 순서로 정렬해 둔다. 같은 partition key를 가진 모든 항목이 sort key 순서로 디스크에 나란히 적혀 있다고 보면 된다. 그래서 Query API가 한 partition key 안에서 sort key 조건(begins_with, between, >, <, =)으로 항목을 좁힐 수 있다.
예를 들어 user 한 명의 주문 내역을 저장한다면 partition key는 userId#42로 두고, sort key는 order#2026-01-01#abc, order#2026-02-15#def처럼 만든다. 2026년 2월 주문만 보고 싶다면 Query(PK=userId#42, SK begins_with "order#2026-02") 한 번이면 된다. 이 쿼리는 사용자 42의 항목이 모인 partition 한 곳만 읽고, 그 안에서 sort key prefix 조건으로 필요한 범위만 좁힌다. 같은 일을 partition key만으로 하려면 사용자별로 항목을 여러 개로 나누어 적거나, sort key 없이 주문 목록 전체를 한 항목에 담아야 한다. 후자는 400 KB 항목 한도에 금방 부딪힌다.
begins_with를 쓰려면 sort key 안에 #로 계층을 넣어 두면 된다. AWS Developer Guide의 "Best practices for using sort keys to organize data"가 권하는 방식도 이거다. country#region#city처럼 적어 두면 한 query로 한국 전체, 한국 서울 전체, 한국 서울 강남 전체를 각각 다른 prefix로 좁힐 수 있다.

hot partition은 partition key 분포가 만든다
hot partition은 한 partition이 1,000 WCU 또는 3,000 RCU 한도에 부딪혀 throttle되는 상태다. partition key 값들의 분포가 한쪽으로 기울어지면 자연스럽게 그 값이 몰린 partition이 먼저 한도에 도달한다.
partition 분포를 결정하는 두 변수가 있다. AWS Developer Guide의 "Designing partition keys to distribute your workload"는 이걸 cardinality(서로 다른 값이 몇 개나 있는가)와 request distribution(요청이 그 값들 사이로 얼마나 균등하게 흩어지는가)으로 적어 둔다. partition key 값 종류가 적거나(low cardinality), 종류는 많아도 트래픽이 몇 개에 몰려 있으면(skewed distribution) hot partition이 생긴다.
내가 처음 고른 country PK는 두 기준에서 모두 나빴다. 사용자가 만 명이 넘어도 country 값은 5개 안팎이고(low cardinality), 한국 비율이 90%(skewed). 같은 데이터를 userId로 PK를 잡았으면 cardinality는 사용자 수만큼 늘고, 요청 분포도 트래픽이 특정 사용자에게 극단적으로 몰리지 않는 한 한쪽 partition으로 쏠리지 않는다.

adaptive capacity는 한도 자체를 올리지 않는다
DynamoDB는 partition key 분포가 흔들리는 워크로드를 자동으로 어느 정도 흡수한다. 2019년 11월에 GA된 instant adaptive capacity가 그 일을 한다. AWS What's New 공지에 따르면 adaptive capacity는 모든 테이블과 global secondary index에 기본 활성화돼 있고, 추가 비용이 없다. partition 사이로 capacity를 재분배하고, 한 partition 안에서 frequently accessed item을 식별해 다른 partition으로 분리하기까지 한다.
다만 adaptive capacity가 partition 한도 자체를 올리지는 않는다. AWS re:Post의 동일 질문에 AWS 답변이 적은 그대로, 한 partition은 여전히 1,000 WCU / 3,000 RCU에서 throttle된다. 지속적인 hot key가 있으면 DynamoDB는 그 partition을 두 partition으로 split해서 capacity를 두 배로 만든다. 이걸 split for heat라고 부르는데, AWS Database Blog "Scaling DynamoDB" 글이 적은 그대로 이 과정은 보통 분 단위로 진행한다. 그 시간 동안 DynamoDB는 이미 뜨거워진 partition을 바로 고르게 펴지 못한다.
또 하나, burst capacity가 있다. AWS DynamoDB Developer Guide "Burst and adaptive capacity"는 DynamoDB가 사용자가 안 쓴 용량을 최대 5분(300초)까지 적립해 둔다고 적는다. 짧은 burst는 이 적립분에서 흡수되지만, 지속적인 트래픽 spike에는 부족하다. 같은 문서는 DynamoDB가 burst capacity를 내부 백그라운드 작업에도 예고 없이 쓴다고 분명히 적는다. 그래서 5분 적립을 운영적으로 가정하고 capacity 계획을 짜면 안 된다.

DynamoDB가 자동으로 해결하는 범위는 partition 운영까지다. partition을 쪼개고 합치는 일은 DynamoDB가 맡지만, partition key를 어떻게 정의해 트래픽을 어떻게 분산시킬지는 사용자가 정한다. 잘못 잡힌 partition key는 adaptive capacity가 끝까지 보정해 주지 못한다.
write sharding은 PK를 인위적으로 분산시킨다
partition key를 자연스럽게 분산시키기 어려운 워크로드도 있다. 시간 기반 PK가 그 예다. 모든 이벤트의 PK를 2026-05-01 같은 날짜 하나로 잡으면 애플리케이션은 그날 요청을 한 PK로만 보낸다. 그러면 DynamoDB는 그 PK에 붙은 쓰기를 같은 지점에서 계속 처리해야 하고, 결국 병목도 거기서 바로 생긴다. AWS Developer Guide "Using write sharding to distribute workloads evenly"는 이런 부분에 random suffix를 붙이라고 권한다.
같은 날짜에 random suffix(예: 1–200 사이 정수)를 붙여 PK 값을 2026-05-01.1, 2026-05-01.2, ..., 2026-05-01.200처럼 200개로 늘리면 애플리케이션은 같은 날 트래픽을 200개 PK로 나눠 보낸다. 그러면 DynamoDB도 그 쓰기를 여러 지점에서 함께 받는다. 한 partition에 9,000 RPS 몰리던 워크로드가 partition 200개로 나뉘면서 각 PK는 평균 45 RPS만 받는다.

대신 읽을 때는 비용이 바로 늘어난다. 그 날 모든 항목을 보고 싶으면 200개 PK 값에 각각 Query를 보내고 결과를 merge해야 한다. 트래픽 패턴을 알고 있으면 random 대신 결정적 hash(예: userId % 200)를 쓰는 변형이 있는데, 같은 사용자의 항목은 항상 같은 shard에 들어가니 single-shard query만으로 그 사용자 데이터를 다 읽을 수 있다.
DynamoDB를 안 쓰는 워크로드
partition key 분포가 자연스럽게 분산되지 않는 워크로드는 DynamoDB와 잘 안 맞는다. 소수의 사용자에게 트래픽 대부분이 몰리는 워크로드(예: 인플루언서 한 명의 타임라인을 모든 follower가 읽는다), 미리 정해진 access pattern 없이 임의 조합으로 ad-hoc 질문을 던지는 분석 워크로드, 강한 일관성과 복잡한 다중 항목 트랜잭션이 절대 조건인 워크로드가 여기에 든다. 첫 번째는 hot partition의 정의다. 두 번째는 Scan이 partition 한도를 우회하지 못하기 때문이고(Scan은 모든 partition을 순회한다), 세 번째는 RDBMS의 영역이다.
partition key를 어떻게 잡느냐가 access pattern을 그대로 받아 적은 결정이라는 말이다. RDBMS는 schema → relations → queries 순서로 갔지만, DynamoDB는 access patterns → key design → schema 순서다. 같은 데이터라도 어떤 query를 가장 자주 보낼지를 먼저 정하지 않으면 PK가 잡히지 않고, PK가 잘못 잡히면 partition 한도가 그 잘못을 그대로 노출한다.
다음에 DynamoDB 테이블을 짤 때는 PK 후보 두세 개를 두고 cardinality와 분포를 먼저 비교한다. country 같은 값으로 PK를 잡지는 않는다.










