tech, developers, and the code underneath

issue 107· review·

Postgres 18's async I/O, four months in

The biggest storage-layer change in years is in production now. Here's what it actually did to real workloads.

Postgres 18 shipped with an asynchronous I/O subsystem — the largest change to how Postgres talks to storage in a very long time. Enough people have run it in production now to say something more useful than "it is fast."

what it does#

Historically Postgres read from disk synchronously: a backend process issues a read, blocks, gets the page, continues. On a spinning disk that was fine — the disk was the bottleneck and you could not do better. On modern NVMe, where the device can service many requests in parallel and a single-threaded synchronous reader leaves most of the device idle, it wasted a great deal of available throughput.

Postgres 18 adds an I/O method abstraction:

io_method = sync          # the old behavior
io_method = worker        # dedicated I/O worker processes (default)
io_method = io_uring      # Linux io_uring, if built with support

Sequential scans, bitmap heap scans, and vacuum use it. Other paths do not yet; this is an incremental rollout across releases.

what people are seeing#

The consistent pattern from the reports I trust:

Large sequential scans on fast NVMe: substantial improvement. Analytical queries scanning a large table are the clearest win. This is exactly what you would predict — the workload was read-parallel and the code was not.

Vacuum: meaningfully faster. This matters more than it sounds. Vacuum being slow is the root of a large class of Postgres operational problems, and anything that makes it finish sooner reduces bloat pressure.

OLTP with a good cache hit rate: little change. If your working set is in shared buffers, you were not doing I/O, so making I/O faster does nothing. Most transactional workloads are in this category and should not expect much.

Cloud block storage: mixed. Network-attached storage has enough latency and enough of its own queueing that the gains are smaller and less predictable than on local NVMe. Test rather than assume.

the tuning notes#

Two settings matter and the defaults are conservative:

io_combine_limit = 128kB        # how much adjacent I/O to merge into one request
effective_io_concurrency = 16   # how many concurrent reads to issue

effective_io_concurrency has historically been set based on spindle count, which is meaningless on NVMe. For a modern SSD, values well above the old defaults are appropriate. Measure — the curve flattens and then degrades, and where it turns depends on your device.

If you are on Linux with a recent kernel and can build with io_uring support, it generally outperforms the worker method by a modest margin. The worker method is the safe default and it is genuinely good.

the other things in 18 worth knowing#

Skip scan for B-tree indexes. A multi-column index on (a, b) can now be used for a query filtering only on b, when a has low cardinality. This eliminates a category of redundant index that people have been maintaining for years.

uuidv7() built in. Time-ordered UUIDs, natively. If you use UUIDs as primary keys, v7 dramatically improves index locality compared to v4, which scatters inserts across the whole B-tree. This is a real performance issue that a lot of applications have and do not know about.

OAuth authentication support, which matters for anyone trying to eliminate static database passwords.

Faster major-version upgrades, with statistics preserved across pg_upgrade. Previously you finished an upgrade with no statistics and a database that planned terribly until you ran ANALYZE across everything. That was a genuine outage risk and it is fixed.

should you upgrade#

Yes, on the normal schedule — after a point release or two, with a tested rollback, having read the release notes for the incompatibilities.

The async I/O is the headline and for most transactional workloads the statistics preservation on upgrade and uuidv7() will matter more day to day.

As always with Postgres: the release is well-tested, the migration is usually boring, and the people who get burned are the ones who skipped four major versions and tried to do it in one jump.

Dom, January 15, 2026

get README in your inbox

One dispatch, no noise. Tech and developer news, plus the occasional long piece on the craft.

subscribe →