고급5분 분량

DynamoDB 병렬 스캔

병렬 스캔은 하나의 Scan을 N개의 독립적인 Scan 요청으로 나누고, 각각이 테이블의 한 Segment를 차지하게 해 여러 워커가 동시에 읽도록 합니다. 한 파티션의 처리량이 허용하는 것보다 빠르게 전체 테이블을 읽는 유일한 방법입니다.

DynamoDB 병렬 스캔이란 무엇인가요?

DynamoDB 병렬 스캔은 하나의 Scan을 N개의 독립적인 요청으로 나누고, 각각이 SegmentTotalSegments를 통해 테이블의 한 Segment를 차지하게 해 여러 워커가 동시에 읽도록 합니다. 한 파티션의 처리량이 허용하는 것보다 빠르게 전체 테이블을 읽는 유일한 방법이지만, 여전히 전체 읽기이므로 스캔한 모든 아이템에 비용을 냅니다.

  • 순차 Scan은 한 번에 한 파티션씩 읽습니다 — 그 속도는 테이블이 아무리 커도 단일 파티션의 처리량에 묶여 있습니다.
  • Segment + TotalSegments는 읽기를 TotalSegments개의 워커에 샤딩합니다. 각 워커는 자신의 조각을 병렬로 스캔합니다.
  • DynamoDB는 파티션 키를 해싱해 세그먼트를 배정합니다. 그래서 조각이 한쪽으로 쏠릴 수 있습니다 — 워커가 많다고 항상 빠른 건 아닙니다.
  • 여전히 Scan입니다. 모든 아이템을 읽는 데 비용을 내며, 비대한 병렬 스캔은 라이브 트래픽 밑에서 테이블의 처리량을 빨아낼 수 있습니다.

순차 Scan이 느린 이유

SQL에서 왔다면 전체 테이블 읽기는 하나의 스트리밍 연산처럼 느껴집니다. DynamoDB에서는 그렇지 않습니다. 테이블의 데이터는 여러 물리 파티션에 걸쳐 있지만, 단일 Scan은 그것들을 페이지당 1 MB씩 한 번에 하나씩 순회합니다.

이는 일반 Scan이 어느 순간에든 한 파티션의 처리량 예산에서만 끌어올 수 있다는 뜻입니다 — 테이블이 유휴 용량을 가진 수십 개의 파티션에 퍼져 있더라도요. 테이블이 클수록 더 오래 기어갑니다. (AWS: 병렬 스캔)

Segment와 TotalSegments로 읽기 나누기

병렬 스캔은 그 병목을 고칩니다. 워커 수를 고르고, TotalSegments를 그 숫자로 설정하고, 각 워커에 서로 다른 0 기반 Segment를 줍니다. 모든 워커가 자기 Scan을 보내고, DynamoDB는 그것들을 동시에 처리합니다.

Worker 0Scan  Segment=0  TotalSegments=4
Worker 1Scan  Segment=1  TotalSegments=4
Worker 2Scan  Segment=2  TotalSegments=4
Worker 3Scan  Segment=3  TotalSegments=4

각 워커는 여전히 LastEvaluatedKey로 독립적으로 페이지를 넘깁니다 — 첫 페이지부터 마지막까지 자기 세그먼트를 소유합니다. 애플리케이션은 네 스트림을 다시 하나로 꿰맵니다. 이제 하나가 아니라 네 파티션 분량의 처리량을 한 번에 읽고 있습니다.

실제 예: 야간 내보내기

텔레메트리 테이블 sensor-readings를 운영한다고 합시다. 각 아이템은 현장 장치 하나의 측정값입니다:

PK = "DEVICE#a83f"          (partition key — the device id)
SK = "TS#2026-06-22T03:14"  (sort key — ISO timestamp)
batteryMv  = 3120
tempC      = 41.8
firmwareTag = "fw-7.2.1"

매일 밤 크론 작업이 전체 테이블을 분석 웨어하우스를 위해 S3로 덤프합니다. 80 GB의 순차 Scan은 몇 시간이 걸리면서도 프로비저닝된 읽기 용량에는 거의 흠집조차 내지 못합니다. 그래서 여덟 워커로 부채꼴로 펼칩니다:

Scan  sensor-readings  Segment=0  TotalSegments=8  ConsistentRead=falseScan  sensor-readings  Segment=7  TotalSegments=8  ConsistentRead=false

여덟 워커, 여덟 세그먼트, 하나의 테이블 읽기가 대략 여덟 배 빨라집니다. 최근 측정값만 필요하다면, 행이 전선을 타기 전에 오래된 타임스탬프를 떨어뜨리도록 FilterExpression을 추가하세요 — 그 표현식을 표현식 빌더에서 만들고 점검하세요:

FilterExpression:  begins_with(SK, :today)

DynamoDB가 아이템을 세그먼트에 배정하는 방법

여기가 사람들이 걸려 넘어지는 부분입니다. DynamoDB는 각 아이템을 세그먼트에 배정할 때 행 수도 바이트 수도 아닌 파티션 키를 해싱해 정합니다.

그래서 같은 PK를 공유하는 모든 아이템은 같은 세그먼트에 들어갑니다. sensor-readings에서 DEVICE#a83f의 모든 측정값은, 그 장치에 타임스탬프가 몇 개든 아이템 컬렉션이 얼마나 크든 상관없이 한 워커에게 갑니다. (AWS: 병렬 스캔)

sensor-readings 테이블파티션 해싱Segment 0DEVICE#a83fDEVICE#1c20Segment 1DEVICE#9be4Segment 2 (비어 있음)

그 결과: 세그먼트는 고르지 않습니다. 한 워커는 수백만 측정값을 가진 수다스러운 장치 셋을 소유하고, 다른 워커는 텅 빈 조각을 뽑을 수도 있습니다. 파티션 키가 뭉치면 TotalSegments를 높여도 도움이 안 됩니다 — 핫한 워커를 기다리는 유휴 워커만 늘립니다. 고른 키 분포가 부채꼴 펼치기가 효과를 보게 만드는 것입니다.

실행 전에 읽기 비용 보기

병렬 스캔은 처리량 이벤트지, 공짜 점심이 아닙니다. 정직한 질문은 "이 전체 테이블을 읽는 데 읽기 단위가 몇 개나 들까?" — 이며, DynoTable은 실제 테이블에 대한 스캔의 계측된 읽기 비용을 세그먼트별로 보여 주어, 야간 작업이 청구서에서 여러분을 놀라게 하지 않도록 합니다.

함정, 그리고 굳이 안 해도 될 때

  • 처리량 절벽. TotalSegments가 높은 스캔은 몇 초 만에 테이블의 전체 읽기 용량을 소비해 라이브 트래픽을 굶길 수 있습니다. 사용자를 서빙하는 테이블에서는 Limit 파라미터로 각 워커를 스로틀하거나 비피크 시간에 스캔하세요. (AWS: 병렬 스캔)
  • 여전히 액세스 패턴에는 잘못된 도구입니다. 병렬 스캔은 의도적인 전체 테이블 작업 — 내보내기, 백필, 마이그레이션 — 을 위한 것입니다. 반복되는 쿼리에 답하려고 손을 뻗고 있다면, 그건 모델링 신호입니다 — GSI를 추가하고 그것을 Query로 만드세요.
  • PartiQL의 SELECT *는 변장한 같은 스캔입니다. 그것은 순차 Scan으로 컴파일됩니다. 진짜로 아이템 간 분석 — GROUP BY, JOIN, 집계 — 이 필요할 때, DynoTable의 SQL Workbench는 테이블을 두드리는 대신 한정된 결과 집합에 대해 그것들을 클라이언트 측에서 실행합니다.
  • 강력한 일관성은 청구서를 두 배로 만듭니다. Scan은 기본적으로 최종적 일관성 읽기를 합니다. 내보내기에서는 진짜로 특정 시점 스냅샷이 필요하지 않은 한 ConsistentRead=false로 두세요.

다음 단계

일상적인 읽기가 절대 스캔을 필요로 하지 않도록 키를 모델링하세요 — 단일 테이블 설계Query vs Scan에서 시작하세요. 전체 테이블 작업이 진짜로 옳은 선택일 때는 DynoTable을 사용해 보세요 — 여러분 자신의 테이블에 대해 병렬 스캔을 돌리고 읽기 비용을 실시간으로 지켜보세요.

업데이트됨