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-pedidos holds 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 with alias/mercadofresco-datos is mandatory, and the anonymisation applied before handing data to development must have been reviewed by the data protection officer.

Contents

  1. What Aurora is and how it differs from RDS
  2. The separation of compute and storage
  3. The distributed volume: six copies across three zones
  4. Why checkpoints disappear
  5. Cluster components
  6. Endpoints: cluster, reader and custom
  7. Failover and real-world timings
  8. Read replicas with millisecond lag
  9. Compatibility with PostgreSQL and MySQL
  10. Aurora Serverless v2 and ACUs
  11. When Serverless v2 pays off and when it does not
  12. Auto scaling of read replicas
  13. Continuous backups, PITR and backtrack
  14. Fast cloning with copy-on-write
  15. Global Database, Aurora ML and parallel query
  16. Migrating mercadofresco-pedidos to Aurora
  17. Checks before and after with the module 5 metrics
  18. Blue/green deployments for schema and versions
  19. Costs and the I/O-Optimized model
  20. Common mistakes and tips
  21. Exercises
  22. 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_extensions that the ones MercadoFresco uses (pg_trgm, postgis for 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-dev

Three 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-dev

The 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-dev

Uses: 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-dev

The 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:

  1. Clone and rehearse first. Before touching production, a copy-on-write clone to measure how long the UPDATE and the CREATE INDEX really take with 48 million rows. Without that figure the window is planned blind. The clone is anonymised and deleted when the work is done.
  2. Create the green environment with create-blue-green-deployment. Aurora creates the copy cluster and keeps it in sync by logical replication.
  3. In green: ALTER TABLE pedidos ADD COLUMN franja_reparto text; (fast, no volatile default), then the UPDATE in batches of 50,000 rows with pauses, so as not to create a giant transaction or send replication lag through the roof, and finally CREATE INDEX CONCURRENTLY idx_pedidos_franja ON pedidos (franja_reparto);.
  4. Verify in green: the count of rows with the column populated equal to the total, a reasonable distribution of values, EXPLAIN on the affected queries showing the new index in use, and the application's test suite pointed at green.
  5. Check that replication lag is zero and run the switchover. Under a minute.
  6. Watch for 24 hours with the alarms from 05-01, keeping the old cluster available.
  7. 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.

© Copyright 2026. All rights reserved