open atlas
↑ Back to track
AWS, hands-on AWS · 03 · 03

Managed databases: RDS, Aurora, and DynamoDB

Managed relational (RDS/Aurora) versus managed NoSQL (DynamoDB). RDS hands off patching, backups, and failover but keeps you owning schema and a vertical ceiling; DynamoDB trades joins for single-digit-ms at any scale — if you design keys to dodge hot partitions.

AWS Middle ◷ 18 min
Level
FoundationsJuniorMiddleSenior

A team flips their RDS database to Multi-AZ the week before a launch, reads “high availability” on the slide, and tells the room “reads are covered now.” They are not. The morning traffic spikes, the read-heavy product page slows to a crawl, and the single primary is pinned at 100% CPU while a perfectly healthy standby sits in another AZ doing nothing but waiting for the primary to die. Multi-AZ never served a single read — the standby is a synchronous understudy for failover, not a second reader. The fix that day was a read replica; the lesson was that “HA” and “read scaling” are different features that happen to both involve a second copy of the database, and confusing them costs you an outage during your busiest hour.

RDS and Aurora: managed relational, with the parts you still own

If you’ve ever debugged a 3 a.m. pager because a database patch broke something, or spent a weekend setting up standby replication by hand, you’ll understand the appeal of managed relational — but “managed” covers a specific list of things, and the gap between what AWS handles and what you still own is exactly where production incidents hide.

Amazon RDS runs the relational engine you already know — PostgreSQL, MySQL, MariaDB, Oracle, or SQL Server — and takes the operational toil off your hands. AWS owns OS and engine patching, automated backups and point-in-time recovery, and the failover machinery. Aurora is a cloud-native variant: a MySQL- and PostgreSQL-compatible engine where AWS replaced the storage layer with a distributed, log-structured fleet replicated six ways across three AZs, giving faster failover and read replicas that share one storage volume. Either way you get SQL: secondary indexes, multi-row transactions, and joins across normalized tables.

What AWS does not take off your hands is the part juniors assume is automatic. You still own the schema, the indexes, and query tuning — a missing index or an N+1 query pattern is your bug, not AWS’s, and managed-ness will not save a table doing sequential scans. And relational on RDS scales vertically: you make the instance bigger. That has a ceiling — the largest instance class is a hard wall, and writes fundamentally go through one primary.

Two features get conflated and must not be. Multi-AZ provisions a synchronous standby replica in a different Availability Zone; every commit is replicated to it before acknowledging. If the primary fails, RDS flips a DNS record to the standby automatically, typically in the 60–120 second range. But AWS is explicit: the single-standby Multi-AZ deployment “isn’t a scaling solution for read-only scenarios. You can’t use a standby replica to serve read traffic.” It is high availability, not read scaling. To scale reads you add read replicas: these replicate asynchronously (so they can lag), you point read traffic at their own endpoints, and — crucially — a read replica can be promoted to a standalone primary, which is a common path for migrations and regional DR.

# Multi-AZ = HA: a synchronous standby that does NOT serve reads
aws rds create-db-instance \
  --db-instance-identifier orders-primary \
  --engine postgres \
  --db-instance-class db.r6g.xlarge \
  --multi-az                      # automatic failover, ~60-120s, NOT a read scaler

# Read replica = read scaling: asynchronous, has its own endpoint, promotable
aws rds create-db-instance-read-replica \
  --db-instance-identifier orders-replica-1 \
  --source-db-instance-identifier orders-primary

DynamoDB: serverless NoSQL that lives or dies by the partition key

DynamoDB is a fully managed, serverless key-value and document store. There are no instances to size — you hand AWS a table and access patterns. Every item has a partition key; DynamoDB feeds that key through an internal hash function, and the output selects the physical partition the item lives on. An optional sort key orders items that share a partition key, enabling range queries within one collection. Done right, this delivers single-digit-millisecond latency at effectively any scale, because a request resolves to one partition by hashing rather than scanning.

You pick a capacity mode. On-demand bills per request and auto-scales instantly — ideal for unpredictable or spiky traffic. Provisioned has you declare read capacity units (RCU) and write capacity units (WCU), optionally with auto scaling. One WCU is a 1 KB/s write; one RCU is a 4 KB strongly-consistent read per second (or two eventually-consistent reads). For richer access you add a GSI (global secondary index) — a different partition key, replicated and eventually consistent — or an LSI (local secondary index), an alternate sort key on the same partition key.

Here is the trap that throttles teams who think they have headroom: the hot partition. AWS designs every partition to deliver at most 3,000 read units/sec and 1,000 write units/sec. If one partition key value is hit disproportionately — a celebrity user, a status = "PENDING" enum, today’s date as a key — all that traffic lands on one partition, hits that per-partition ceiling, and you get throttled even though the table’s total provisioned capacity is barely touched. The whole game is designing keys with high cardinality so load spreads evenly; for unavoidably hot keys you write-shard by suffixing the key. And DynamoDB has no joins and no ad-hoc queries — you model for known access patterns up front, often via single-table design, and choose eventual vs strong consistency per read.

// One item, modeled for access by user then by recent order (sort key)
{
  "PK": { "S": "USER#42" },          // partition key: high cardinality, spreads load
  "SK": { "S": "ORDER#2026-06-04" }, // sort key: range queries within the user
  "total": { "N": "129.00" },
  "status": { "S": "PENDING" }       // do NOT make this the partition key: hot partition
}
DimensionRDS / Aurora (relational)DynamoDB (NoSQL)
Query modelSQL: joins, ad-hoc queries, transactionsKey-based; model for access patterns, no joins
ScalingVertical (bigger instance) + read replicas; has a ceilingHorizontal by partition; effectively unbounded
High availabilityMulti-AZ synchronous standby, ~60–120s failoverBuilt-in; replicated across 3 AZs automatically
Latency at scaleDepends on indexes/tuning; degrades under loadSingle-digit ms if keys spread evenly
Classic failure modeHitting the vertical ceiling; assuming Multi-AZ scales readsHot partition throttling; needing a join you can’t run
Why this works

Why does a hot partition throttle you while the dashboard shows spare capacity? DynamoDB’s per-partition limits (3,000 RCU/sec, 1,000 WCU/sec) are physical, not logical. Total table capacity is split across partitions, so 100,000 provisioned WCU spread over many partitions is useless to a single key that maps to one partition — that key still caps at 1,000 WCU/sec and the rest goes to throttling. Adaptive capacity can lend a hot partition some of its neighbors’ unused throughput, which softens but does not erase the limit. The only durable fix is key design: spread the writes so no single physical partition is the bottleneck.

The decision: rich queries versus massive simple scale

The rule seniors carry: relational with rich queries, transactions, joins, and moderate scale → RDS/Aurora; massive scale, simple key-based access, predictable latency, and serverless ops → DynamoDB. Pricing for both is illustrative and region-dependent — confirm current rates on the RDS pricing and DynamoDB pricing pages — but the shape differs: RDS bills per instance-hour plus storage whether busy or idle; DynamoDB on-demand bills per request and truly drops toward zero at idle.

The classic lift-and-shift disaster is taking a normalized relational schema, copying it table-for-table onto DynamoDB “for scale,” and then discovering the app needs a join DynamoDB cannot do — forcing N round trips or a scan that defeats the whole point. Model your access patterns first; if they need ad-hoc joins, you wanted a relational database.

Pick the best fit

A multiplayer game's session-state store: hundreds of thousands of writes/sec at peak, accessed only by a sessionId key, needs predictable low latency, and the team wants zero database servers to operate. Pick the database.

Quiz

You enable RDS Multi-AZ and expect your read-heavy traffic to speed up. It doesn't. Why?

Quiz

Your DynamoDB table is provisioned with plenty of total capacity, yet one access pattern keyed on order status keeps getting throttled. What's happening?

Recall before you leave
  1. 01
    Contrast RDS Multi-AZ with read replicas, and say what RDS does and does not manage for you.
  2. 02
    Explain DynamoDB's partition model and the hot-partition trap, and when to choose DynamoDB over RDS.
Recap

Managed databases on AWS split into two families. RDS and Aurora are managed relational: AWS owns patching, backups, and failover, and you get SQL with joins, transactions, and ad-hoc queries — but you still own the schema, indexes, and query tuning, and you scale vertically into a hard ceiling. Two RDS features are constantly confused: Multi-AZ is a synchronous standby in another AZ that fails over automatically in roughly 60–120 seconds and serves no reads (high availability, not read scaling), while read replicas are asynchronous, have their own endpoints to absorb read load, and can be promoted to a primary. DynamoDB is serverless NoSQL: the partition key is hashed to choose a physical partition, an optional sort key orders items within it, and you get single-digit-millisecond latency at any scale — provided your keys spread evenly. Each partition caps at about 3,000 read units and 1,000 write units per second, so a low-cardinality key produces a hot partition that throttles you while total capacity sits idle; the cure is high-cardinality keys or write-sharding. DynamoDB has no joins, so you model for access patterns up front. The decision is the whole lesson: rich queries, transactions, and joins at moderate scale want RDS/Aurora; massive scale, simple key-based access, predictable latency, and zero servers to operate want DynamoDB. Prices are illustrative and region-dependent — always confirm on the RDS and DynamoDB pricing pages. Now when you see a database choice in a design review, ask two questions first: does the access pattern need joins or ad-hoc queries? And if you chose DynamoDB, what is the cardinality of the partition key — because that single attribute will determine whether you get single-digit milliseconds or a throttling storm.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.