Aurora
Choosing MySQL- or PostgreSQL-Compatibility Before Backtrack and Autovacuum Make the Choice for You
- Which engine (MySQL or PostgreSQL) the team already runs and has tooling/expertise for
- Whether the workload includes long-running analytics/reporting queries against read replicas
- Whether the recovery plan assumes Backtrack (MySQL-only)
- Dependence on any PostgreSQL extension not on Aurora's supported list
Production premise
In practice, you pick the engine your team already runs. Tools, ORMs, backups, and the people themselves are already tied to MySQL or PostgreSQL. Fighting that rarely pays.
But "compatible" is not "identical," and one difference shows up before you've written a single query: Aurora PostgreSQL does not run vacuum the way stock PostgreSQL runs it.
Quick background, if you haven't lived in this corner of Postgres:
every delete or update leaves dead rows behind, and vacuum is the
background process that cleans them up. Vacuum also has a deadline to
meet. PostgreSQL numbers its transactions with a counter that
eventually wraps around, and every table must be fully vacuumed
before its counter gets close to the limit (autovacuum_freeze_max_age)
— or old data becomes unreadable.
Aurora adds adaptive autovacuum (rds.adaptive_autovacuum, on by
default). As a table's transaction counter approaches that limit,
Aurora automatically turns vacuum up. Stock PostgreSQL will not do
that on its own; you'd have to notice the climb and tune by hand.
Failure mode & inflection point
Adaptive autovacuum covers the wraparound deadline. It does nothing for a second problem, and this one exists only because of how Aurora is built.
In ordinary PostgreSQL replication, a replica replays the primary's changes onto its own copy of the data, and the two copies mostly stay out of each other's way. Aurora does not work that way. The writer and all the read replicas share one storage layer. That is why Aurora replicas are quick to create and don't fall behind on replay — and it is also why a query running on a reader can reach back and stall cleanup on the writer.
The chain goes like this. A long-running query on a reader still
needs the old versions of the rows it is reading, so the replica
tells the writer to keep them. PostgreSQL calls this
hot_standby_feedback. In standard RDS PostgreSQL it is a setting
you can turn on or off. In Aurora PostgreSQL it is on by default and
cannot be turned off. The writer's vacuum must now leave those old
rows alone, so cleanup on the writer stalls for as long as the query
on the reader runs.
You notice it like this: vacuum looks like it is running, but the
CloudWatch metric MaximumUsedTransactionIDs keeps climbing toward
your autovacuum_freeze_max_age value and will not come down. When
you see that, do not start tuning vacuum on the writer. Look for
long-running queries, or sessions sitting idle inside a transaction,
on the readers. That is the actual blocker.
One more asymmetry, on the MySQL side: Backtrack — Aurora MySQL's ability to rewind the whole cluster back to a moment in the past (page 7) — does not exist for PostgreSQL at all. If your recovery runbook assumes it does, you will find that out in the middle of an incident.
Production guardrail
Building something genuinely new, with no existing stack to match?
Default to PostgreSQL. On Aurora specifically, the PostgreSQL side
gets the more active feature development, the broader extension
ecosystem (pgvector, if anything you build touches embeddings), and
type and constraint checking that rejects bad data loudly — MySQL
would accept the same data silently and cost you later.
Choose MySQL when you can name the reason: your team already runs MySQL tooling, ORMs, or a CMS that requires it (WordPress and Drupal both do); you need Backtrack's fast rewind (page 7) and restoring a snapshot is not fast enough for you; or your team's MySQL experience is deep enough that switching would cost more in ramp-up than PostgreSQL buys in features.
If you do land on PostgreSQL, set one alarm before anything else:
watch MaximumUsedTransactionIDs against your
autovacuum_freeze_max_age value. When it starts climbing, check the
readers first — a long-running query or an idle-in-transaction
session there is the likely cause. Since you cannot turn
hot_standby_feedback off in Aurora, the fix is to cap how long
analytics and reporting queries are allowed to run on replicas.
Tuning vacuum on the writer will not help.
Last check before migration planning: list every PostgreSQL extension your team depends on and check it against Aurora's supported list. Aurora PostgreSQL has historically been slower than vanilla PostgreSQL to add support for newer extensions.