Skip to content

DynamoDB capacity modes and scaling

DynamoDB scales along two independent dimensions: throughput, measured in read and write units, and storage, which grows without a ceiling. An individual item is capped at 400 KB; a table is not capped at all.

  • 1 read unit = one strongly consistent read per second of an item up to 4 KB, or two eventually consistent reads per second of an item up to 4 KB. Larger items consume proportionally more, rounded up to the next 4 KB.
  • 1 write unit = one write per second of an item up to 1 KB. Larger items consume proportionally more, rounded up to the next 1 KB.

The units are called request units in on-demand mode and capacity units in provisioned mode; the arithmetic is identical.

DynamoDB has two capacity modes. Auto scaling is a feature of provisioned mode rather than a third mode, and global tables are a replication feature that works with either.

On-demand is AWS’s default and recommended mode. You do not specify throughput; you pay per request, and a table driving no traffic costs nothing for throughput.

  • No capacity planning, monitoring or scaling configuration.
  • A new on-demand table sustains 4,000 writes and 12,000 reads per second immediately.
  • The table instantly accommodates up to double its previous peak traffic. Once sustained, that becomes the new peak, so the ceiling ratchets upwards with use.
  • Throttling is possible: if traffic exceeds double the previous peak within 30 minutes, requests can be throttled while DynamoDB allocates capacity. Space growth over at least 30 minutes, or pre-warm the table using warm throughput, before a planned step change such as a launch or a migration.
  • The default table-level ceiling of 40,000 read and 40,000 write request units still applies and is raisable through Service Quotas. A per-table maximum throughput can also be configured deliberately, to bound cost.

Since the November 2024 price reduction on on-demand throughput, on-demand is cheaper than provisioned for a large share of real workloads, and provisioned is the optimisation rather than the default.

You specify read and write capacity units and pay for them whether or not they are used. Provisioned mode is worth the operational effort when traffic is steady and predictable enough to forecast, because reserved capacity can then be purchased against it for a further discount.

  • Throttles when demand exceeds the provisioned figure, unless auto scaling has already raised it.
  • Requires monitoring and periodic revision of the bounds.
  • Capacity can be increased as often as needed. Decreases are rate-limited: four are available at the start of each UTC day, one more becomes available each hour up to a maximum of four in hand, allowing up to 27 decreases across a full day.

A table can be switched from provisioned to on-demand up to four times in a rolling 24-hour window, and from on-demand to provisioned at any time.

Application Auto Scaling adjusts provisioned capacity between bounds you set, tracking a target utilisation percentage (70% by default).

How it works: CloudWatch watches the table’s consumed capacity metrics, breaches of the target trigger a scaling action, and Application Auto Scaling issues an UpdateTable call that changes the table’s provisioned capacity within the configured minimum and maximum. Global secondary indexes are scaled the same way, with their own settings.

Two limitations are worth knowing before relying on it:

  • It reacts to sustained change rather than to a spike, so it does not protect against a sudden burst — that is what on-demand is for.
  • It scales down poorly on a table with no traffic at all, because with no consumed capacity there are no data points to act on. If a table must sit idle cheaply, use on-demand instead.

DynamoDB stores a table in partitions, and each partition is designed to serve at most 3,000 read units and 1,000 write units per second and to hold about 10 GB. The service adds partitions as a table grows in size or throughput; this is not something you configure or can observe directly.

Partition keys are hashed, and the hash range is divided between partitions — with two partitions each takes roughly half the key space, with three roughly a third. Consequently the key you choose, not any arithmetic you do, decides how evenly work lands.

Older training material teaches a formula for computing a table’s partition count from its provisioned capacity and size. It no longer describes how the service behaves and should be ignored: adaptive capacity now applies to both on-demand and provisioned tables, and DynamoDB shifts throughput towards the partitions that are actually being read and written. What remains true is the per-partition ceiling above — which is why a single very hot key still throttles no matter how much capacity the table has.

The classic mistake is a partition key with too few distinct values, or one that concentrates current writes.

Time-series sensor data illustrates it. Using the date as the partition key and the sensor ID as the sort key puts every reading taken today into one partition: that partition takes the entire write load while the others sit idle, and it throttles at 1,000 write units per second however much capacity the table has.

Inverting the keys fixes it. Using the sensor ID as the partition key and the date as the sort key spreads today’s writes across as many partitions as there are sensors, while still allowing an efficient query for one sensor’s readings over a date range. The partition keys page covers the choice in more detail.

DAX is an in-memory, write-through cache that fronts a table and speaks the DynamoDB API, cutting read latency from milliseconds to microseconds.

Worth using for applications making repeated lookups of the same items and for read-heavy workloads generally. Not worth using for write-heavy applications — writes pass through to the table synchronously and a high write rate churns the cache through its LRU eviction — or for applications that already cache locally.

  • Design the partition key for cardinality and even access first; everything else is easier afterwards.
  • Avoid dates, monotonically increasing sequences and low-cardinality flags as partition keys for high-volume tables.
  • Start new and unpredictable workloads on on-demand, and move to provisioned only once the traffic shape is known and steady.
  • Put DAX in front of read-heavy access patterns rather than raising read capacity.
  • Alarm on ReadThrottleEvents and WriteThrottleEvents, not only on consumed capacity.