AWS DynamoDB Deep-Dive Skill
Deep analysis of DynamoDB tables, capacity planning, index health, and operational metrics.
MANDATORY: Discovery-First Pattern
Always list tables and get basic descriptions before deep-diving.
Phase 1: Discovery
#!/bin/bash
export AWS_PAGER=""
echo "=== DynamoDB Tables ==="
TABLES=$(aws dynamodb list-tables --output text --query 'TableNames[]')
echo "$TABLES" | tr '\t' '\n'
echo ""
echo "=== Table Summary ==="
for table in $TABLES; do
aws dynamodb describe-table --table-name "$table" --output text \
--query "Table.[TableName,TableStatus,BillingModeSummary.BillingMode,ItemCount,TableSizeBytes,GlobalSecondaryIndexes[].IndexName|join(',',@)]" &
done
wait
echo ""
echo "=== Auto-Scaling Targets ==="
aws application-autoscaling describe-scalable-targets --service-namespace dynamodb --output text \
--query 'ScalableTargets[].[ResourceId,ScalableDimension,MinCapacity,MaxCapacity]' 2>/dev/null | head -20
Phase 2: Analysis
#!/bin/bash
export AWS_PAGER=""
END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S")
START_TIME=$(date -u -d "7 days ago" +"%Y-%m-%dT%H:%M:%S" 2>/dev/null || date -u -v-7d +"%Y-%m-%dT%H:%M:%S")
echo "=== Throttle Events (7d) ==="
for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do
{
read_throttle=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ReadThrottleEvents \
--dimensions Name=TableName,Value="$table" \
--start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Sum \
--output text --query 'Datapoints[0].Sum')
write_throttle=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name WriteThrottleEvents \
--dimensions Name=TableName,Value="$table" \
--start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Sum \
--output text --query 'Datapoints[0].Sum')
printf "%s\tReadThrottles:%s\tWriteThrottles:%s\n" "$table" "${read_throttle:-0}" "${write_throttle:-0}"
} &
done
wait
echo ""
echo "=== Consumed vs Provisioned Capacity ==="
for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do
{
consumed_read=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedReadCapacityUnits \
--dimensions Name=TableName,Value="$table" \
--start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Average \
--output text --query 'Datapoints[0].Average')
consumed_write=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedWriteCapacityUnits \
--dimensions Name=TableName,Value="$table" \
--start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Average \
--output text --query 'Datapoints[0].Average')
printf "%s\tAvgReadCU:%.1f\tAvgWriteCU:%.1f\n" "$table" "${consumed_read:-0}" "${consumed_write:-0}"
} &
done
wait
echo ""
echo "=== GSI Status ==="
for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do
aws dynamodb describe-table --table-name "$table" --output text \
--query "Table.GlobalSecondaryIndexes[].[\"$table\",IndexName,IndexStatus,ItemCount,Projection.ProjectionType]" 2>/dev/null &
done
wait
echo ""
echo "=== Streams & TTL ==="
for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do
{
stream=$(aws dynamodb describe-table --table-name "$table" --output text \
--query 'Table.StreamSpecification.StreamEnabled')
ttl=$(aws dynamodb describe-time-to-live --table-name "$table" --output text \
--query 'TimeToLiveDescription.TimeToLiveStatus')
printf "%s\tStream:%s\tTTL:%s\n" "$table" "${stream:-disabled}" "${ttl:-DISABLED}"
} &
done
wait
echo ""
echo "=== Backups ==="
aws dynamodb list-backups --output text \
--query 'BackupSummaries[].[TableName,BackupName,BackupStatus,BackupCreationDateTime,BackupType]' | head -10
echo ""
echo "=== Contributor Insights ==="
for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do
aws dynamodb describe-contributor-insights --table-name "$table" --output text \
--query "[TableName,ContributorInsightsStatus]" 2>/dev/null &
done
wait
Output Format
- Target ≤50 lines per output
- Use
--output text --query for all commands
- Tab-delimited fields: TableName, Metric, Value
- Aggregate capacity metrics as averages over the period
- Never dump full table items -- show metadata and metrics only
Anti-Hallucination Rules
- NEVER assume resource names — always discover via CLI/API in Phase 1 before referencing in Phase 2.
- NEVER fabricate metric names or dimensions — verify against the service documentation or
--help output.
- NEVER mix CLI commands between service versions — confirm which version/API you are targeting.
- ALWAYS use the discovery → verify → analyze chain — every resource referenced must have been discovered first.
- ALWAYS handle empty results gracefully — an empty response is valid data, not an error to retry.
Counter-Rationalizations
| Shortcut |
Counter |
Why |
| "I'll skip discovery and check known resources" |
Always run Phase 1 discovery first |
Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" |
Follow the full discovery → analysis flow |
Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" |
Audit configuration explicitly |
Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" |
Always check relevant metrics when available |
API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" |
Try the command and report the actual error |
Assumed permission failures prevent useful investigation; actual errors are informative |
Common Pitfalls
- Billing mode: PAY_PER_REQUEST vs PROVISIONED -- capacity metrics differ significantly
- GSI throttling: GSIs have independent capacity -- throttled GSIs can cause table-level write throttling
- Hot partitions: Use Contributor Insights to identify hot partition keys
- On-demand scaling: On-demand tables can throttle if traffic exceeds 2x previous peak within 30 minutes
- Auto-scaling delay: Auto-scaling reacts to CloudWatch alarms with ~5 minute delay -- not instant
- Item size: Max 400KB per item -- check
AverageItemSize from table description
- Stream retention: DynamoDB Streams retain data for 24 hours only -- process events promptly
- Backup types: AWS_BACKUP (managed by AWS Backup), SYSTEM (continuous backups), USER (on-demand)
1---2name: managing-aws-dynamodb-deep3description: Use when working with Aws Dynamodb Deep — aWS DynamoDB deep-dive management covering table configurations, capacity analysis, GSI/LSI health, auto-scaling policies, streams, backups, contributor insights, and TTL settings. Use when performing deep analysis of DynamoDB tables, optimizing capacity and throughput, debugging throttling, or auditing table configurations beyond basic inventory.4---56# AWS DynamoDB Deep-Dive Skill78Deep analysis of DynamoDB tables, capacity planning, index health, and operational metrics.910## MANDATORY: Discovery-First Pattern1112**Always list tables and get basic descriptions before deep-diving.**1314### Phase 1: Discovery1516```bash17#!/bin/bash18export AWS_PAGER=""1920echo "=== DynamoDB Tables ==="21TABLES=$(aws dynamodb list-tables --output text --query 'TableNames[]')22echo "$TABLES" | tr '\t' '\n'2324echo ""25echo "=== Table Summary ==="26for table in $TABLES; do27 aws dynamodb describe-table --table-name "$table" --output text \28 --query "Table.[TableName,TableStatus,BillingModeSummary.BillingMode,ItemCount,TableSizeBytes,GlobalSecondaryIndexes[].IndexName|join(',',@)]" &29done30wait3132echo ""33echo "=== Auto-Scaling Targets ==="34aws application-autoscaling describe-scalable-targets --service-namespace dynamodb --output text \35 --query 'ScalableTargets[].[ResourceId,ScalableDimension,MinCapacity,MaxCapacity]' 2>/dev/null | head -2036```3738### Phase 2: Analysis3940```bash41#!/bin/bash42export AWS_PAGER=""4344END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S")45START_TIME=$(date -u -d "7 days ago" +"%Y-%m-%dT%H:%M:%S" 2>/dev/null || date -u -v-7d +"%Y-%m-%dT%H:%M:%S")4647echo "=== Throttle Events (7d) ==="48for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do49 {50 read_throttle=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ReadThrottleEvents \51 --dimensions Name=TableName,Value="$table" \52 --start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Sum \53 --output text --query 'Datapoints[0].Sum')54 write_throttle=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name WriteThrottleEvents \55 --dimensions Name=TableName,Value="$table" \56 --start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Sum \57 --output text --query 'Datapoints[0].Sum')58 printf "%s\tReadThrottles:%s\tWriteThrottles:%s\n" "$table" "${read_throttle:-0}" "${write_throttle:-0}"59 } &60done61wait6263echo ""64echo "=== Consumed vs Provisioned Capacity ==="65for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do66 {67 consumed_read=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedReadCapacityUnits \68 --dimensions Name=TableName,Value="$table" \69 --start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Average \70 --output text --query 'Datapoints[0].Average')71 consumed_write=$(aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ConsumedWriteCapacityUnits \72 --dimensions Name=TableName,Value="$table" \73 --start-time "$START_TIME" --end-time "$END_TIME" --period 604800 --statistics Average \74 --output text --query 'Datapoints[0].Average')75 printf "%s\tAvgReadCU:%.1f\tAvgWriteCU:%.1f\n" "$table" "${consumed_read:-0}" "${consumed_write:-0}"76 } &77done78wait7980echo ""81echo "=== GSI Status ==="82for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do83 aws dynamodb describe-table --table-name "$table" --output text \84 --query "Table.GlobalSecondaryIndexes[].[\"$table\",IndexName,IndexStatus,ItemCount,Projection.ProjectionType]" 2>/dev/null &85done86wait8788echo ""89echo "=== Streams & TTL ==="90for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do91 {92 stream=$(aws dynamodb describe-table --table-name "$table" --output text \93 --query 'Table.StreamSpecification.StreamEnabled')94 ttl=$(aws dynamodb describe-time-to-live --table-name "$table" --output text \95 --query 'TimeToLiveDescription.TimeToLiveStatus')96 printf "%s\tStream:%s\tTTL:%s\n" "$table" "${stream:-disabled}" "${ttl:-DISABLED}"97 } &98done99wait100101echo ""102echo "=== Backups ==="103aws dynamodb list-backups --output text \104 --query 'BackupSummaries[].[TableName,BackupName,BackupStatus,BackupCreationDateTime,BackupType]' | head -10105106echo ""107echo "=== Contributor Insights ==="108for table in $(aws dynamodb list-tables --output text --query 'TableNames[]'); do109 aws dynamodb describe-contributor-insights --table-name "$table" --output text \110 --query "[TableName,ContributorInsightsStatus]" 2>/dev/null &111done112wait113```114115## Output Format116117- Target ≤50 lines per output118- Use `--output text --query` for all commands119- Tab-delimited fields: TableName, Metric, Value120- Aggregate capacity metrics as averages over the period121- Never dump full table items -- show metadata and metrics only122123## Anti-Hallucination Rules1241251. **NEVER assume resource names** — always discover via CLI/API in Phase 1 before referencing in Phase 2.1262. **NEVER fabricate metric names or dimensions** — verify against the service documentation or `--help` output.1273. **NEVER mix CLI commands between service versions** — confirm which version/API you are targeting.1284. **ALWAYS use the discovery → verify → analyze chain** — every resource referenced must have been discovered first.1295. **ALWAYS handle empty results gracefully** — an empty response is valid data, not an error to retry.130131## Counter-Rationalizations132133| Shortcut | Counter | Why |134|----------|---------|-----|135| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |136| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |137| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |138| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |139| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |140141## Common Pitfalls142143- **Billing mode**: PAY_PER_REQUEST vs PROVISIONED -- capacity metrics differ significantly144- **GSI throttling**: GSIs have independent capacity -- throttled GSIs can cause table-level write throttling145- **Hot partitions**: Use Contributor Insights to identify hot partition keys146- **On-demand scaling**: On-demand tables can throttle if traffic exceeds 2x previous peak within 30 minutes147- **Auto-scaling delay**: Auto-scaling reacts to CloudWatch alarms with ~5 minute delay -- not instant148- **Item size**: Max 400KB per item -- check `AverageItemSize` from table description149- **Stream retention**: DynamoDB Streams retain data for 24 hours only -- process events promptly150- **Backup types**: AWS_BACKUP (managed by AWS Backup), SYSTEM (continuous backups), USER (on-demand)