Aurora Serverless vs Provisioned: Capacity That Follows the Load
7 min readBytePatterns
Aurora Serverless vs provisioned: compute in ACUs that scales between a min and max you set, or a fixed instance class billed by the hour, and which wins.
Aurora Serverless vs provisioned in one line: an Aurora Serverless v2 instance (the documentation now just says Aurora serverless) scales its compute in Aurora capacity units between a minimum and a maximum you set and is billed per second for the ACUs it uses, while a provisioned instance has a fixed instance class you pick, billed per instance-hour whether it is busy or idle. The storage underneath is the same Aurora cluster volume either way, so the choice is only about compute, and one cluster can mix both. Facts are from the AWS documentation listed at the end, as of October 2026.
| Aurora Serverless v2 | Provisioned | |
|---|---|---|
| Capacity | ACUs, each about 2 GiB of memory plus CPU and networking, moving inside your range | an instance class such as db.r6g.2xlarge, constant |
| Changing it | automatic, in steps as small as 0.5 ACU, without dropping connections | modify the instance class, with a brief outage per instance |
| Billing shape | ACU usage per second; zero compute while auto-paused | per instance-hour; Reserved Instances for steady use |
The problem it solves
A shop's database idles at night, climbs through the morning and spikes when a promotion email goes out at noon. Size a provisioned writer for the spike and you pay for it all night; size it for the average and the noon queries queue. A staging database is worse: busy for two hours a day, idle for twenty-two. Provisioned capacity is a bet on a number made in advance. Serverless capacity replaces the bet with a range, and charges a different rate for the flexibility.
The intuition
Serverless is a range, not a size. You set the cluster's minimum and maximum in ACUs. Each serverless writer and reader has its own current capacity, measured every second, and Aurora raises it when CPU, memory or network get tight and lowers it when they do not. Scaling happens while connections are open and transactions are running, usually on the same host, so the application never reconnects. The larger the current capacity, the larger the step, so a busy instance climbs faster. The maximum depends on engine and platform version: up to 256 ACUs on Aurora MySQL and up to 384 on recent Aurora PostgreSQL versions.
The minimum is a real decision. An idle cluster still consumes roughly the number of instances times the minimum. On recent engine versions the minimum can be 0, which turns on auto-pause: after a quiet interval you choose, from five minutes to a day, an instance with no user connections pauses and its compute charge stops; the next connection resumes it, typically in about 15 seconds. A minimum above zero keeps the buffer pool warm, and the documentation recommends a floor large enough to hold the working set.
Readers can follow the writer or not. A serverless reader in promotion tier 0 or 1 never drops below the writer's current capacity, so it can take over at full size on failover. Readers in tiers 2 to 15 scale on their own load, useful for a reporting replica that should shrink when the reports finish.
Provisioned is a fixed box. A db.r or db.x class has a set amount of memory and CPU. Costs are easy to forecast, Reserved Instances discount steady-state use, and Aurora usage may be eligible for Database Savings Plans. Growing means modifying the class, with a brief outage per instance; the documentation recommends provisioned when that is acceptable and load is predictable.
Mixing is allowed. A mixed-configuration cluster can run a large provisioned writer with serverless readers, or a serverless writer with steady provisioned readers.
When to use which
- Spiky or unpredictable load, new apps with no history, many small databases: serverless, with a sensible minimum.
- Dev, test and internal tools idle most of the day: serverless with a minimum of 0, if a 15-second first connection is acceptable.
- Steady, high utilisation around the clock: provisioned, with Reserved Instances or Savings Plans.
- Steady writes, bursty reads: a provisioned writer and serverless readers in tiers 2 to 15.
- A writer that outgrows the serverless maximum: provisioned, or move reads off the writer.
Watch it run
The animation comes from the RDS vs DynamoDB lesson and shows the other way AWS scales a database. Writes are hashed by partition key onto three partitions; distinct customer keys spread evenly, a flash-sale key sends every write to one partition, which throttles while the others idle, and write sharding with key suffixes spreads the load again. That is horizontal: more partitions, each holding part of the data. Aurora serverless scales the opposite way, vertically: one writer gets more ACUs and still takes every write. Keep both pictures in mind when an interviewer asks "how does it scale?"
RDS vs DynamoDB
Step 1 of 10
Every DynamoDB item has a partition key. Three customers are about to write orders.
The same interactive animation as the lesson — step through it with the controls.
The code
A model of cost shape, not of AWS pricing. Capacity is in ACU-equivalents per hour, a provisioned instance is sized for the peak and costs 1 unit per ACU-hour, and serverless follows demand inside its range at a premium per ACU-hour. The premium is an assumption you would replace with your Region's prices, so the model prints three:
# a MODEL of cost shape, not AWS pricing: capacity in ACU-equivalents per hour
SPIKY = [2] * 8 + [6, 10, 12, 10, 8, 30, 32, 12, 8, 6, 4, 3] + [2] * 4 # 24 hours
STEADY = [24, 26, 28, 30, 30, 32, 30, 28] * 3 # 24 hours
def provisioned(load):
"""Fixed capacity sized for the peak, paid every hour (1 unit per ACU-hour)."""
return max(load) * len(load)
def serverless(load, min_acu, max_acu, premium):
"""Capacity follows demand inside [min, max]; each ACU-hour costs `premium` units.
`premium` is an assumption of this model, not a published ratio."""
return sum(min(max(acu, min_acu), max_acu) for acu in load) * premium
for name, load in (("spiky", SPIKY), ("steady", STEADY)):
utilisation = sum(load) / provisioned(load)
print(f"{name:6} peak {max(load):2} ACU, utilisation {utilisation:.0%}")
print(" provisioned :", provisioned(load), "units/day")
for premium in (1.5, 2.0, 3.0):
print(f" serverless x{premium}:", serverless(load, 0.5, 64, premium), "units/day")
# spiky peak 32 ACU, utilisation 21%
# provisioned : 768 units/day
# serverless x1.5: 247.5 units/day
# serverless x2.0: 330.0 units/day
# serverless x3.0: 495.0 units/day
# steady peak 32 ACU, utilisation 89%
# provisioned : 768 units/day
# serverless x1.5: 1026.0 units/day
# serverless x2.0: 1368.0 units/day
# serverless x3.0: 2052.0 units/day
The same peak, opposite answers. The spiky day uses a fifth of the capacity a peak-sized instance pays for, so serverless wins even at three times the rate; the steady day uses almost all of it, so serverless loses at every premium. The rule falls out: serverless is cheaper when average utilisation is below one over the premium. The second block states that rule, shows what the floor costs on an idle dev database, and checks the rule against the brute-force comparison on random days:
import random
def serverless_wins(load, min_acu, max_acu, premium):
"""Closed form: serverless is cheaper when the clamped average utilisation
is below 1 / premium."""
clamped = [min(max(a, min_acu), max_acu) for a in load]
return sum(clamped) / (max(load) * len(load)) < 1 / premium
for premium in (1.5, 2.0, 3.0):
print(f"premium x{premium}: serverless wins below {1 / premium:.0%} utilisation")
# the floor matters on a quiet day: dev database, busy 2 hours out of 24
DEV = [0] * 10 + [4, 4] + [0] * 12
for floor in (0, 0.5, 2):
print(f"min {floor} ACU:", serverless(DEV, floor, 16, 2.0), "units/day")
random.seed(11)
ok = True
for _ in range(3000):
load = [random.choice([0, 0.5, 1, 2, 4, 8, 16, 32]) for _ in range(24)]
if max(load) == 0:
continue
lo, hi = random.choice([0, 0.5, 2]), random.choice([16, 32, 64])
premium = random.choice([1.5, 2.0, 3.0])
ok &= serverless_wins(load, lo, hi, premium) == (serverless(load, lo, hi, premium) < provisioned(load))
print(ok)
# premium x1.5: serverless wins below 67% utilisation
# premium x2.0: serverless wins below 50% utilisation
# premium x3.0: serverless wins below 33% utilisation
# min 0 ACU: 16.0 units/day
# min 0.5 ACU: 38.0 units/day
# min 2 ACU: 104.0 units/day
# True
On the dev database, a floor of 2 ACUs costs more than six times what auto-pause does, all of it for hours when nobody is connected. The model ignores real effects such as the 15-second resume and a cold buffer pool after a pause; those are the price of the cheaper line.
The complexity
- Serverless bills ACU usage per second. The idle floor is the number of instances times the minimum, and the ceiling is the number of instances times the maximum, so the range is also a budget cap.
- Provisioned bills per instance-hour, in one-second increments with a ten-minute minimum after a billable status change, and Reserved Instances lower the rate in exchange for a term.
- Storage and I/O are billed the same way for both, under Aurora Standard or Aurora I/O-Optimized, because the cluster volume does not care what compute sits on top.
Where it goes wrong
- A minimum that is too low for production. Capacity and buffer cache shrink overnight, and the morning ramp starts cold. Set the floor near the working set.
- A maximum that is too low. At the cap, serverless behaves like an undersized provisioned instance. Watch
ServerlessDatabaseCapacityandACUUtilization. - Auto-pause that never pauses. Any open user connection, an RDS Proxy, or binlog or logical replication keeps instances awake, and a serverless writer never pauses while the cluster holds a provisioned instance.
- Auto-pause in front of a strict timeout. The first connection waits for the resume; clients with short connect timeouts fail. Retry and lengthen the timeout.
- A tier-2 reader promoted on failover. It may be small and has to scale up under full write load. Keep one reader in tier 0 or 1.
- Serverless for a flat 24/7 load. It pays the flexibility rate for flexibility nobody uses.
When it shows up in interviews
As a cost follow-up in a design round, "the database is idle at night, can we save?", or as a certification scenario with "unpredictable workload" in the question. The strong answer names ACUs, the min and max range, scaling without reconnects, and the utilisation argument. For the bigger choice of engine, see DynamoDB vs RDS vs Aurora; for availability, which works the same for both capacity types, see RDS Multi-AZ vs read replica.
How to say it in an interview
"Aurora storage is the same either way; the choice is compute. Serverless v2 instances scale in ACUs, about 2 GiB each, between a min and max I set, in place and without dropping connections, and I pay per second for what is used; with a minimum of zero they auto-pause. Provisioned instances are a fixed class billed per hour, cheaper per unit with Reserved Instances. So I pick by utilisation: spiky, unknown or mostly idle workloads go serverless, steady high utilisation goes provisioned, and a cluster can mix them, for example a provisioned writer with serverless readers for bursty reads."
Frequently asked questions
What is the difference between Aurora Serverless and provisioned?
Both use the same Aurora storage. Serverless instances scale compute automatically in Aurora capacity units between a minimum and maximum you set and bill per second of use. Provisioned instances have a fixed instance class you choose, bill per instance-hour, and change size only when you modify them.
Is Aurora Serverless cheaper than provisioned?
It depends on utilisation. Serverless costs more per unit of capacity but only charges for capacity in use, so it wins for spiky, unpredictable or mostly idle workloads. For steady, high utilisation around the clock, a right-sized provisioned instance, especially with Reserved Instances, is usually cheaper. Compare with your own load curve.
Can Aurora Serverless v2 scale to zero?
Yes, on supported engine versions: setting the minimum capacity to 0 ACUs enables automatic pause. After an idle interval with no user connections, the instance pauses and its compute charge stops; storage is still billed. The next connection resumes it, typically in about 15 seconds.
Sources
- How Aurora serverless works — Amazon Aurora User Guide
- Scaling to zero ACUs with automatic pause and resume for Aurora serverless — Amazon Aurora User Guide
- Performance and scaling for Aurora serverless — Amazon Aurora User Guide
- Amazon Aurora pricing — Amazon Web Services