RDS, Aurora & DynamoDB

Relational vs NoSQL, replicas, caching.

Intermediate30 min · lesson 9 of 15

A relational database works like a filing cabinet full of cross-referenced folders. Pull one record and it points you at ten related ones, and the cabinet itself enforces the rules about what is allowed to go where. DynamoDB works like a coat check. You hand over a numbered ticket and get exactly one item back in milliseconds, whether the venue is holding fifty coats or fifty million. Neither one is *better*. They answer different questions, and the SAA (Solutions Architect Associate) exam loves asking which question you are really answering. In this lesson you provision both from the aws CLI (command-line interface), wire up the levers for staying available and for scaling reads, and meet the caching and warehousing services that round out the family.

RDS hands you a database, not a server to babysit

Amazon RDS (Relational Database Service) runs a real database engine for you: PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, or IBM Db2, on instances that AWS operates. You still own the schema, the queries, and the indexes. AWS owns the endless half nobody enjoys: provisioning, patching the operating system, taking backups, and keeping the failover plumbing alive. Two ideas in the create call below carry most of the design weight. --multi-az gives you a standby, a copy living in a second AZ (Availability Zone, a physically separate set of data centres inside the same region) that receives every write at the same moment as the primary and takes over on its own if the primary or its whole AZ dies. A read replica is a different animal with its own command. It is a copy that receives writes *after the fact*, has its own address, and exists so you can aim SELECT traffic at it on purpose. Same word, replica. Opposite jobs. The next section pulls them apart.

provision a Multi-AZ PostgreSQL instance
# Multi-AZ Postgres in private subnets, encrypted at rest,
# master password generated and held by Secrets Manager
aws rds create-db-instance \
--db-instance-identifier app-db \
--engine postgres --engine-version 17.4 \
--db-instance-class db.t4g.medium \
--allocated-storage 100 --storage-type gp3 \
--multi-az \
--db-subnet-group-name private-db-subnets \
--vpc-security-group-ids sg-0a1b2c3d4e5f67890 \
--master-username dbadmin --manage-master-user-password \
--storage-encrypted --backup-retention-period 7 \
--no-publicly-accessible
{
"DBInstance": {
"DBInstanceIdentifier": "app-db",
"DBInstanceStatus": "creating",
"Engine": "postgres",
"MultiAZ": true,
"StorageEncrypted": true,
"PubliclyAccessible": false,
"MasterUserSecret": {
"SecretArn": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:rds!db-3f8e91c2-7a44-4d2e-9c1a-0b5c6d7e8f90-Ab12Cd",
"SecretStatus": "creating"
}
}
}
# ~10 min later: grab the endpoint your application connects to
aws rds describe-db-instances --db-instance-identifier app-db \
--query 'DBInstances[0].[DBInstanceStatus,Endpoint.Address,MultiAZ]' --output table
# | available | app-db.cxk3mp0d1r2s.eu-west-1.rds.amazonaws.com | True |

Every flag in that call is a decision you would otherwise make badly at 2am. --db-subnet-group-name pins the instance to private subnets, because a database has no business holding a public IP (Internet Protocol) address, and --no-publicly-accessible says so out loud. --storage-encrypted turns on encryption at rest through KMS (Key Management Service, the AWS key store). You *cannot* switch that on later without taking a snapshot, copying it encrypted, and migrating onto the copy, so choose it on day one. --manage-master-user-password skips the plaintext password altogether. RDS generates one, parks it in Secrets Manager, and rotates it every seven days by default, so the credential never lands in your shell history or your Terraform state. One sharp edge: RDS refuses to create a read replica from an instance whose password it manages this way, with SQL Server the lone exception. That is why you hand password management back before scaling out reads below.

Multi-AZ keeps you alive; read replicas keep you fast

This is the distinction the exam tests harder than any other, so get it into your bones. The Multi-AZ standby copies synchronously: the primary does not tell your application "saved" until the standby has the write too. A failover therefore loses nothing that was committed. The price is that you can *never read from the standby*. It sits there, fully paid for, waiting. Failover works by repointing the instance's DNS (Domain Name System, the internet's address book) record at the standby, usually inside 60 to 120 seconds, and your application keeps using the same endpoint name throughout. Read replicas copy asynchronously: the primary acknowledges the write straight away and ships it onward afterwards. Each replica gets its own endpoint, may trail the primary by milliseconds or by minutes, and can sit in a different region entirely. MySQL, MariaDB, and PostgreSQL instances take up to 15 of them. Any replica can be *promoted* into a standalone database, which is exactly what turns a cross-region replica into a disaster-recovery building block. One newer option to keep in your back pocket: a Multi-AZ DB cluster runs one writer plus *two standbys you can actually read from*, giving you both benefits at a higher bill.

scale reads with a replica, then measure its lag
# Prep: RDS can't create a replica while it manages the master password
# in Secrets Manager — switch app-db to a self-managed password first
aws rds modify-db-instance --db-instance-identifier app-db \
--master-user-password "$DB_PASSWORD"
# Asynchronous read replica: its own endpoint, no failover role
aws rds create-db-instance-read-replica \
--db-instance-identifier app-db-ro-1 \
--source-db-instance-identifier app-db \
--db-instance-class db.t4g.medium
{
"DBInstance": {
"DBInstanceIdentifier": "app-db-ro-1",
"DBInstanceStatus": "creating",
"ReadReplicaSourceDBInstanceIdentifier": "app-db",
"MultiAZ": false
}
}
# How far behind is it? This is the lag you must design around.
aws cloudwatch get-metric-statistics --namespace AWS/RDS \
--metric-name ReplicaLag --statistics Average --period 60 \
--dimensions Name=DBInstanceIdentifier,Value=app-db-ro-1 \
--start-time 2026-07-13T09:00:00Z --end-time 2026-07-13T09:05:00Z
# "Datapoints": [ { "Average": 0.42, "Unit": "Seconds" } ]
Replica lag can swallow a user's own edit
A user saves their profile. The next page load happens to read from an asynchronous replica, and the edit is gone. That is replica lag. It usually stays under a second, right up until a burst of writes or one long-running transaction stretches it to minutes. Send reads that must see the user's own writes back to the primary or to a cache, put an alarm on the ReplicaLag CloudWatch metric, and never park a replica behind an endpoint your checkout flow depends on.

Aurora: same engines, storage rebuilt from scratch

Amazon Aurora speaks MySQL and PostgreSQL on the wire, so your drivers and your SQL (Structured Query Language) keep working untouched. What changes is the warehouse behind the counter. A normal database writes whole data pages down onto one disk volume. An Aurora instance sends only its redo log records, the short "here is what changed" notes a database writes before applying the change itself, to a shared storage layer that keeps six copies across three Availability Zones and can lose two of them without losing a write. That design explains why Aurora's numbers beat standard RDS. Storage grows on its own up to 128 TiB (tebibytes, roughly 140 terabytes). Up to 15 replicas read from the *same shared volume*, so lag normally sits well under 100 milliseconds instead of seconds. Failover promotes a replica in under 30 seconds most of the time, because there is no backlog for the new primary to catch up on. Your application talks to two names: the writer endpoint, which always follows whichever instance is primary right now, and the reader endpoint, which spreads connections across the replicas. Two variants show up on the exam. Aurora Serverless v2 scales compute in small steps called ACUs (Aurora Capacity Units), all the way down to zero when nothing is happening, then wakes back up on demand, which suits spiky traffic and dev environments. Aurora Global Database copies the storage layer into other regions with lag typically under a second, giving you DR (disaster recovery) figures plain RDS cannot touch: an RPO (recovery point objective, how much data you can afford to lose) of about one second and an RTO (recovery time objective, how long you can afford to be down) usually under a minute. The catch is the bill, roughly 20% above equivalent RDS instances by the commonly quoted figure, and you get only the two engine flavors.

DynamoDB: a coat check the size of a region

Amazon DynamoDB is a serverless key-value and document database. No instances, no patching, no connection pools. You create a table and call an API (application programming interface, where your code sends ordinary HTTPS web requests and gets answers back). Underneath, DynamoDB runs each item's partition key through a hash function to decide which physical partition holds it, then splits partitions on its own as data or traffic grows. That is the entire scaling story, and it is also the entire constraint. Every partition gets a bounded slice of throughput. A key with low *cardinality* (the count of distinct values it can hold) like Country funnels the whole crowd onto one *hot partition* and starts throttling, while a high-cardinality key like UserId spreads the same load thinly. Get the key right and reads come back in single-digit milliseconds at any size. Billing goes one of two ways: per request (PAY_PER_REQUEST, the right call for spiky or unknown traffic) or per unit of provisioned capacity (cheaper once the load is steady). Reads are eventually consistent by default, meaning they can briefly miss the newest write, and they cost half of a strongly consistent read, which the CLI asks for with --consistent-read.

create and query a DynamoDB table
# Serverless table: no capacity to size, billed per request
aws dynamodb create-table \
--table-name Sessions \
--attribute-definitions AttributeName=UserId,AttributeType=S \
AttributeName=SessionId,AttributeType=S \
--key-schema AttributeName=UserId,KeyType=HASH \
AttributeName=SessionId,KeyType=RANGE \
--billing-mode PAY_PER_REQUEST
# "TableStatus": "CREATING" → ACTIVE within seconds
# Write an item, then read it back with strong consistency
aws dynamodb put-item --table-name Sessions \
--item '{"UserId":{"S":"u-1042"},"SessionId":{"S":"s-9001"},"Expires":{"N":"1786579200"}}'
aws dynamodb get-item --table-name Sessions \
--key '{"UserId":{"S":"u-1042"},"SessionId":{"S":"s-9001"}}' \
--consistent-read --return-consumed-capacity TOTAL
{
"Item": {
"UserId": {"S": "u-1042"},
"SessionId": {"S": "s-9001"},
"Expires": {"N": "1786579200"}
},
"ConsumedCapacity": { "TableName": "Sessions", "CapacityUnits": 1.0 }
}

The trade-offs are sharp. One item cannot exceed 400 KB (kilobytes). There are no joins. You can query efficiently only by the table's keys, or by a secondary index you define alongside them for a second query shape. That turns relational design on its head. You write down how the application will read the data first, then build the table around those reads. In exchange you get a database with no maintenance window and the same latency whether it serves zero requests a second or several million.

Caches, warehouses, and choosing between them

Two more services finish the exam picture. Amazon ElastiCache runs managed in-memory stores (Valkey, Redis OSS, or Memcached) that sit in front of any database like a notepad on the desk beside the filing cabinet. Repeated reads get answered from memory in microseconds, and a cache node costs a fraction of another read replica, because a replica bills as a whole database instance. DynamoDB has its own purpose-built version, DAX (DynamoDB Accelerator), a write-through cache that drops read latency from milliseconds to microseconds. It speaks the DynamoDB API, so you swap in the DAX client and leave your queries alone. And when the question is analytics, scanning terabytes to answer something like "what did each region earn last quarter", a transactional database is the wrong shape entirely. Amazon Redshift is the columnar data warehouse AWS built for that job.

Which data service? Start from the access pattern
What does the workload need?
Ask how the data gets read before you pick an engine
Joins, transactions, fixed schema
RDS / Aurora
Relational engine: Multi-AZ for availability, read replicas for read scale
Key-value at massive scale
DynamoDB
Serverless; single-digit-ms reads when the partition key has high cardinality
Hot reads hammering the DB
ElastiCache / DAX
In-memory cache absorbs repeated reads at microsecond latency, cheaper than a replica
Analytics over terabytes
Redshift
Columnar data warehouse for aggregate scans, not transactional queries
Answer the access-pattern question first and the service picks itself.

The habit worth building as an architect: ask how the data will be read before you pick the engine, then reach for the matching lever. Multi-AZ to survive a failure. Replicas and caches to survive read volume. Partition-key design to survive DynamoDB's throughput limits. The next lesson, High availability & DR, pulls the camera back from single databases to whole architectures: what RPO and RTO mean once you have to write them into a contract, how backup-and-restore stacks up against pilot-light and active-active, and what an outage is actually allowed to cost you.

Multi-AZ RDS is a synchronous standby built for failover. It is never a read scaler, whatever an exam option tries to tell you. Read replicas are asynchronous and can lag, which makes them good for read traffic and wrong as your only availability story if you pretend they are always in step.

DynamoDB asks you to know your access patterns before you create the table: how many distinct values your partition key holds, how big your items get, and whether you need transactions or strongly consistent reads. Single-table design is a choice you can take or leave. Hot partitions are not.

Try this

Check the Multi-AZ flag on an RDS instance, then list your DynamoDB tables and see how each one is billed. Run this against lab resources only.

terminal
aws rds describe-db-instances --query 'DBInstances[].{Id:DBInstanceIdentifier,Engine:Engine,MultiAZ:MultiAZ,Class:DBInstanceClass}' --output table
aws dynamodb list-tables --output table
aws dynamodb describe-table --table-name orders \
--query '{Table:TableName,Billing:BillingModeSummary.BillingMode,Keys:KeySchema}' --output json
output
-------------------------------------------------
| DescribeDBInstances |
+--------+----------+----------+----------------+
| Class | Engine | MultiAZ | Id |
+--------+----------+----------+----------------+
| db.t3.micro | postgres | True | lab-pg |
+--------+----------+----------+----------------+
orders
{"Table":"orders","Billing":"PAY_PER_REQUEST","Keys":[{"AttributeName":"pk","KeyType":"HASH"}]}

Takeaway

Three lines to keep in your head: Multi-AZ buys availability, read replicas buy read scale, and DynamoDB buys predictable single-digit-millisecond reads for as long as your keys match the way you query.

Next: take one path through an application you know and decide, in a single paragraph, whether it wants a relational database or DynamoDB. Name the failover behavior and the consistency guarantee you are signing up for.

Quick check
01Your RDS primary is buried under SELECT traffic every day at peak. Which change actually gives you more read capacity, and why?
Incorrect — No. The Multi-AZ standby replicates synchronously for exactly one reason, failover. You can never read from it; it waits its turn.
Correct — A read replica is an asynchronously replicated copy with its own endpoint, built for the job of offloading and scaling reads.
Incorrect — No. Only the primary serves traffic. The standby sits idle until a failover flips DNS to it, so it never shares the load.
Incorrect — No. Read replicas offload reads, and ElastiCache or DAX soak up hot repeated reads more cheaply than another replica.
02How does Aurora's storage layer keep your data, and how much of it can fail before writes stop?
Incorrect — No. That describes an RDS Multi-AZ standby, not Aurora's shared distributed storage.
Correct — Six copies across three Availability Zones, and two can go missing while writes carry on being accepted.
Incorrect — No. That undercounts the copies and traps them in one Availability Zone. Aurora deliberately spans three.
Incorrect — No. Aurora replicas read from the same shared storage volume. None of them carries a separate copy of the data.
03An Amazon RDS primary is being pounded by a small set of identical read queries, repeated over and over, against reference data that changes maybe three times a day. The team wants that read load gone for the least money. Which option is MOST cost-effective?
Correct — A cache node answers hot repeated reads in microseconds and costs far less than the full database instance a read replica bills as.
Incorrect — It would work, but a replica bills as a whole database instance, which is expensive for a handful of repeated queries.
Incorrect — No. The synchronous Multi-AZ standby serves no reads at all. It waits for a failover.
Incorrect — No. Rebuilding a relational workload on DynamoDB is slow and costly when an in-memory cache already fixes the hot-read problem.

Related