Node.js의 DynamoDB Scan (AWS SDK v3)
아래의 do/while은 방어적 코딩이 아닙니다. 필터가 걸린 Scan 페이지는 빈 Items 배열로 돌아오면서도 테이블에 남은 부분이 여전히 있을 수 있으므로, 첫 응답에서 멈추는 것은 일치 항목이 있는 테이블에서 스캔이 0건을 보고하게 만드는 방법입니다. 이 작업을 아예 피해야 할 때는 Query vs. Scan에서 다룹니다.
코드
import {DynamoDBClient, ScanCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new ScanCommand({
TableName: 'Music',
FilterExpression: '#filter0 >= :filterValue0',
ExpressionAttributeNames: {
'#filter0': 'Year'
},
ExpressionAttributeValues: {
':filterValue0': {N: '2010'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Matched ${items.length} items`);빈 페이지 두 개, 284.5 읽기 단위, 항목 8개
픽스처는 각각 약 3.9 KB인 곡 600개이고, 그중 정확히 8개가 Year >= 2010이며 정렬상 마지막에 놓입니다. 위 루프가 실제로 받는 것은 다음과 같습니다:
| 왕복 | Items.length | ScannedCount | 읽기 단위 | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 0 | 271 | 128.5 | 있음 |
| 2 | 0 | 271 | 128.5 | 있음 |
| 3 | 8 | 58 | 27.5 | 없음 |
연속된 두 페이지가 아무것도 반환하지 않으면서 각각 128.5 읽기 단위를 씁니다. if (!response.Items.length) return을 하는 코드는 테이블이 비었다고 보고합니다. API 레퍼런스는 규칙을 분명히 밝힙니다. "a scan result can result in no items meeting the criteria and the Count will result in zero", 그리고 별도로 "a FilterExpression is applied after the items have already been read; the process of filtering does not consume any additional read capacity units".
두 번째 문장은 청구서가 읽는 방식으로 읽으세요. 필터는 공짜이고, 필터가 버린 것들은 공짜가 아닙니다. 항목 8개를 내주는 데 284.5 읽기 단위이며, 필터를 전혀 걸지 않았을 때와 같은 금액입니다.
2026-07-28에 node v24.18.0의 @aws-sdk/client-dynamodb 3.1095.0으로 DynamoDB Local(amazon/dynamodb-local)에 대해 측정했습니다. 개수와 용량은 엔진이 반환한 응답 필드 그대로입니다.
설명
ExclusiveStartKey: lastEvaluatedKey는 첫 번째 순회에서undefined입니다. v3 직렬화기는undefined멤버를 버리므로, 객체 리터럴 하나가 첫 요청과 이후의 모든 요청을 함께 처리합니다. 대신{}를 넘기면ValidationException: The provided starting key is invalid로 실패합니다.response.Items ?? []는 실제 일을 합니다. 위 표와 함께 보세요. 널 병합 연산자는 아무것도 일치하지 않은 페이지에서 누적기를 정직하게 유지하고,while은 그 페이지들을 지나서도 루프를 살려 둡니다.#filter0은 장식이 아닙니다.Year는 AWS 예약어 목록에 있으며, 별칭 없이 쓰면ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year가 반환됩니다.Limit은 반환된 항목이 아니라 읽은 항목을 셉니다. 이 필터에서Limit: 10은Count: 0과ScannedCount: 10을 내놓습니다. 이것은 결과 열 개를 달라는 방법이 아니라 용량 급증을 막는 조절 손잡이입니다.Segment/TotalSegments는 테이블 전체 스캔을 워커들에게 나눕니다. 이는 비용이 아니라 실제 소요 시간을 나누는 것입니다. 동일한 284.5 단위를 더 빠르게, 더 동시에 쓸 뿐입니다.
실제 테이블에서 드는 비용
항목 8개에 284.5 읽기 단위라는 것이 문제의 형태이며, 이는 결과가 아니라 테이블 크기에 비례해 선형으로 커집니다. 핫 경로에 필터 스캔을 올리기 전에 DynamoDB 요금 계산기에서 여러분의 항목 크기와 트래픽으로 테이블 전체 읽기의 값을 매겨 보고, 같은 액세스 패턴을 Query로 바꿔 주는 GSI와 비교해 보세요.
스크립트에서 앞이 안 보이는 채로 스캔하는 대신, 필터와 페이지네이션이 적용된 결과 그리드로 테이블을 GUI에서 탐색하려면 DynoTable을 다운로드하세요.
관련 가이드
- Query vs. Scan — (드물게)
Scan이 정당화되는 경우. - DynamoDB Scan은 왜 느리고 비싼가요? — 비용 모델과 이를 피하는 방법.
- DynamoDB ProvisionedThroughputExceededException — 테이블 전체 스캔이 프로비저닝된 테이블의 용량에 무슨 짓을 하는지.
- DynamoDB ThrottlingException — 또 다른 스로틀링과 지수 백오프가 이를 처리하는 방법.