PostgreSQL 18 shipped asynchronous I/O. The dominant flavor on Linux was io_uring; everything else fell back to a worker pool controlled by io_method=worker. Early benchmarks from pganalyze, Aiven, and Better Stack showed real wins on read-heavy workloads with large sequential scans. They also showed that the worker fallback needed careful tuning — the default worker count did not always keep up with a saturated scan, and cranking it up hurt small-query workloads.
PostgreSQL 19 addresses this in two ways: the worker pool can tune itself, and you can finally see what the I/O layer is doing without guessing.
Self-tuning workers
Four new GUCs show up for io_method=worker:
io_min_workers— floor for the poolio_max_workers— ceiling for the poolio_worker_idle_timeout— how long an idle worker hangs around before being reapedio_worker_launch_interval— how quickly new workers are added under load
Taken together, these let the worker pool behave like an autoscaler: scale up when queries are actually waiting on I/O, scale back down when they are not. The static worker knob you tuned on 18 is no longer the right setting for most workloads.
If you set a worker count by hand on PostgreSQL 18, take it out and benchmark the defaults on 19. The hand-tuned number you picked was right for a specific workload shape, and the self-tuning range handles a broader one. The upper bound still matters — io_max_workers is a hard ceiling, and a machine with eight cores will still suffer if you set it to 32.
Read-ahead for large requests
The read-ahead scheduler was reworked for large I/O requests. The specific change: rather than fire a fixed number of read-ahead requests regardless of request size, the scheduler now scales the prefetch window with the size of the actual scan. In practice, this matters for wide table scans and for index range scans that pull in many heap pages per index entry.
Expect gains on the workloads where PostgreSQL 18 async I/O already helped, and some new gains on workloads where 18 did not quite break even.
EXPLAIN ANALYZE (IO)
EXPLAIN ANALYZE gains an IO option. It reports asynchronous I/O activity per plan node — reads issued, reads completed, bytes transferred. This is the single most useful operational change in the 19 async-I/O work. Before this, diagnosing a plan that was slower than expected meant cross-referencing pg_stat_io with the plan output manually. Now the information lives in the plan.
Use it. Particularly for any workload where you saw uneven results on 18 async I/O: the IO option will tell you whether you are actually getting the overlap you expected, or whether the executor is serializing more than it should.
What to do
Re-benchmark on 19 with defaults. If you hand-tuned the worker count on 18, unset it, and set io_min_workers / io_max_workers only as a floor and ceiling. Turn on EXPLAIN ANALYZE (IO) the next time you are looking at a slow read-heavy plan.
The point of PostgreSQL 18’s async I/O was that reads could stop blocking the executor. The point of PostgreSQL 19’s is that the system can now tune itself sensibly and tell you what it is doing.