DynamoDB의 복합 정렬 키
는 파티션 키에 정렬 키를 더한 것입니다. 이를 강력하게
만드는 요령은 정렬 키 안에 무엇을 넣느냐입니다: 계층 구조를 구분자로 이어붙인 하나의
문자열로 인코딩하면, 단일 Query가 서브트리 전체를 정렬 순서대로 읽습니다 — 조인도, 재귀도,
두 번째 왕복도 없이.
DynamoDB에서 복합 정렬 키는 어떻게 동작하나요?
복합 정렬 키는 계층 구조를 구분자로 이어붙인 하나의 문자열 — root/photos/2026/ — 에 담고,
DynamoDB는 이를 UTF-8 바이트 순서로 저장합니다. 그 배치가 이미 트리와 일치하므로,
begins_with(SK, "root/photos/")를 쓴 단일 Query가 서브트리 전체를 경로 순서대로 읽습니다.
조인도, 재귀도, 두 번째 왕복도 없이 — 인접한 에 대한 접두사
스캔일 뿐입니다.
- 정렬 키는 단순한 ID가 아니라 정렬 가능한 문자열입니다. 경로를 그 안에 담으세요 —
root/photos/2026/— 그러면 DynamoDB가 파티션의 아이템을 UTF-8 바이트 순서로 자동 정렬합니다. - 구분자는 접두사 매칭을 서브트리 읽기로 바꿉니다.
begins_with(SK, "root/photos/")는 그 폴더의 모든 하위 항목을 한 번의 쿼리로 반환합니다. - 정렬 키는 범위 조건을 지원하지, 임의의 필터는 지원하지 않습니다.
begins_with,between,>,<가 제공됩니다 — 필요한 읽기가Scan이 아니라 접두사나 범위가 되도록 키를 설계하세요. - 구분자는 핵심을 담당합니다. 경로 세그먼트 안에 나타날 수 없는 것을 고르세요. 그러지 않으면 서로 무관한 두 가지가 충돌합니다.
정렬 키가 승부의 전부인 이유
SQL에서 넘어왔다면 폴더 트리를 parent_id 셀프 조인으로 모델링하고 재귀적으로 순회할
겁니다 — 레벨마다 쿼리 한 번씩. DynamoDB에서 그것은 조인이 없는 키-값 저장소를 상대로 한 N+1
지뢰입니다.
DynamoDB는 모든 아이템을 파티션 키 아래에서 정렬 키로 정렬해 저장하며, 문자열은 UTF-8 바이트 순서로 저장합니다 (AWS: Query 키 조건). 따라서 정렬 키가 곧 경로라면, 물리적 배치가 이미 트리와 일치합니다. 읽기는 그래프 순회가 아니라 인접한 슬라이스에 대한 접두사 스캔이 됩니다.
바로 그 발상의 전환입니다: 정렬 키는 정확히 일치시키는 식별자가 아닙니다. 그것은 정렬 가능한 주소입니다. 그것을 잘 설계하면 쿼리는 저절로 따라옵니다.
파일 시스템 트리 모델링하기
계정별 파일 트리를 저장한다고 해봅시다. 계정당 하나의 드라이브가 자연스러운 파티션이고, 그 안의 경로가 정렬 키입니다.
| PK | SK | node_type | bytes |
|---|---|---|---|
| DRIVE#a91 | root/ | folder | - |
| DRIVE#a91 | root/docs/ | folder | - |
| DRIVE#a91 | root/docs/taxes.pdf | file | 88210 |
| DRIVE#a91 | root/photos/ | folder | - |
| DRIVE#a91 | root/photos/2026/ | folder | - |
| DRIVE#a91 | root/photos/2026/beach.jpg | file | 284910 |
| DRIVE#a91 | root/photos/2026/sunset.jpg | file | 512004 |
여기서 두 가지 고유한 규칙이 일을 하고 있습니다:
PK = DRIVE#<account>는 한 계정의 트리 전체를 하나의 에 유지하므로, 어떤 서브트리 읽기든 단일 파티션Query가 됩니다.SK는 전체 경로이며 폴더에는 끝에/가 붙습니다. 그 끝의 슬래시는 의도적입니다 — 폴더가 자기 자식들보다 앞에 정렬되게 하고,root/photos/를root/photos라는 이름의 형제 파일과 구분되게 합니다.
서브트리를 한 번의 쿼리로 읽기
root/photos/ 아래의 모든 것 — 폴더, 하위 폴더, 파일 — 을 재귀적으로 나열하세요:
Query
KeyConditionExpression = PK = :drive AND begins_with(SK, :prefix)
:drive = "DRIVE#a91"
:prefix = "root/photos/"
이는 root/photos/, root/photos/2026/, beach.jpg, sunset.jpg를 — 경로 순서대로, 하나의
과금된 읽기로 — 반환합니다. 드라이브 전체가 아니라 그 슬라이스의 아이템에 대해서만 비용을
지불합니다.
DynoTable에서는 바로 이 begins_with 쿼리를 경로 정렬 키에 실행하면 폴더와 그 하위 항목이
경로 순서대로 돌아옵니다 — 손으로 써야 할 자리표시자 구문 따위 없이.
원시 KeyConditionExpression(이름, 값, begins_with)을 직접 코드에 넣어야 하나요?
DynamoDB 표현식 빌더에서 만들고 복사하세요.

서브트리 전체가 아니라 한 단계만 나열하기
begins_with는 재귀적 읽기를 줍니다. 재귀적이지 않은 디렉터리 나열 — root/photos/의
직속 자식만, 그보다 깊은 것은 제외 — 을 하려면 depth 속성을 저장하고 정렬 키 범위에 필터를
더하거나, 경로를 parent GSI로 분리하세요. 가장 단순한 방법은 parent 속성
(root/photos/)을 유지하고 그것을 키로 하는 GSI를 두는 것입니다.
요점: 정렬 키는 접두사와 범위 질문에 저렴하게 답합니다. "직속 자식만"은 다른
질문입니다 — FilterExpression이 효율적이길 바라지 말고 명시적으로 모델링하세요. 필터는 읽기
이후에 실행되고, 버리는 아이템 하나하나에 대해 비용을 지불합니다.
구분자를 신중하게 고르기
구분자는 여러분의 데이터 계약의 일부입니다. 두 가지 규칙:
- 경로 세그먼트 안에 절대 나타나서는 안 됩니다. 파일명에
/가 들어갈 수 있다면/는 잘못된 구분자입니다 —a/b라는 파일은b를 담은 폴더a와 구별되지 않습니다. 예약된 바이트(어떤 팀은#나 제어 문자를 씁니다)를 고르고 세그먼트에서는 금지하세요. - 경계에서의 정렬 순서에 유의하세요.
/(0x2F)는 숫자와 문자보다 앞에 정렬되며, 보통 트리 순서에서 원하는 결과입니다. 구분자를 바꾸면 정렬 순서가 바뀝니다 — 실제 데이터로 검증하세요.
복합 정렬 키 vs. 별도의 정렬 속성
복합 정렬 키 (root/photos/2026/x) | 순수 ID 정렬 키 + parent 속성 | |
|---|---|---|
| 서브트리 읽기 | begins_with 쿼리 한 번 | 재귀 쿼리(N+1) 또는 GSI 순회 |
| 정렬 | 경로 순서, 공짜 | 명시적 정렬 속성을 추가해야 함 |
| 이동 / 이름 변경 | 모든 하위 항목 재작성 | parent 포인터 하나 갱신 |
| 직속 자식 나열 | depth 속성 또는 GSI 필요 | 자연스러움 (parent = x) |
복합 키는 읽기가 서브트리 모양이고 정렬이 중요할 때 이깁니다. 플랫 ID 모델은 트리가 끊임없이 변할 때 이깁니다. 읽기가 많은 대부분의 계층 구조 — 파일 트리, 카테고리 트리, 조직도 — 는 복합 쪽으로 기웁니다.
함정과 다음 단계
- 키에 과하게 욱여넣지 마세요. 인코딩하는 모든 것은 불변이며 접두사로만 인덱싱됩니다. 등호로 쿼리하는 속성은 정렬 키에 밀어 넣을 게 아니라 각자의 필드나 GSI에 있어야 합니다.
- 정렬 키는 임의의
WHERE를 할 수 없습니다. 오직begins_with,between, 비교 연산자뿐입니다.FilterExpression에 손이 간다면 십중팔구 키를 잘못 모델링한 것입니다 — Query vs. Scan을 보세요. - 키 설계를 더 깊이 파는 내용은 단일 테이블 설계에 있습니다. 서브트리 읽기에 기본 테이블이 아니라 인덱스가 필요한 경우는 GSI vs. LSI를 보세요.
표현식 빌더에서 begins_with 키 조건을 만든 다음,
DynoTable을 내려받아 자신의 테이블에 이 접두사 쿼리를 실행하고 서브트리가 경로
순서대로 돌아오는 것을 지켜보세요. (그리고 SQL에 두고 온 셀프 JOIN은요? 필요할 때는
DynoTable의 SQL Workbench가 여전히 실행해 줍니다.)


