Back to docs
Operations
Runbook: DynamoDB Throttling
**Applies to:** the ~27 app tables provisioned by the CDK stack, all named `dagy-<thing>-<env>` (e.g. `dagy-flows-<env>`, `dagy-runs-<env>`, `dagy-task-runs-<env>`, `dagy-schedules-<env>`, `dagy-rate-limits-<env>`).
Severity: Sev-2 (usually manifests as API 5xx or stuck runs before it is Sev-1).
Symptoms
ProvisionedThroughputExceededException/ThrottlingExceptionin API, scheduler, launcher, or worker logs.- Runs stuck in
QUEUED, schedules firing late, or 5xx from write-heavy endpoints. - CloudWatch
ThrottledRequests/ReadThrottleEvents/WriteThrottleEventsclimbing on a table or one of its GSIs.
Diagnosis
export ENV=develop REGION=us-east-1
-
Find which tables are throttling (scan the usual hot tables):
for T in flows runs task-runs schedules rate-limits usage-events; do echo "== dagy-$T-$ENV ==" aws cloudwatch get-metric-statistics --region "$REGION" \ --namespace AWS/DynamoDB --metric-name ThrottledRequests \ --dimensions Name=TableName,Value=dagy-$T-$ENV \ --start-time "$(date -u -d '30 min ago' +%Y-%m-%dT%H:%M:%SZ)" \ --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --period 300 --statistics Sum done -
Read vs write throttles on the hottest table:
aws cloudwatch get-metric-statistics --region "$REGION" \ --namespace AWS/DynamoDB --metric-name WriteThrottleEvents \ --dimensions Name=TableName,Value=dagy-runs-$ENV \ --start-time "$(date -u -d '30 min ago' +%Y-%m-%dT%H:%M:%SZ)" \ --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --period 300 --statistics Sum -
Check the table's billing mode / capacity:
aws dynamodb describe-table --region "$REGION" \ --table-name dagy-runs-$ENV \ --query 'Table.{Billing:BillingModeSummary.BillingMode,RCU:ProvisionedThroughput.ReadCapacityUnits,WCU:ProvisionedThroughput.WriteCapacityUnits,GSIs:GlobalSecondaryIndexes[].IndexName}' -
GSI hot-partition check — throttles on an index name (not the base table) point at a low-cardinality GSI partition key. Inspect the same metric with
Name=GlobalSecondaryIndexName,Value=<gsi>added.
Remediation
- On-demand tables rarely throttle steadily; a burst throttle self-heals as
the table adapts. Add jittered client retry (the readiness probe uses
max_attempts=0deliberately, but application clients should retry). - Provisioned tables → raise capacity or switch to on-demand:
aws dynamodb update-table --region "$REGION" \ --table-name dagy-runs-$ENV \ --billing-mode PAY_PER_REQUEST - Hot partition → the fix is a key-design change (more partition-key
cardinality), not a capacity bump. File a follow-up; the migrations runner
(
python -m dagy_api.persistence.migrations) is the tool for a safe repartition. - Runaway writer (e.g. a backfill dispatching too fast) → pause it:
POST /backfills/{id}/cancel, or throttle the scheduler tick.
Escalation
- Page L2 if throttling persists after capacity change or if it is caused by a hot partition needing a schema migration.
- Note: all tables have PITR enabled, so if a bad migration/backfill caused the load, point-in-time restore is available as a last resort — coordinate with L3 before restoring (it creates a new table).