How AWS Aurora & RDS achieves durable writes

When I’m setting up a database for a new project, AWS Aurora RDS is my default. That puts me in the minority among the founders I’ve met through YC — most of them reach for Supabase, and when I ask why, the answers are usually some mix of “it’s faster to set up” and “I don’t want to think about a VPC.” Both fair. My answer is the mirror image: I’m comfortable in the AWS ecosystem, the database slots into my CDK setup with one construct, and, more importantly, I used to be the security officer at a healthcare startup, where HIPAA and the realistic possibility of jail time made delegating as much of the security responsibility to AWS as possible feel like the right call.

A note on terminology before we go further. AWS sells managed databases under the umbrella name “Amazon RDS,” but there are really two different things in that bucket. There’s traditional RDS — Postgres, MySQL, MariaDB, Oracle, SQL Server — running on standard EC2 compute with EBS volumes attached, replicated the way those engines have always replicated. And there’s Aurora — AWS’s own engine, MySQL- and Postgres-compatible at the wire level, but architecturally a completely different beast underneath. People often say “RDS” when they specifically mean the first thing, and “Aurora” when they mean the second. That’s the convention I’ll use for the rest of this post: “RDS” for the traditional engines on EBS, “Aurora” for the distributed-storage engine. They are sold side by side, and choosing between them is one of the first decisions you make when you set up a new database — and, as I’ll get to, it’s a decision with real consequences for durability that aren’t obvious from the marketing pages.

But “I don’t want to be in the room when something goes wrong” is not the same as “I don’t want to know how it works.” If you’re a software engineer using a managed database, you still need to understand the underlying principles — how durability is achieved, what the tradeoffs are, what you’re paying for. This post is the version of that explanation I worked out for myself when I started deploying Aurora seriously.

Three things to figure out first

Setting up a new RDS database starts with three coupled decisions. How many instances do I need? More instances means better availability and more read capacity, but also a bigger bill. How many availability zones do I want to span? More AZs means better resilience against datacenter-scale failures, but also more networking cost and complexity. And how does each of those choices affect the durability and availability of my data?

The answers are different for traditional RDS than they are for Aurora — and the reason they’re different is the part that took me the longest to internalize.

What an RDS instance actually is

A clarification that took me a moment when I first started: an RDS instance is not a copy of your database. It’s a compute node — a server running Postgres or MySQL. The actual data lives on storage attached to that server. When you set rdsInstances: 2, you’re not asking for two copies of the database; you’re asking for two servers serving the same data.

The roles those servers can play depend on the engine. A writer (sometimes called the primary) accepts writes. A standby doesn’t accept writes, and traditionally doesn’t accept reads either — it sits there absorbing replication traffic so it can take over if the writer dies. A read replica is a server that serves read-only traffic. The terminology gets fuzzier in modern setups: a “hot standby” is a standby that also serves reads, and an “Aurora Replica” is a reader instance that doubles as a failover target. We’ll come back to that last one, because it’s the source of a persistent misconception.

A quick aside on the words you’ll see in any replication discussion. Synchronous replication means the writer waits for every replica before acknowledging the commit. Semi-synchronous means it waits for at least one — the rest catch up asynchronously. Asynchronous means the writer acknowledges as soon as it’s written locally and replicates in the background. Each is a tradeoff between commit latency and how much data you can lose on a failure.

Traditional RDS Multi-AZ

Traditional RDS Multi-AZ is shaped like the database you’d build at home. A primary instance and a standby instance, each with their own attached EBS volume, kept in lockstep by synchronous block-level replication. Every write goes to the primary’s disk and the standby’s disk before the client gets COMMIT OK. If the primary dies, DNS flips to the standby — which has been silently mirroring every write — and it becomes the new primary.

There’s a newer variant called “Multi-AZ DB cluster” that uses two readable standbys instead of one non-readable one, with semi-synchronous replication: the primary waits for at least one of the two standbys to acknowledge before committing. That gets you faster failover (typically under 35 seconds) and some read scaling, but the shape is fundamentally the same — full database instances, each owning its storage, kept in sync via block-level replication. AWS has a more detailed write-up of how Multi-AZ RDS works if you want the full picture.

Aurora throws that shape out

There’s still a writer instance and one or more reader instances, but they’re stateless compute nodes on top of a shared distributed storage service. The actual durable state lives down there, replicated as six copies spread across three availability zones, two copies per AZ. The instances on top can come and go; the storage volume is the source of truth.

The word shared in that sentence is doing a lot of work, and it’s the most important difference from the traditional setup. In a traditional RDS Multi-AZ deployment, each instance has its own EBS volume — the primary writes to its disk, the standby writes to its disk, and a replication protocol keeps the two disks in sync. Two instances, two storage volumes, with bytes flowing between them. In Aurora, there is one storage volume, and every instance you provision points at it. The writer writes to it. Every reader reads from it. There is no per-instance disk to keep in sync, because the instances don’t own any data — they’re just compute attached to the same shared volume.

This is the part that makes Aurora hard to talk about, because the storage layer is invisible. You don’t provision it. You don’t see the six copies in the AWS console. You can’t SSH to a storage node. As far as your CDK config knows, you have a writer instance and some number of readers, and those instances seem to be where your data lives. They aren’t — they’re stateless caches in front of a storage service that AWS runs and you never directly touch.

That separation is what enables everything else. Failover stops being “wait for the standby to catch up and promote it” and becomes “attach a new compute node to the existing storage.” Read scaling stops requiring log shipping between full instances, because every reader queries the same volume. And — the part this post is really about — durability stops being tied to instance count.

What “durable” actually means here

A write is durable when, if every component in the synchronous commit path failed right after COMMIT OK returned, the write would still be there when the system came back. In Aurora, that property comes from the storage quorum.

When the writer commits a transaction, it doesn’t write a row to a disk on its own server. It generates redo log records and fans them out, in parallel, to all six storage nodes. It then waits for any four of them to acknowledge. As soon as four OKs come back — possibly from any subset of the six, picked dynamically — the writer returns COMMIT OK to the client.

The remaining two nodes are still going to receive the log; they just might be a bit slower. Storage nodes gossip with each other and pull missing log records from peers, so the volume self-heals in the background. The write is durable the moment four nodes have it.

Three things about this matter more than they look. First, the four are not a fixed set — any four count, so a single slow node never blocks a commit. Second, the fan-out is parallel, not serial; commit latency is the latency of the fourth-fastest node, not the slowest. Third, the reader instance is not in this picture at all. Durability comes from the storage layer; the reader is a separate compute node that reads from the same volume, and it can lag a few milliseconds behind without affecting whether your write is safe.

The replica misconception

This brings me to the misconception I had to argue myself out of. AWS calls reader instances “Aurora Replicas.” That naming makes it sound like the reader is a copy of your database — like the standby in traditional RDS, but readable. It isn’t.

The reader instance is a compute node. It has no durable copy of the data. It reads from the same shared storage volume the writer reads and writes from. The actual replicas — in the sense of “redundant copies of the data that protect against loss” — are the six storage nodes underneath, which you can’t see or address.

So when someone asks “how many replicas does Aurora have?”, the honest answer is “six storage replicas you can’t see, plus zero-to-fifteen reader instances you can.” The first number is what determines durability. The second is what determines failover speed and read throughput.

If I deleted my reader instance entirely and ran with just a writer, my writes would be exactly as durable as before. The six storage copies would still exist, the 4-of-6 quorum would still hold, and a single committed transaction would still survive an AZ outage plus an additional node failure. The only thing I’d lose is fast failover — without a warm reader to promote, AWS would have to provision a brand-new compute instance on the existing storage, which takes minutes instead of seconds.

That’s the cleanest way I’ve found to think about it: the readers are about availability, not durability. Confusing the two leads you to believe you need lots of reader instances for safety, when the actual durability is already baked into the storage layer regardless of what your compute layer looks like.

Why six copies and not two?

The instinct from textbook database design is that one extra synchronous copy ought to be enough. Writer commits locally, replicates synchronously to one peer, returns OK. Two copies, one on a different machine — durable, right?

It’s correct in the narrow sense, but it falls apart when you start asking “for how long?” When a node fails, rebuilding it from the survivor takes hours for a multi-terabyte volume. During that rebuild window, you’re effectively down to one copy, and if the surviving node also fails, you’ve lost data. AWS targets eleven nines of durability, and the math doesn’t work with two copies.

So you need more copies — six, in particular, because spreading them two-per-AZ across three AZs lets you tolerate an entire AZ failure plus an additional node failure and still have enough left to satisfy quorum. That “AZ + 1” tolerance is the durability story Aurora is actually selling.

But six copies opens a new question: do you wait for all six? If yes, every commit is bottlenecked by the slowest of six nodes, every routine maintenance event blocks writes, and tail latency is dominated by whichever node happens to be having a bad second. The system would be durable but unusable.

Quorum is what reconciles “enough copies for real durability” with “fast enough to be useful.” Wait for any four, let two lag, fix them in the background. The four that ack a given commit might be different from the four that ack the next one — none of them is special.

Why four for writes and three for reads

The numbers aren’t chosen for taste. Aurora’s read quorum is three out of six, and the relationship Vw + Vr > N — which here is 4 + 3 > 6 — is the whole point.

The general rule: if you have N replicas and require W acks for a write and R responses for a read, then as long as W + R > N, every read quorum overlaps with every preceding write quorum by at least one node. So the read is guaranteed to include at least one node that participated in the latest commit, and you can identify the latest value among the responses by picking the highest log sequence number. Strong consistency, no leader election in the read path, no consensus protocol layered on top.

Aurora’s choice of W = 4 and R = 3 is the smallest pair that satisfies this rule and tolerates a full AZ failure on the write side. This is Dynamo-style quorum applied to a relational storage system, and it’s elegant in the way quorum systems usually are: the math does the work that ad-hoc replication management would otherwise have to do.

What you give up with two AZs

I deployed my cluster with two AZs to keep costs down, which means the VPC subnet group only spans two availability zones — say us-west-2a and us-west-2b. Aurora still creates six copies of the storage, but it can only spread them across the two AZs my subnet group exposes, three per AZ.

The quorum thresholds don’t change — still 4-of-6 to write, 3-of-6 to read — but the failure tolerance does. Lose one of my two AZs and I lose three storage nodes at once. Three remain, which is enough to satisfy a read quorum but not a write quorum. The cluster goes read-only until the AZ comes back or I intervene manually.

A three-AZ deployment loses only two storage nodes per AZ failure and ends up with four survivors — enough to keep writing through the outage. That’s the “AZ failure is transparent” line Aurora’s marketing leans on, and it genuinely requires the third AZ. Two-AZ is cheaper and survives single-disk failures fine, but it trades write availability during a full-AZ outage. Worth knowing before you ship.

Two instances or three?

The same kind of reasoning applies on the compute side. With one writer and one reader, a writer failure promotes the reader in about thirty seconds, and then Aurora provisions a replacement reader in the background. During the few minutes that takes, you have no warm failover target — and if the new writer also fails in that window, you’re back to the slow path of provisioning a brand-new instance from storage, which is a multi-minute outage.

With one writer and two readers, the same failover happens, but afterward you still have a writer plus one healthy reader. The replacement of the lost instance happens in the background without exposing you. The cost is roughly fifty percent more on the instance bill — storage cost is unchanged, since that’s per-volume, not per-instance.

For anything I actually care about staying up, three instances is the right call. The post-failover redundancy gap is the main reason; production HA shouldn’t include a multi-minute window of zero failover capacity.

So why pick Aurora over traditional RDS?

I’ve been writing as if Aurora is the obvious default. It’s worth stating why explicitly, because the AWS console treats traditional RDS and Aurora as peer options — same wizard, same pricing page — and it would be easy to assume the difference is just “Aurora is a bit faster, costs a bit more.”

The actual difference is durability, and it’s not subtle. With traditional RDS, you get a single instance writing to a single EBS volume. EBS itself is replicated within an AZ, so a single disk failure won’t lose data, but the database is in one AZ and can be lost to an AZ-level failure. To get cross-AZ durability you have to explicitly enable Multi-AZ, which provisions one synchronous standby in another AZ — exactly two synchronous copies of every write. Better than nothing, but the durability story is “we have a second machine with the same data on it,” and the replication is at the block-storage layer between two specific instances.

With Aurora, you don’t enable anything. The moment you create the cluster, every write is fanned out to six storage copies across three AZs and quorum-committed before the client gets COMMIT OK. You can’t turn it off; you can’t accidentally provision a single-AZ Aurora cluster. The eleven-nines durability story is built into the storage layer, not into a checkbox.

The cost difference is small enough that for any production workload where losing committed writes would be a problem, picking traditional RDS over Aurora is hard to justify. Use traditional RDS when you specifically need an engine Aurora doesn’t support (Oracle, SQL Server, MariaDB), or when you’re running a tiny dev environment where the durability story doesn’t matter and the slightly lower instance pricing does. Use Aurora for everything else. That’s not an “Aurora is a bit better” recommendation; it’s a recommendation grounded in the fact that Aurora gives you a categorically different durability guarantee out of the box, and you don’t have to think about it to get it.

The takeaway

What I find clarifying about Aurora’s design, having now drawn it a few times, is how cleanly it separates concerns that traditional databases conflate. Durability lives in the storage layer, enforced by quorum, and is independent of how many compute instances you run. Read scaling and HA failover live in the compute layer, decided by how many readers you provision. The two layers talk to each other through log records flowing one way and shared storage being read both ways, but they don’t share fate.

So when my client gets COMMIT OK, here’s what I now believe has happened. The writer fanned the redo log out to six storage nodes I can’t see and didn’t provision. Four of them — possibly any four — wrote it to durable storage and acknowledged. The other two are catching up and will get there shortly, by direct delivery if they’re just slow, by gossip if they were briefly unreachable. The reader instance, meanwhile, knows nothing about this transaction yet and won’t for a few more milliseconds, but if the writer’s AZ falls into the ocean in the next second, the reader can be promoted and find every committed write already waiting in the storage layer it shares with what used to be the writer.

That’s the part that took me a while to internalize. Aurora’s durability story isn’t a clever replication protocol between the database instances I provisioned. It’s an admission that those instances are the wrong layer to be doing replication at all — and that the actual replicas, the six copies that protect my data from being lost, live somewhere I’m not allowed to look.

Further reading