DynamoDB 병렬 스캔
병렬 스캔은 하나의 Scan을 N개의 독립적인 Scan 요청으로 나누고, 각각이 테이블의 한
Segment를 차지하게 해 여러 워커가 동시에 읽도록 합니다. 한 파티션의 처리량이 허용하는
것보다 빠르게 전체 테이블을 읽는 유일한 방법입니다.
DynamoDB 병렬 스캔이란 무엇인가요?
DynamoDB 병렬 스캔은 하나의 Scan을 N개의 독립적인 요청으로 나누고, 각각이 Segment와 TotalSegments를 통해 테이블의 한 Segment를 차지하게 해 여러 워커가 동시에 읽도록 합니다. 한 파티션의 처리량이 허용하는 것보다 빠르게 전체 테이블을 읽는 유일한 방법이지만, 여전히 전체 읽기이므로 스캔한 모든 아이템에 비용을 냅니다.
- 순차
Scan은 한 번에 한 파티션씩 읽습니다 — 그 속도는 테이블이 아무리 커도 단일 파티션의 처리량에 묶여 있습니다. Segment+TotalSegments는 읽기를TotalSegments개의 워커에 샤딩합니다. 각 워커는 자신의 조각을 병렬로 스캔합니다.- DynamoDB는 파티션 키를 해싱해 세그먼트를 배정합니다. 그래서 조각이 한쪽으로 쏠릴 수 있습니다 — 워커가 많다고 항상 빠른 건 아닙니다.
- 여전히
Scan입니다. 모든 아이템을 읽는 데 비용을 내며, 비대한 병렬 스캔은 라이브 트래픽 밑에서 테이블의 처리량을 빨아낼 수 있습니다.
순차 Scan이 느린 이유
SQL에서 왔다면 전체 테이블 읽기는 하나의 스트리밍 연산처럼 느껴집니다. DynamoDB에서는 그렇지
않습니다. 테이블의 데이터는 여러 물리 파티션에 걸쳐 있지만, 단일 Scan은 그것들을 페이지당
1 MB씩 한 번에 하나씩 순회합니다.
이는 일반 Scan이 어느 순간에든 한 파티션의 처리량 예산에서만 끌어올 수 있다는 뜻입니다 —
테이블이 유휴 용량을 가진 수십 개의 파티션에 퍼져 있더라도요. 테이블이 클수록 더 오래 기어갑니다.
(AWS: 병렬 스캔)
Segment와 TotalSegments로 읽기 나누기
병렬 스캔은 그 병목을 고칩니다. 워커 수를 고르고, TotalSegments를 그 숫자로 설정하고, 각
워커에 서로 다른 0 기반 Segment를 줍니다. 모든 워커가 자기 Scan을 보내고, DynamoDB는
그것들을 동시에 처리합니다.
Worker 0 → Scan Segment=0 TotalSegments=4
Worker 1 → Scan Segment=1 TotalSegments=4
Worker 2 → Scan Segment=2 TotalSegments=4
Worker 3 → Scan 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=false
…
Scan sensor-readings Segment=7 TotalSegments=8 ConsistentRead=false
여덟 워커, 여덟 세그먼트, 하나의 테이블 읽기가 대략 여덟 배 빨라집니다. 최근 측정값만
필요하다면, 행이 전선을 타기 전에 오래된 타임스탬프를 떨어뜨리도록 FilterExpression을
추가하세요 — 그 표현식을 표현식 빌더에서 만들고 점검하세요:
FilterExpression: begins_with(SK, :today)DynamoDB가 아이템을 세그먼트에 배정하는 방법
여기가 사람들이 걸려 넘어지는 부분입니다. DynamoDB는 각 아이템을 세그먼트에 배정할 때 행 수도 바이트 수도 아닌 파티션 키를 해싱해 정합니다.
그래서 같은 PK를 공유하는 모든 아이템은 같은 세그먼트에 들어갑니다. sensor-readings에서
DEVICE#a83f의 모든 측정값은, 그 장치에 타임스탬프가 몇 개든 아이템 컬렉션이 얼마나 크든
상관없이 한 워커에게 갑니다.
(AWS: 병렬 스캔)
그 결과: 세그먼트는 고르지 않습니다. 한 워커는 수백만 측정값을 가진 수다스러운 장치 셋을
소유하고, 다른 워커는 텅 빈 조각을 뽑을 수도 있습니다. 파티션 키가 뭉치면 TotalSegments를
높여도 도움이 안 됩니다 — 핫한 워커를 기다리는 유휴 워커만 늘립니다. 고른 키 분포가 부채꼴
펼치기가 효과를 보게 만드는 것입니다.
실행 전에 읽기 비용 보기
병렬 스캔은 처리량 이벤트지, 공짜 점심이 아닙니다. 정직한 질문은 "이 전체 테이블을 읽는 데 읽기 단위가 몇 개나 들까?" — 이며, DynoTable은 실제 테이블에 대한 스캔의 계측된 읽기 비용을 세그먼트별로 보여 주어, 야간 작업이 청구서에서 여러분을 놀라게 하지 않도록 합니다.
함정, 그리고 굳이 안 해도 될 때
- 처리량 절벽.
TotalSegments가 높은 스캔은 몇 초 만에 테이블의 전체 읽기 용량을 소비해 라이브 트래픽을 굶길 수 있습니다. 사용자를 서빙하는 테이블에서는Limit파라미터로 각 워커를 스로틀하거나 비피크 시간에 스캔하세요. (AWS: 병렬 스캔) - 여전히 액세스 패턴에는 잘못된 도구입니다. 병렬 스캔은 의도적인 전체 테이블 작업 — 내보내기, 백필, 마이그레이션 — 을 위한 것입니다. 반복되는 쿼리에 답하려고 손을 뻗고 있다면, 그건 모델링 신호입니다 — GSI를 추가하고 그것을 Query로 만드세요.
- PartiQL의
SELECT *는 변장한 같은 스캔입니다. 그것은 순차Scan으로 컴파일됩니다. 진짜로 아이템 간 분석 —GROUP BY,JOIN, 집계 — 이 필요할 때, DynoTable의 SQL Workbench는 테이블을 두드리는 대신 한정된 결과 집합에 대해 그것들을 클라이언트 측에서 실행합니다. - 강력한 일관성은 청구서를 두 배로 만듭니다.
Scan은 기본적으로 최종적 일관성 읽기를 합니다. 내보내기에서는 진짜로 특정 시점 스냅샷이 필요하지 않은 한ConsistentRead=false로 두세요.
다음 단계
일상적인 읽기가 절대 스캔을 필요로 하지 않도록 키를 모델링하세요 — 단일 테이블 설계와 Query vs Scan에서 시작하세요. 전체 테이블 작업이 진짜로 옳은 선택일 때는 DynoTable을 사용해 보세요 — 여러분 자신의 테이블에 대해 병렬 스캔을 돌리고 읽기 비용을 실시간으로 지켜보세요.