A photorealistic image of an elephant that is all the shades of the aurora borealis, standing in a nordic landscape.

Second in a series of dispassionate tours of managed PostgreSQL services. The first covered Amazon RDS for PostgreSQL. Aurora sits one tab over in the same AWS console and is architecturally unlike anything else on the list.

Overview

Amazon Aurora PostgreSQL is a managed database service that accepts the PostgreSQL wire protocol and runs a PostgreSQL-compatible query engine on top of a storage layer that is not PostgreSQL’s. Compute and storage are separated. A single Aurora cluster presents one or more compute instances (a writer and optional readers) attached to a shared, distributed storage volume that replicates redo log records synchronously to six copies across three Availability Zones.

That last sentence is the whole thesis of Aurora, and most of what’s distinctive about operating it follows from that sentence. The writer does not ship WAL to disk in the way a stock PostgreSQL server does. The writer ships redo records to the Aurora storage service; the storage service applies them to pages it materializes on demand. Read replicas do not replay WAL from a primary’s log stream; they read from the same distributed volume and invalidate their local buffer cache based on redo notifications from the writer. Failover does not involve streaming-replication catch-up; it involves promoting a replica to bind to the same storage volume.

This changes many things. It does not change everything. Aurora is still a fork of PostgreSQL with a modified storage engine; it still executes the parser, planner, and executor you’d recognize. Most of what SQL does works. Most extensions that AWS allows will behave the way you expect. Most of the surprises are at the edges: durability semantics, replica lag, vacuum, backup, and the set of features that depend on on-disk WAL or filesystem-level access.

This post focuses on Aurora PostgreSQL specifically. Aurora MySQL is a different product with similar architectural bones and different details; nothing below should be assumed to generalize.

Architecture

The storage layer

The Aurora storage volume is a distributed, log-structured service internal to AWS. It is presented to the database compute instance as a single volume that auto-scales up to a documented maximum. At time of writing, AWS documents a 128 TiB ceiling per cluster for Aurora PostgreSQL, with storage growth in 10 GB increments as needed.

Writes are persisted by sending redo log records to six storage nodes distributed across three Availability Zones (two per AZ). A write is acknowledged to the database instance when four of the six storage nodes have durably accepted the record. Reads from the compute instance to storage are served by a quorum protocol that requires agreement from three of the six nodes. This 4/6 write, 3/6 read arrangement is the source of Aurora’s AZ-failure-tolerance claim: an entire AZ can be unavailable and writes still complete.

The storage service materializes data pages on demand from redo. The compute instance does not issue fsync on data files; it does not have data files in the traditional sense. What it has is a buffer cache and a redo stream. When the compute instance needs a page that isn’t cached, it asks the storage service for the current version of that page.

This is the single biggest departure from vanilla PostgreSQL, and its implications propagate outward.

The compute layer

An Aurora PostgreSQL cluster has one writer instance and zero or more reader instances, up to fifteen readers per cluster. All instances in the cluster attach to the same storage volume. Readers share the underlying storage with the writer; there is no separate copy to replicate to.

Instance types are Aurora-specific (e.g., db.r6g.*, db.r7g.*, db.x2g.* families, plus the Graviton variants and serverless). Memory and CPU on a compute instance matter for the query engine and buffer cache; they do not affect storage throughput directly, because storage is elsewhere.

Aurora Serverless v2 is a variant in which the compute layer auto-scales by Aurora Capacity Units (ACUs) in response to load. It is not the same product as Serverless v1, which was deprecated as of March 31, 2025.

flowchart TB subgraph AZ1[Availability Zone 1] Writer[(Writer Instance)] Reader1[(Reader 1)] S1a[Storage Node] S1b[Storage Node] end subgraph AZ2[Availability Zone 2] Reader2[(Reader 2)] S2a[Storage Node] S2b[Storage Node] end subgraph AZ3[Availability Zone 3] Reader3[(Reader 3)] S3a[Storage Node] S3b[Storage Node] end Writer -- redo records --> S1a Writer -- redo records --> S1b Writer -- redo records --> S2a Writer -- redo records --> S2b Writer -- redo records --> S3a Writer -- redo records --> S3b Reader1 -. page reads .-> S1a Reader2 -. page reads .-> S2a Reader3 -. page reads .-> S3a classDef storage fill:#ffe9b3,stroke:#b58900 classDef writer fill:#c3e0ff,stroke:#2a6fb3 classDef reader fill:#d5f0d5,stroke:#2a8a3e class S1a,S1b,S2a,S2b,S3a,S3b storage class Writer writer class Reader1,Reader2,Reader3 reader

The writer sends redo to all six storage nodes; a 4-of-6 quorum acknowledges the write. Readers read pages directly from storage and maintain buffer caches invalidated by redo notifications from the writer.

Replicas and lag

Because readers share the same storage volume as the writer, Aurora does not have replication lag in the streaming-replication sense. What it has instead is a different kind of lag: a reader’s buffer cache can hold a page version that is older than what storage has. When the writer updates a page and emits redo, readers receive notifications and invalidate their cached copy of that page; subsequent reads pull the current version from storage.

The observable replica lag on an Aurora reader is typically in the tens of milliseconds range under normal load. It is not zero. An application that reads-after-writes on a reader can still see stale data. This is the same class of correctness issue as with streaming replication, but the mechanism and the typical magnitude are different.

Failover

A writer failure triggers promotion of one of the existing readers to writer. Because the storage volume is shared, there is no data to copy or replay; promotion is a matter of rebinding the cluster endpoint to a different compute instance and letting that instance take over writes. AWS documents failover times “typically under 30 seconds” for Aurora PostgreSQL with at least one reader in the cluster.

If the cluster has no reader, failover requires launching a new compute instance and takes materially longer: on the order of minutes. A single-instance Aurora cluster is a supported configuration; it is not a highly-available one.

Endpoints

An Aurora cluster exposes multiple DNS endpoints:

The cluster endpoint routes to the current writer. Applications that need to write always use this endpoint.

The reader endpoint round-robins across the cluster’s readers. This is a DNS-based load balancer: a connecting client is given one reader’s address, and connection pools established through this endpoint will not rebalance without reconnection.

Instance endpoints point to specific compute instances regardless of role. These exist for operational work (connecting to a specific replica for analysis).

Custom endpoints let you define your own subsets. For example, one endpoint that routes to three specific reader instances.

The DNS-based reader endpoint has the same limitation every DNS-based load balancer has: long-lived pooled connections stick to whichever reader they were first handed. Spreading load across readers requires the connection pool to cycle connections or the application to reconnect.

Superuser and permissions

The same constraint RDS has applies here: there is no true SUPERUSER role available to customers. The rds_superuser role gives elevated privileges but cannot, for example, install arbitrary extensions or write directly to the filesystem. The set of extensions available in Aurora PostgreSQL is a proper subset of those available in community PostgreSQL, and it differs somewhat from the set available in RDS. Aurora has its own extension allow-list, and it is worth checking the current version of the documentation rather than assuming parity with RDS.

Features

Backups and point-in-time recovery

Aurora backups are continuous and automatic. Because the storage layer is already a distributed log, “backup” is not a separate process that copies data off the database. It is the retention of redo log records in the storage service, combined with periodic materialized snapshots. Point-in-time restore works by replaying the redo stream to a specific point.

The retention window for continuous backups is configurable, with a documented maximum (currently 35 days). Manual snapshots can be retained indefinitely.

Restoring to a new cluster from a point in time is materially faster than the equivalent RDS operation in most cases, because the restore is performed by the storage service rather than by copying a logical or physical backup into a new volume. A PITR still involves provisioning a new compute instance and binding it to the restored storage volume. In practice, “faster” here means “tens of minutes” versus “hours” for large databases; it is not instant.

Cloning

Aurora supports copy-on-write cloning of a cluster. Cloning produces a new cluster whose storage volume is initially shared with the source; as the clone diverges, it writes only the changed pages. This is genuinely useful for workloads that need a full copy of a production database for analysis, testing, or backfill, because the initial clone is fast and does not double storage cost until divergence is significant.

Cloning is an Aurora-specific operational tool; the closest analogue in RDS is the snapshot-and-restore path, which is slower and produces a fully independent copy.

Backtrack

Aurora PostgreSQLd does not support Backtrack (the feature that lets you “rewind” a database to an earlier point without a full restore) despite Aurora MySQL supporting it for some time.

The alternative is point-in-time restore to a new cluster, which is a full restore rather than an in-place rewind.

Global Database

Aurora Global Database replicates a cluster across AWS regions. The replication is performed by the storage service, not by PostgreSQL streaming replication. The primary region has a writer and up to fifteen readers; secondary regions have up to sixteen readers each and can be promoted to primary in a regional-failure scenario.

Two observations about Global Database that do not appear on the marketing page in bold:

First, the replication lag between regions is typically measured in “seconds, usually under one.” This is fine for many applications and not fine for others. If a workload requires writes to be visible at a secondary region within milliseconds, Global Database is not the answer; the speed of light and the laws of AWS inter-region networking are what they are.

Second, “write forwarding,” the feature that lets a secondary region’s endpoint accept writes and forward them to the primary, is available but operates with semantics that matter. A write forwarded from a secondary has higher latency than a local write in the primary region, and consistency modes (eventual, session, global) trade latency against visibility. Applications that naively enable write forwarding without reading the consistency-mode documentation will encounter surprises

Serverless v2

Aurora Serverless v2 auto-scales compute in fractional Aurora Capacity Units, typically in half-ACU increments, in response to load. Scaling is continuous rather than stepwise at the instance level. The serverless compute can sit in the same cluster as provisioned compute. A cluster can have a provisioned writer and serverless readers, or serverless writer and readers.

The non-obvious thing about Serverless v2 is that it does not scale to zero by default for Aurora PostgreSQL in most configurations; there is a minimum ACU floor. If the workload requires zero-cost idle, that constraint matters.

Performance Insights

Performance Insights works on Aurora essentially the same as on RDS. Same retention tiers (seven-day free, long-term paid), same SQL-level top-query aggregation, same session-state breakdown. The Aurora-specific wait events are documented and somewhat richer than vanilla PostgreSQL wait events because of the additional storage-layer wait types.

I/O-Optimized pricing mode

Aurora has two cluster-level pricing modes: standard and I/O-Optimized. In standard mode, storage is billed by consumption and I/O operations are billed per request. In I/O-Optimized, storage carries a premium but I/O is not metered.

The user requested no pricing, so I will not attempt a dollar comparison. The operational observation is that the choice of mode is not just a pricing choice: changes how you should think about index design, query patterns, and the acceptability of sequential scans. On standard pricing, a query that reads a lot of pages is a cost event; on I/O-Optimized, it is not. Over the lifetime of a workload whose access patterns evolve, this choice matters more than it looks like on the slider in the console.

Non-brochure concerns

These are operational realities of Aurora PostgreSQL that are true, documented, and not prominent in the marketing material.

Aurora is a fork of PostgreSQL

Aurora PostgreSQL tracks upstream PostgreSQL versions with a lag. A new major version of community PostgreSQL typically becomes available on Aurora months after it has been available on RDS, and RDS itself lags upstream by months. This is because the storage-layer integration has to be ported forward for each major version. An organization that wants to be on the latest PostgreSQL major version the week it ships will not find Aurora to be a match.

Additionally, some behaviors differ subtly from community PostgreSQL because the storage layer is not community PostgreSQL. The most-visible-to-users example is that pg_stat_wal and similar statistics reflect the Aurora storage service’s behavior rather than the on-disk WAL of a stock installation. Tuning advice that assumes stock WAL behavior (about full_page_writes, checkpoint tuning, wal_compression, etc.) does not always translate.

No on-disk WAL, and what follows

Because Aurora does not write WAL to a conventional WAL directory, several PostgreSQL features that depend on that mechanism either do not exist or operate differently:

pg_basebackup is not usable in the traditional sense for getting a physical copy of an Aurora database. Logical export via pg_dump is the supported path.

Physical streaming replication to an external, non-Aurora PostgreSQL replica is not available. Logical replication (publications/subscriptions, or AWS Database Migration Service) is the path out of Aurora if you need to go elsewhere.

Tools that parse WAL for CDC (pgoutput-based CDC tools, wal2json, Debezium) operate via logical replication on Aurora. This is often fine. It is not identical to the way these tools work on a stock PostgreSQL server, and the set of operations captured by logical replication has well-known limitations (DDL is not replicated, large-object operations require specific handling, etc.). These limitations are PostgreSQL’s, not Aurora’s, but they are relevant because logical replication is the only out-of-cluster replication path.

Vacuum and bloat

Vacuum runs on the writer and writes through redo to the storage layer like any other operation. The storage layer takes care of the I/O, but the vacuum work itself (identifying dead tuples, clearing index entries, updating visibility maps) happens in the writer process. This means a vacuum-heavy workload is bounded by the writer’s CPU and memory, not by the storage throughput.

The practical consequence is that undersizing the writer on a workload with heavy update or delete activity produces the same bloat-and-vacuum-chasing problems it would on any PostgreSQL server. Aurora’s distributed storage is not a vacuum solvent; it is storage.

Cache warmup and failover

Because readers share storage with the writer, a failover is fast in the sense of “the new writer binds to the storage volume quickly.” It is not instant in the sense of “the new writer has a warm cache.” The promoted reader’s buffer cache was populated for its read workload, not the writer’s; queries that depended on specific pages being hot may experience latency spikes immediately after failover while the cache refills from the storage layer.

This is not a problem in most applications. It is a problem in applications that have tight latency budgets at the p99 or p999 level, and it is worth knowing about before the first unplanned failover in production.

Replication to and from Aurora

Getting data into Aurora from a non-Aurora PostgreSQL source is supported via:

  • AWS Database Migration Service (DMS), which supports ongoing logical replication and cutover patterns.
  • pg_dump / pg_restore.
  • Logical replication (publication on the source, subscription on Aurora): supported with version-appropriate considerations.

Getting data out of Aurora to a non-Aurora PostgreSQL target uses the same mechanisms in reverse, with the caveat that physical replication is not an option. Migrating off Aurora is not impossible; it is not as simple as pg_basebackup and streaming replication, either. This is worth knowing at the start of the relationship with Aurora, not at the end.

Extension support and the allow-list

Aurora’s extension allow-list differs from RDS’s. Some extensions available on one are not on the other, and the set on both changes with each minor-version release. The pg_hint_plan status, for example, has historically been different between RDS and Aurora.

Custom extensions (the RDS “Trusted Language Extensions” / pg_tle path) are available on Aurora, with the same constraints: the extension must be expressible in a procedural language that RDS/Aurora permits, and C extensions that require loading shared libraries are not permitted outside the allow-list.

Failover behavior on cluster-level changes

Many cluster-level parameter changes, and some minor-version upgrades, require a cluster restart. Aurora’s “rolling” behavior for these changes (whether readers are restarted independently, whether there is an implicit failover to avoid writer downtime) is feature-specific and occasionally surprising. The documentation is precise; the intuition from RDS, where the same parameter groups apply and the failover semantics are similar but not identical, is not always reliable.

Monitoring beyond Performance Insights

Aurora exposes additional metrics beyond what RDS provides: storage-layer metrics, redo throughput, quorum health. These are available in CloudWatch under Aurora-specific metric namespaces. An organization that is going to operate Aurora at scale will want to ingest these into the same observability stack the rest of the fleet lives in, rather than relying solely on the AWS console.

The parameter-group model

Same as RDS. Parameter groups are immutable copies assigned to a cluster; modifying a parameter group modifies the group, and the cluster applies those changes on next reboot (for static parameters) or immediately (for dynamic parameters). The subset of PostgreSQL parameters that are settable is smaller than in a stock install. The subset that are static-only (requiring a reboot, for example) is larger.

Positives

Aurora is an exceptionally good fit for workloads that match its architectural assumptions:

Read-heavy workloads with horizontal reader scaling. Up to fifteen readers sharing the same storage volume, with replica lag in the tens of milliseconds, is a more natural scale-out than the three-reader Multi-AZ DB Cluster on RDS, and the readers are cheaper per-unit than provisioning full additional instances with independent storage.

Failover speed. Typical failover under 30 seconds for a cluster with at least one reader is better than RDS’s typical 60–120 seconds for Multi-AZ single-standby. For latency-sensitive production systems, this is meaningful.

Clone-based workflows. Copy-on-write cloning is a real operational advantage for any team that needs fresh production-like data for test or analysis. The fact that the clone is fast and does not double storage cost until divergence is the thing that makes it actually useful in practice, rather than theoretically useful.

Storage elasticity. Storage grows automatically in 10 GB increments up to the cluster maximum, and is billed on actual consumption in standard mode. There is no provision-for-peak problem.

Backup semantics. Continuous backup with point-in-time restore is the default, not a thing to configure. Restores are fast relative to the size of the database.

Integration with AWS. Performance Insights, Enhanced Monitoring, IAM authentication, KMS encryption, VPC networking, Blue/Green Deployments, AWS Backup: all present, all integrated, all managed from the same console as the rest of the AWS estate.

Global Database for cross-region durability. Storage-layer cross-region replication is simpler and faster than the streaming-replication-based alternatives, for workloads where sub-second cross-region lag is acceptable.

Negatives

Version lag. Aurora’s support for new PostgreSQL major versions lags community PostgreSQL, often by many months. Teams with a “must be on the newest version” constraint will be disappointed.

Vendor lock-in. Migrating onto Aurora is supported. Migrating off is supported but narrower: logical replication and pg_dump are the tools, and neither is as operationally convenient as the physical-replication paths available in community PostgreSQL. A multi-year commitment to Aurora is a commitment to leaving through one of those two doors if the time comes.

Extension allow-list. The allow-list is shorter than community PostgreSQL’s universe of extensions. Workloads that require specific extensions such as PostGIS, TimescaleDB, pg_cron in its full form, recent versions of pgvector, or any custom C extension should verify availability and version before committing.

Parameter constraints. The subset of postgresql.conf settings that are user-tunable is smaller than in a stock install. Some performance knobs that a DBA would reach for on a self-hosted server are not available.

No true SUPERUSER. Same as RDS. If a workload requires operations that only a true superuser can perform )certain maintenance tooling, direct filesystem interaction, ad-hoc COPY ... FROM PROGRAM) Aurora will not permit them.

Per-instance compute sizing still matters. The distributed storage does not eliminate the fact that PostgreSQL’s planner, executor, and autovacuum workers live on a single writer. Undersizing the writer is still possible.

Cost model is complicated. Even setting aside the dollar amounts, the matrix of provisioned vs. serverless, standard vs. I/O-Optimized, plus per-region data transfer, backup storage, Global Database replication, and Performance Insights retention, makes forecasting a cost-per-workload nontrivial. Teams often underestimate the effective monthly bill at the time they commit.

“Aurora-specific” operational knowledge. The storage architecture is different enough from stock PostgreSQL that debugging some performance issues requires understanding the Aurora storage model, CloudWatch Aurora-specific metrics, and the nuances of the redo-based replication. A team staffed entirely with PostgreSQL generalists will have a learning curve.

Write-forwarding semantics. Global Database write forwarding is an attractive feature that has enough consistency-mode subtleties to be the source of production bugs in applications that do not read the documentation carefully.

Best-fit workloads and organizations

Read-heavy OLTP applications with fifteen-or-fewer reader instances’ worth of scale, where replica lag in the tens of milliseconds is acceptable. This is Aurora’s sweet spot.

SaaS products with multi-tenant scaling that can benefit from fast cloning for customer-specific environments or blue-green cutover patterns.

Applications with strict AZ-failure tolerance requirements where the six-copy three-AZ durability model provides a clear and auditable answer to “what happens if an Availability Zone is lost.”

Multi-region workloads that need active-active or hot-standby cross-region behavior, where Global Database’s sub-second replication is acceptable and write-forwarding’s consistency modes have been properly evaluated.

Organizations deeply invested in the AWS ecosystem: (IAM, VPC, CloudWatch, Secrets Manager, KMS) for which “another AWS service” is the lowest-friction way to add a database.

Poor fits

Workloads requiring extensions not on the Aurora allow-list, particularly vendor-focused extensions such as TimescaleDB or Citus. Aurora is not the product for these.*

Organizations that want to be on the newest PostgreSQL major version. Aurora will be behind.

Write-heavy workloads that don’t benefit from reader scale-out. The distributed storage doesn’t make a single writer faster; it distributes durability. A workload that is bottlenecked on writer CPU, writer memory, or write-hot-spot contention will see similar numbers on Aurora and on a well-tuned RDS Multi-AZ instance of comparable size.

Workloads requiring physical replication or filesystem access. Aurora does not offer a path to these.

Organizations with a strong “portability” requirement. The Aurora fork and the Aurora storage layer are AWS-specific. Leaving requires logical migration.

Next in this series: Google Cloud SQL for PostgreSQL, Google’s more conventional managed-PostgreSQL offering. After the Aurora architecture, it will read like a return to first principles.