중급4분 분량

DynamoDB 키 조건 표현식

키 조건 표현식은 여러분이 Query에 전달하는 KeyConditionExpression입니다 — DynamoDB가 항목을 찾는 데 사용하는 요청의 유일한 부분입니다. 나머지 모든 것(필터, 프로젝션)은 읽기가 이미 계량된 후에 실행됩니다.

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

키 조건 표현식은 QueryKeyConditionExpression으로, DynamoDB에게 어떤 항목을 읽을지 알려줍니다. 는 등식(PK = :v)이어야 하고, 는 하나의 범위 연산자 — =, <, <=, >, >=, BETWEEN, 또는 begins_with — 를 취합니다. 필터와 달리, 무엇이 읽히고 청구되는지를 결정합니다.

  • 는 등식이어야 합니다. PK = :v, 그 외에는 없습니다 — 범위도, begins_with도, IN도 안 됩니다. DynamoDB는 이를 해싱하여 하나의 파티션을 찾습니다.
  • 는 범위 연산자를 취합니다. =, <, <=, >, >=, BETWEEN, 또는 begins_with — 여기가 을 잘라내는 곳입니다.
  • 필터가 아닙니다. 키 조건은 무엇이 읽히고 청구되는지를 결정합니다. 반면 FilterExpression은 여러분이 읽기 비용을 치른 후에 결과를 다듬을 뿐입니다.
  • 정렬 키는 바이트 순서입니다. 범위 연산자는 사전식으로 비교하므로, 정렬 키 문자열을 어떻게 포맷하느냐가 여러분의 쿼리 능력입니다.

파티션 키가 등식에 묶여 있는 이유

DynamoDB는 파티션 키를 해싱하여 물리적 파티션으로 항목을 저장합니다. 해시는 범위가 아니라 하나의 위치를 줍니다 — 그래서 가로질러 스캔할 것이 없습니다.

그것이 PK > :vbegins_with(PK, :v)가 즉각 거부되는 이유입니다. 엔진은 테이블 전체를 읽지 않고는 "키가 X로 시작하는 모든 파티션"에 답할 수 없으며, 그것이 바로 DynamoDB가 피하도록 만들어진 Scan입니다.

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

정렬 키가 능력이 사는 곳

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

연산자읽는 것용도
SK = :v정확한 항목 하나키로 지정한 특정 자식
SK < / <= / > / >= :v한쪽이 열린 조각 하나"이 지점 이후의 모든 것"
SK BETWEEN :a AND :b닫힌 범위 (양끝 포함)경계 지어진 창 — 날짜 범위
begins_with(SK, :p)접두사 조각PK 아래의 타입이나 계층

키에는 LIKE도, CONTAINS도, ENDS_WITH도 없습니다. 부분 문자열과 접미사 매칭은 바이트 순서가 아니어서 전체 읽기를 강제하므로 — 설계상 API가 허용하지 않습니다. 부분 문자열 매칭은 FilterExpressioncontains()로 존재하지만(이미 읽기 비용을 치른 곳입니다), 접미사 매칭은 서버 측에서 아예 제공되지 않습니다 — 뒤집힌 키를 저장하거나 클라이언트 측에서 필터링하세요. (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은 양쪽 경계를 포함하므로 끝점을 신중하게 고르세요 — 여기서의 하나 차이는 가장자리 메시지를 조용히 떨어뜨리거나 중복시킵니다.

이 표현식 중 어느 것이든, ExpressionAttributeValues 맵을 대신 채운 채로, DynamoDB 표현식 빌더에서 조립하고 복사할 수 있습니다 — begins_withBETWEEN 구문을 처음부터 올바르게 잡는 데 편리합니다.

이 빌더는 pk = … AND begins_with(sk, …) 쿼리로 사전 설정되어 있습니다 — 연산자를 바꿔 KeyConditionExpression이 갱신되는 것을 보세요.

요청 만들기
생성된 코드
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#2026-06"
    }
  }
})

DynoTable에서 보기

같은 키 조건을 실제 채널 파티션에 대해 실행하세요. 파티션 키 필터를 설정하는 순간 DynoTable이 Query를 발행합니다 — 그래서 컬렉션 전체가 아니라 그 조각만 로드합니다.

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

값비싼 실수는 키가 할 일을 하려고 FilterExpression에 손을 뻗는 것입니다. 필터는 PostedAt을 참조조차 할 수 없습니다 — 그것은 정렬 키이고, DynamoDB는 키 속성에 대한 필터를 ValidationException으로 거부합니다. 그래서 우회책은 날짜를 평범한 비키 속성(MessageDate)으로 복제하여 대신 그것으로 필터링하는 것입니다.

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

이것은 위의 begins_with 키 조건과 동등해 보이고 같은 행을 반환합니다 — 하지만 전체 채널 파티션을 먼저 읽은 뒤, 그날 바깥의 모든 것을 버립니다. 여러분은 전체 읽기에 대해 청구됩니다.

필터는 결코 읽기 비용을 줄이지 않습니다. DynamoDB가 항목을 계량한 후에 실행되며, 필터링된 Scan과 같은 지뢰입니다. 어떤 술어가 키 조건에 들어갈 수 있다면, 거기에 속합니다.

해결책은 상류에 있습니다. 어떤 액세스 패턴을 하나의 PK 등식에 정렬 키 범위를 더한 것으로 표현할 수 없다면, 그것은 모델링 신호입니다. 정렬 키를 다시 빚거나, 그 패턴에 맞게 키가 지정된 인덱스를 추가하세요 — 키를 어떻게 배치할지는 GSI 대 LSI싱글 테이블 디자인을 참고하세요.

함정과 다음 단계

  • 파티션 키는 항상 =입니다. 범위는 절대 안 됩니다. 파티션에 걸친 범위가 필요하다면, 단일 Query를 넘어선 것입니다.
  • 쿼리당 정렬 키 조건 하나. 두 정렬 키 술어를 AND할 수 없습니다. BETWEEN 또는 begins_with를 고르되, 둘 다는 안 됩니다.
  • 예약어에는 별칭이 필요합니다. TimestampName으로 명명된 키는 ExpressionAttributeNames(#ts)를 사용해야 하며, 그렇지 않으면 쿼리가 오류를 냅니다. (AWS: 예약어)
  • BETWEEN은 포함입니다. 양쪽 끝점이 매칭됩니다 — 그에 맞게 경계를 설계하세요.

표현식 빌더에서 키 조건의 초안을 잡은 뒤, DynoTable을 사용해 보고 여러분의 테이블에 대해 실행하여 각 키 조건이 정확히 어떤 조각을 반환하는지 확인하세요.

업데이트됨