DynamoDB Capacity Mode Switching: On-Demand to Provisioned Around Cliffs

DynamoDB Capacity Mode Switching: On-Demand to Provisioned Around Cliffs

On-demand DynamoDB is wonderful until a known cliff (Black Friday, product launch, nightly batch) makes provisioned-plus-auto-scaling dramatically cheaper—if you switch without inventing throttles mid-checkout. The unfair advantage is a rehearsal playbook: pre-warm provisioned capacity, flip modes in a maintenance window you control, and keep adaptive capacity headroom for hot partitions.

⚡ TL;DR: Never flip modes under peak; pre-provision RCU/WCU above the cliff; use auto-scaling with aggressive scale-in cooldown; verify CloudWatch ThrottledRequests stays zero for 30 minutes post-cut; keep a fast rollback to on-demand. Pair with DynamoDB Hot Key Mitigation and Lambda Reserved Concurrency.

Know your cliff shape

# ✅ Estimate peak from Insights / billing metrics, not folklore
# PeakConsumedWriteCapacityUnits * safety (1.4–2.0) → target provisioned WCU
peak_wcu = 12_000   # from CloudWatch max over last 4 similar events
safety = 1.5
target_wcu = int(peak_wcu * safety)  # 18_000
# On-demand bill ≈ peak * hours * price; provisioned = reserved + auto-scale burst
Mode Best when Failure mode
On-demand Spiky / unknown Cost cliffs surprise finance
Provisioned + auto-scale Predictable cliffs Under-provision → throttle
Provisioned + reserved Steady 24×7 Waste if traffic vanishes

Switch without throttling checkout

# ✅ Pre-warm: create provisioned table settings BEFORE traffic arrives
# 1) UpdateTable → BillingMode=PROVISIONED with RCU/WCU >= target
# 2) Wait TableStatus=ACTIVE and Consumed < Provisioned for 10+ min
# 3) Enable Application Auto Scaling (target 70% utilization)
# 4) Only then route launch traffic / open feature flag

# ❌ Flip to provisioned at T-0 with default 5 WCU — instant throttle storm
# ✅ boto3 cutover helper (run in change window, not in the request path)
import boto3

ddb = boto3.client("dynamodb")

def to_provisioned(table: str, rcu: int, wcu: int) -> None:
    ddb.update_table(
        TableName=table,
        BillingMode="PROVISIONED",
        ProvisionedThroughput={"ReadCapacityUnits": rcu, "WriteCapacityUnits": wcu},
    )
    waiter = ddb.get_waiter("table_exists")
    waiter.wait(TableName=table)  # ACTIVE again

def rollback_on_demand(table: str) -> None:
    ddb.update_table(TableName=table, BillingMode="PAY_PER_REQUEST")

Hot partitions still bite

Mode switching does not fix a single hot partition. Adaptive capacity helps, but shard write keys before the cliff. Monitor SuccessfulRequestLatency, ThrottledRequests, and per-partition metrics if enabled.

# ✅ Canary: synthetic checkout writer at 20% of cliff for 15 minutes post-switch
# Abort / rollback if ThrottledRequests > 0 sustained for 60s

❌ Assuming on-demand → provisioned is instant and free of account-level soft limits — file limit increases days ahead for large WCU jumps.

Closing checklist

✅ Dos
– ✅ Size from historical peak × safety factor
– ✅ Pre-warm provisioned capacity in a quiet window
– ✅ Enable auto-scaling with slow scale-in
– ✅ Canary write path before opening the floodgates
– ✅ Document rollback to on-demand as a one-command runbook

❌ Don’ts
– ❌ Don’t switch modes during the cliff itself
– ❌ Don’t ignore hot-key sharding before a launch
– ❌ Don’t set scale-in cooldown so aggressive you thrash
– ❌ Don’t skip account quota tickets for large WCU

Related reading

Last updated on September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply