DynamoDB returns ItemCollectionSizeLimitExceededException when a local secondary index (LSI) or table partition exceeds the 10 GB size limit for item collections. This occurs when too much data accumulates under a single partition key value, requiring data redistribution or archival strategies.
The ItemCollectionSizeLimitExceededException error in DynamoDB indicates that a specific item collection has exceeded the 10 GB size limit. An "item collection" refers to all items that share the same partition key value in a table or local secondary index. This limit exists because: 1. **Partition management**: DynamoDB partitions data based on partition key values 2. **Local secondary indexes**: LSIs maintain sorted collections of items within each partition 3. **Performance guarantees**: The 10 GB limit ensures predictable performance for queries and scans When you exceed this limit, DynamoDB cannot add new items to that partition key value until you reduce the collection size. This is a hard limit that cannot be increased, unlike provisioned throughput limits.
First, determine which partition key values are hitting the 10 GB limit:
# Use CloudWatch metrics to identify hot partitions
aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedWriteCapacityUnits --dimensions Name=TableName,Value=YourTableName --start-time $(date -u -d "1 hour ago" +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 300 --statistics Sum
# Scan for large item collections (careful - can be expensive)
aws dynamodb scan --table-name YourTableName --select COUNT --filter-expression "begins_with(partitionKey, :prefix)" --expression-attribute-values '{":prefix":{"S":"problematic-value"}}'
# Check for LSIs with large collections
aws dynamodb describe-table --table-name YourTableName --query 'Table.LocalSecondaryIndexes[*].IndexName'Analysis approach:
- Review application logs for failed write operations
- Check which partition key values appear in error messages
- Analyze data access patterns in your application code
- Use DynamoDB Streams to monitor write patterns
Modify your partition key design to distribute data across multiple partitions:
// BEFORE: Single partition key for all metrics
const partitionKey = 'system-metrics';
// AFTER: Sharded partition keys
function getShardedPartitionKey(metricName) {
const shardId = Math.floor(Math.random() * 100); // 0-99
return `metric-${metricName}-shard-${shardId}`;
}
// For time-series data, include timestamp granularity
function getTimeBasedPartitionKey(entity, timestamp) {
const date = new Date(timestamp);
const month = date.toISOString().substring(0, 7); // YYYY-MM
return `${entity}-${month}`;
}
// For user data, use composite keys
function getUserPartitionKey(userId, dataType) {
return `user-${userId}-${dataType}`;
}Sharding strategies:
- Add random suffixes (0-99, 0-999) based on expected volume
- Include timestamp components (year, month, day) for time-series
- Use composite keys combining entity type with sub-category
- Implement consistent hashing for deterministic shard assignment
Implement data lifecycle management to remove old items:
// Example: Delete items older than retention period
async function deleteOldItems(tableName, partitionKey, retentionDays) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
// Query for old items (assuming sort key is timestamp)
const params = {
TableName: tableName,
KeyConditionExpression: 'pk = :pk AND sk < :cutoff',
ExpressionAttributeValues: {
':pk': { S: partitionKey },
':cutoff': { N: cutoffDate.getTime().toString() }
}
};
const items = [];
let lastEvaluatedKey;
do {
if (lastEvaluatedKey) {
params.ExclusiveStartKey = lastEvaluatedKey;
}
const result = await dynamodb.query(params).promise();
items.push(...result.Items);
lastEvaluatedKey = result.LastEvaluatedKey;
} while (lastEvaluatedKey);
// Batch delete in chunks of 25
for (let i = 0; i < items.length; i += 25) {
const chunk = items.slice(i, i + 25);
const deleteRequests = chunk.map(item => ({
DeleteRequest: {
Key: { pk: item.pk, sk: item.sk }
}
}));
await dynamodb.batchWriteItem({
RequestItems: {
[tableName]: deleteRequests
}
}).promise();
}
}
// Alternative: Move to archival table
async function archiveOldItems(sourceTable, archiveTable, partitionKey) {
// Similar pattern but with PutRequest to archive table
// and DeleteRequest from source table in same batch
}Retention strategies:
- Implement TTL (Time to Live) for automatic expiration
- Schedule Lambda functions for periodic cleanup
- Move historical data to DynamoDB Standard-IA or S3
- Consider DynamoDB Streams + Lambda for real-time archival
If LSIs are causing the issue, consider alternative indexing strategies:
// BEFORE: LSI on frequently updated attribute
// This creates large collections if many items share partition key
// AFTER: Use global secondary index (GSI) instead
// GSIs have their own partition key and don't contribute to item collection size
// Or: Denormalize data into separate tables
async function writeToDenormalizedTable(mainTable, summaryTable, item) {
// Write detailed data to main table with fine-grained partition key
await dynamodb.putItem({
TableName: mainTable,
Item: {
pk: { S: `detail-${item.id}-${Date.now()}` },
// ... other attributes
}
}).promise();
// Write summary to separate table
await dynamodb.updateItem({
TableName: summaryTable,
Key: { pk: { S: `summary-${item.category}` } },
UpdateExpression: 'ADD #count :inc',
ExpressionAttributeNames: { '#count': 'itemCount' },
ExpressionAttributeValues: { ':inc': { N: '1' } }
}).promise();
}
// Use sparse indexes for conditional data
// Only include items in GSI when they have specific attributesIndexing alternatives:
- Replace LSIs with GSIs when possible
- Implement composite GSIs for different query patterns
- Use DynamoDB Accelerator (DAX) for read-heavy patterns
- Consider Amazon Athena for analytical queries on exported data
Add proactive monitoring and throttling to prevent future issues:
// Monitor partition sizes
class PartitionMonitor {
constructor(tableName) {
this.tableName = tableName;
this.partitionCounts = new Map();
this.warningThreshold = 8 * 1024 * 1024 * 1024; // 8 GB warning
}
async trackWrite(partitionKey) {
const count = (this.partitionCounts.get(partitionKey) || 0) + 1;
this.partitionCounts.set(partitionKey, count);
// Estimate size (adjust based on your average item size)
const estimatedSize = count * 1024; // 1KB per item
if (estimatedSize > this.warningThreshold) {
console.warn(`Partition ${partitionKey} approaching 10 GB limit`);
// Trigger mitigation: switch to sharded key, throttle writes, etc.
return this.getShardedKey(partitionKey);
}
return partitionKey;
}
getShardedKey(originalKey) {
const suffix = Math.floor(Math.random() * 10);
return `${originalKey}-shard-${suffix}`;
}
}
// CloudWatch alarm for partition growthbash
# Create CloudWatch alarm for partition growth
aws cloudwatch put-metric-alarm --alarm-name "DynamoDB-Partition-Growth-Alarm" --alarm-description "Alarm when partition approaches 10 GB limit" --metric-name UserErrors --namespace AWS/DynamoDB --statistic Sum --period 300 --evaluation-periods 2 --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold --dimensions Name=TableName,Value=YourTableName Name=Operation,Value=PutItem --alarm-actions arn:aws:sns:us-east-1:123456789012:alarm-topic
```
Monitoring strategies:
- Track estimated partition sizes in application memory
- Set up CloudWatch alarms for UserErrors metric
- Implement circuit breakers for problematic partitions
- Log all ItemCollectionSizeLimitExceededException occurrences
For persistent issues, consider fundamental table redesign:
// Problematic design: Single partition for all events
// Partition key: "events"
// Sort key: timestamp
// Better design: Time-based partitioning
// Partition key: "events-2024-01", "events-2024-02", etc.
// Sort key: timestamp
// Even better: Sharded time-based partitioning
// Partition key: "events-2024-01-shard-0" through "events-2024-01-shard-9"
// Sort key: timestamp
// For user data: Composite partition keys
// Partition key: "user-12345-profile", "user-12345-orders", "user-12345-prefs"
// Instead of: "user-12345" with different item types in sort key
// Use separate tables for different data access patterns
const tables = {
userProfiles: 'UserProfilesTable',
userOrders: 'UserOrdersTable',
systemMetrics: 'SystemMetricsTable',
auditLogs: 'AuditLogsTable'
};Redesign principles:
- One table per access pattern when possible
- Fine-grained partition keys (more distinct values)
- Separate volatile data from archival data
- Consider DynamoDB Streams + Lambda for cross-table consistency
- Use DynamoDB transactions for multi-table atomic operations
## Understanding DynamoDB Partition Limits
### Item Collection Size Limit Details:
- 10 GB hard limit per partition key value
- Applies to both base table and all local secondary indexes combined
- Counts all versions of items (including deleted items until garbage collected)
- Based on uncompressed data size
- Cannot be increased via support ticket
### Local Secondary Index (LSI) Considerations:
- LSIs share the same partition key as base table
- Each LSI creates its own item collection within the partition
- All LSIs + base table must stay under 10 GB total
- LSIs cannot be added after table creation
- Consider GSIs instead for flexible indexing
### Partition Key Design Best Practices:
High-cardinality attributes:
- User IDs, session IDs, UUIDs
- Device IDs with high uniqueness
- Transaction IDs, order numbers
Avoid low-cardinality attributes:
- Status fields (active/inactive)
- Boolean flags (true/false)
- Small enum values (category A/B/C/D)
Time-series data patterns:
- ❌ Bad: "metrics" (single partition)
- ✅ Better: "metrics-2024-01", "metrics-2024-02"
- ✅ Best: "metrics-2024-01-shard-0" through "metrics-2024-01-shard-9"
### Data Lifecycle Management:
TTL (Time to Live):
- Automatically deletes items after expiry
- Reduces item collection size over time
- Background process, not immediate
- Useful for session data, temporary states
DynamoDB Streams + Lambda:
- Archive old data to S3
- Aggregate data into summary tables
- Implement custom retention policies
- Maintain data for compliance while reducing active size
### Migration Strategies:
Dual-write pattern:
1. Write to old and new table designs simultaneously
2. Use DynamoDB Streams to backfill historical data
3. Gradually shift read traffic to new design
4. Archive old table after validation
Blue-green deployment:
1. Create new table with improved design
2. Use data pipeline to copy/migrate data
3. Update application to use new table
4. Monitor performance before deleting old table
### Cost Considerations:
- Large scans/queries for data cleanup can be expensive
- GSIs have additional storage and throughput costs
- Data migration may incur significant read/write costs
- Consider cost of maintaining multiple tables vs. sharding
### When to Contact AWS Support:
- Need guidance on large-scale data migration
- Suspected data corruption issues
- Production-critical applications requiring immediate resolution
- Complex multi-table redesign requiring architecture review
ValidationException: The provided key element does not match the schema
How to fix "ValidationException: The provided key element does not match the schema" in DynamoDB
UnrecognizedClientException: The security token included in the request is invalid
How to fix "UnrecognizedClientException: The security token included in the request is invalid" in DynamoDB
TransactionCanceledException: Transaction cancelled
How to fix "TransactionCanceledException: Transaction cancelled" in DynamoDB
RequestLimitExceeded: Throughput exceeds the current throughput limit for your account
How to fix "RequestLimitExceeded: Throughput exceeds the current throughput limit for your account" in DynamoDB
InternalServerError: Internal Server Error
How to fix "InternalServerError" in DynamoDB