중급7분 분량

DynamoDB GROUP BY: GROUP BY 절 없이 집계하는 방법

DynamoDB에는 GROUP BY가 없습니다. COUNT, SUM, AVG도 없습니다 — 네이티브 API에도, PartiQL에도요. DynamoDB는 분석 엔진이 아니라 키-값 / 문서 저장소이므로, 집계는 쿼리 플래너가 대신 해주는 것이 아니라 여러분이 만드는 것입니다.

DynamoDB에서 GROUP BY를 할 수 있나요?

안 됩니다. DynamoDB에는 GROUP BY도, HAVING도, COUNT/SUM/AVG 같은 집계 함수도 없습니다 — 네이티브 API에도, PartiQL에도요. PartiQL의 SELECTWHEREORDER BY만 받습니다. 집계는 데이터가 변할 때 총합을 미리 계산하거나(원자적 카운터 또는 Streams + Lambda 롤업), 읽은 뒤 앱 측에서 그룹화하여 처리합니다.

  • DynamoDB의 PartiQL SELECT 문법은 SELECT … FROM … [WHERE …] [ORDER BY …]이며 — 그게 전부입니다. GROUP BY도, HAVING도, 집계 함수도, JOIN도 없습니다 (AWS PartiQL SELECT 참조).
  • DynamoDB가 "항목 전반에 걸친 SUM이나 COUNT 같은 집계 작업을 네이티브로 지원하지 않기" 때문에, AWS 자체의 지침은 데이터가 변할 때 집계를 미리 계산하고 그 결과를 일반 항목으로 저장하는 것입니다 (AWS: 구체화된 집계).
  • 대안 — 모든 항목을 읽은 다음 앱에서 집계하는 것 — 은 동작하지만, 매 쿼리마다 테이블 전체를 읽는 비용을 지불합니다.
  • 일회성 탐색에는 DynoTable의 SQL WorkbenchGROUP BY / COUNT / SUM / AVG를 라이브 테이블에 대해 직접 실행합니다 — DynamoDB의 PartiQL 엔드포인트가 거부하는 그 SQL입니다.

DynamoDB에서 집계가 어려운 이유

DynamoDB에는 스캔 시점 집계 엔진이 없습니다. QueryScan은 항목을 반환하지, 접지 않습니다. Scan은 테이블 전체를 1 MB씩 읽고, 소비하는 용량은 유지하는 행이 아니라 읽는 항목을 기준으로 합니다 — FilterExpression은 스캔 후에 하지만 결과 반환 전에 적용되므로, 비용을 낮추지 않으면서 결과 집합만 좁힙니다 (AWS Scan API 참조: 필터는 "추가 읽기 용량 단위를 소비하지 않음"; 용량은 반환된 항목이 아니라 스캔된 항목 크기 기준). 애초에 합계나 카운트를 걸어둘 GROUP-BY 훅이 없습니다.

PartiQL도 이를 바꾸지 않습니다. PartiQL은 같은 엔진 위의 SQL-호환 방언이므로 같은 한계를 물려받습니다 — 새로운 실행 모델이 아니라 구문 표면입니다. 문서화된 SELECT 문법에는 GROUP BY 토큰이 그저 없습니다. PartiQL과 진짜 SQL의 전체 격차는 PartiQL 대 SQL을 보세요.

그래서 질문은 "어떻게 GROUP BY를 쓰는가"가 아니라 — "내 집계가 어디에 살고, 언제 계산되는가"입니다. 세 가지 답이 있습니다.

패턴 1: 쓰기 시점에 집계(원자적 카운터)

그룹을 미리 안다면 — 상태별 카운트, 고객별 총합, 월별 다운로드 — 카운터 항목을 두고 매 쓰기마다 갱신하세요.

증분이 원자적이고 동시성에 안전하도록 ADD 업데이트 표현식을 사용하세요. ADD는 숫자와 집합에 작동하며 read-modify-write 경합을 피하므로, 같은 카운터를 증분하는 두 쓰기가 서로를 절대 덮어쓰지 않습니다(AWS는 원자적 ADD가 "read-modify-write 경합 조건을 피한다"고 언급):

UpdateItem
Key                         { pk: "STATS#orders", sk: "status#shipped" }
UpdateExpression            "ADD orderCount :one"
ExpressionAttributeValues   { ":one": 1 }

이것이 여러분의 SELECT COUNT(*) … GROUP BY status입니다 — 다만 카운트가 이미 항목으로 거기 앉아 있어, 한 자릿수 밀리초의 GetItem으로 읽을 수 있습니다. 트레이드오프: 쓰기 시점에 그룹화 키를 알아야 하고, 카운터 갱신을 쓰기 경로에 결합합니다. 앱이 쓰기 후에 하지만 카운터 갱신 전에 충돌하면, 둘이 어긋나 동기화가 깨집니다 — 이것이 바로 다음 패턴이 분리하는 실패 모드입니다.

패턴 2: DynamoDB Streams + Lambda 롤업

쓰기 경로에 집계 로직을 두고 싶지 않거나 — 쓰기가 쉽게 감쌀 수 없는 평범한 PutItem이라면 — 그것을 하류로 옮기세요. 이것이 AWS 자체의 권장 패턴인 구체화된 집계입니다 (AWS: 구체화된 집계 쿼리에 GSI 사용):

  1. 앱이 원시 항목(주문, 다운로드, 이벤트)을 씁니다. 집계 로직 없음.
  2. DynamoDB Streams가 쓰기를 스트림 레코드로 포착합니다.
  3. 스트림에 연결된 Lambda가 새 항목을 읽고, 그룹(상태, 월, 카테고리…)을 도출해, 원자적 UpdateItem으로 해당 집계 항목에 ADD합니다 — 이는 많은 호출이 같은 카운터를 건드릴 때 "read-modify-write 경합 조건을 피합니다".
  4. 미리 계산된 집계를 쿼리합니다 — 종종 롤업 항목만 인덱싱하는 희소 GSI를 통해서요. 그러면 "이번 달 상위 10개"는 Limit 10의 단일 Query가 됩니다.

희소 GSI 트릭: 집계 항목만 인덱싱된 속성(예: Month)을 가지므로 원시 이벤트 행은 자동으로 인덱스에서 제외됩니다 — "테이블 전체 항목의 작은 일부"라서 인덱스를 저렴하게, 읽기를 빠르게 유지합니다.

이는 집계를 쓰기 경로에서 분리하고 쓰기를 단순하게 유지하지만, 최종적 일관성이라는 비용이 따릅니다 — AWS는 "다운로드가 기록되고 집계가 갱신되기까지 몇 초의 지연"을 언급합니다. 대시보드, 리더보드, 추세 카운터에는 괜찮습니다.

같은 재시도 주의가 적용됩니다: 재시도된 Lambda 호출은 ADD를 다시 실행하므로 "재시도는 카운트를 한 번 이상 증가"시켜 근사 값을 남깁니다. 정확한 카운트에는 멱등성을 추가하세요(예: 소스 항목의 id를 키로 한 조건 표현식); 그렇지 않으면 작은 오차는 분석과 리더보드에 괜찮습니다.

패턴 3: Scan/Query 후 앱 측 그룹화

무차별 옵션: 항목을 읽고 코드에서 그룹화합니다.

groups = {}
for item in paginate(table.scan()):       # 또는 한 파티션에 대해 query()
    key = item["status"]
    groups[key] = groups.get(key, 0) + 1

이것은 올바르고 때로는 옳은 선택입니다 — 하지만 비용에 대해 정직해지세요. Scan테이블의 모든 항목을 읽고, 읽기 용량은 필터 여부와 관계없이 같습니다. 그래서 전체 Scan에 대한 앱 측 그룹화는 매 집계마다 테이블 전체를 읽는 비용을 지불하며, 지연이 테이블과 함께 늘어납니다. AWS는 "읽기 시점에 스캔하고 카운트"하는 것을 "지연이 문제 되지 않는 매우 작은 데이터셋에만 적합"하다고 나열합니다 (AWS: 왜 집계를 미리 계산하는가).

Query로 단일 파티션으로 범위를 좁히면(예: 한 고객의 주문 세기), 앱 측 그룹화는 완벽히 합리적입니다 — 항목 컬렉션 하나만 읽기 때문입니다. 둘 사이의 전체 비용 격차는 Query 대 Scan을 보세요. 주어진 집계 스캔이 실행 전에 무엇을 읽을지 추정하려면, 대표 항목의 크기를 항목 크기 계산기로 재세요 — 읽기 용량은 4 KB마다 올림이므로 항목 크기가 비용을 좌우합니다.

진짜 즉석 분석 SQL을 DynamoDB 테이블에 대해 — 한 번만 실행할 "GROUP BY status, 세기" 같은 — 원하는 경우, AWS의 답은 별도 엔진을 그것에 겨누는 것입니다: Amazon Athena DynamoDB 커넥터는 Lambda 커넥터를 통해 진짜 SQL(GROUP BY, 집계, 심지어 다른 소스로의 JOIN까지)로 테이블을 쿼리하게 해줍니다 (AWS: Amazon Athena DynamoDB 커넥터). 뒤에서 테이블을 스캔하므로 핫 패스가 아니라 리포팅/BI 도구입니다.

어떤 패턴을 사용하나?

필요한 것…사용
핫 읽기 경로의 알려진 그룹 총합패턴 1 — 원자적 카운터(ADD)
쓰기 경로를 건드리지 않는 집계패턴 2 — Streams + Lambda 롤업
한 파티션으로 범위를 좁힌 카운트패턴 3 — Query 후 앱에서 그룹화
어긋남 없는 정확한 총합멱등성 가드를 포함한 패턴 1/2
탐색 중 일회성 GROUP BYDynoTable Workbench(아래) 또는 Athena
SQL로 반복적인 BI/리포팅Athena DynamoDB 커넥터

DynoTable의 SQL Workbench에서 GROUP BY 직접 실행하기

위 패턴들은 프로덕션에서 집계를 제공하는 방법입니다. 하지만 테이블을 탐색할 때 — "지금 상태별로 주문이 몇 개지?" — Lambda를 프로비저닝하거나 Athena를 세우고 싶지 않습니다. 쿼리를 입력하고 싶을 뿐입니다.

그것이 DynoTable의 SQL Workbench가 하는 일입니다. 진짜 SQL — GROUP BY, COUNT, SUM, AVG, HAVING, 심지어 JOIN까지 — 를 라이브 DynamoDB 테이블에 대해 직접 실행하며, 읽어들인 행에 대해 집계를 클라이언트 측에서 수행합니다. DynamoDB의 PartiQL 엔드포인트가 거부하는 그 SQL입니다:

SELECT status, COUNT(*) AS orders, SUM(total) AS revenue
FROM "Orders"
GROUP BY status
HAVING SUM(total) > 1000
ORDER BY revenue DESC

정직한 설명: 내부적으로 DynoTable은 API가 허용하는 방식으로 항목을 읽고(가능하면 Query, 어쩔 수 없으면 Scan), 구체화한 다음, Workbench에서 그룹화를 수행합니다 — 패턴 3과 같은 "읽은 다음 집계" 메커니즘이며, 다만 루프가 없고 DynamoDB의 액세스 패턴 규칙 내입니다. 핫 읽기 경로의 프로덕션 롤업을 대체하기 위해서가 아니라 탐색과 즉석 분석을 위해 만들어졌습니다. 그것을 위해서는 미리 계산하세요(패턴 1 / 2).

같은 쐐기의 JOIN 쪽 — DynoTable은 PartiQL이 할 수 없는 교차 테이블 조인도 실행합니다 — 은 DynamoDB JOIN을 보세요. 바로 이 기능에 대해 GUI 클라이언트를 비교 중이라면? DynamoDB GUI 비교를 보세요.

FAQ

DynamoDB PartiQL이 GROUP BY를 지원하나요? 아니요. DynamoDB의 PartiQL SELECTWHEREORDER BY만 지원합니다 — GROUP BY, HAVING, 집계 함수, JOIN은 없습니다. 문법은 문서화된 대로 SELECT … FROM … [WHERE …] [ORDER BY …]입니다.

DynamoDB 테이블 전체에 대해 COUNT(*)를 할 수 있나요? 집계 함수로는 안 됩니다 — PartiQL에는 그것이 없습니다. API는 Scan/QuerySelect=COUNT를 주며, 이는 일치하는 항목의 카운트를 반환하지만 여전히 스캔이 건드린 모든 항목을 읽고(과금)합니다 (AWS Scan API 참조: 용량은 반환된 항목이 아니라 검사된 항목 기준). 자주 읽는 총합에는 카운터 항목을 유지하세요(패턴 1).

파티션 키로 GROUP BY를 할 수 있나요? DynamoDB나 PartiQL에서는 안 됩니다. "파티션 키별"이 알려진 액세스 패턴이라면, 키마다 집계 항목 하나를 원자적 ADD로 유지하거나(패턴 1), Streams + Lambda로 롤업하세요(패턴 2).

그룹별 SUM이나 AVG는 어떻게 하나요? SUM: 그룹별 누적 합계를 유지하고 쓰기 시점에 ADD하세요. AVG: 합계와 카운트를 둘 다 저장하고 읽기 시점에 나누세요 — 네이티브 평균은 없습니다. 일회성 탐색 AVG에는 DynoTable의 SQL Workbench 또는 Athena DynamoDB 커넥터를 통해 실행하세요.

partiql group by 우회 방법이 있나요? PartiQL 측에는 없습니다. 집계를 미리 계산해(카운터/Streams) 롤업 항목을 SELECT하거나, GROUP BY가 있는 엔진에서 실행하세요 — 즉석에는 DynoTable의 Workbench, 반복 리포팅에는 Athena.


Lambda를 작성하지 않고 여러분의 테이블에 대해 GROUP BY를 실행하고 싶으신가요? DynoTable 사용해보기 후 SQL Workbench를 라이브 테이블에 겨누세요.

업데이트됨