Aurora Serverless v2: the basics
Why a provisioned database that's idle most of the time is the wrong shape
Provisioned RDS and provisioned Aurora bill you for a fixed instance — a db.r6g.large, say — for every hour it exists, whether it's serving ten thousand queries a second or sitting completely idle overnight. The instance is a box you rent by the hour, and its price has nothing to do with how hard you're working it. A dev database that's busy from 9 to 5 on weekdays and silent the other ~75% of the week still pays the full 168-hour weekly rate.
Aurora Serverless v2 changes the unit of billing from "instance-hours" to "capacity-hours." Capacity is measured in ACUs — Aurora Capacity Units, where 1 ACU is roughly 2 GB of RAM plus proportional CPU and networking, billed at about $0.12 per ACU-hour in US-East. You set a floor (MinCapacity) and a ceiling (MaxCapacity), and Aurora scales the running capacity up and down within that band in fine-grained steps, in near-real-time, in response to load. When the workload goes quiet, capacity drops toward the floor and the bill drops with it.
The flag fires when a database shows low average utilisation with idle stretches — typical of dev/test, internal tools, spiky or seasonal workloads, and the long tail of many small databases. Those are exactly the cases where paying a flat 24/7 instance rate is wasteful, and where letting capacity follow demand turns most of the idle hours into near-zero cost.
In this lesson you'll learn how to tell which databases are good Serverless v2 candidates and which should stay on provisioned-plus-Reserved-Instance, how ACU min/max sizing works, why v2 scales toward a configured floor rather than the hard zero the deprecated v1 offered, and the safe migration path of adding a Serverless v2 reader to the cluster and failing over. You'll see the CloudWatch queries that expose idle time and the exact CLI call that configures the scaling band — plus the edge cases that bite, like cold-ish scale-up latency and the cost crossover where steady high utilisation makes provisioned cheaper.
The weekend that never paid for itself
A team measured a single dev Aurora cluster on a db.r6g.large — about $0.29/hour, roughly $210 a month flat. CloudWatch showed it handled real queries for about 45 hours a week and sat at near-zero connections the other 123. They moved it to Serverless v2 with MinCapacity 0.5 and MaxCapacity 8. The busy hours cost a little more per hour at peak, but the idle 123 hours/week collapsed to the 0.5-ACU floor — about $0.06/hour. The cluster's monthly bill dropped to roughly $70. The punchline: across 40 similar dev and test databases, the same change saved more than the entire team's annual conference budget, and not one engineer noticed a difference in the working day.
Converting an idle database in action
Marcus runs the platform team at a logistics company. A finance review flags that their non-production Aurora fleet — 18 clusters on fixed db.r6g.large and db.r6g.xlarge instances — is costing about $4,800 a month, and most of them are dev and CI databases.
He pulls 14 days of CloudWatch on one of them. DatabaseConnections averages 3 during working hours and drops to zero from 7pm to 8am and all weekend. CPUUtilization sits around 6% with brief spikes to 30% during CI runs. This is the textbook idle-most-of-the-time profile: a fixed instance is paying full price for ~120 idle hours a week.
Rather than rebuild the cluster, Marcus adds a Serverless v2 reader to the existing Aurora cluster, lets it sync, then fails over so the serverless instance becomes the writer — a few seconds of connection blip, no data migration. He sets MinCapacity 0.5 and MaxCapacity 8 so CI bursts still get headroom. Projected drop on that one cluster: from ~$210 to ~$75 a month; across the candidate set, roughly $3k a month.
First, confirm the idle profile. Pull connection counts over two weeks — long stretches near zero are the strongest signal that a fixed instance is overpaying.
14-day hourly connection counts: the idle windows that a 24/7 instance is paying for.
Now configure the Serverless v2 scaling band on the cluster. MinCapacity is the floor the cluster scales down to when idle; MaxCapacity is the spend ceiling and the headroom for bursts.
Setting the ACU band: 0.5 ACU floor (~$0.06/hr idle) up to 8 ACU for CI bursts.
Aurora Serverless v2 under the hooddeep dive
Serverless v2 is not a separate engine mode — it's an instance class (db.serverless) that lives inside an ordinary Aurora cluster alongside, or instead of, provisioned instances. That's why migration is non-destructive: you add a db.serverless reader to an existing provisioned cluster, let it catch up, then fail over to make it the writer. There's no dump-and-restore and no new endpoint; the cluster endpoint stays the same and clients reconnect through the same DNS after a few-second failover blip.
Capacity is measured in ACUs. One ACU is approximately 2 GB of memory with proportional vCPU and network, billed at roughly $0.12 per ACU-hour in US-East — so 4 ACUs (~8 GB) is about $0.48/hour, comparable to a provisioned db.r6g.large but only while you're actually running at 4 ACUs. Aurora adjusts capacity in fine increments within your MinCapacity/MaxCapacity band, scaling up in seconds under load and easing down as it subsides. Crucially, v2 scales toward your configured floor, not to a hard stop: classically MinCapacity was 0.5 ACU (so an idle cluster still costs ~$0.06/hour but stays warm with no cold-start), and newer configurations support scaling down to 0 ACU for auto-pause-like behaviour. This is the key difference from the deprecated Serverless v1, which paused to truly zero but suffered multi-second resume latency on the first connection.
The cost crossover is the thing to get right. Because per-ACU-hour pricing is set so that running at steady capacity costs a premium over an equivalent always-on provisioned instance, a database pinned near its peak 24/7 is cheaper on provisioned-plus-Reserved-Instance than on serverless. Serverless wins precisely when capacity spends most of its time well below peak — idle nights, quiet weekends, spiky bursts. The break-even rule of thumb: if average utilisation is below roughly 40-50% of peak with real idle windows, serverless usually wins; if it's steady and high, stay provisioned and commit.
# Non-destructive migration: add a Serverless v2 reader to an existing provisioned cluster.
aws rds create-db-instance \
--db-instance-identifier dev-orders-serverless \
--db-cluster-identifier dev-orders-aurora \
--db-instance-class db.serverless \
--engine aurora-postgresql
# Wait for it to become available and catch up on replication.
aws rds wait db-instance-available --db-instance-identifier dev-orders-serverless
# Fail over so the serverless instance becomes the writer, then remove the old provisioned one.
aws rds failover-db-cluster \
--db-cluster-identifier dev-orders-aurora \
--target-db-instance-identifier dev-orders-serverless What is the impact of leaving idle databases on provisioned instances?
The direct cost is the most visible: a fixed instance bills the same whether it's saturated or idle. A db.r6g.large at ~$0.29/hour is ~$210/month, and a dev database that's genuinely used ~45 hours a week is paying for ~123 idle hours every week — about three-quarters of the bill bought nothing. Multiply across a non-production fleet of dozens of clusters and that's thousands of dollars a month of pure idle-time spend.
The second-order impact is sizing inertia. Once a fixed instance is chosen it tends to be sized for the worst case — the CI burst, the month-end batch, the demo — and then runs at that size permanently. Serverless removes the incentive to over-provision for peaks, because peaks only cost more during the peak. Teams that stay on fixed instances keep paying peak-sized bills for average-sized workloads, and the gap compounds as workloads drift quieter over time.
There's a commitment-economics trap in both directions. Reserved Instances and the RDS-equivalent reservations apply to provisioned instances, not to serverless ACU-hours, so committing reservations against an idle database locks in the wrong shape — you pay for a size you barely use for one to three years. But the inverse is also a trap: moving a genuinely steady, high-utilisation production database to serverless can raise the bill, because per-ACU-hour at sustained capacity exceeds a committed provisioned instance. Right-targeting is the whole game.
Finally, leaving the fleet on fixed instances makes spend less responsive to reality. When a project winds down or traffic seasonally drops, a serverless database's bill falls automatically; a provisioned one keeps charging the same until someone manually resizes or deletes it — and that someone usually never gets around to it. Usage-following cost turns "remember to scale this down" from a chore nobody does into the default behaviour.
How do you convert idle databases to Serverless v2 safely?
Conversion is a four-step loop that runs on the FinOps cadence: measure utilisation honestly, segment the fleet into serverless-wins vs commit-wins, migrate non-destructively, and set a scaling band that protects both performance and budget.
1. Measure utilisation before you decide anything
Pull at least 14 days of CloudWatch for each candidate: DatabaseConnections and CPUUtilization at minimum, plus FreeableMemory if the workload is memory-bound. You're looking for two things — average utilisation as a fraction of peak, and the presence of genuine idle windows (nights, weekends, between batch runs). Sustained zero-connection stretches are the clearest serverless signal; a flat, high, always-busy line is the clearest signal to leave it on provisioned.
2. Segment the fleet: serverless wins vs commit wins
Apply the crossover rule. Below roughly 40-50% average utilisation with real idle time, serverless almost always wins — most of the week collapses toward the floor. Steady, high, 24/7 utilisation is cheaper on provisioned plus a Reserved Instance, because per-ACU-hour at sustained capacity exceeds an equivalent committed instance. Never move a good reservation candidate to serverless, and never buy a reservation for a database you're about to make serverless.
3. Migrate non-destructively via reader-then-failover
Don't rebuild the cluster. Add a db.serverless instance as a reader to the existing Aurora cluster, let it catch up on replication, then fail over so it becomes the writer and remove the old provisioned instance. The cluster endpoint is unchanged, so clients just reconnect after a few-second blip — no data migration, no new connection string. For a database that isn't already Aurora, migrate it into Aurora first (via snapshot restore or DMS) before adding the serverless instance.
4. Set MinCapacity and MaxCapacity deliberately
MinCapacity is the warm floor: 0.5 ACU keeps an idle cluster responsive at ~$0.06/hour with no cold-start, while newer configs allow scaling to 0 ACU for auto-pause-like behaviour at the cost of a brief resume on the next connection — fine for dev, not for anything latency-sensitive. MaxCapacity is both the performance headroom for bursts and your spend ceiling, so size it to cover the real peak (the CI run, the batch job) and no more. Review the band quarterly as the workload drifts.
# Verify the workload is a serverless candidate (low average CPU with idle windows),
# then set a scaling band that covers bursts without leaving an oversized ceiling.
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS --metric-name CPUUtilization \
--dimensions Name=DBClusterIdentifier,Value=dev-orders-aurora \
--start-time $(date -u -d '14 days ago' +%FT%TZ) \
--end-time $(date -u +%FT%TZ) \
--period 3600 --statistics Average Maximum \
--query 'sort_by(Datapoints,&Maximum)[-1]'
# Floor 0.5 ACU (warm, ~$0.06/hr idle), ceiling 8 ACU (CI burst headroom + spend cap).
aws rds modify-db-cluster \
--db-cluster-identifier dev-orders-aurora \
--serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=8 \
--apply-immediately Quick quiz
Question 1 of 5A dev Aurora cluster on a db.r6g.large averages 6% CPU with zero connections every night and weekend, but spikes to 30% during CI runs. What's the right move?
You scored
0 / 5
Keep learning
Dig deeper into Aurora Serverless v2 mechanics, capacity sizing, and the cost trade-offs.
- Aurora Serverless v2 documentation Authoritative reference on ACUs, scaling configuration, the db.serverless instance class, and migration from provisioned.
- Amazon Aurora pricing Per-ACU-hour and provisioned-instance rates — the numbers behind the serverless-vs-provisioned crossover math.
- Choosing between Aurora Serverless v2 and provisioned How to size MinCapacity and MaxCapacity and reason about when serverless is the cheaper option.
- FinOps Foundation — Cloud Rate and Usage Optimization How matching pricing model to usage fits the broader FinOps lifecycle and operating model.
You've completed Convert idle RDS to Aurora Serverless v2. You now know how to read utilisation to spot idle-heavy candidates, where the serverless-vs-provisioned crossover sits, the non-destructive reader-then-failover migration path, and how to set a MinCapacity floor that stays warm and a MaxCapacity ceiling that caps spend. The next time a finance review flags a flat non-production database line, you'll have a four-step loop ready to run.
Back to the library