
PostgreSQL 18.4 landed on May 14, 2026, and with four patch releases under its belt, the 18.x branch is unambiguously production-ready. Three features in this release deserve your immediate attention: asynchronous I/O, virtual generated columns, and the built-in uuidv7() function. Each one directly affects how you write schemas and configure your database — and two of them can be adopted with a single line change. If you’re still on PostgreSQL 17, this is the post that makes the case for upgrading before PG17 hits end-of-life in November 2026.
uuidv7() — Finally, a Built-In That Doesn’t Fragment Your Indexes
Until PostgreSQL 18, gen_random_uuid() was the go-to for UUID primary keys. It works, but it has a well-known cost: random UUIDs scatter inserts across the B-tree index, causing page splits and write amplification at scale. The fix existed in userland — the uuid-ossp extension, custom trigger functions, application-level generation — but none of it was ergonomic.
PostgreSQL 18 introduces uuidv7() as a native built-in, implementing UUID version 7 per RFC 9562. The first 48 bits encode a millisecond-precision timestamp, which means UUIDv7 values are monotonically increasing within the same millisecond. The practical result: sequential inserts, minimal B-tree fragmentation, and CRUD performance equivalent to SERIAL or GENERATED AS IDENTITY — with none of the coordination overhead.
-- Before: random UUID, random index fragmentation
id uuid DEFAULT gen_random_uuid() PRIMARY KEY
-- After: time-ordered UUID, sequential inserts
id uuid DEFAULT uuidv7() PRIMARY KEY
That’s a one-line change in your migration. Existing tables can be updated with ALTER TABLE events ALTER COLUMN id SET DEFAULT uuidv7(); for new rows. PostgreSQL 18 also ships uuid_extract_timestamp(), which pulls the embedded timestamp from a UUIDv7 value — useful for debugging and partition routing without a separate created_at column. Better Stack’s UUIDv7 guide covers the performance comparison in detail.
Async I/O — 2–3x Read Throughput, Zero Application Code Changes
The headline feature of PostgreSQL 18 is a rewritten I/O subsystem. Prior to this release, PostgreSQL read data synchronously: one request, wait for completion, next request. Modern NVMe storage can handle dozens of concurrent I/O requests, and PostgreSQL was leaving most of that capacity unused. The new asynchronous I/O (AIO) subsystem issues multiple read requests in parallel, letting the database exploit the hardware it’s actually running on.
Benchmarks show up to 2–3x throughput improvement for read-heavy workloads, with P99 latency dropping 30–50% on NVMe-backed systems. Sequential scans, bitmap heap scans, and VACUUM all benefit. Writes and WAL operations are not yet covered — this is a reads-first implementation — but for analytics, reporting, and any I/O-bound query workload, the gains are real. pganalyze’s deep dive on async I/O is the best technical reference available.
The configuration change is minimal. PostgreSQL 18 introduces the io_method parameter with three options:
# postgresql.conf
io_method = worker # default — safe, works on all platforms
io_method = io_uring # Linux only, requires build with --with-liburing
io_method = sync # old behavior — do not use unless you have a reason
The default, worker, uses dedicated background processes for I/O and is the safe choice on all platforms. The io_uring option interacts directly with the Linux kernel’s ring buffer interface and is faster still, but requires a PostgreSQL build with --with-liburing. AWS RDS and most managed providers currently ship with the worker backend. One important note: io_method requires a server restart to change.
Virtual Generated Columns — Computed Fields Without the Storage Bill
PostgreSQL has supported generated columns since version 12, but only in STORED form — values computed at write time and saved to disk. That covers index-heavy use cases well, but there’s a large category of derived fields you query occasionally and have no need to persist: computed totals, display strings, date extractions from timestamps. Forcing those into stored columns wastes disk space and inflates table bloat.
PostgreSQL 18 adds VIRTUAL generated columns, which compute their value at query time with no disk footprint. As of PG18, VIRTUAL is the default — if you write a generated column without specifying STORED, it’s virtual. Neon’s reference on virtual generated columns documents the full syntax and constraints.
CREATE TABLE orders (
id uuid DEFAULT uuidv7() PRIMARY KEY,
qty integer NOT NULL,
unit_price numeric(10,2) NOT NULL,
subtotal numeric GENERATED ALWAYS AS (qty * unit_price) VIRTUAL,
created_at timestamptz DEFAULT now(),
order_date date GENERATED ALWAYS AS (created_at::date) VIRTUAL
);
The trade-off is predictable: virtual columns cannot be directly indexed. If you need to filter or sort on a derived field frequently, use STORED and build an index on it. Use VIRTUAL for everything else — it keeps your schema honest without the storage cost. You can add virtual columns to existing tables with ALTER TABLE orders ADD COLUMN subtotal numeric GENERATED ALWAYS AS (qty * unit_price) VIRTUAL;
What to Actually Do Today
If you’re running PostgreSQL 17 or earlier, here’s a concrete checklist:
- Swap UUID defaults. For any new tables, use
uuidv7()instead ofgen_random_uuid(). For existing high-write tables, evaluate whether a migration makes sense — the index fragmentation problem compounds over time. - Enable async I/O. If you upgraded to PG18, it’s already on (
io_method = workeris the default). If you’re on a custom build withsync, switch toworkerand restart. - Audit your generated columns. If you added
STOREDcolumns as workarounds before PG18, check whether they actually need to be stored. Switch low-frequency derived fields toVIRTUAL. - Plan your upgrade. PostgreSQL 17 reaches end-of-life in November 2026. The pg_upgrade tool now preserves optimizer statistics, making the process faster than previous major upgrades. See the official PostgreSQL 18 release announcement for the full feature list.
PostgreSQL 18 is not a glamorous release. It doesn’t ship a new query language or a paradigm shift. What it ships is three features that quietly fix persistent annoyances and unlock performance you already paid for in hardware. That’s the kind of release you actually deploy.













