DynamoDB 한계와 쿼터, 실제 서비스로 검증
DynamoDB의 한계는 무엇인가요?
항목은 400KB, 파티션 키는 2,048바이트, 정렬 키는 1,024바이트로 제한됩니다. 배치 쓰기는 항목 25개, 배치 읽기는 키 100개, 트랜잭션은 작업 100개까지 받습니다. 테이블 하나는 글로벌 보조 인덱스 20개와 로컬 보조 인덱스 5개를 가질 수 있습니다. 이 페이지의 모든 수치는 실제로 Amazon DynamoDB에 요청을 보내고 돌아온 응답을 읽어서 확인한 것입니다.
이 수치들을 어떻게 확인했는가
AWS는 근거 없이 쿼터 수치만 공개하며, 보통은 그걸로 충분합니다 — 그 수치가 설계 결정을 떠받치게 되어, 그것이 400,000바이트를 뜻하는지 409,600바이트를 뜻하는지, 속성 이름까지 포함해서 세는지, 그리고 그 선을 넘었을 때 서비스가 정확히 뭐라고 말하는지 알고 싶어지기 전까지는요.
그래서 직접 찔러 봤습니다. 아래 첫 번째 표의 모든 한계에 대해, 문서화된
값에 정확히 걸치는 요청을 만들어 us-east-1의 실제 서비스로 보냈습니다.
그다음 그보다 한 단위 넘어서는 두 번째 요청을 보냈습니다. 첫 번째는 반드시
수락되고 두 번째는 반드시 거부되어야 합니다 — 문서의 말을 그대로 믿는 대신,
그 한 쌍이 실제 경계를 짚어 줍니다. 마지막 열의 거부 메시지는 서비스가
직접 내놓은 문장을 그대로 캡처한 것이며, 다시 옮겨 적지 않았습니다.
네 개 행은 거부 쪽에서만 확인할 수 있었습니다. 이들은 CreateTable 한계로,
수락 쪽을 확인하려면 인덱스 20개짜리 테이블을 만들고 각각이 활성화되기를
기다려야 하는데, 그 수치는 거부 메시지가 이미 그대로 말해 줍니다.
확인 방법 열은 어느 쪽인지 알려 줍니다. 장식이 아닙니다.
검증된 한계
| 제한 | 값 | 확인 방법 | 초과 시 서비스가 반환하는 값 |
|---|---|---|---|
| 최대 항목 크기 | 409,600바이트 | 409,600에서는 수락, 409,601에서는 거부 | ValidationException: Item size has exceeded the maximum allowed size |
| 최대 파티션 키 값 | 2,048바이트 | 2,048에서는 수락, 2,049에서는 거부 | ValidationException: One or more parameter values were invalid: Size of hashkey has exceeded the maximum size limit of2048 bytes |
| 최대 정렬 키 값 | 1,024바이트 | 1,024에서는 수락, 1,025에서는 거부 | ValidationException: One or more parameter values were invalid: Aggregated size of all range keys has exceeded the size limit of 1024 bytes |
| 최대 중첩 깊이 | 32단계 | 32에서는 수락, 33에서는 거부 | ValidationException: 1 validation error detected: Nesting Levels have exceeded supported limits: Attributes in the item have nested levels beyond supported limit |
| 최대 표현식 길이 | 4,096바이트 | 4,096에서는 수락, 4,097에서는 거부 | ValidationException: 1 validation error detected: Invalid ConditionExpression: Expression size has exceeded the maximum allowed size; |
| BatchWriteItem당 최대 항목 수 | 25개 | 25에서는 수락, 26에서는 거부 | ValidationException: 1 validation error detected: Value '<your request>' at 'requestItems' failed to satisfy constraint: Map value must satisfy constraint: [Member must have length less than or equal to 25, Member must have length greater than or equal to 1] |
| BatchGetItem당 최대 키 수 | 100개 | 100에서는 수락, 101에서는 거부 | ValidationException: 1 validation error detected: Value at 'RequestItems.<table-name>.member.Keys' failed to satisfy constraint: Member must have length less than or equal to 100 |
| TransactWriteItems당 최대 작업 수 | 100개 | 100에서는 수락, 101에서는 거부 | ValidationException: 1 validation error detected: Value '<your request>' at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 100 |
| 테이블당 글로벌 보조 인덱스 | 20개 | 거부 쪽만 확인 | ValidationException: One or more parameter values were invalid: GlobalSecondaryIndex count exceeds the per-table limit of 20 |
| 테이블당 로컬 보조 인덱스 | 5개 | 거부 쪽만 확인 | ValidationException: One or more parameter values were invalid: Number of LocalSecondaryIndexes exceeds per-table limit of 5 |
| 인덱스당 프로젝션된 비키 속성 | 20개 | 거부 쪽만 확인 | ValidationException: 1 validation error detected: Value '<your request>' at 'globalSecondaryIndexes.1.member.projection.nonKeyAttributes' failed to satisfy constraint: Member must have length less than or equal to 20 |
| 테이블당 프로젝션된 비키 속성 | 100개 | 거부 쪽만 확인 | ValidationException: One or more parameter values were invalid: Number of projected attributes in all indexes exceeds limit of 100, number of projected attributes:120 |
환경: Amazon DynamoDB, 실제 서비스, us-east-1, AWS SDK for JavaScript v3로
2026-08-27에 프로브.
프로브가 밝혀낸 것
400KB는 409,600바이트를 뜻하며, 속성 이름까지 셉니다. 정확히 409,600바이트로 측정된 항목은 수락되었고, 409,601바이트는 거부되었습니다. 이 측정은 모든 속성 이름과 모든 값의 UTF-8 길이를 더한 것으로, 저희 항목 크기 계산기가 구현한 것과 같은 계산 방식입니다 — 프로브는 그 라이브러리로 페이로드를 만들었으므로, 둘이 일치하는 건 주장이 아니라 구조상 그렇습니다. 이 한계 뒤에 있는 더 깊은 모델링 문제는 DynamoDB 항목 크기 한계 가이드에서 따로 다룹니다.
다들 인용하는 프로젝션 속성 한계는 사실 틀린 수치입니다. AWS
쿼터 페이지는
"테이블의 모든 로컬·글로벌 보조 인덱스를 합쳐 최대 100개 속성"이라는
수치 하나만 문서화하며, 인덱스별 상한은 전혀 언급하지 않습니다. 인덱스별
상한은 실제로 존재하며, 20입니다. 비키 속성 21개를 인덱스 하나에
프로젝션하는 CreateTable은 테이블 전체 합계가 100 근처에도 가기 전에
거부됩니다. 20이라는 수치는 문서화되어 있긴 하지만, API 레퍼런스의
Projection
페이지에만, 배열 멤버 제약("Maximum number of 20 items")으로 적혀 있습니다.
쿼터 페이지만 보고 인덱스를 설계하면, 쿼터 페이지가 괜찮다고 말하는
스키마를 API가 거부하게 됩니다. 두 수치 모두 위 표에 있으며, 각각 그것을
증명하는 거부 메시지와 함께 실려 있습니다.
메시지 두 개에는 AWS 자신의 오타가 들어 있습니다. 조용히 고치지 않고
여기 그대로 재현했습니다 — maximum size limit of2048 bytes는 공백이
하나 빠져 있고, number of projected attributes:120도 마찬가지로 공백이
빠져 있습니다. 이 문자열로 로그를 grep한다면, 올바르게 읽히는 문장이
아니라 서비스가 실제로 보내는 그대로를 grep하세요.
정렬 키는 합산해서 측정됩니다. 정렬 키 거부 메시지가 "정렬 키"가 아니라 "모든 range key의 합산 크기(Aggregated size of all range keys)"라고 말하는 이유는, 같은 1,024바이트 예산이 테이블의 정렬 키와 그 항목이 속한 모든 로컬 보조 인덱스의 정렬 키를 함께 커버하기 때문입니다.
아무것도 일으키지 않는 1MB 페이지
위의 모든 한계는 여러분을 거부함으로써 스스로를 알립니다. Query와 Scan의
페이지 한계는 그렇지 않습니다. 이 선을 넘으면 DynamoDB는 오류도 경고도
없이 짧은 페이지와 LastEvaluatedKey를 반환합니다 — "내 Scan이 테이블의
일부만 반환했다"가 흔히 겪는 놀라움인 이유이자,
페이지네이션이 선택 사항이 아닌 이유입니다.
즉 인용할 오류 메시지가 없다는 뜻이므로, 유발하는 대신 직접 측정했습니다:
항목당 1,000바이트일 때 한 페이지는 1,029개 항목을 담고
LastEvaluatedKey를 반환했습니다 — 항목 데이터 1,029,000바이트이며,
1,030번째 항목은 다음 요청으로 남았습니다. 항목당 5,000바이트일 때는
한 페이지가 208개 항목을 담고 LastEvaluatedKey를 반환했습니다 —
항목 데이터 1,040,000바이트이며, 209번째 항목은 다음 요청으로 남았습니다.
두 페이지 모두 1MiB의 항목 데이터를 담지 못했습니다 — 첫 번째는 약 19,576바이트가 모자랐습니다. 즉 페이지 예산은 항목 자체 바이트보다 항목당 더 많이 청구합니다.
서로 다른 항목 크기로 두 번 측정하면 이를 정확히 짚어내기에 충분합니다.
페이지를 항목 수 × (항목 바이트 + 항목당 오버헤드) ≤ 예산으로 놓으면,
두 측정치와 모두 맞아떨어지는 정수 바이트 오버헤드는 7가지뿐이며,
그중 정확히 하나만 예산을 딱 떨어지는 2진 메가바이트에 놓습니다. 항목당
오버헤드 19바이트, 예산은 1,048,551바이트에서 1,048,971바이트 사이 —
이 범위는 1,048,576을 포함합니다. DynamoDB의 "1MB"는 자체 쿼터
페이지가 밝히듯 2진 단위이며, 항목 바이트에 더해 항목당 오버헤드에도
쓰입니다. 항목당 약 19바이트를 이 몫으로 예산에 잡아 두세요.
직접 찔러보지 않은 쿼터
아래 한계는 직접 측정한 것이 아니라 AWS를 인용한 것입니다. 계정 수준 쿼터로, 대부분 요청하면 조정 가능하며, 이 값에 도달한다는 것은 시간당 청구되는 처리량을 프로비저닝하거나, 테이블을 수천 개 만들거나, 1년치 예약 용량에 커밋한다는 뜻입니다. 그중 어느 것도 직접 찔러본 것이 아니므로, 그렇게 제시하지도 않습니다. 모든 행의 출처는 AWS Amazon DynamoDB의 쿼터 페이지입니다.
| 쿼터 | 기본값 | 조정 가능 | 직접 확인하지 않은 이유 |
|---|---|---|---|
| 리전당 계정별 테이블 수 | 2,500 | 예 | 2,501번째가 실패하는 걸 보려고 테이블 2,500개를 만들면, 누군가 정리해야 할 계정이 남습니다. |
| 테이블당 프로비저닝된 처리량 | RCU 40,000 및 WCU 40,000 | 예 | 40,000 유닛을 프로비저닝하면 요청을 단 하나도 보내지 않아도 시간당 요금이 청구됩니다. |
| 계정당 프로비저닝된 처리량 | RCU 80,000 및 WCU 80,000 | 예 | 같은 이유가 두 배이며, 계정 전체 설정까지 바꿔 버립니다. |
| 테이블당 온디맨드 처리량 | RRU 40,000 및 WRU 40,000 | 예 | 이 값에 도달하려면 초당 40,000건의 요청을 지속해야 하는데, 이는 청구서가 딸린 부하 테스트입니다. |
| 계정당 활성 예약 용량 | 용량 유닛 1,000,000 | 예 | 예약 용량은 1년치 구매 약정이지, 찔러볼 수 있는 대상이 아닙니다. |
| 테이블 크기 | 실질적인 제한 없음 | — | AWS는 테이블이 항목 수와 바이트 모두 제한이 없다고 명시합니다. 찾을 경계 자체가 없습니다. |
설계할 때 염두에 둬야 할 한계
대부분은 절대 마주치지 않을 것입니다. 실제 설계를 좌우하는 몇 가지만 꼽으면:
- 항목당 400KB는 쿼터가 아니라 모델링 제약입니다. 이 값에 근접하는 항목은 보통 임베디드 리스트로 저장된, 경계 없는 일대다 관계입니다. 항목 크기 한계를 참고하세요.
- 1MB 페이지는 여러분이 작성하는 모든 Query와 Scan을 지배합니다.
LastEvaluatedKey를 무시하는 코드는 데이터가 페이지 하나를 넘어서는 날 조용히 틀려집니다. - 배치 쓰기당 25개, 배치 읽기당 100개라는 항목 수는 여러분의 대량 로드 루프 형태를 결정합니다. 배치 작업을 참고하세요.
- 트랜잭션당 100개 작업은 DynamoDB를 관계형처럼 다루려다가 사람들이 부딪히는 한계입니다. 트랜잭션을 참고하세요.
- GSI 20개, LSI 5개, 프로젝션 속성 100개는 처리량 쿼터보다 훨씬 더 자주 액세스 패턴 설계를 제약하며, LSI 개수는 테이블을 만드는 순간 고정됩니다. 인덱스 프로젝션과 GSI 대 LSI를 참고하세요.
위에서 인용한 거부 메시지 대부분은 DynamoDB 오류 아래에
그 메시지를 일으키는 요청과 함께 자체 페이지를 가지고 있습니다. 네 개의
CreateTable 거부 메시지는 그렇지 않습니다 — 이 페이지를 위해 따로
캡처했습니다.
항목을 쓰지 않고 400KB 선에 맞는지 확인하려면, 항목 크기 계산기가 브라우저에서 바로 실행됩니다. 여러분의 테이블에 있는 항목을 직접 들여다보려면, DynoTable이 데스크톱 DynamoDB 클라이언트입니다.