중급6분 분량

DynamoDB 정렬 키 전략: 3가지 패턴과 선택 기준

DynamoDB 기본 키는 하나 또는 두 개의 속성입니다: 단독이거나, 파티션 키 더하기 정렬 키입니다. 파티션 키는 어느 물리 파티션이 항목을 담는지 결정합니다.

정렬 키는 그 파티션 안에서 항목의 순서를 결정합니다 — 그리고 그 순서가 Query를 강력하게 만드는 요소입니다.

정렬 키를 잘못 고르면 데이터를 쓸 수는 있어도, 하나의 컬렉션에서 범위 읽기, 정렬, 여러 액세스 패턴을 잃습니다.

SQL에서 넘어오면 사후에 ORDER BY나 보조 인덱스에 손이 갑니다. DynamoDB에서는 순서를 미리 키에 구워 넣거나, 아니면 얻지 못합니다.

DynamoDB 정렬 키는 어떻게 작동하나요?

DynamoDB 정렬 키는 파티션 안에서 항목을 정렬하므로, Query가 항목을 하나씩 가져오는 대신 범위 읽기 — >=, between, begins_with — 를 할 수 있습니다. 문자열 정렬 키는 UTF-8 바이트로 정렬되고(Number는 숫자로 정렬됨), 따라서 바이트 순서가 여러분이 읽고 싶은 순서와 같아지도록 문자열 키(ISO-8601 타임스탬프, 0으로 채운 숫자)를 설계하세요.

  • 정렬 키는 여러분의 파티션 내 인덱스입니다. 디스크상의 을 정렬하므로, Query가 단일 GetItem이 아니라 범위 읽기(>=, between, begins_with)를 할 수 있습니다.
  • 문자열 정렬 키는 UTF-8 바이트로 정렬됩니다(Number는 숫자로 정렬됨). 바이트 순서가 읽고 싶은 순서와 같아지도록 문자열 키를 설계하세요 — ISO-8601 타임스탬프, 0으로 채운 숫자, 결코 날것의 UUID나 6/23/2026이 아니라.
  • 잘 빚어진 정렬 키 하나가 많은 액세스 패턴을 서비스합니다. (EVT#<timestamp>)는 접두사이자 범위입니다 — GSI가 필요 없습니다.
  • 방향은 공짜입니다. ScanIndexForward = false는 같은 비용으로 최신순으로 읽습니다. 그것을 흉내 내려고 뒤집은 타임스탬프를 저장하지 마세요.

왜 정렬 키가 지렛대인가

정렬 키가 없으면, 파티션의 모든 항목은 전체 기본 키로만 주소 지정할 수 있습니다 — 잘해야 GetItem입니다. 정렬 키를 추가하면 DynamoDB는 항목을 파티션 안에서 그것으로 정렬해 저장하며, 이것이 Query를 잠금 해제합니다.

즉 범위 조건(>=, between), 접두사 매칭(begins_with), 그리고 오름차순이나 내림차순으로 읽는 ScanIndexForward 플래그입니다.

AWS DynamoDB 개발자 안내서에 따르면, 하나의 파티션 키를 공유하는 모든 항목은 항목 컬렉션을 이루며, 디스크상에서 정렬 키로 정렬됩니다.

그러니 정렬 키는 두 번째 식별자에 불과한 게 아닙니다. 파티션 안에서 여러분이 쿼리하는 대상인 인덱스입니다.

그 순서는 인코딩된 정렬 키에 대한 바이트 순서입니다: 문자열은 UTF-8 바이트로 비교되고, 숫자는 숫자로 비교됩니다. 이 한 가지 사실이 아래의 거의 모든 전략을 이끕니다.

범위 쿼리가 의미를 가지려면, 바이트 순서가 여러분이 읽고 싶은 순서와 일치해야 합니다.

전략 1: 정렬 키를 정렬 가능하게 만들기

가장 흔한 실수는 의미 있게 정렬되지 않는 정렬 키입니다. 무작위 UUID는 고유성을 주지만 유용한 범위 쿼리는 주지 않습니다 — 바이트 순서가 임의적이라 "마지막 20개를 달라"가 불가능해집니다.

대신, 여러분이 정렬하고 필터하는 값을, 바이트 순서가 논리적 순서와 같은 표현으로 정렬 키 안에 인코딩하세요. 타임스탬프라면 사전식으로 정렬 가능한 형식을 뜻합니다: ISO-8601 문자열이나 0으로 채운 에포크입니다.

ISO-8601은 문자열 비교가 연대순 비교와 같도록 설계되었습니다 — 정확히 범위 쿼리가 필요로 하는 것입니다. 6/23/2026 같은 형식은 피하세요. 달이 넘어가는 순간 잘못 정렬됩니다.

숫자(버전 카운터, 점수)로 정렬한다면, 429보다 앞이 아니라 뒤에 정렬되도록 문자열이 아니라 DynamoDB의 네이티브 Number 타입을 사용하세요.

숫자가 복합 문자열 정렬 키 안에 살아야 한다면, 고정 너비까지 0으로 채우세요.

전략 2: 계층 구조를 위한 복합 정렬 키

정렬 키는 세그먼트를 구분자로 — 가장 흔하게는 # — 연결해 계층 구조를 인코딩할 수 있습니다. 그러면 하나의 begins_with 조건이 하위 트리 전체를 선택합니다:

SK
EVENT#2026-06#01#login
EVENT#2026-06#03#export
EVENT#2026-07#02#login

begins_with(SK, "EVENT#2026-06#")는 6월의 이벤트만 반환합니다. 더 넓은 begins_with(SK, "EVENT#")는 전부 반환합니다.

세그먼트 순서는 설계 결정입니다. 굵은 것에서 세밀한 것으로(연 → 월 → 일)는 관련 항목을 인접하게 유지하므로, 범위 읽기가 파티션 곳곳으로 흩어지는 대신 저렴한 쿼리 하나로 남습니다.

전략 3: ScanIndexForward로 방향 제어하기

DynamoDB는 항목을 오름차순 정렬 키 순서로 저장하고 기본적으로 그렇게 읽습니다. 최신순으로 — 활동 피드의 자연스러운 순서 — 읽으려면, QueryScanIndexForward = false를 설정하세요.

이것은 스키마 결정이 아니라 읽기 시점 플래그입니다: 같은 컬렉션이 같은 비용으로 두 방향 모두를 서비스합니다. 내림차순 읽기를 얻으려고 타임스탬프를 반전("역 에포크"를 저장)하지 마세요.

하나의 항목 컬렉션, 오름차순으로 한 번 저장되어, 어느 방향으로든 읽힙니다:

ScanIndexForward = trueScanIndexForward = false항목 컬렉션 (PK 하나)SK EVT#09:00SK EVT#14:00SK EVT#next-day오래된 먼저최신 먼저

같은 항목, 같은 파티션, 같은 비용 — 읽기 방향만 다릅니다.

실제 예제: 액터 범위 감사 로그

SaaS 제품에서 액터 — 사용자, 서비스, API 키 — 가 생성한 타임스탬프 이벤트를 기록하고, 두 가지 읽기가 있다고 합시다:

  1. 한 액터의 활동 스트림, 최신 이벤트 우선.
  2. 조사를 위한, 시간 창 안의 한 액터 이벤트(예: "두 배포 사이의 모든 것").

두 읽기 모두 단일 액터에 범위 지정되므로, 액터가 파티션 키이고 이벤트 시간이 정렬 키입니다. 같은 테이블이 나중에 다른 엔터티도 담을 수 있도록 일반적인 키 이름을 사용하세요:

PKSKattributes
ACTOR#u_8814EVT#2026-06-23T09:12:04Zaction=login, ip, ua
ACTOR#u_8814EVT#2026-06-23T14:05:11Zaction=export, target
ACTOR#u_8814EVT#2026-06-24T08:40:55Zaction=login, ip, ua
ACTOR#svc_billingEVT#2026-06-23T00:00:00Zaction=invoice.run

EVT# 접두사에 ISO-8601 타임스탬프를 더하면 정렬 가능한 정렬 키가 됩니다. 읽기 1은 최신순을 위한 ScanIndexForward = false를 붙인 Query PK = "ACTOR#u_8814"입니다. 읽기 2는 정렬 키에 대한 between 조건으로 같은 파티션을 좁힙니다:

Query
PK = "ACTOR#u_8814"
AND SK BETWEEN "EVT#2026-06-23T00:00:00Z"
AND "EVT#2026-06-23T23:59:59Z"

하나의 컬렉션, 두 개의 액세스 패턴, GSI 없음 — 정렬 키가 접두사(EVT#)이자 범위(타임스탬프) 이기 때문입니다. 내림차순 읽기와 창 읽기는 같은 순서의 같은 항목입니다. 매개변수만 다릅니다.

그 키 조건을 손으로 만들면, between 경계를 헛짚거나 속성 이름의 예약어 이스케이프를 그르치기 쉽습니다.

DynamoDB Expression Builderbegins_withbetween 정렬 키 조건에 대한 KeyConditionExpression, ExpressionAttributeNames, ExpressionAttributeValues를 생성합니다.

런타임에 이스케이프를 디버깅하는 대신, 그것을 곧바로 여러분의 SDK 호출에 복사하세요.

DynoTable에서 해보기

정렬 키 설계는 반복적입니다: 대표적인 항목을 몇 개 쓰고, 범위 쿼리를 실행하고, 행이 기대하는 순서로 돌아오는지 확인합니다. GUI에서 살아 있는 테이블에 대해 그것을 하는 편이 코드로 왕복하는 것보다 낫습니다.

DynoTable에서 정렬 키에 대한 between 조건으로 액터의 감사 로그 컬렉션을 쿼리하며, 결과는 최신순으로 정렬됨.
DynoTable에서 정렬 키에 대한 between 조건으로 액터의 감사 로그 컬렉션을 쿼리하며, 결과는 최신순으로 정렬됨.

정렬 방향을 뒤집고, between 경계를 좁히고, 코드 한 줄 쓰지 않고 반환되는 컬렉션이 바뀌는 것을 지켜보세요 — 커밋하기 전에 정렬 키 설계를 확인하는 가장 빠른 방법입니다.

함정과 다음 단계

  • 정렬 키는 파티션 안에서 고유해야 합니다. 두 이벤트가 타임스탬프를 공유할 수 있다면, 복합이 고유하게 유지되도록 정렬 키에 구별자(시퀀스 번호나 짧은 id)를 덧붙이세요.
  • 핫 파티션은 정렬로 우회할 수 없습니다. 한 액터가 나머지보다 훨씬 많은 이벤트를 생성하면, 정렬 키는 여러분을 구하지 못합니다 — 부하를 퍼뜨리는 파티션 키 설계가 필요합니다. 싱글 테이블 디자인을 참고하세요.
  • 두 번째 정렬 순서에는 두 번째 인덱스가 필요합니다. 베이스 테이블의 정렬 키는 하나의 순서를 줍니다. 같은 항목을 다르게(가령 이벤트 유형으로) 정렬하려면, 다른 정렬 키를 가진 GSI를 추가하고 — 로컬 대 글로벌 보조 인덱스 트레이드오프를 저울질하세요.
  • "나중에 정렬"하려고 Scan에 손대지 마세요. Scan 후 클라이언트 측에서 정렬하면 전체 테이블을 읽고 순서를 버립니다. 그것이 Scan 위험 요소입니다. 대신 순서를 정렬 키에 밀어 넣으세요.

키 조건이 맞으면, DynoTable을 사용해 컬렉션을 모델링하고, 오름차순과 내림차순 쿼리를 나란히 실행하고, 출시 전에 실제 데이터에 대해 정렬 키 전략을 검증하세요.

업데이트됨