About

Before You Switch RDS to Multi-AZ: Double the Price, a Standby You Cannot Read, and an Order That Matters

What we ran into moving a Single-AZ production database: a terraform branch that never fired, a snapshot we could not stop, and storage that doubles too

We moved a production RDS for PostgreSQL instance from Single-AZ to Multi-AZ. After reading this you can decide what Multi-AZ actually buys you (shorter outages during failures and maintenance), what it does not (read performance), where the price doubles, and which order makes the switch hurt less.

It started with one question in a review: "Is there a reason this was never Multi-AZ?" We looked. There was no reason.

The code to turn it on was there. It just never fired

The terraform had a branch that enabled Multi-AZ for production only.

locals {
  rds_multi_az = var.stage == "production"
}

variable "stage" {
  default = "staging"  # this is the production directory
}

There was no tfvars file, and the Makefile never passed -var stage=.... So rds_multi_az was always false. A default from the initial build had survived for years.

If a setting turns on under a condition, check that the condition actually becomes true. What the code says and what is applied are two different things.

There was one more. Nobody had run apply for almost two months, and storage autoscaling had grown allocated_storage to twice the value in the code. Applying as-is would have made terraform try to shrink the storage and fail, because RDS storage can only grow. Running terraform plan on a schedule would have shown both problems long ago.

Multi-AZ does exactly two things

It keeps a standby instance in another AZ (a separate group of data centers in the same region) and replicates to it synchronously.

  • A write commits only after both primary and standby have it

  • You cannot read from the standby. It just waits

People get the second one wrong all the time. The AWS docs say it plainly: high availability is not a read scaling solution, and the standby cannot serve read traffic. "We doubled it, so it should be faster" — it is not. Every commit now makes a round trip to another AZ, so write latency goes up a little. You usually will not notice, but batches that do heavy INSERT or UPDATE work will add it up.

Multi-AZ instance this post Primary (AZ-a) sync Standby (AZ-b) reads: no auto failover: yes price: instance x2, storage x2 Read replica for read scaling Primary async Replica reads: yes auto failover: no (promote by hand) price: one more instance Multi-AZ DB cluster availability + reads Writer semi-sync Reader Reader reads: yes (2 readers) auto failover: yes storage x3, NVMe classes only

What it buys, 1: automatic failover

When the AZ, the instance or the storage has a problem, the standby is promoted and the endpoint's DNS points to the new primary. You do not change any app settings. The docs say failover typically takes 60 to 120 seconds.

Every existing connection drops at that moment, though. If the app has no reconnect or retry, users see errors for that minute or two. Turning on Multi-AZ does not make the app sturdier by itself.

What it buys, 2: shorter maintenance outages — the biggest win in practice

Changing the instance class on Single-AZ means stop, resize, start. Ours was down for about three minutes.

On Multi-AZ, OS patches go standby first, then a promotion, then the old primary. The docs say that failover typically lasts less than a minute. For class changes the docs only say Multi-AZ can minimize downtime. We changed the class while still on Single-AZ, so we did not measure the Multi-AZ number.

At first I assumed minor engine upgrades would get this short too. They do not. Reading the docs again: an engine upgrade modifies primary and standby at the same time, so both are down. To cut engine-upgrade downtime, look at Blue/Green Deployments instead.

Still, no longer being scared of OS patches and class changes is a lot. You get out of the loop where downtime makes you postpone patches, and postponed patches pile up into a bigger job.

What it buys, 3: backups stop pausing the primary

A snapshot on Single-AZ suspends I/O for a few seconds to a few minutes (per the docs). On Multi-AZ for PostgreSQL and MySQL the backup is taken from the standby, so the primary's I/O does not pause.

We hit this head-on. Before the work we took a manual snapshot as insurance. The volume bandwidth was already saturated, so a few dozen GB took about 40 minutes. During that time the instance was backing-up and our modify request was rejected.

InvalidDBInstanceState: Database instance is not in available state.
InvalidDBSnapshotState: Cannot delete the snapshot because it is not
  currently in the available or failed state.

Trying to delete the running snapshot to stop it gets rejected too, as the second line shows. We sat through 40 minutes of our maintenance window.

Price: instance and storage both double

From the public AWS price list (Tokyo, PostgreSQL, on-demand, 2026-10-29 edition), at 730 hours a month.

Single-AZ Multi-AZ
db.t3.large $163.52 $327.04
db.r6g.xlarge $394.20 $787.67
gp3 storage $0.138/GB-month $0.276/GB-month

Estimate from the instance price alone and you will be wrong. Storage doubles too. Our first estimate only looked at the instance and came out well below the real number.

Which of the three to buy — Multi-AZ, read replica, Multi-AZ DB cluster

Read from standby Replication Auto failover Tokyo gp3 storage
Multi-AZ instance No Sync Yes $0.276 (x2)
Read replica Yes Async No (promote by hand) Billed with the replica instance
Multi-AZ DB cluster Yes (2 readers) Semi-sync Yes $0.414 (x3)

They are not mutually exclusive. If the goal is to not go down, Multi-AZ. If the goal is to spread reads, a read replica. If you need both, use both or look at the cluster. The cluster only takes classes with local NVMe such as r6gd or m6gd, so you cannot carry your current class over as-is.

The order: scale up first, Multi-AZ second

Turning it on is online.

aws rds modify-db-instance --db-instance-identifier <id> --multi-az --apply-immediately

Leave out --apply-immediately and it waits for the next maintenance window. Your late-night session turns into nothing.

Online does not mean light. RDS builds the standby volumes from a snapshot of the primary's volumes, and those volumes fill in their data in the background (per the docs). On an instance whose I/O is already saturated, that drags on and the service slows down the whole time. One snapshot taking 40 minutes was the evidence.

So we picked this order.

  1. Take a downtime window and raise the class (Single-AZ, about 3 minutes down) — get the bandwidth first
  2. Then turn on Multi-AZ (online) — the sync is shorter and hurts less

Watch progress like this.

aws rds describe-db-instances --db-instance-identifier <id> --query 'DBInstances[0].{Status:DBInstanceStatus,MultiAZ:MultiAZ,Secondary:SecondaryAvailabilityZone,Storage:StorageOperationStatus}'

Once SecondaryAvailabilityZone is filled, the standby exists. Even after the status returns to available, the standby volumes may still be filling in the background. That shows as StorageOperationStatus being Initializing. Ours took 20 to 40 minutes for a few dozen GB.

The AWS docs actually recommend against converting a production instance in place. Their suggested route is to create a read replica, convert it to Multi-AZ, and promote it. If your service is sensitive to write latency, look at that first. We converted in place and made room in bandwidth first with the order above.

Coming from a T class, your dashboard says No data

CPUCreditBalance only exists for burstable (T class) instances, so it stops after moving to r6g. The widget shows "No data" and nothing is broken. If you had an alarm on that metric it will misfire, so clean it up beforehand.

Turn it on or not

Turn it on: services where a few minutes of downtime hits revenue or trust right away. Teams that need regular patches or class changes but keep postponing them because of downtime — that alone pays for it. Databases big enough that I/O pauses during backups hurt.

Leave it off: dev and staging (it just doubles the bill). Internal systems that can be down for a few minutes and have a generous recovery target.

What else shows up when you look

Reviewing Multi-AZ, the same instance turned up a string of settings left over from the initial build.

  • Backup retention of 1 day — backup_retention_period = 1 was hard-coded in terraform. Point-in-time recovery only covered 24 hours, so anything noticed a day later could not be undone

  • Storage encryption off — turning it on means snapshot copy, restore and an endpoint switch, with its own downtime. It cannot be changed in place

  • Autoscaling at its ceiling — allocated storage already equaled the maximum, so there was no room left to grow

Look at infrastructure properly once and this kind of thing always turns up. A Multi-AZ review is a good excuse to do it.

If you are working out redundancy for a production database, the order of a class change, or cleaning up infrastructure where terraform and reality have drifted apart, ask us at https://www.negabaro.com/contact.

How It Works

1

Check that the terraform Multi-AZ condition actually evaluates to true and that plan shows no drift

2

Take a downtime window and raise the instance class (Single-AZ means stop, resize, start)

3

Turn on Multi-AZ with --multi-az --apply-immediately (online; the standby volumes are built from a snapshot)

4

Watch SecondaryAvailabilityZone and StorageOperationStatus until the standby is ready

5

Clean up widgets and alarms on the T-class-only CPUCreditBalance metric

Pros

  • ✓ Automatic failover with the same endpoint
  • ✓ OS patches go to the standby first, so downtime is just the failover
  • ✓ Backups come from the standby, so the primary does not pause (PostgreSQL, MySQL)

Cons

  • ✗ Instance and storage both cost twice as much
  • ✗ The standby cannot be read, so read performance stays the same
  • ✗ Connections drop on failover, and engine upgrades still mean downtime

Use Cases

Production databases where a few minutes down hits revenue or trust Databases where OS patches and class changes keep getting postponed because of downtime Databases big enough that I/O pauses during backups hurt
← Bot Blocking Isn't One Header — Five Layers, Seen from the Site's Side ECS Exec Session Logs Are Empty? Check Your Image for script(1) First →