입문6분 분량

DynamoDB에서 COUNT, SUM, 집계하는 방법

DynamoDB에는 내장 집계가 딱 하나 있습니다: Select=COUNT로 일치한 항목을 세는 것. 네이티브 SUM, AVG, MIN, MAX는 없습니다. 그리고 그나마 얻을 수 있는 개수조차 센 항목을 전부 읽고(과금하고) 나서야 나옵니다. 이 가이드는 실제로 지원되는 것, 사람들이 손을 뻗는 근사치, 그리고 정말로 필요할 때 테이블에 대해 실제 COUNT/SUM/AVG를 실행하는 방법을 다룹니다.

DynamoDB는 SUM, COUNT, 집계 함수를 할 수 있나요?

대체로 아니요. DynamoDB의 유일한 내장 집계는 Select=COUNT로, 일치한 항목 개수를 반환하지만 여전히 모든 항목을 읽고(과금하고) 나서야 나옵니다. 네이티브 SUM, AVG, MIN, MAX는 없으며, PartiQL도 어느 것도 추가하지 않습니다. GROUP BY가 있는 실제 집계를 하려면 앱에서 접거나, 카운터를 유지하거나, DynoTable의 Workbench에서 SQL을 실행하세요.

  • Select=COUNT는 일치한 항목의 수를 반환하지만, DynamoDB는 그것을 산출하려고 여전히 모든 항목을 읽습니다 — 저렴한 "count" 비용이 아니라 Scan/Query 읽기 비용 전액을 지불합니다.
  • 네이티브 SUM, AVG, MIN, MAX는 없습니다. DynamoDB의 읽기 연산은 항목을 반환하지, 그것을 하나의 숫자로 접지 않습니다. PartiQL도 집계를 추가하지 않습니다.
  • DescribeTable.ItemCount는 공짜지만 근사치이며 "대략 6시간마다" 갱신됩니다 — 대시보드 타일에는 괜찮지만 정확한 무언가에는 틀립니다.
  • 정확한 COUNT/SUM/AVG/MIN/MAX(및 GROUP BY)가 필요하면 앱에서 집계하거나, 카운터를 유지하거나, DynoTable의 SQL Workbench에서 실행하세요(아래).

항목 세기: Select=COUNT

QueryScan 모두 Select 파라미터를 받습니다. 그것을 COUNT로 설정하면 응답이 항목 대신 개수를 담습니다:

aws dynamodb scan \
  --table-name Orders \
  --select COUNT \
  --filter-expression "#s = :open" \
  --expression-attribute-names '{"#s":"status"}' \
  --expression-attribute-values '{":open":{"S":"OPEN"}}'

응답은 두 개의 숫자를 줍니다 (AWS: 결과의 항목 세기):

  • Count — "필터 표현식(있는 경우)이 적용된 후에 남은 항목의 수."
  • ScannedCount — "ScanFilter가 적용되기 전에 평가된 항목의 수." 필터가 없으면 ScannedCountCount와 같습니다.

만 있고 그 안의 중복을 세야 한다면, 전달하는 조건 + 필터가 바로 DynamoDB 표현식 빌더가 생성하는 것입니다 — 위의 FilterExpressionExpressionAttributeNames/Values 맵, 그리고 Query로 한 파티션 안에서 셀 때의 KeyConditionExpression까지 — JSON을 손으로 이스케이프하지 않고.

큰 테이블을 세는 사람들을 무는 함정 두 가지 더:

  • 1 MB 페이지 한도는 여전히 적용됩니다. "Scan 결과 집합의 크기가 1 MB보다 크면, ScannedCountCount는 전체 항목의 부분 개수만 나타냅니다" (AWS Scan 문서). 각 응답의 LastEvaluatedKey를 다음 요청의 ExclusiveStartKey로 넘겨 페이지네이션하며 누계를 유지해야 실제 숫자를 얻습니다 — DynamoDB 페이지네이션에서 다룬 바로 그 루프입니다.
  • 좁은 QueryScan을 이깁니다. Query에서의 Select=COUNT는 테이블 전체가 아니라 대상 파티션의 항목만 계량합니다. 파티션 키를 고정할 수 있다면(기본 테이블이든 GSI든) 거기서 세세요 — 세기에 적용된 Query vs Scan 비용 격차입니다.

Select=COUNT vs ItemCount(그리고 그것이 묵은 이유)

DescribeTable은 읽기 비용 없이 공짜로 ItemCount(및 TableSizeBytes)를 반환합니다. 함정은 API 참조 자체에 있습니다: "DynamoDB는 이 값을 대략 6시간마다 갱신합니다. 최근 변경 사항은 이 값에 반영되지 않을 수 있습니다." 그러니 테이블의 실제 상태보다 한참 뒤처질 수 있습니다.

Select=COUNTDescribeTable.ItemCount
정확성정확(일치한 집합에 대해)근사
최신성실시간약 6시간마다 갱신
비용센 항목마다 읽고 과금공짜(메타데이터)
부분집합 필터/세기 가능 여부예(필터 표현식)아니요 — 테이블 전체만

대략적인 "이 테이블 얼마나 큰가" 감을 잡거나 대시보드 타일에는 ItemCount를 쓰세요. 정확하거나 필터링되거나 현재의 숫자가 필요하면 — 그리고 읽기 비용을 감수한다면 — Select=COUNT를 쓰세요. 정말로 실시간이면서 공짜인 것이 필요하면 카운터를 직접 추적하세요(아래 집계 패턴 참조).

네이티브 SUM/AVG/MIN/MAX가 없는 이유

DynamoDB의 읽기 연산은 항목을 반환합니다. 결과 집합을 스칼라로 접을 쿼리 플래너가 없으므로, SUM이나 AVG를 계산할 것이 아무것도 없습니다. 세기는 API가 제공하는 유일한 접기이며, Select=COUNT를 통해서입니다.

PartiQL도 이를 바꾸지 않습니다. PartiQL SELECT 문법SELECT {{expression}} [, …] FROM {{table}}[.{{index}}] [WHERE …] [ORDER BY {{key}} …]이며, 여기서 expression은 "* 와일드카드나 하나 이상의 속성 이름 또는 문서 경로로 이루어진 프로젝션 목록으로 형성된 프로젝션"입니다. 그 문법에는 집계 함수도 GROUP BY 절도 없습니다 — 그리고 ORDER BY{{key}}를 받는데, "반환된 결과를 정렬하는 데 사용할 해시 키 또는 정렬 키"로 문서화되어 있습니다. 모든 PartiQL SELECT는 여전히 GetItem, Query, Scan으로 컴파일되므로, SELECT SUM(total) FROM "Orders"는 애초에 표현할 수 없습니다. (PartiQL의 한계에 대한 더 많은 내용은 PartiQL vs SQL에.)

집계 패턴(카운터, 스트림, 앱 측)

DynamoDB가 대신 집계해주지 않으므로, 확립된 패턴들은 그 작업을 다른 곳으로 밀어냅니다:

  • 유지되는 카운터 항목. 전용 항목(예: PK = "STATS#orders")을 두고, 모든 쓰기마다 UpdateItem으로 숫자 속성에 ADD하세요. 그러면 집계를 읽는 것은 단일 GetItem이 됩니다 — 정확하고 저렴하지만, 증가 로직과 그 일관성, 그리고 한 카운터가 두들겨 맞을 때의 경합은 여러분 몫입니다.
  • 를 집계기에 연결. 스트림을 켜고 항목이 변할 때 누계(개수, 합계)를 갱신하는 Lambda에 연결하세요. AWS Streams 문서에 따르면, 스트림의 StreamViewType을 설정해 각 레코드가 NEW_AND_OLD_IMAGES — "항목의 새 이미지와 옛 이미지 둘 다" — 를 담게 할 수 있으며, 이는 다시 스캔하지 않고 SUM 스타일 집계를 최신으로 유지하기에 충분합니다. 스트림 레코드에는 24시간 수명이 적용되므로("샤드 내 스트림 레코드는 24시간 후 자동으로 제거됩니다") 소비자는 뒤처지지 않아야 합니다.
  • 앱 측 접기. 일치한 항목을 페이지네이션하며 자신의 코드에서 SUM/AVG/MIN/MAX를 누적하세요. 정확하지만, 매번 모든 항목을 읽고(과금하고) — Select=COUNT와 같은 비용 프로필에 데이터 전송까지 더해집니다.
  • 분석으로 오프로드. 무겁거나 임시적인 분석 집계에는 테이블을 S3로 내보내 Athena로 쿼리하거나, 웨어하우스로 스트리밍하세요. AWS S3 내보내기 문서에 따르면, 내보내기는 "읽기 용량 단위를 소모하지 않으며" "Athena 같은 AWS 서비스를 사용해 분석과 복잡한 쿼리를 수행"하게 해줍니다 — 요청별 집계를 넘어섰을 때 AWS가 권장하는 경로입니다.

각각은 단순함을 쓰기 시점의 장부 관리(카운터, 스트림)나 읽기 시점의 비용(앱 측 스캔)과 맞바꿉니다. 어떤 패턴도 DynamoDB 자체가 SUM을 공짜로 계산하게 만들지 못합니다. 이 트레이드오프의 그룹화 버전 — 테이블 전체가 아니라 키별로 집계하기 — 은 별도의 가이드입니다: DynamoDB GROUP BY.

DynoTable의 SQL Workbench에서 COUNT/SUM/AVG 실행하기

페이지네이션 스캔 루프나 Lambda를 쓰지 않고 그저 답 — "OPEN 주문이 몇 개고, 그 합계는 얼마인가" — 만 필요할 때, DynoTable의 SQL Workbench는 실제 집계를 실행합니다. DynamoDB의 실제 Query/Scan 런타임을 통해 테이블을 실체화한 다음, 그 위에서 단일 SELECT를 실행합니다 — 집계, GROUP BY, HAVING, DISTINCT: DynamoDB의 액세스 패턴 규칙 안에서의 SQL.

-- Runs in the DynoTable Workbench (NOT in PartiQL):
SELECT status,
       COUNT(*)        AS orders,
       SUM(total)      AS revenue,
       AVG(total)      AS avg_order,
       MIN(total)      AS smallest,
       MAX(total)      AS largest
FROM orders
GROUP BY status
ORDER BY revenue DESC

계산된 집계에 대한 COUNT, SUM, AVG, MIN, MAX, GROUP BY, ORDER BY가 — DynamoDB나 PartiQL은 어느 것도 표현할 수 없는데(PartiQL의 ORDER BY는 키 속성으로 제한됩니다) — 하나의 문장에 담겼습니다. 이것은 SQL for DynamoDB와 같은 분석적 쐐기입니다. 전체 그룹화 이야기는 DynamoDB GROUP BY를 보세요.

Workbench는 그 아래의 액세스 모델에 대해 정직합니다, 가짜 Postgres 흉내가 아닙니다:

  • 행은 여전히 DynamoDB의 실제 Query/Scan을 통해 옵니다. 테이블 전체에 대한 GROUP BY는 아래에서 여전히 Scan입니다 — Workbench는 그 비용을 숨기지 않고 드러냅니다, 같은 Query vs Scan 트레이드오프입니다.
  • 집계는 행이 도착한 뒤 실체화된 스칼라 속성에 대해 실행됩니다.

FAQ

스캔 없이 DynamoDB에서 항목을 셀 수 있나요? 정확히는 아닙니다. 정확하고 현재인 개수를 얻으려면 항목을 읽어야 합니다 — Select=COUNT도 센 항목마다 계량합니다. 스캔 없는 유일한 선택지는 근사치인 DescribeTable.ItemCount(약 6시간마다 갱신)이거나, 쓰기마다 직접 유지하는 카운터 항목입니다.

GSI로 항목을 어떻게 세나요? 인덱스에 대해 Query(또는 Scan)를 Select=COUNT로 실행하세요. 좁은 GSI 파티션을 통한 세기는 기본 테이블을 스캔하는 것보다 훨씬 저렴합니다. 그 인덱스 파티션의 항목만 읽기 때문입니다 — 필요한 세기를 중심으로 인덱스를 모델링하세요.

DescribeTable.ItemCount는 정확한가요? 근사치입니다. API 참조는 DynamoDB가 ItemCountTableSizeBytes를 "대략 6시간마다" 갱신하며, "최근 변경 사항은 이 값에 반영되지 않을 수 있다"고 명시합니다. 정확하거나 실시간인 숫자가 중요한 곳에는 쓰지 마세요.

DynamoDB는 SUM이나 AVG를 할 수 있나요? 네이티브로도, PartiQL로도 안 됩니다 — PartiQL SELECT 문법에는 집계 함수가 없습니다. 애플리케이션에서 집계하거나, (선택적으로 DynamoDB Streams를 통해) 카운터를 유지하거나, DynoTable의 SQL Workbench에서 SUM/AVG를 실행하세요.

CountScannedCount의 차이는 무엇인가요? ScannedCount는 필터 전에 DynamoDB가 평가한 항목 수이고, Count는 그 후 남은 수입니다. 필터 표현식이 없으면 둘은 같습니다. 둘 사이의 큰 격차는 비효율적인 세기를 뜻합니다.


스캔 루프를 작성하지 않고 DynamoDB 데이터를 합계, 평균, 그룹화해야 하나요? DynoTable을 내려받아 Workbench 탭에서 실행하세요. 클라이언트를 먼저 비교하시나요? 일반 DynamoDB GUI와 견주어 어디에 서는지 보세요.

업데이트됨