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 →