Aurora PostgreSQL 16.15 / 17.11 / 18.6: idle connections use 2× memory, and new connections use 3× CPU
This is a report of isolated tests on new, empty Amazon Aurora PostgreSQL clusters, run on 2026-10-22 in eu-west-1.
The engine release of 2026-09-29 (16.15, 17.11, 18.6) changes the cost of each client connection compared with the previous minor versions:
- Memory: each idle connection costs about 3.5–4 MiB more.
- The memory is allocated when the backend starts and stays in use until the backend exits.
- It does not show in
pg_backend_memory_contexts.
- CPU: setting up a new TLS connection costs about 3× more server CPU (≈ 30 ms instead of ≈ 9 ms).
- Latency and capacity:
- Opening a connection takes about 20 ms longer.
- A db.r8g.xlarge is CPU-bound at about 134 new connections/s, against about 390/s on 18.4.
RDS for PostgreSQL and community PostgreSQL of the same major versions do not show the memory increase.
Each cluster had:
- One writer instance, db.r8g.xlarge (4 vCPU, 32 GiB), unless stated otherwise.
- Default cluster and instance parameter groups (
default.aurora-postgresqlNN). - Storage encryption on, Enhanced Monitoring at 1–5 s, everything else default.
Settings that affect memory were the same on every Aurora version:
max_connections= 3449max_worker_processes= 8max_wal_senders= 20shared_buffers= 2681446 (8 kB pages)huge_pages= on
All client connections:
- use TLS (
sslmode=require); - connect as the master user to the
postgresdatabase; - run only
SELECT 1.
Method (memtest.ts):
- Measure CloudWatch
FreeableMemoryfor 4 minutes. - Open 300 connections and run
SELECT 1on each one once a minute. - Hold them for 8 minutes.
- Close them and measure for another 4 minutes.
The result is (average before − average during) / 300. Repeated runs agreed within ±0.3 MiB.
| Engine | MiB per idle connection |
|---|---|
| Aurora 14.17 | 2.7 |
| Aurora 16.8 | 3.0 |
| Aurora 16.15 | 6.0 |
| Aurora 17.4 | 3.1 |
| Aurora 17.5 | 3.3 |
| Aurora 17.6 | 3.4 |
| Aurora 17.7 | 3.4 |
| Aurora 17.9 | 3.5 |
| Aurora 17.10 | 3.4 |
| Aurora 17.11 | 6.9 |
| Aurora 18.4 | 3.4–3.6 |
| Aurora 18.6 | 7.3 |
| RDS for PostgreSQL 16.15 (db.r8g.large, default parameter group) | 2.0 |
| RDS for PostgreSQL 17.11 (db.r8g.large, default parameter group) | 2.0 |
| RDS for PostgreSQL 18.6 (db.r8g.large, default parameter group) | 2.1 |
Community PostgreSQL 16.9 / 17.8 / 18.6 (Docker, huge_pages=off, delta of cgroup anon) |
1.6 / 1.5 / 1.7 |
| Change | MiB per connection |
|---|---|
Aurora 16.15 with rds.enable_memory_management = 0 |
6.2 |
| Aurora 18.6, ≈ 3,750 backend slots (max_connections 3470, max_worker_processes 150, max_wal_senders 110) | 7.3 |
| Aurora 18.6, ≈ 1,050 backend slots (max_connections 1000, max_worker_processes 20, max_wal_senders 10) | 7.1 |
These are the pg_backend_memory_contexts totals of a fresh backend:
| Version | Memory contexts | Cost per connection (Test 1) |
|---|---|---|
| 17.10 | 2,545 kB | 3.4 MiB |
| 17.11 | 2,617 kB | 6.9 MiB |
| 18.4 | 3,231 kB | 3.4–3.6 MiB |
| 18.6 | 3,303 kB | 7.3 MiB |
The contexts are almost the same on the good and the bad versions.
| 16.8 | 17.11 / 18.6 | |
|---|---|---|
inactive growth per 300 connections |
+0.75 GB | +1.9 GB |
| Page tables growth | +80–110 MB on all versions |
The test cluster had 32 GiB of RAM and 8 GiB of swap. It added idle connections in steps of 100. After each step it ran SELECT 1 on every connection.
| Connections | FreeableMemory | Swap used | Note |
|---|---|---|---|
| 0 | 7.7 GB | 0 | |
| 600 | 3.4 GB | 0 | |
| 1,000 | 0.97 GB | 0 | |
| 1,300 | 0.3–0.5 GB | 1.9 GB | swap-in ≈ 0 |
| 1,500 | 0.37 GB | 4.2 GB | swap-in ≈ 0–80 kB/s |
| 2,000 | 0.33 GB | – | all connections alive |
| 2,100 | – | – | one backend crashed; the postmaster terminated all backends (57P02) |
Second run with 1,300 connections:
| Phase | Swap used | Swap-in | Latency p50 / p99 |
|---|---|---|---|
| Idle, 4 min | 1.84 → 1.91 GB | 0–28 kB/s | – |
SELECT 1 on every connection |
2.39 GB | 1 kB/s | 31 / 131 ms (client outside the VPC) |
Catalog query (pg_class ⋈ pg_attribute …) on every connection |
3.30 GB | 13 kB/s | 39 / 271 ms |
| Close 500 connections | 3.38 → 1.21 GB | short 3.3 MB/s, then 0 | – |
Results:
- Backends that run queries do not page the swapped memory back in.
- Closing backends frees their swapped pages: about 4.3 MiB per closed connection.
Setup (conn.ts):
- Two new clusters, 18.4 and 18.6, both db.r8g.xlarge.
- The client was a pod in the same VPC. Query round-trip time on an open connection: 0.5 ms on both.
- Sequential connects alternated between the two clusters, 8 × 100 each.
- Storms ran for 60 s each, one cluster at a time.
- CPU numbers come from Enhanced Monitoring (1 s).
| 18.4 | 18.6 | |
|---|---|---|
| TLS connect + first query, sequential, n=800 (p50 / p90 / p99) | 13.3 / 14.6 / 16.6 ms | 33.1 / 34.9 / 37.1 ms |
| Storm, 20 concurrent clients | 378 conn/s, CPU avg 81 % | 134 conn/s, CPU 100 % |
| Storm, 60 concurrent clients | 395 conn/s, CPU avg 89 % | 134 conn/s, CPU 100 % |
| Server CPU per new connection (CPU × 4 vCPU / rate) | ≈ 9 ms | ≈ 30 ms |
| Idle CPU | 3–4 % | 3–4 % |
These items appear in the release notes of 17.11 (and of the 16.15 and 18.6 releases of the same date):
- "Added a new column,
worker_threads_cpu_usage_percent, to theaurora_stat_resource_usage()function. This column reports the CPU usage percentage of a backend process's worker threads." - "Fixed an issue that could cause a database instance to restart due to a memory management issue in Aurora Storage connection handling."
Worker threads started in every backend would match all three results:
- thread stacks or per-thread malloc arenas that are touched once and never used again;
- higher CPU and latency to set up a connection.
This is an inference. Nothing in the documentation confirms it.
The scripts use Bun and the postgres driver.
memtest.tsalso needs the AWS CLI. It reads FreeableMemory from CloudWatch.conn.tsonly needs network access to both clusters.