PostgreSQL 18: The 5 Features That Actually Matter for Production
Every major PostgreSQL release gets a changelog the length of a novel. Most of it won't change how you run your database. PostgreSQL 18 is different — a handful of features here genuinely reshape what production looks like.
I've been testing PG18 since the September 2025 release. Here are the five changes that actually matter if you're running PostgreSQL in production.
1. Async I/O — The One That Changes Everything
PostgreSQL has always read data synchronously. One page at a time, blocking until the disk responds. On local NVMe, that's fast enough. On cloud storage — AWS EBS, Azure Managed Disks, GCP Persistent Disks — every read carries 1–5ms of network latency. Multiply that by millions of pages, and you're watching queries crawl.
PG18 introduces a new async I/O subsystem with three modes:
| Mode | How It Works | Best For |
|---|---|---|
sync | Legacy behavior (same as PG17) | Troubleshooting, baselines |
worker (default) | Dedicated I/O worker processes handle reads in parallel | Cloud storage, general production |
io_uring | Linux kernel ring buffers — zero syscall overhead | Local NVMe, latency-sensitive workloads |
The default effective_io_concurrency jumped from 1 to 16. That alone tells you how much untapped parallelism existed in the I/O path.
Benchmarks from PlanetScale on 300GB datasets show the worker mode outperforming PG17 on network-attached storage across the board. Cold cache reads with io_uring dropped from 15 seconds to 5.7 seconds — roughly a 3x improvement. Even warm cache scenarios saw 20–25% gains.
The catch: if your working set fits in shared_buffers and your buffer cache hit rate is above 99%, you won't notice a difference. This feature shines when PostgreSQL actually hits disk.
2. UUIDv7 — Finally, Sortable UUIDs in Core
If you've been using UUIDv4 as a primary key, you've been paying a hidden tax. UUIDv4 is random, which means every INSERT scatters across the B-tree index — random I/O, page splits, index bloat.
PG18 adds uuidv7() as a built-in function. UUIDv7 packs a millisecond-precision timestamp into the first 48 bits, making new IDs monotonically increasing. Inserts hit the right edge of the B-tree like auto-increment, but you keep global uniqueness for distributed systems.
-- Generate a time-sorted UUID
SELECT uuidv7();
-- a0194e1c-0a1b-7d42-9e85-3f8a2b7c1d4e
-- Extract the embedded timestamp
SELECT uuid_extract_timestamp(uuidv7());
-- 2026-03-25 09:14:22.123+00
No extensions needed. No uuid-ossp, no pgcrypto. It's in core. You can even query time ranges directly on the primary key — no separate created_at column required.
3. Preserved Planner Stats Through pg_upgrade
This one doesn't sound exciting until you've lived through a major version upgrade on a large database.
Before PG18, pg_upgrade wiped all planner statistics. The moment you switched over, the optimizer knew nothing about your data distribution. Query plans went haywire. You had to run ANALYZE on every table before performance recovered — which on a database with hundreds of tables could take hours.
PG18 preserves pg_class and pg_stats data through upgrades. The optimizer starts with the same knowledge it had before. Internally, pg_dump --statistics-only extracts the metadata (adding only seconds to the upgrade), and new restore functions import it into the fresh cluster.
Even better: it works retroactively when upgrading from PG14, 15, 16, or 17 to PG18.
# After upgrade, only regenerate the stats that couldn't be preserved
vacuumdb --all --analyze-in-stages --missing-stats-only
The dangerous "performance cliff" after major upgrades is gone.
4. B-tree Skip Scan — Fewer Indexes, More Flexibility
Before PG18, composite B-tree indexes enforced the leftmost prefix rule. An index on (status, created_at) was useless if you only queried by created_at.
Skip scan changes this. If the leading column has low cardinality — say 4 distinct status values — PostgreSQL now generates dynamic equality constraints and runs targeted probes for each value. An index on (status, created_at) now serves queries filtering only by created_at.
The numbers from pgedge benchmarks on selective queries: execution time dropped from 94ms (sequential scan) to 0.2ms (skip scan with 12 buffer reads instead of 10,384).
The limitation: if the skipped column has thousands of distinct values, skip scan won't kick in — the planner correctly recognizes that thousands of index probes would be worse than a sequential scan. It's cost-based, automatic, and requires no query changes.
The practical impact: you need fewer indexes. One composite index can now serve query patterns that previously required separate indexes.
5. OAuth 2.0 Authentication — SSO for Your Database
PostgreSQL 18 adds native OAuth 2.0 via the SASL OAUTHBEARER mechanism. Instead of passwords, clients present bearer tokens from your identity provider.
# pg_hba.conf
host myapp all 0.0.0.0/0 oauth issuer=https://keycloak.example.com/realms/company scope="openid profile"
It works with any OpenID Connect provider — Keycloak, Azure AD, Auth0, Google. The flow supports device authorization for interactive clients like psql, and token-based authentication for applications.
For teams already running centralized identity management (and if you followed our Keycloak + Nextcloud SSO guide, you're halfway there), this eliminates password management for database connections entirely. Same IdP, same MFA policies, same audit trail.
Note: this requires PostgreSQL compiled with --with-libcurl. Most distributions are still catching up, so check your build before planning a migration.
Honorable Mentions
A few more changes worth knowing about:
- Virtual generated columns are now the default (computed on read, zero storage overhead)
- Data checksums enabled by default on new clusters — silent corruption detection out of the box
- SIMD-optimized JSON escaping — 2–4x faster for JSON-heavy workloads using SSE2/NEON/AVX-512
- Wire protocol v3.2 — first update since 2003, adds 256-bit cancel request keys for security hardening
OLD/NEWin RETURNING — track before/after values in a single UPDATE statement
Running PostgreSQL 18 on Elestio
If you want to try PG18 without managing the upgrade yourself, PostgreSQL on Elestio gives you a managed instance starting at ~$16/month on NVMe storage — which happens to be where the async I/O improvements shine most. Automated backups, SSL, and monitoring included.
For existing PG17 users: the preserved planner statistics feature alone makes the upgrade to PG18 the smoothest major version jump PostgreSQL has ever had.
Thanks for reading. See you in the next one 👋