With the basket and the sessions out of the way, mercadofresco-pedidos can breathe again. The
180,000 daily writes have gone, VACUUM no longer competes for I/O on Friday afternoons and 35 % of
the instance's capacity has been freed up. But the instance is still the very same one we created in
02-04: a db.t3.small running PostgreSQL 16, Multi-AZ, with one read replica and storage that grows
by 12 GB every month.
And it still has the three problems that module 5 documented. Multi-AZ failover takes between 60 and 120 seconds, which on a Friday at 19:00 means between 900 and 1,800 orders left hanging. The read replica lag rises to 8 seconds at the peak, quite enough for a customer to confirm an order and not see it in their history. And the instance is paid for around the clock, including in the small hours, when there are three orders an hour.
Amazon Aurora is the relational database that AWS designed specifically for the cloud, compatible
with PostgreSQL and MySQL but with an internal architecture completely unlike that of RDS. In this
lesson Marta comes to understand that architecture, decides between provisioned instances and
Serverless v2 using MercadoFresco's real load profile, and migrates mercadofresco-pedidos with less
than two minutes of downtime.
Cost warning. Aurora is more expensive per compute hour than RDS and, depending on the model, charges for I/O separately. A forgotten test cluster costs tens of dollars a month. Delete whatever you create to practise. All the data is fictitious and the figures are estimates for
eu-west-1.Compliance warning.
mercadofresco-pedidosholds customers' personal data (name, delivery address, telephone, purchase history). Any operation in this module —cloning, restoring a backup, replication— creates a full copy of that data and falls under the GDPR. Encryption withalias/mercadofresco-datosis mandatory, and the anonymisation applied before handing data to development must have been reviewed by the data protection officer.
Contents
- What Aurora is and how it differs from RDS
- The separation of compute and storage
- The distributed volume: six copies across three zones
- Why checkpoints disappear
- Cluster components
- Endpoints: cluster, reader and custom
- Failover and real-world timings
- Read replicas with millisecond lag
- Compatibility with PostgreSQL and MySQL
- Aurora Serverless v2 and ACUs
- When Serverless v2 pays off and when it does not
- Auto scaling of read replicas
- Continuous backups, PITR and backtrack
- Fast cloning with copy-on-write
- Global Database, Aurora ML and parallel query
- Migrating
mercadofresco-pedidosto Aurora - Checks before and after with the module 5 metrics
- Blue/green deployments for schema and versions
- Costs and the I/O-Optimized model
- Common mistakes and tips
- Exercises
- Conclusion
What Aurora is and how it differs from RDS
RDS is PostgreSQL —the same software you would install on your own server— managed by AWS: AWS applies the patches, takes the backups and handles failover, but the engine and its storage are the usual ones, with an EBS volume attached to an instance.
Aurora rewrites the storage layer. It keeps PostgreSQL's query engine —which is why MercadoFresco's
SELECT statements work without touching a single line— but replaces everything underneath with a
distributed storage service of its own.
| RDS PostgreSQL | Aurora PostgreSQL | |
|---|---|---|
| Storage | One EBS volume per instance | Shared distributed volume |
| Copies of the data | 1 (plus the Multi-AZ one) | 6, across 3 AZs |
| Growth | Provisioned and extended by hand | Automatic, 10 GB at a time up to 128 TB |
| Read replicas | Maximum 5, logical or physical replication | Up to 15, on the same volume |
| Replica lag | Seconds | Milliseconds |
| Failover | 60-120 s | Under 30 s, typically 10-20 |
| Backup | Scheduled snapshot | Continuous to S3, no impact |
| Serverless scaling | No | Serverless v2, by ACU |
| Base cost | Lower | Higher per hour; often lower per unit of performance |
The separation of compute and storage
This is the central idea, and almost every advantage follows from it. In a traditional database, the instance is the database: it holds the engine, the cache and the data files. If the instance dies, you have to start another one and recover the data.
In Aurora, the instance holds only the query engine and the cache. The data lives in a separate storage service, shared by every instance in the cluster.
graph TD
W["Writer instance<br/>db.r6g.large"] -->|writes redo log records| V
R1["Read replica 1"] -->|reads| V
R2["Read replica 2"] -->|reads| V
subgraph V["Aurora distributed volume · grows by itself up to 128 TB"]
A1["AZ eu-west-1a<br/>copy 1 · copy 2"]
A2["AZ eu-west-1b<br/>copy 3 · copy 4"]
A3["AZ eu-west-1c<br/>copy 5 · copy 6"]
end
V -->|continuous backup| S3["Amazon S3<br/>PITR with no impact on performance"]
The practical consequences are direct:
- Adding a replica copies no data. An instance starts up and attaches to the volume that already exists. It takes minutes, not hours, and consumes no I/O from the writer.
- Storage grows by itself. There is no need to provision 500 GB "just in case" or to extend the volume at three in the morning. You pay for what you use, in 10 GB increments.
- Failover moves no data. A replica already attached to the same volume is promoted; it is a change of role, not a recovery.
- Resizing the instance is quick, because the data does not move.
The distributed volume: six copies across three zones
Aurora keeps six copies of every data block, spread across three availability zones, two per zone. The write quorum is 4 out of 6 and the read quorum is 3 out of 6.
Those numbers have a concrete, verifiable consequence:
| Failure | Are writes still possible? | Are reads still possible? |
|---|---|---|
| Loss of 1 copy | Yes (5 of 6 ≥ 4) | Yes |
| Loss of 2 copies | Yes (4 of 6 = 4) | Yes |
| Loss of a whole AZ (2 copies) | Yes | Yes |
| Loss of an AZ plus one extra copy | No | Yes (3 of 6 = 3) |
In other words, Aurora tolerates losing a whole availability zone without losing write capability,
and a zone plus an extra disk without losing read capability. Compare that with
mercadofresco-pedidos on RDS Multi-AZ: two copies, and losing the primary means a 60-120 s failover.
Repair is also automatic and block by block: if a segment becomes corrupted, Aurora rebuilds it from the quorum in the background without anyone noticing.
Why checkpoints disappear
PostgreSQL writes first to the redo log (WAL) and periodically flushes the modified pages from memory
to disk: that is a checkpoint. It is an intense, periodic I/O operation and it produces the
latency spikes that show up on the mercadofresco-produccion dashboard every few minutes.
Aurora sends only the redo log to storage. It does not ship data pages: the storage nodes are the ones that apply the records and materialise the pages, continuously and in a distributed fashion. The consequences:
- There are no checkpoints and their latency spikes vanish along with them.
- Write traffic towards storage drops sharply —AWS talks of an order of magnitude— because what is sent is the log, not whole pages.
- Recovery after a failure is almost instantaneous: there is no long log to replay, because the storage is already up to date.
This is the technical reason why Aurora outperforms PostgreSQL on EBS with the same instance class, and why failover drops from minutes to seconds.
Cluster components
| Component | What it is | In MercadoFresco |
|---|---|---|
| Cluster | The logical unit: volume plus instances | aurora-mercadofresco-pedidos |
| Writer instance | The only one that accepts writes (one per cluster) | aurora-mf-escritor in eu-west-1a |
| Read replica | Up to 15, read-only, same volume | aurora-mf-lector-1 in eu-west-1b |
| Subnet group | Where the cluster lives | sng-mercadofresco-datos |
| Security group | Who can connect | sg-mercadofresco-basedatos |
| Parameter group | Engine configuration | Cluster-level and instance-level, separate |
A detail that confuses a lot of people: there are two parameter groups. The cluster one holds
what affects every instance and the volume (rds.logical_replication, time zone); the instance
one holds what is specific to each of them (work_mem, max_connections). Changing a parameter in
the wrong place raises no error: it simply does nothing.
Endpoints: cluster, reader and custom
Aurora offers several DNS names, and using the wrong one is a common and expensive mistake.
| Endpoint | Where it points | Use |
|---|---|---|
| Cluster (writer) | Always to the current writer instance | All writes and reads that demand the latest data |
| Reader | Spreads the load across the replicas | Read-only queries |
| Instance | To one specific instance | Diagnostics; never in the application |
| Custom | To a group of instances you define | Isolating workloads: reports, ETL |
The cluster endpoint follows failover: when a replica is promoted, DNS points to it within
seconds. That is why the application must always use it, and why you have to make sure the client
does not cache DNS beyond the TTL (Java, by default, caches for ever: you have to adjust
networkaddress.cache.ttl).
Custom endpoints solve a real problem MercadoFresco had back in 02-04: if Sara launches a heavy
query against the reader endpoint, it may land on the very replica that is serving the shop. With a
custom endpoint that groups only aurora-mf-lector-informes, her queries end up isolated on an
instance nobody else is using.
Failover and real-world timings
When the writer instance fails, Aurora promotes a replica. Since they all share the volume, there is no data to copy and no log to replay.
| RDS Multi-AZ (today) | Aurora | |
|---|---|---|
| Mechanism | Synchronous standby replica | Promotion of an active replica |
| Typical time | 60-120 s | 10-30 s |
| Does the standby serve reads? | No, it sits idle | Yes, the replicas work |
| Promotion priority | — | Configurable, tiers 0-15 |
Tier priority deserves attention: Aurora promotes the replica in the lowest tier and, all else being equal, the largest one. If MercadoFresco puts the reporting replica in tier 15, it guarantees that an instance sized for something else never ends up as the production writer.
Translated into Friday at 19:00: with RDS the window costs between 900 and 1,800 orders; with Aurora, between 150 and 450. It is not zero —you still have to retry— but it is the difference between an incident and a blink.
Read replicas with millisecond lag
In RDS, a read replica is another database that receives the log and applies it. That work takes
time, and at the Friday peak mercadofresco-pedidos-lectura builds up 8 seconds of lag.
In Aurora, the replica reads from the same volume. It applies no logs to materialise data: it merely invalidates the pages it has cached. Typical lag is 10 to 20 milliseconds.
The functional consequence is the one that matters to Luis: today, after confirming an order, the application cannot read from the replica because the order might not be there. With 15 ms of lag that read becomes safe in practice, and the whole "show me my history" workload can move to the reader endpoint. With one honest caveat: 15 ms is not zero. For the read immediately after a critical write —checking stock before taking payment— the cluster endpoint is still the one to use.
Compatibility with PostgreSQL and MySQL
Aurora comes in two editions and you have to pick the one that matches the source engine. MercadoFresco runs PostgreSQL 16, so it goes to Aurora PostgreSQL: the application, the driver, the queries and the extensions all work the same.
What to check before writing the migration off as trivial:
- Extensions. Aurora supports a broad catalogue, but not one identical to PostgreSQL's. Check
with
SELECT * FROM pg_available_extensionsthat the ones MercadoFresco uses (pg_trgm,postgisfor the delivery zones) are available. - Versions. Aurora trails the most recent PostgreSQL release. If the source is 16 and Aurora offers 16.x there is no problem; if the source were 17, you would have to wait.
- Parameters. Some PostgreSQL parameters do not exist in Aurora because its storage makes them irrelevant; the checkpoint-related ones are the obvious example.
- Outbound logical replication. It works, but verify it if any integration relies on it.
Aurora Serverless v2 and ACUs
Aurora Serverless v2 replaces the fixed instance class with capacity that scales continuously, measured in ACUs (Aurora Capacity Units). One ACU is roughly 2 GiB of memory with proportional CPU and network.
You configure a minimum and a maximum —from 0 to 256 ACUs, in increments of 0.5— and Aurora adjusts capacity within seconds, without dropping connections. That is the essential difference from v1, which paused and resumed with outages of tens of seconds.
# Create the Serverless v2 cluster, encrypted and tagged as the project requires.
aws rds create-db-cluster \
--db-cluster-identifier aurora-mercadofresco-pedidos \
--engine aurora-postgresql --engine-version 16.4 \
--database-name pedidos --master-username mfadmin \
--manage-master-user-password \
--master-user-secret-kms-key-id alias/mercadofresco-datos \
--db-subnet-group-name sng-mercadofresco-datos \
--vpc-security-group-ids sg-mercadofresco-basedatos \
--storage-encrypted --kms-key-id alias/mercadofresco-datos \
--serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=8 \
--backup-retention-period 14 \
--enable-cloudwatch-logs-exports postgresql \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=basedatos Key=Propietario,Value=marta \
Key=CentroCoste,Value=plataforma \
--region eu-west-1 --profile mercadofresco-devThree decisions in that command are worth understanding. --manage-master-user-password has Aurora
create and rotate the password in Secrets Manager, integrating with what we set up in 04-03 instead
of duplicating the mechanism. MinCapacity=0.5 is not zero: the cluster never stops completely, so
the first query of the day does not wait for a start-up. And MaxCapacity=8 is the ceiling that caps
the bill if a query runs away: without it, an accidental Seq Scan can scale a long way and bill it.
When Serverless v2 pays off and when it does not
The answer depends on the load profile. This is MercadoFresco's, as measured in module 5:
| Slot | Weekly hours | Load | ACUs needed |
|---|---|---|---|
| Friday peak 17:00-21:00 | 4 | 900 orders/h | 6-8 |
| Weekday 09:00-22:00 | 74 | 150-300 orders/h | 2-3 |
| Weekend daytime | 24 | 200 orders/h | 2-3 |
| Night 00:00-07:00 | 49 | 3-10 orders/h | 0.5-1 |
| Reporting window | 7 | Aggregations | 4-6 (until 06-04) |
Weighted average: ≈2.4 ACUs. With provisioned instances you would have to size for the peak —a
db.r6g.large, 2 vCPU and 16 GiB— and pay for it all 168 hours of the week.
Provisioned db.r6g.large |
Serverless v2 (0.5-8 ACU) | |
|---|---|---|
| Monthly compute cost | ~205 USD | ~106 USD |
| Behaviour at the peak | Fixed; if it falls short, it degrades | Scales to 8 ACUs in seconds |
| Behaviour overnight | Paid for just the same | 0.5 ACUs |
| Bill predictability | Total | Variable, capped by the maximum |
Serverless v2 pays off with variable load that has deep troughs (MercadoFresco's case), in development and pre-production environments used only during office hours, and when the peak is hard to predict. It does not pay off with flat 24×7 load —a provisioned instance with Reserved Instances is cheaper—, when you need an exactly predictable bill, or when sustained load is so high that the maximum is hit all the time.
One honest nuance: per ACU, Serverless v2 is more expensive per unit of capacity than an equivalent provisioned instance. It only wins if the trough is genuinely exploited. With MercadoFresco's load the night trough is 49 of 168 weekly hours: it is exploited, and heavily.
Auto scaling of read replicas
Independently of Serverless v2, Aurora scales the number of replicas with Application Auto Scaling, based on the average CPU of the replicas or the number of connections.
aws application-autoscaling register-scalable-target \
--service-namespace rds --scalable-dimension rds:cluster:ReadReplicaCount \
--resource-id cluster:aurora-mercadofresco-pedidos \
--min-capacity 1 --max-capacity 4 \
--region eu-west-1 --profile mercadofresco-dev
aws application-autoscaling put-scaling-policy \
--service-namespace rds --scalable-dimension rds:cluster:ReadReplicaCount \
--resource-id cluster:aurora-mercadofresco-pedidos \
--policy-name escalado-lectores-mercadofresco --policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration \
'{"TargetValue":60.0,
"PredefinedMetricSpecification":{"PredefinedMetricType":"RDSReaderAverageCPUUtilization"},
"ScaleInCooldown":600,"ScaleOutCooldown":300}' \
--region eu-west-1 --profile mercadofresco-devThe scale-in cooldown (600 s) is longer than the scale-out one (300 s) for the same reason as in the ASG in 02-01: it is better to scale out fast and scale in slowly. And with Serverless v2 each new replica also scales its own capacity, so the two mechanisms combine.
Continuous backups, PITR and backtrack
Backups are continuous and go to S3, with no window and no impact on performance, because the storage takes them, not the instance. Retention is set between 1 and 35 days; MercadoFresco uses 14.
PITR lets you restore to any second within the retention period, always to a new cluster. That matters: it is not an "undo" on the existing cluster. Restoring and redirecting the application takes 10 to 20 minutes.
Backtrack is different and exists only in Aurora MySQL: it rewinds the cluster in place, in
seconds, up to 72 hours back. It is the ideal tool for undoing an UPDATE without a WHERE. Since
MercadoFresco runs PostgreSQL, it does not have it, and that is worth stating plainly because
counting on backtrack in a PostgreSQL cluster is a frequent mistake. The alternative in PostgreSQL is
a PITR restore to a new cluster: slower, but just as effective.
Fast cloning with copy-on-write
Cloning creates a new cluster that shares the original's volume and consumes storage of its own only for the blocks that change: it is copy-on-write. A clone of a 340 GB database takes minutes and starts out occupying practically nothing.
aws rds restore-db-cluster-to-point-in-time \
--source-db-cluster-identifier aurora-mercadofresco-pedidos \
--db-cluster-identifier aurora-mf-pruebas-luis \
--restore-type copy-on-write --use-latest-restorable-time \
--db-subnet-group-name sng-mercadofresco-datos \
--vpc-security-group-ids sg-mercadofresco-basedatos \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=desarrollo \
Key=Componente,Value=basedatos Key=Propietario,Value=luis \
Key=CentroCoste,Value=desarrollo \
--region eu-west-1 --profile mercadofresco-devUses: testing a schema migration with real volume, reproducing an incident with the exact data from the moment it happened, measuring the impact of a new index before creating it in production.
GDPR warning. A clone contains all the customers' personal data: names, addresses, telephone numbers and purchase history. Handing it to development just like that is an internal transfer of personal data that almost certainly is not covered by the legal basis on which the data was collected. The correct procedure at MercadoFresco is: clone, immediately run an anonymisation script that replaces identifying data with synthetic values while preserving the statistical distribution, and only then grant access to the clone; encrypt it with the same key, and set up automatic deletion after 7 days. This procedure must be documented and reviewed by the data protection officer before it is used, and its execution is recorded in
trail-mercadofresco. An anonymised clone is an excellent tool; a clone that has not been anonymised, sitting on a laptop, is a data breach waiting to happen.
And a cost warning: the clone starts out cheap but grows as it diverges. If Luis runs a migration that rewrites half the tables, the clone ends up occupying half the original. Clones are disposable; automatic deletion after 7 days is a cost measure too.
Global Database, Aurora ML and parallel query
Aurora Global Database replicates the cluster to other regions with typical lag under a second,
using replication at the storage layer that consumes none of the writer's capacity. It allows
low-latency local reads and regional recovery with an RPO of about a second and an RTO of a minute.
MercadoFresco does not need it today —it operates in Spain from eu-west-1— but it is the lever for
the expansion plan into France or Portugal: catalogue and history reads would be served locally while
writes would still travel to Ireland.
Aurora Machine Learning lets you call SageMaker or Bedrock from SQL, with functions that take columns and return predictions. A plausible case for MercadoFresco would be scoring the default risk of an order inside the query itself. It is mentioned so that you know it exists; developing it is outside the scope of this course.
Parallel query (Aurora MySQL only) pushes part of the filtering and aggregation down to the storage layer, greatly accelerating analytical queries. It is tempting for Sara's reports, but MercadoFresco runs PostgreSQL and, above all, that problem has a better answer: Redshift, in 06-04. Aurora must not become the analytical warehouse.
Migrating mercadofresco-pedidos to Aurora
There are two routes, and the choice depends on how much downtime you can tolerate.
| Snapshot and restore | Promoted read replica | |
|---|---|---|
| Downtime | 1-4 hours | 1-2 minutes |
| Complexity | Low | Medium |
| Rollback | Easy: the source is untouched | Easy until promotion |
| When | Non-critical environments | Production |
MercadoFresco uses the second: an Aurora read replica is created from the RDS instance and kept in sync by replication; when lag reaches zero, it is promoted.
sequenceDiagram
participant RDS as mercadofresco-pedidos (RDS)
participant AUR as aurora-mercadofresco-pedidos
participant APP as Shop
RDS->>AUR: 1 · Create the Aurora read replica (hours, no downtime)
RDS-->>AUR: 2 · Continuous replication until lag is 0
APP->>RDS: 3 · Normal traffic meanwhile
Note over APP: 4 · Window: maintenance mode, 60 s
RDS-->>AUR: 5 · Verify lag = 0 and stop writes
AUR->>AUR: 6 · Promote the cluster
APP->>AUR: 7 · Switch the endpoint and resume
# 1) Create the Aurora read replica from the existing RDS instance.
aws rds create-db-cluster \
--db-cluster-identifier aurora-mercadofresco-pedidos \
--engine aurora-postgresql --engine-version 16.4 \
--replication-source-identifier \
arn:aws:rds:eu-west-1:111122223333:db:mercadofresco-pedidos \
--db-subnet-group-name sng-mercadofresco-datos \
--vpc-security-group-ids sg-mercadofresco-basedatos \
--storage-encrypted --kms-key-id alias/mercadofresco-datos \
--serverless-v2-scaling-configuration MinCapacity=0.5,MaxCapacity=8 \
--region eu-west-1 --profile mercadofresco-dev
# 2) Watch the lag until it reaches zero, before the window.
aws cloudwatch get-metric-statistics --namespace AWS/RDS \
--metric-name AuroraBinlogReplicaLag \
--dimensions Name=DBClusterIdentifier,Value=aurora-mercadofresco-pedidos \
--start-time 2026-08-09T00:00:00Z --end-time 2026-08-09T01:00:00Z \
--period 60 --statistics Maximum \
--region eu-west-1 --profile mercadofresco-devThe switchover window, minute by minute: the shop's maintenance mode is switched on (a 503 response
served from CloudFront, prepared in 03-04); lag is confirmed to be 0; the cluster is promoted with
promote-read-replica-db-cluster; the endpoint is changed in the
/mercadofresco/produccion/basedatos/endpoint parameter in Parameter Store; the ASG is restarted with
a rolling update; and maintenance mode is switched off. Target time: 90 seconds.
And the rule that is not up for negotiation: the original RDS instance is not deleted until 7 days of stable operation have passed. It costs 118 USD a month; an emergency rollback without it costs a great deal more.
Checks before and after with the module 5 metrics
Without a baseline there is no way to prove the migration was worth it. The previous week is captured and compared with the following week:
| Metric | Source | Before (RDS) | Target (Aurora) |
|---|---|---|---|
TiempoConfirmacionPedido p99 |
MercadoFresco/Tienda |
1,900 ms | < 900 ms |
ReadLatency p99 |
AWS/RDS |
22 ms | < 8 ms |
Maximum ReplicaLag at the peak |
AWS/RDS |
8,000 ms | < 100 ms |
| Failover duration (drill) | Drill | 96 s | < 30 s |
Maximum DatabaseConnections |
AWS/RDS |
178 of 200 | Unchanged, with headroom |
Monthly cost of Componente=basedatos |
Cost Explorer | 118 USD | 130-150 USD |
Two checks that are not metrics and are just as compulsory. First, trigger a failover on purpose
with failover-db-cluster outside peak hours and measure how long the application really takes to
recover: the bottleneck is almost never Aurora but the client's connection pool, which keeps trying
the old connection. Second, restore a backup and check that the data is complete, which is the
lesson 05-05 left us with.
Blue/green deployments for schema and versions
An Aurora blue/green deployment creates a green cluster that is a synchronised copy of the
production blue cluster. The changes are applied to green —a new engine version, a long
ALTER TABLE, a new index— while blue carries on serving; you test; and the switchover takes less
than a minute, with AWS checking first that replication is up to date.
Advantages over doing it live: an ALTER TABLE on a 340 GB table blocks nothing in production, real
performance can be tested with the replicated workload, and rolling back is nothing more than
switching the roles over again.
Limitations you need to know: the green cluster costs money while it exists (compute is doubled), not every schema change is supported —those that break logical replication, such as dropping a column used in the primary key, are not— and you have to check what happens to sequences after the switchover. For MercadoFresco it is the default route for version upgrades and large schema migrations, and it combines with what we will see in module 8.
Costs and the I/O-Optimized model
Aurora charges for four things: compute (per instance hour or per ACU-hour), storage (per GB-month), I/O (per million read and write operations against the volume) and backups beyond the size of the cluster.
I/O is the unpredictable item, and that is why there are two models:
| Aurora Standard | Aurora I/O-Optimized | |
|---|---|---|
| Compute | Base price | ~30 % more expensive |
| Storage | 0.10 USD/GB-month | ~2.25× more expensive |
| I/O | Charged separately | Included |
| When it pays off | Low or moderate I/O | When I/O exceeds 25 % of the bill |
Monthly estimate for MercadoFresco:
| Item | RDS today | Aurora Serverless v2 Standard |
|---|---|---|
| Compute | db.t3.small Multi-AZ: 60 USD |
2.4 average ACUs × 730 h × 0.12 = 210 USD |
| Read replica | 30 USD | Included in reader scaling |
| Storage | 340 GB × 0.127 = 43 USD | 340 GB × 0.10 = 34 USD |
| I/O | Included in gp3 | ~180 M ops × 0.20/M = 36 USD |
| Backups | Included up to the size | Included up to the size |
| Total | ≈118 USD | ≈280 USD |
It has to be said plainly: Aurora comes out more expensive. The justification is not the saving, it is what those extra 162 USD buy: a 20-second failover instead of 96, replica lag in milliseconds instead of 8 seconds, six copies across three AZs instead of two, cloning for development, blue/green for schema changes and capacity that follows demand. For a shop that takes its money at the Friday peak, 162 USD a month is less than a single minute of downtime.
And two levers to bring it down: reduce MaxCapacity once 06-05 takes the catalogue load away, and
evaluate I/O-Optimized if I/O grows —at 36 USD out of 280, today it does not pay off.
Common Mistakes and Tips
Using the instance endpoint in the application. It works, right up until failover: then the application is still pointing at an instance that is now a replica and every write fails. Always use the cluster endpoint.
Not tuning the client's DNS cache. The endpoint changes within seconds, but if the client caches DNS indefinitely —the JVM's default behaviour— the application takes minutes or hours to find out. It is the most frequent reason why a "fast" failover does not look fast.
Counting on backtrack in Aurora PostgreSQL. It does not exist: it is exclusive to Aurora MySQL. In PostgreSQL, the plan for undoing a human error is a PITR restore to a new cluster, and it has to have been rehearsed before you need it.
Setting MinCapacity too low. At 0.5 ACUs, a cluster that has been idle for hours has a cold
cache: the first query of the peak can be slow while it scales. If the peak is predictable —and Friday
is— it is worth raising the minimum during that slot.
Setting MaxCapacity too high "just in case". Serverless v2 scales as far as you let it, and a
runaway query can spend weeks billing 64 ACUs without anyone noticing. The maximum is a spending
limit, not an aspiration; pair it with an alarm on ServerlessDatabaseCapacity.
Cloning production and handing it to development without anonymising. That is a data protection incident, not a shortcut. Anonymising is part of the procedure, not an extra.
Changing a parameter in the wrong group. Cluster parameters and instance parameters are different groups and getting it wrong raises no error: the change simply has no effect.
Treating Aurora as if it were an analytical warehouse. It is tempting once you discover the 15 replicas and parallel query. It is still a row-oriented engine: an aggregation over 40 million rows will be slow and expensive. That job belongs to Redshift (06-04).
Tip: rehearse failover on day one. aws rds failover-db-cluster outside peak hours, stopwatch in
hand, measuring how long the application takes, not the cluster. It is the only way to discover
the DNS cache problem before a Friday discovers it for you.
Tip: put an alarm on ServerlessDatabaseCapacity. If the cluster has been pinned to the maximum
for days, either it is badly sized or a query has degraded. Both are things you want to know before
the bill arrives.
Exercises
Exercise 1: designing the cluster topology
Design the complete topology of aurora-mercadofresco-pedidos knowing that it must serve: the shop
(writes and critical reads), the customer's order history (reads that tolerate 20 ms of lag), customer
service queries (exploratory, occasionally heavy) and the nightly export towards the analytical
warehouse.
Specify: how many instances and in which AZs, what promotion priority tier you assign to each, which endpoint each workload uses, and what reader auto scaling configuration you would set. Justify why the nightly export must not use the general reader endpoint.
Exercise 2: choosing the capacity model
A prepared-meal subscription company, with an architecture similar to MercadoFresco's, has this profile: practically constant traffic around the clock (between 400 and 600 orders an hour, with no overnight trough because it operates across three time zones), 1.2 TB of data, and a peak of three times the normal load on the first day of each month, when subscriptions renew. Its I/O bill on Aurora would be roughly 34 % of the total.
Answer: (a) Serverless v2 or provisioned instances, and why?; (b) Standard or I/O-Optimized?; (c) how would you handle the monthly peak with the option you chose?; (d) which two metrics would you watch to know whether the decision was the right one three months later?
Exercise 3: planning a risky schema change
MercadoFresco needs to add a franja_reparto column to the pedidos table (340 GB, 48 million rows)
with a value calculated from the address, and to create an index on it. In PostgreSQL,
ALTER TABLE ... ADD COLUMN with a non-volatile default is fast, but the mass UPDATE that follows
and the CREATE INDEX are not.
Write the full plan: which Aurora mechanism you use, the steps in order, how you check that the result is correct before exposing it, what the rollback plan is at each point, what additional cost the operation carries and in which time slot you would schedule it. Also state what you would have done differently if the database were still on RDS.
Solutions
Solution 1
Topology: 4 instances.
| Instance | AZ | Role | Tier | Capacity |
|---|---|---|---|---|
aurora-mf-escritor |
eu-west-1a |
Writer | 0 | 0.5-8 ACU |
aurora-mf-lector-1 |
eu-west-1b |
General reads | 1 | 0.5-8 ACU |
aurora-mf-lector-2 |
eu-west-1a |
General reads (auto scaling) | 1 | 0.5-8 ACU |
aurora-mf-lector-lotes |
eu-west-1b |
Export and customer service | 15 | 2-8 ACU |
Endpoints by workload: the shop uses the cluster endpoint for writes and for the read that
comes immediately after a write (checking stock before taking payment). The order history uses the
reader endpoint, because 20 ms of lag is perfectly acceptable there. Customer service and the
nightly export use a custom endpoint that contains only aurora-mf-lector-lotes and nothing
else.
Auto scaling: a minimum of 1 reader and a maximum of 3 in the general group, with target tracking
on RDSReaderAverageCPUUtilization at 60 %, a 300 s cooldown when scaling out and 600 s when scaling
in. aurora-mf-lector-lotes stays outside the scalable group because its capacity is decided by
the nightly window, not by average CPU.
Why the export does not use the general reader endpoint: because that endpoint spreads the load across every replica in the group and there is no way to exclude one query from it. An export that reads 40 million rows fills the replica's cache with historical pages and evicts the hot catalogue pages —exactly the performance interference we diagnosed in 06-01, reproduced inside Aurora. The custom endpoint isolates it on an instance that nobody else uses. And tier 15 on that instance guarantees that, if the writer goes down, the machine configured for batch work is never the one promoted.
Solution 2
(a) Provisioned instances. There is no trough: the load sits between 400 and 600 orders an hour around the clock, so Serverless v2's structural advantage —not paying when nobody is there— never materialises. Since per ACU it is more expensive than the equivalent provisioned capacity, on flat load it comes off worse. On top of that, constant load makes it possible to buy Reserved Instances for one or three years, with discounts Serverless v2 does not offer in the same way. The right decision is a reserved provisioned instance, sized for the normal load.
(b) I/O-Optimized. The rule of thumb is that it pays off when I/O exceeds roughly 25 % of the bill, and here it is 34 %. I/O-Optimized makes compute about 30 % more expensive and storage considerably more, but it removes the I/O line entirely; with 1.2 TB it is worth doing the specific storage calculation, because that is the item that rises most. At 34 % I/O it comes out in favour, and as a valuable side effect the bill becomes predictable, which is exactly what a subscription company needs in order to budget.
(c) The monthly peak. With provisioned instances there are two levers. The main one is read replica auto scaling, scheduled in advance for the first day of the month: the reader minimum is raised the night before and lowered 24 hours later. If the peak also affects writes —subscription renewals generate writes— the second lever is changing the writer instance class, which in Aurora is quick because the data does not move, taking advantage of a low-traffic window and accepting a failover of about 20 seconds. What you must not do is size the instance for the peak on day 1 and pay for it on the other 30.
(d) Two metrics three months later. First, the average CPU and memory utilisation of the writer
instance: if it stays below 30 % for a sustained period, it was oversized and there is money on the
table; if it regularly exceeds 70 %, there is no headroom left for day 1. Second, the I/O cost that
would have been paid on Standard, which can be worked out from VolumeReadIOPs and
VolumeWriteIOPs, to verify that I/O-Optimized is still the right option; if the application changes
and I/O falls, the decision is worth revisiting.
Solution 3
Mechanism: an Aurora blue/green deployment. It is exactly the use case it was built for: a long schema change on an enormous table which, applied live, would block writes for a very long stretch of time.
Steps:
- Clone and rehearse first. Before touching production, a copy-on-write clone to measure how long
the
UPDATEand theCREATE INDEXreally take with 48 million rows. Without that figure the window is planned blind. The clone is anonymised and deleted when the work is done. - Create the green environment with
create-blue-green-deployment. Aurora creates the copy cluster and keeps it in sync by logical replication. - In green:
ALTER TABLE pedidos ADD COLUMN franja_reparto text;(fast, no volatile default), then theUPDATEin batches of 50,000 rows with pauses, so as not to create a giant transaction or send replication lag through the roof, and finallyCREATE INDEX CONCURRENTLY idx_pedidos_franja ON pedidos (franja_reparto);. - Verify in green: the count of rows with the column populated equal to the total, a reasonable
distribution of values,
EXPLAINon the affected queries showing the new index in use, and the application's test suite pointed at green. - Check that replication lag is zero and run the switchover. Under a minute.
- Watch for 24 hours with the alarms from 05-01, keeping the old cluster available.
- Delete the old environment after 7 days.
Rollback: up to step 5, you delete the green environment and nothing has happened —production has not been touched at any point. After the switchover the old cluster still exists and you can go back to it, with the important caveat that the writes that happened after the switchover are not in it: that is why step 6 watches closely and why the window is chosen during low activity.
Additional cost: the green cluster doubles compute while it exists. If the operation lasts 8 hours and the cluster runs at 4 ACUs, that is about 32 ACU-hours, less than 4 USD, plus the divergent storage. It is negligible against the risk it removes.
Time slot: Tuesday or Wednesday in the small hours, between 02:00 and 05:00, which is the deepest trough. Never a Thursday or a Friday, because of the proximity to the peak; never a Monday, because if something goes wrong you want working days ahead of you.
What would have changed on RDS: there is no blue/green with a one-minute switchover for this case,
so the plan would have been to create a read replica, apply the changes there, promote it and redirect
—more manual, slower and with more downtime— or else run the UPDATE in batches directly in
production over several nights, watching for locks and replica lag. On top of that, the rehearsal in
step 1 would have required restoring a full 340 GB snapshot, with hours of waiting and the cost of
the full storage, instead of a clone that starts in minutes and takes up almost no space. That
difference —being able to rehearse cheaply— is one of the least cited and most useful reasons for Aurora.
Conclusion
mercadofresco-pedidos is no longer a PostgreSQL instance on EBS: it is
aurora-mercadofresco-pedidos, a cluster with the same engine and a completely different storage
layer. You understand that difference and where its advantages come from: the separation of compute
and storage, which turns adding a replica into starting an instance on data that already exists; the
distributed volume with six copies across three AZs, with a quorum of 4 of 6 to write and 3 of 6
to read, which tolerates losing a whole zone while still accepting orders; and the disappearance of
checkpoints, because Aurora sends only the redo log and it is the storage nodes that materialise the
pages.
You know the cluster components and —what prevents the most incidents— the endpoints: the cluster one that follows failover and that the application must always use, the reader one for anything that tolerates 15 ms of lag, the instance one that must never appear in the application, and the custom ones that isolate reports and the nightly export so they do not poison the cache of the replica that serves the shop. With failover of 10 to 30 seconds instead of 96, tier priority so the batch instance is never promoted, and replica lag in milliseconds that finally allows the customer's history to be served from a replica.
You can decide between Serverless v2 and provisioned using the real load profile: 2.4 ACUs on
average against a peak of 8, with 49 of 168 weekly hours in the overnight trough, is the case where
Serverless v2 wins; flat 24×7 load is the case where it loses, because per ACU it is more expensive.
And you know that a MinCapacity set too low leaves the cache cold and a MaxCapacity set too high
is a bill with no brakes. You have a grip on continuous backups to S3 with no impact, the PITR
that always restores to a new cluster, the fact that backtrack does not exist in PostgreSQL
however often you are told otherwise, and copy-on-write cloning that lets Luis rehearse a
migration with 340 real GB in minutes —with the anonymisation, encryption and 7-day deletion procedure
the GDPR demands and that the data protection officer must review.
The migration was done with a promoted read replica and 90 seconds of downtime, not with a
snapshot and four hours, and it was measured: TiempoConfirmacionPedido p99 from 1,900 ms to under
900, ReplicaLag from 8 seconds to under 100 ms, failover from 96 s to under 30. With the original
RDS instance left intact for seven days, a failover drill run deliberately and a verified restore. And
with the bill put on the table without decoration: from 118 to 280 USD a month, because Aurora is
not chosen to save money but to buy availability, and 162 USD is less than a minute of downtime on a
Friday. Plus blue/green as the default route for large schema changes and engine versions.
One workload remains that Aurora is not going to solve however many replicas we throw at it. Sara's
reports still aggregate between 6 and 40 million rows in order to use 4 columns out of 40, and they
still read complete rows, uncompressed and unparallelised, because Aurora —like PostgreSQL— is a
row-oriented, transactional engine. Isolating them on a custom endpoint stops them poisoning the shop,
but it does not make them fast: they still take minutes, and Sara is still waiting. In 06-04,
"Amazon Redshift", those reports leave the transactional database for good: we will look at columnar
storage and massively parallel execution, the star schema with hechos_pedidos and its dimensions,
distribution and sort keys, loading with COPY from mercadofresco-informes-analitica, Redshift
Serverless, and the honest comparison with Athena so you know when a data warehouse is needed and when
it is not.
AWS Course
Module 1: Introduction to AWS
- What Is AWS?
- Setting Up Your AWS Account
- AWS Global Infrastructure
- The AWS Management Console
- AWS CLI and SDKs
Module 2: Core AWS Services
Module 3: Networking and Content Delivery
Module 4: Security and Identity
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager and Parameter Store
- AWS Shield
- AWS WAF
Module 5: Monitoring and Management
Module 6: Databases
Module 7: Application Integration
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Integration Patterns: Idempotency, Retries and Dead-Letter Queues
