중급4분 분량

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 키 조건). 그래서 정렬 키가 경로 그 자체라면, 물리적 레이아웃이 이미 트리와 일치합니다. 읽기는 그래프 순회가 아니라 연속된 슬라이스에 대한 접두사 스캔이 됩니다.

그것이 핵심의 전환입니다. 정렬 키는 정확히 일치시켜야 하는 식별자가 아닙니다. 정렬 가능한 주소입니다. 잘 설계하면 쿼리가 거저 따라 나옵니다.

파일 시스템 트리 모델링

계정별 파일 트리를 저장한다고 합시다. 계정당 하나의 드라이브가 자연스러운 파티션이고, 그 안의 경로가 정렬 키입니다.

PKSKnode_typebytes
DRIVE#a91root/folder-
DRIVE#a91root/photos/folder-
DRIVE#a91root/photos/2026/folder-
DRIVE#a91root/photos/2026/beach.jpgfile284910
DRIVE#a91root/photos/2026/sunset.jpgfile512004
DRIVE#a91root/docs/folder-
DRIVE#a91root/docs/taxes.pdffile88210

여기서 일하는 두 가지 본래의 관례:

  • 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 Expression Builder에서 만들고 복사하세요.

DynoTable에서 경로 정렬 키에 begins_with 쿼리를 실행하여, 폴더와 그 자손을 경로 순서로 반환하는 모습.
DynoTable에서 경로 정렬 키에 begins_with 쿼리를 실행하여, 폴더와 그 자손을 경로 순서로 반환하는 모습.

서브트리 전체가 아니라 한 레벨만 나열하기

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를 참고하세요.

Expression Builderbegins_with 키 조건을 만든 다음, DynoTable을 다운로드하여 이런 접두사 쿼리를 자신의 테이블에서 실행하고 서브트리가 경로 순서로 돌아오는 모습을 지켜보세요.

업데이트됨