Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

67장. DynamoDB 인덱스 — LSI · GSI

이 장에서 말하고자 하는 것

앞 장에서 우리는 DynamoDB가
“파티션 키 + 정렬 키” 로 데이터를 꺼낸다는 걸 봤다.

그런데 운영을 하다 보면 같은 데이터에 대해
다른 키로 조회하고 싶을 때 가 생긴다.

orders 테이블
  Primary Key: (user_id, created_at)

질문 1: "u-1 사용자의 주문 목록" → Primary Key로 가능
질문 2: "status='paid' 인 주문 목록" → Primary Key로 불가능
질문 3: "상품 id로 주문 검색" → 불가능

이걸 푸는 도구가

인덱스 (Index)

다.

DynamoDB는 두 종류의 인덱스를 제공한다.

  • LSI (Local Secondary Index)
  • GSI (Global Secondary Index)

1. 큰 그림

테이블의 Primary Key      = "주된 조회 경로"
인덱스(LSI / GSI)         = "추가 조회 경로"

인덱스는 원본 테이블의 읽기 전용 사본 처럼 동작한다.

DynamoDB가 자동으로 동기화해준다.


2. LSI — Local Secondary Index

같은 파티션 키를 공유하면서
다른 정렬 키 로 한 번 더 보고 싶을 때.

원본:    (user_id, created_at)
LSI:     (user_id, total)
"u-1의 주문을 금액 큰 순으로" → LSI로 Query

특징:

  • 파티션 키는 원본과 같다
  • 테이블 만들 때만 생성 가능 (나중에 추가 불가)
  • 같은 파티션 키 안에서 최대 10GB
  • 사용 빈도가 점점 줄어들고 있다 (GSI가 더 유연)

3. GSI — Global Secondary Index

완전히 다른 파티션 키 + 정렬 키 로 한 번 더 본다.

원본:  (user_id, created_at)
GSI:   (status, created_at)
"status='paid' 인 주문을 최신순으로" → GSI로 Query

특징:

  • 파티션 키도 정렬 키도 자유롭게 선택
  • 언제든 추가 · 삭제 가능
  • 별도의 처리량 / 비용
  • 비동기 복제 (약간의 지연 가능)

운영에서 추가 조회 경로가 필요하면 거의 항상 GSI


4. 인덱스에 무엇을 담을지

GSI를 만들 때 프로젝션(projection) 을 고른다.

프로젝션담는 데이터비용
KEYS_ONLY키 + 인덱스 키만가장 쌈
INCLUDE키 + 지정 속성보통
ALL모든 속성가장 비쌈, 가장 편함

매번 원본 테이블을 다시 GetItem 해야 한다면 ALL을 검토 — IO 절감


5. 인덱스 비용 모델

GSI는

  • 별도의 쓰기 용량 (원본에 쓸 때 인덱스에도 쓰기)
  • 별도의 읽기 용량
  • 별도의 스토리지

를 가진다.

인덱스 한 개 추가 = 쓰기 처리량이 ~2배가 될 수 있음

설계 단계에서 인덱스 수를 줄이는 게 중요하다.


6. “Single Table Design” 의 핵심

DynamoDB 고급 패턴 중 하나.

한 테이블 안에 여러 종류의 엔티티를 담고
GSI로 다양한 접근 경로를 만든다

PK = "USER#u-1"     SK = "PROFILE"      → 사용자 프로필
PK = "USER#u-1"     SK = "ORDER#o-1"    → 주문
PK = "USER#u-1"     SK = "ORDER#o-2"    → 주문
PK = "ORDER#o-1"    SK = "META"         → 주문 메타

이렇게 키를 설계해 “한 번에 사용자 + 모든 주문” 같은 조회를 단일 Query로 끝낸다.

초보자에게 권장하기는 어려운 패턴이지만,
DynamoDB가 진가를 발휘하는 구간이다.


7. 우리 서비스에서

[DynamoDB "orders"]
  PK: user_id
  SK: created_at

GSI: "by-status"
  PK: status         (paid, shipped, cancelled)
  SK: created_at

GSI: "by-product"
  PK: product_id
  SK: created_at
  • 기본 조회: 사용자별 주문
  • “결제 완료 주문 큐” → GSI by-status
  • “이 상품 누가 샀나” → GSI by-product

8. 직접 확인해보기 — CLI

GSI 추가

aws dynamodb update-table \
  --table-name orders \
  --attribute-definitions AttributeName=status,AttributeType=S AttributeName=created_at,AttributeType=S \
  --global-secondary-index-updates \
    '[{"Create":{"IndexName":"by-status","KeySchema":[{"AttributeName":"status","KeyType":"HASH"},{"AttributeName":"created_at","KeyType":"RANGE"}],"Projection":{"ProjectionType":"ALL"}}}]'

GSI로 Query

aws dynamodb query \
  --table-name orders \
  --index-name by-status \
  --key-condition-expression "#s = :s" \
  --expression-attribute-names '{"#s": "status"}' \
  --expression-attribute-values '{":s": {"S": "paid"}}'

9. 코드로는 이렇게 생겼다 — Terraform

resource "aws_dynamodb_table" "orders" {
  name         = "orders"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "user_id"
  range_key    = "created_at"

  attribute {
    name = "user_id"
    type = "S"
  }
  attribute {
    name = "created_at"
    type = "S"
  }
  attribute {
    name = "status"
    type = "S"
  }
  attribute {
    name = "product_id"
    type = "S"
  }

  global_secondary_index {
    name            = "by-status"
    hash_key        = "status"
    range_key       = "created_at"
    projection_type = "ALL"
  }

  global_secondary_index {
    name            = "by-product"
    hash_key        = "product_id"
    range_key       = "created_at"
    projection_type = "ALL"
  }
}

10. 이렇게 쓰면 망한다 — 안티패턴

안티패턴 1. 모든 컬럼마다 GSI를 만든다

쓰기 비용이 곱절로 늘어난다.

정말 필요한 조회 경로만 인덱스로

안티패턴 2. GSI에 카디널리티 낮은 키 (예: gender)

hot partition으로 이어진다.

안티패턴 3. GSI의 일관성에 강한 기대를 한다

GSI는 비동기 복제다 — 약간 지연될 수 있다.

강한 일관성이 필요한 조회는 원본 테이블 Query

안티패턴 4. LSI를 새로 시작하는 테이블에 우선적으로 쓴다

LSI는 제약이 많다 (생성 후 추가 불가, 10GB 한도).
GSI 가 거의 항상 더 유연하다.


11. 한 줄로 정리

GSI는 DynamoDB에서 추가 조회 경로를 만드는 도구이며,
비용·지연을 의식하면서 “정말 필요한 인덱스만” 두는 게 핵심이다


12. 이 장의 핵심 정리

  1. LSI는 같은 파티션 키 + 다른 정렬 키.
  2. GSI는 완전히 다른 키 조합 — 더 유연하다.
  3. 인덱스는 원본의 읽기 전용 사본처럼 동작한다.
  4. 인덱스 하나가 쓰기 처리량을 ~2배로 늘릴 수 있다.
  5. GSI는 비동기 복제다 — 강한 일관성이 필요하면 원본을 쓴다.
  6. Single Table Design은 강력하지만 학습 곡선이 있다.