중급6분 분량

DynamoDB 필터링 전략

DynamoDB에서 "필터링"은 같은 단어를 쓰는 네 가지 서로 다른 것을 의미합니다. 셋은 읽고 과금되기 전에 데이터를 좁히고, 하나 — Filter라는 이름을 가진 그것 — 은 후에 좁힙니다. 어느 것이 어느 것인지 아는 것이 이 기술의 대부분입니다.

DynamoDB에서 필터링은 어떻게 동작하나요?

DynamoDB에는 필터링하는 네 가지 방법이 있고, 그중 단 하나만 과금된 후에 실행됩니다. 파티션 키는 파티션을 고르고, 정렬 키는 슬라이스를 좁히며, 희소 인덱스는 속성의 존재로 필터링합니다 — 이 셋 모두 계량 전에 읽기 비용을 줄입니다. FilterExpression은 읽기 후에 실행되므로 응답은 줄이지만 청구서는 절대 줄이지 않습니다.

  • 파티션 키가 가장 저렴한 필터입니다: 파티션을 고르므로 테이블의 나머지는 절대 건드리지 않습니다.
  • 정렬 키는 파티션 안에서 begins_with, between, <, >로 필터링합니다 — 여전히 과금 전이고, 여전히 저렴합니다.
  • 희소 인덱스는 부재로 필터링합니다: 아이템은 인덱싱된 속성을 가질 때만 인덱스에 나타나므로, 인덱스가 필터링된 집합입니다.
  • **FilterExpression**이 함정입니다: DynamoDB가 읽기를 계량한 후 실행되므로 응답 크기는 줄이지만 비용은 절대 줄이지 않습니다.

예제 설정

제품 카탈로그입니다. 하나의 테이블, 파티션 키 PK, 정렬 키 SK:

PK = "DEPT#kitchen"   SK = "PROD#00194"

모든 제품은 price, inStock(불리언), clearanceAt(unix 타임스탬프, 클리어런스로 표시된 아이템에만 존재)도 함께 가집니다. 한 부서의 아이템은 파티션을 공유하며 제품 id로 정렬됩니다.

네 가지 액세스 패턴을 원합니다. 각각이 다른 필터링 전략에 매핑되며 — 어느 하나에서든 잘못된 선택은 영원히 비용을 낼 Scan입니다.

파티션 키로 필터링하기

"kitchen의 모든 제품을 줘." 파티션 키가 이를 직접 처리합니다:

Query  PK = "DEPT#kitchen"

DynamoDB는 정확히 하나의 파티션을 읽습니다. 테이블의 다른 어떤 것도 건드려지거나 과금되지 않습니다. 이것이 중요한 의미에서 무료인 유일한 필터입니다 — QueryScan의 차이입니다.

SQL에서 넘어왔다면 이것은 거꾸로처럼 느껴집니다: 인덱스를 스캔하는 WHERE department = 'kitchen'이 없고, 그냥 파티션을 이름으로 부릅니다. 이름을 부를 수 없다면, 그것은 쿼리 문제가 아니라 모델링 문제입니다.

정렬 키로 필터링하기

"PROD#00100부터의 kitchen 제품을 줘." 정렬 키는 파티션 안에서 좁히며, 읽기가 계량되기 전에 그렇게 합니다:

Query  PK = "DEPT#kitchen"  AND  SK between "PROD#00100" AND "PROD#00200"

정렬 키 조건은 의도적으로 제한됩니다: =, <, <=, >, >=, between, begins_with. OR도, 임의의 술어도 없습니다.

그 제약이 읽기를 겨냥된 상태로 유지하는 것입니다 — DynamoDB는 파티션 전체가 아니라 연속된 슬라이스를 훑습니다.

여기서의 지렛대는 정렬 키를 어떻게 인코딩하느냐입니다. 패턴이 "가격대별"이라면 PROD#<id> 정렬 키는 도움이 안 됩니다 — 가격을 키에 구워 넣어야 합니다.

그것은 쿼리 시점이 아니라 설계 시점에 내리는 정렬 키 전략 결정입니다.

희소 인덱스로 필터링하기

"지금 클리어런스 중인 모든 것을 줘." 대부분의 제품은 그렇지 않으므로, 몇 안 되는 것을 찾으려고 카탈로그를 읽고 싶지 않습니다.

희소 인덱스는 이를 부재로 해결합니다. 글로벌 보조 인덱스는 아이템이 인덱스의 키 속성을 둘 다 가질 때만 그 아이템을 포함합니다.

GSI 파티션 키를 clearanceAt으로 — 클리어런스 아이템에만 존재 — 설정하면, 인덱스는 그 외에 아무것도 담지 않습니다.

AWS는 이를 명시합니다: GSI는 "인덱싱된 속성을 가진 아이템만 포함"하므로, 키 속성이 없는 아이템은 그냥 전파되지 않습니다 (AWS — 희소 인덱스 활용하기).

아니오베이스 테이블 모든 제품clearanceAt 있음?ClearanceIndex로 복제됨인덱스에 없음인덱스 쿼리 = 클리어런스 아이템만

이제 쿼리는 클리어런스 아이템만 읽고, 그것에 대해서만 과금됩니다:

Query  ON ClearanceIndex   GSI_PK = "CLEARANCE"   (sorted by clearanceAt)

필터는 당신이 데이터를 때 일어났습니다 — clearanceAt을 설정할지 말지 선택하면서. 인덱스가 곧 필터링된 집합입니다. 어느 인덱스 유형이 맞는지는 GSI vs LSI를 참고하세요.

FilterExpression으로 필터링하기

"재고가 있는 kitchen 제품을 줘." inStock은 키 속성이 아니므로 FilterExpression에 손을 뻗습니다:

Query  PK = "DEPT#kitchen"
Filter inStock = true

여기 함정이 있습니다. DynamoDB는 kitchen 파티션의 모든 아이템을 읽고, 그 모두에 대해 용량을 계량하고, 그 다음 재고 없는 것들을 떨굽니다.

공식 규칙: 필터 표현식은 "Query가 끝난 후, 결과가 반환되기 전에 적용"되며 "어떤 추가 읽기 용량 단위도 소비하지 않습니다" — 전체 읽기에 대해 이미 비용을 냈으니까요 (AWS — Query의 필터 표현식).

그래서 kitchen에 10,000개 제품이 있고 12개가 재고에 있다면, 10,000개를 읽는 비용을 냅니다. 응답은 작지만, 청구서는 작지 않습니다. FilterExpression은 회선을 건너는 페이로드를 줄일 뿐, 읽기는 절대 줄이지 않습니다.

두 번째, 더 날카로운 모서리가 있습니다: 페이지네이션은 필터링 전에 계량됩니다. 한 페이지는 일치 항목 1 MB가 아니라 읽은 아이템 1 MB입니다.

필터는 LastEvaluatedKey가 설정된 빈 페이지를 반환할 수 있습니다 — DynamoDB가 1 메가바이트를 통째로 읽고, 아무것도 일치시키지 못하고, 빈 배열을 건넵니다. 계속 페이지를 넘기고, 모든 빈 페이지에 비용을 냈습니다.

표현식 — 이름, 값, 올바른 예약어 이스케이프 — 을 DynamoDB Expression Builder로 만들어 #inStock/:val 자리표시자가 첫 시도에 올바르게 되도록 하세요.

넷을 비교하기

언제 필터링하나읽기 비용 줄임?술어 능력설정 비용
파티션 키읽기 전예 — 한 파티션동등성만무료(키 그 자체)
정렬 키읽기 전예 — 슬라이스범위 / begins_with정렬 키 설계
희소 인덱스읽기 전예 — 인덱스만속성의 존재추가 GSI + 쓰기 비용
**FilterExpression읽기 후아니오**거의 모든 조건없음

표를 위에서 아래로 읽으세요: 술어 능력은 올라가고, 비용 통제는 내려갑니다. FilterExpression은 이미 읽은 아이템에서 실행되기 때문에 무엇이든 정확히 표현할 수 있습니다 — 그것이 바로 비용을 절약할 수 없는 같은 이유입니다.

DynoTable에서 보기

필터를 걸어 Query를 실행할 때, 읽은 아이템과 반환된 아이템 사이의 간극이 핵심 전부입니다. DynoTable은 소비된 용량을 결과 수 옆에 드러냅니다 — 그래서 파티션 전체를 조용히 읽는 필터가 월별 청구서에 숨지 않고 보입니다.

필터가 답할 수 없는 진짜 아이템 간 질문 — "부서별 평균 가격", "리뷰와 조인된 재고 제품" — 에 대해, DynoTable의 SQL Workbench는 테이블 전체 Scan으로 컴파일되는 대신 한정된 결과 집합에 대해 GROUP BY, JOIN, 집계를 클라이언트 쪽에서 실행합니다.

함정과 다음 단계

  • FilterExpression을 주요 액세스 경로로 쓰지 마세요. 패턴이 흔하다면 키나 희소 인덱스로 모델링하세요. 필터는 마지막 자투리 좁히기를 위한 것이지, 그 대부분을 위한 것이 아닙니다.
  • 빈 페이지를 주의하세요. 필터를 건 쿼리는 아무것도 반환하지 않으면서 오래 페이지를 넘길 수 있습니다. LastEvaluatedKey를 존중하세요. 빈 페이지가 "끝"을 의미한다고 가정하지 마세요.
  • 희소 인덱스도 공짜가 아닙니다. 거기에 떨어지는 모든 아이템에 대해 쓰기 용량과 스토리지가 듭니다 — 속성이 드물 때는 저렴하고, 그렇지 않을 때는 덜 저렴합니다.

필터를 건 읽기가 실제로 얼마나 들지를 용량 계산기로 추정하고, DynoTable을 사용해 자신의 테이블에서 소비된 용량을 반환된 행과 대비해 지켜보세요.

업데이트됨