중급4분 분량

DynamoDB 키 조건 표현식

키 조건 표현식(key condition expression)Query에 넘기는 KeyConditionExpression입니다 — 요청에서 DynamoDB가 아이템을 찾는 데 쓰는 유일한 부분이죠. 나머지(필터, 프로젝션)는 읽기가 이미 계측된 후에 실행됩니다.

DynamoDB에서 키 조건 표현식이란 무엇인가요?

키 조건 표현식은 Query에 넘기는 KeyConditionExpression으로, DynamoDB가 어떤 아이템을 읽을지 알려줍니다. 는 반드시 동등 비교(PK = :v)여야 하고, =, <, <=, >, >=, BETWEEN, 또는 begins_with 중 하나의 범위 연산자를 받습니다. 필터와 달리, 무엇이 읽히고 과금되는지를 결정하는 부분입니다.

  • 파티션 키는 반드시 동등 비교입니다. PK = :v, 그뿐 — 범위도, begins_with도, IN도 없습니다. DynamoDB가 그것을 해시해 한 파티션을 찾습니다.
  • 정렬 키는 범위 연산자를 받습니다. =, <, <=, >, >=, BETWEEN, 또는 begins_with — 여기가 아이템 컬렉션을 잘라 내는 곳입니다.
  • 필터가 아닙니다. 키 조건은 무엇이 읽히고 과금되는지를 결정하고, FilterExpression은 읽기에 값을 치른 후 결과를 다듬을 뿐입니다.
  • 정렬 키는 바이트 순서입니다. 범위 연산자는 사전식으로 비교하므로, 정렬 키 문자열을 어떻게 형식화하느냐 곧 쿼리 능력입니다.

파티션 키가 동등 비교에 묶이는 이유

DynamoDB는 파티션 키를 물리 파티션으로 해시해 아이템을 저장합니다. 해시는 범위가 아니라 하나의 위치를 주므로 — 가로질러 스캔할 게 없습니다.

그래서 PK > :vbegins_with(PK, :v)가 단호히 거부됩니다. 엔진은 테이블 전체를 읽지 않고는 "키가 X로 시작하는 모든 파티션"에 답할 수 없는데, 그게 바로 그것이 피하도록 만들어진 Scan입니다.

SQL에서 왔다면 이게 거꾸로 느껴집니다. Postgres에서 WHERE id LIKE 'order%'는 사소하니까요. DynamoDB에서 파티션 키는 검색 가능한 컬럼이 아니라 주소입니다.

정렬 키가 능력이 사는 곳

한 파티션 안에서 아이템은 정렬 키로 정렬되어 저장됩니다. 그 순서가 범위 연산자가 활용하는 것입니다 — DynamoDB가 한 위치로 탐색해 앞으로 읽어 나가죠.

연산자읽는 것용도
SK = :v정확한 아이템 하나키로 지목한 특정 자식
SK < / <= / > / >= :v한쪽이 열린 슬라이스"이 지점 이후 전부"
SK BETWEEN :a AND :b닫힌 범위(양끝 포함)한정된 구간 — 날짜 범위
begins_with(SK, :p)접두사 슬라이스PK 아래의 한 유형이나 계층

키에는 LIKE도, CONTAINS도, ENDS_WITH도 없습니다. 부분 문자열과 접미사 매칭은 바이트 순서가 아니라 전체 읽기를 강요할 테니 — 설계상, API가 허락하지 않습니다. 그것들은 이미 값을 치른 FilterExpression에 삽니다. (AWS: 키 조건 표현식)

실전 예제: 채팅 앱의 메시지

채널 기반 채팅을 짓는다고 합시다. 하나의 테이블, 채널로 파티셔닝되고, 메시지 시간으로 정렬됩니다. 독자적인 키 스키마:

  • 파티션 키 ChannelRefCH#{channelId}
  • 정렬 키 PostedAt — ISO-8601 타임스탬프, MSG#2026-06-23T14:05:00Z

MSG# 접두사는 메시지 행을 정렬 가능하게 유지하고, 같은 채널 아래 함께 둘 수 있는 다른 행 유형(고정 설정, 멤버십)과 구별되게 합니다.

채널의 최신 메시지 불러오기. 파티션 키만, 최신순:

KeyConditionExpression:  ChannelRef = :ch
ExpressionAttributeValues:  { ":ch": "CH#general" }
ScanIndexForward:  false

ScanIndexForward: false는 정렬된 컬렉션을 거꾸로 훑습니다 — 클라이언트 측 정렬 없이 "가장 최근 먼저"를 얻는 저렴한 방법이죠.

begins_with로 특정 날. 타임스탬프가 정렬 키이고 텍스트로 저장되므로, 날짜 접두사가 깔끔한 슬라이스입니다:

KeyConditionExpression  ChannelRef = :ch AND begins_with(PostedAt, :day)
:ch    "CH#general"
:day   "MSG#2026-06-23"

이것은 2026-06-23의 모든 메시지를, 그리고 그것만 읽습니다 — DynamoDB가 접두사로 탐색하고 그 끝에서 벗어나면 멈춥니다. 이것이 작동하는 건 오직 접두사가 바이트로 정렬된 문자열의 진정한 왼쪽 앵커이기 때문입니다.

BETWEEN으로 정밀한 구간. "14:00 시간대의 메시지"에는 접두사보다 포함 범위가 낫습니다:

KeyConditionExpression  ChannelRef = :ch AND PostedAt BETWEEN :lo AND :hi
:ch    "CH#general"
:lo    "MSG#2026-06-23T14:00:00Z"
:hi    "MSG#2026-06-23T14:59:59Z"

BETWEEN은 양 경계를 포함하므로, 끝점을 신중히 고르세요 — 여기서 off-by-one은 경계 메시지를 조용히 떨어뜨리거나 두 배로 셉니다.

DynamoDB 표현식 빌더에서 이 표현식 중 무엇이든 ExpressionAttributeValues 맵을 채운 채로 조립하고 복사할 수 있습니다 — begins_withBETWEEN 문법을 처음부터 맞게 잡는 데 유용합니다.

DynoTable에서 직접 보기

같은 키 조건을 실제 채널 파티션에 실행하고, 소비된 용량이 라이브로 갱신되는 걸 지켜보세요. 그래야 컬렉션 전체가 아니라 슬라이스를 읽고 있음을 확인할 수 있습니다.

함정: 키 조건을 필터와 혼동하기

비싼 실수는 키의 일을 하려고 FilterExpression에 손을 뻗는 것입니다.

KeyConditionExpression:  ChannelRef = :ch
FilterExpression:        begins_with(PostedAt, :day)

이건 위의 begins_with 키 조건과 같아 보이고 같은 행을 돌려줍니다 — 하지만 채널 파티션 전체를 먼저 읽고, 그날 밖의 모든 것을 버립니다. 전체 읽기에 과금됩니다.

필터는 결코 읽기 비용을 줄이지 않습니다. DynamoDB가 아이템을 계측한 후에 실행되니까요 — 필터된 Scan과 같은 함정이죠. 술어가 키 조건에 들어갈 수 있다면, 거기에 속합니다.

해결은 상류에 있습니다. 액세스 패턴을 하나의 PK 동등 비교 더하기 정렬 키 범위로 표현할 수 없다면, 그건 모델링 신호입니다. 정렬 키를 다시 빚거나, 그 패턴에 맞춰 키잉된 인덱스를 추가하세요 — 키를 어떻게 배치할지는 GSI vs LSI싱글 테이블 디자인을 보세요.

함정과 다음 단계

  • 파티션 키는 항상 =입니다. 범위는, 절대로, 안 됩니다. 파티션을 가로지르는 범위가 필요하다면, 단일 Query를 벗어난 것입니다.
  • 쿼리당 정렬 키 조건 하나. 두 정렬 키 술어를 AND할 수 없습니다. BETWEEN 또는 begins_with를 고르되, 둘 다는 안 됩니다.
  • 예약어는 별칭이 필요합니다. TimestampName으로 이름 지은 키는 ExpressionAttributeNames(#ts)를 써야 합니다 — 안 그러면 쿼리가 오류납니다. (AWS: 예약어)
  • BETWEEN은 포함적입니다. 양 끝점이 일치하므로 — 그에 맞춰 경계를 설계하세요.

표현식 빌더에서 키 조건을 초안 잡은 뒤, DynoTable을 써보세요. 여러분 테이블에 실행하고 그것이 실제로 소비하는 용량을 지켜보세요.

업데이트됨