PostgreSQL Field Guide

PostgreSQL 18 features and upgrade notes

PostgreSQL 18 (GA 2025-09) highlights — asynchronous I/O, uuidv7(), virtual generated columns, B-tree skip scan, RETURNING OLD/NEW — with upgrade notes

PostgreSQL 18 reached GA on 2025-09-25 and is supported until 2030-11-14. As of this review date the current minor is 18.4; see the version and support policy for the live support snapshot. Every feature claim below is checked against the PostgreSQL 18 release notes.

Asynchronous I/O (AIO)

PostgreSQL 18 is the first release with a real asynchronous I/O subsystem, building on the streaming I/O framework introduced in PostgreSQL 17. I/O can be issued without blocking the backend on each request, which benefits sequential scans, vacuum, and bitmap heap scans.

The behavior is controlled by io_method:

  • sync — traditional synchronous I/O;
  • worker — background I/O worker processes; this is the default, chosen for broad compatibility;
  • io_uring — Linux io_uring, available on supported kernels.

The PostgreSQL 18 announcement reports benchmark gains of up to 3× in certain scenarios. Treat that as an upper bound, not an expectation: measure your own workload before and after switching io_method, and move from worker to io_uring only after testing.

Native uuidv7()

SELECT uuidv7();

UUID v7 embeds a 48-bit millisecond timestamp, so generated values are roughly time-ordered. Compared with random v4 UUIDs, time-ordered keys insert into the right edge of a B-tree, which keeps primary-key indexes compact and cache-friendly under high write rates. New tables can adopt it directly:

CREATE TABLE events (
  id uuid PRIMARY KEY DEFAULT uuidv7()
);

Virtual generated columns

ALTER TABLE orders ADD COLUMN
  total numeric GENERATED ALWAYS AS (qty * price) VIRTUAL;

Before PostgreSQL 18, generated columns were always STORED — computed at write time and occupying disk. PostgreSQL 18 adds VIRTUAL generated columns, computed when the row is read and occupying no storage, and makes VIRTUAL the default; write-time behavior remains available through the explicit STORED keyword.

B-tree skip scan

A multicolumn B-tree index on (a, b) used to require a predicate on the leading column a to be useful. PostgreSQL 18 can perform skip scans: the executor iterates over the distinct values of a and descends into each b range, so queries that filter only on a non-leading column can still use the index. Some indexes that existed only to cover the second column may now be redundant — verify with EXPLAIN (ANALYZE, BUFFERS) on real data before dropping anything.

RETURNING OLD / NEW

INSERT, UPDATE, DELETE, and MERGE can now return both the old and new row versions explicitly:

UPDATE products SET price = price * 1.1
RETURNING OLD.price AS prev_price, NEW.price AS new_price;

This replaces the common CTE or self-join workarounds for capturing before/after values in one statement.

Also in PostgreSQL 18

  • EXPLAIN ANALYZE includes BUFFERS output automatically, so buffer statistics no longer need an explicit option in the common case.
  • COPY FROM gained REJECT_LIMIT to bound how many invalid rows an ON_ERROR ignore copy may discard before failing. (ON_ERROR ignore itself was added in PostgreSQL 17.)
  • pg_stat_io reports I/O in bytes and adds WAL I/O rows; correspondingly, the read/sync columns were removed from pg_stat_wal — update dashboards that referenced them.
  • pg_upgrade now retains optimizer statistics, shortening the window of degraded plans right after a major upgrade.

PostgreSQL 18 does not include built-in TDE

Transparent Data Encryption was proposed during the PostgreSQL 18 cycle but was not merged, and PostgreSQL 18 ships without built-in page-level encryption. Encryption at rest still requires filesystem or disk-level encryption (for example LUKS), storage-level features, or a vendor distribution that provides TDE. Do not plan around a core TDE feature arriving in this version.

Upgrade notes for PostgreSQL 18

  • A major upgrade requires pg_upgrade, dump/restore, or logical replication; the data directory is not compatible across majors.
  • initdb now enables data checksums by default. Because pg_upgrade requires matching checksum settings, the new --no-data-checksums initdb option exists for upgrading old clusters that were initialized without checksums.
  • MD5 password authentication is deprecated and ALTER ROLE emits warnings when setting MD5 passwords; plan the move to SCRAM rather than suppressing the warnings.
  • As noted above, pg_stat_wal lost its read/sync columns to pg_stat_io; adjust monitoring collectors.
  • Verify extension, driver, pool, and backup-tool support for 18 individually; pg_upgrade cannot prove third-party modules are compatible.

Version path

Sources

Last updated on

On this page