Skip to content

6. Capacity and cost

In most databases, cost is a machine you rent and performance is a mystery you investigate. Here it is inverted: performance is guaranteed and cost is arithmetic you can do on paper. That is a genuine engineering advantage, and only if you actually do the arithmetic.

1 WCU = one write of up to 1 KB per second
1 RCU = one strongly consistent read of up to 4 KB
per second
= TWO eventually consistent reads of 4 KB
ROUNDING IS ALWAYS UP, PER ITEM
a 1.2 KB write = 2 WCU
a 0.1 KB write = 1 WCU
a 9 KB strongly consistent read = 3 RCU
a 9 KB eventually consistent read = 1.5 RCU
TRANSACTIONS COST DOUBLE (ch 9)
TransactWriteItems 2 WCU per KB per item
TransactGetItems 2 RCU per 4 KB per item

Two consequences fall straight out of the rounding rule: small items are proportionally expensive (a 100-byte write costs the same as a 1 KB one), and large items are linearly expensive (a 390 KB item costs 390 WCU to write). Chapter 13 uses both facts.

ON-DEMAND (pay per request)
✓ no capacity planning at all
✓ scales instantly to any traffic shape
✓ perfect for new, spiky or unknown workloads
✗ roughly 5–7× the unit price of provisioned
✗ surprise traffic = surprise bill
PROVISIONED (pay per hour of capacity)
✓ much cheaper per request at steady load
✓ reserved capacity discounts it further
✓ auto-scaling adjusts within bounds
✗ throttling when traffic exceeds what you set
✗ auto-scaling reacts in MINUTES, not seconds
THE DECISION, IN PRACTICE
new product, unknown traffic → ON-DEMAND
steady 24/7 load → PROVISIONED +
auto-scaling +
reserved capacity
predictable daily peaks → PROVISIONED with
scheduled scaling
sharp unpredictable spikes → ON-DEMAND
development and test → ON-DEMAND

You can switch modes, with a cooldown between changes, so starting on-demand and moving to provisioned once the shape of the traffic is known is a perfectly good plan — and the order that wastes the least money.

PROVISIONED TABLE, 100 WCU
traffic 80 WCU → fine
traffic 120 WCU → burst capacity absorbs it for a
while (unused capacity from the
last ~5 minutes)
sustained 120 → burst exhausted →
ProvisionedThroughputExceededException
→ the SDK retries with backoff
→ your p99 latency climbs, then
requests start failing
THREE THINGS THAT LOOK LIKE THROTTLING
TABLE capacity exceeded → raise capacity
PARTITION ceiling hit → fix the key (ch 3)
GSI capacity exceeded → raise the GSI, and
it backpressures the
table (ch 7)

Distinguishing them is the whole diagnostic skill, because only the first is solved by money.

A worked example, because this is the chapter’s real point.

APPLICATION
1,000,000 users
each reads their profile 10×/day (1 KB)
each writes an event 5×/day (0.5 KB)
profile reads are eventually consistent
READS 10,000,000/day ÷ 86,400 ≈ 116/s
1 KB → 1 RCU strongly, 0.5 eventually
≈ 58 RCU sustained
WRITES 5,000,000/day ÷ 86,400 ≈ 58/s
0.5 KB → 1 WCU (rounded up!)
≈ 58 WCU sustained
PEAK traffic is not flat. assume 3× daily peak
→ provision ~175 RCU / ~175 WCU, or
auto-scale between 60 and 250
ORDER-OF-MAGNITUDE COSTS (us-east-1 list prices,
check current pricing — these move)
provisioned ~$0.00013 per WCU-hour
~$0.000025 per RCU-hour
on-demand ~$1.25 per million writes
~$0.25 per million reads
storage ~$0.25 per GB-month
PITR backup ~$0.20 per GB-month
streams ~$0.02 per 100k read requests

The point is not the numbers — they change — but that you can compute them before writing code, from the access-pattern list of chapter 8. No other database in this collection lets you do that.

① SCANS ON A REQUEST PATH
one endpoint doing a full scan per call. the
table is small in dev and enormous in prod.
② INDEXES YOU FORGOT
every GSI duplicates writes. four GSIs = 5× the
write cost of the base table. (ch 7)
③ STRONGLY CONSISTENT BY DEFAULT
some SDK wrappers default to strong reads.
instant 2× on every read.
④ SMALL ITEMS, HUGE COUNTS
a 60-byte telemetry item still costs 1 WCU.
batching them into larger items cuts cost
proportionally. (ch 13)
⑤ ON-DEMAND ON A STEADY WORKLOAD
the convenience premium, paid forever.
⑥ TRANSACTIONS USED ROUTINELY
double cost for guarantees a conditional write
often provides. (ch 9)
Terminal window
aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB \
--metric-name ConsumedWriteCapacityUnits \
--dimensions Name=TableName,Value=AppData \
--start-time 2026-09-22T00:00:00Z --end-time 2026-09-23T00:00:00Z \
--period 300 --statistics Sum
ALERT ON
ThrottledRequests > 0 (sustained)
ReadThrottleEvents / WriteThrottleEvents
ConsumedCapacity ÷ Provisioned > 80%
SuccessfulRequestLatency p99
per-GSI consumed capacity — separately!

Per-index metrics matter because a table can look healthy while one of its indexes is the thing failing.

Chapter 7 says a GSI is a second copy. Here is what that means in money.

BASE TABLE
100 writes/s × 1 KB = 100 WCU
ADD ONE GSI, projection ALL
every write also writes the index
100 writes/s × 1 KB = 100 WCU (index)
───────────────────────────────────────
total 200 WCU ← doubled
ADD THREE MORE GSIs
total 500 WCU ← 5×
PROJECTION CHANGES THE ARITHMETIC
ALL index item ≈ base item → 1 WCU
INCLUDE (2
small attrs) index item ≈ 0.3 KB → 1 WCU
KEYS_ONLY index item ≈ 0.1 KB → 1 WCU
…all three still round UP to 1 WCU. Projections
save meaningfully only when items are LARGE enough
that the index copy crosses fewer KB boundaries.

That is a genuinely counter-intuitive result and worth keeping: for small items, KEYS_ONLY saves storage and read cost but not write capacity, because everything rounds to one unit anyway.

A write that does not change any index key or any
projected attribute does NOT cost index capacity.
→ putting volatile attributes OUTSIDE the
projection keeps hot updates off the index
entirely
RESERVED CAPACITY
• buy WCU/RCU for 1 or 3 years, up front or
partially
• roughly 50–75% off list, depending on term
• applies to PROVISIONED capacity only — never to
on-demand
• per region, per account
WORTH IT WHEN
the baseline is genuinely steady and you will
still be running this table in a year

The usual shape is reserved capacity for the floor, auto-scaling above it — you pay the discounted rate for the traffic you always have, and list price only for the peaks.

□ ReturnConsumedCapacity in dev and in load tests
□ a CloudWatch alarm on throttles
□ AWS Budgets alert on the DynamoDB line item
□ TTL on anything with a natural lifetime (ch 12) —
TTL deletes are FREE, DeleteItem is not
□ on-demand for non-production tables
□ a decision, written down, about which reads may
be eventually consistent

That TTL line is quietly one of the largest savings available: expiring old items with a TTL attribute costs nothing, while deleting them yourself costs a WCU each.

  • 1 WCU = 1 KB written per second; 1 RCU = 4 KB read strongly, or 8 KB eventually. Everything rounds up per item.
  • Therefore small items are proportionally expensive and large items linearly so — both are levers.
  • Transactions cost double.
  • On-demand removes planning at roughly 5–7× the unit price; provisioned plus auto-scaling plus reserved capacity is far cheaper at steady load. Start on-demand, switch once the shape is known.
  • Auto-scaling reacts in minutes, so sudden spikes throttle anyway; schedule capacity for known events.
  • Throttling has three distinct causes — table capacity, the per-partition ceiling, and GSI capacity — and only the first is fixed by money.
  • You can compute the bill from the access patterns before writing code. Do it.
  • The six ways bills go wrong: scans on request paths, forgotten indexes, default strong reads, tiny items at huge counts, on-demand on steady load, and routine transactions.
  • Turn on per-table and per-index metrics, alarm on throttles and 80% utilisation, and use TTL — which deletes for free.

Next: secondary indexes — local and global, what they cost, and the backpressure that makes a throttled index a table-wide problem.

Written by Sepuri Sai Krishna

© 2026CC BY 4.0Source