IntermediateReferenceDatabases

PostgreSQL 19 asynchronous I/O - worker autoscaling and read-ahead

What PostgreSQL 19 changes in the asynchronous I/O subsystem that arrived in 18 - I/O workers that scale themselves between io_min_workers and io_max_workers, better read-ahead scheduling for large requests, and the EXPLAIN IO output that shows it working.

Applies to
PostgreSQL19PostgreSQL 19 was still in beta at the last review (Beta 4, 24 September 2026 is the latest pre-release) - the GUC names and defaults are from the beta documentation and can still shift before GA. Comparisons are against PostgreSQL 18, where asynchronous I/O first shipped.
emergingUpdated Reviewed

PostgreSQL 19 asynchronous I/O - worker autoscaling and read-ahead

Asynchronous I/O arrived in PostgreSQL 18: instead of a backend issuing one read and blocking on it, scans submit a stream of prefetch requests that the kernel or a pool of I/O workers services in parallel. 18 shipped it with a fixed pool - io_workers workers, no more, no fewer, whatever the load. 19 makes the pool elastic, improves how large requests are scheduled, and adds the EXPLAIN output that lets you see the machinery.

The autoscaling worker pool

io_method still defaults to worker, but the fixed io_workers count is gone. Four new GUCs govern the pool:

GUC Default What it does
io_min_workers 2 Floor of the I/O worker pool
io_max_workers 8 Ceiling of the I/O worker pool
io_worker_idle_timeout 1min Idle workers are retired after this
io_worker_launch_interval 100ms New workers cannot be launched more often than this

The shape of the trade-off is the classic autoscaler: io_min_workers guarantees a warm pool so the first prefetch burst does not wait on process launch; io_max_workers caps how much process and memory overhead a busy cluster spends on I/O; io_worker_launch_interval stops a spiky workload from thrashing the process table. On a host that shared its io_workers between queries and vacuum, the ceiling now decides how hard vacuum’s streaming reads can push in parallel.

Figure 1. The elastic worker pool
Figure 1. The elastic worker poolBackends submit prefetch requests to a shared queue; an autoscaling pool of I/O workers drains it against storage, sized between io_min_workers and io_max_workers.backend 1prefetchbackend 2prefetchI/O queueread-aheadworker poolmin 2, max 8storage
The pool replaces the fixed io_workers count of 18: it grows under load up to io_max_workers, shrinks back when workers idle past io_worker_idle_timeout, and cannot launch faster than io_worker_launch_interval.
Text equivalent
  1. Scan nodes submit read-ahead requests to the I/O queue instead of blocking on each read.
  2. The worker pool drains the queue, scaling between io_min_workers (2) and io_max_workers (8).
  3. Idle workers retire after io_worker_idle_timeout (1min); launches throttle to io_worker_launch_interval (100ms).
  4. The EXPLAIN IO option shows the queue depth, request counts, waits and concurrency per scan.

The release notes also list improved read-ahead scheduling for large requests - the scheduler that decides how far ahead of the scan prefetch runs. The beta documentation does not expose a knob for it; it is a correctness-of-throughput change you observe through EXPLAIN (ANALYZE, IO) rather than tune.

Seeing it: the EXPLAIN IO option

EXPLAIN gains an IO option that reports, per scan node that provides the information: the prefetch queue’s average and maximum distance and its capacity, plus issued I/O requests - total count, average request size, number of I/O waits, and the average number of concurrent requests.

EXPLAIN (ANALYZE, IO, BUFFERS) SELECT count(*) FROM events WHERE ts > now() - interval '7 days';

Read it with BUFFERS: read blocks tell you what the scan asked for, the IO block tells you how far the prefetcher ran ahead of the consumption point and how often the scan had to wait. auto_explain gains a matching auto_explain.log_io option that adds the same reporting to logged plans, and auto_explain.log_extension_options lets modules such as pg_plan_advice attach their own EXPLAIN options to auto_explain output.

What changed, and in which release

Behaviour PG 18 PG 19
I/O worker count Fixed at io_workers (default 3) Autoscaled between io_min_workers (2) and io_max_workers (8)
Read-ahead scheduling Basic prefetch ordering Improved scheduling for large requests (no new knob)
EXPLAIN I/O visibility Buffer counts only IO option: queue depth, request counts and sizes, waits, concurrency
auto_explain No I/O reporting auto_explain.log_io
COPY FROM parsing Byte-wise text/CSV parsing SIMD CPU instructions

COPY FROM parsing belongs on this page rather than the SQL one because the speedup is in the input path: text and CSV input now use SIMD CPU instructions, which matters most on the bulk loads the I/O subsystem also serves.

Where it breaks

  • Assuming io_workers still exists - the GUC is replaced by the min/max pair; a tuned postgresql.conf line for it is an unknown-setting error on 19, not a silent miss.
  • Judging the pool by process count - idle workers retire after io_worker_idle_timeout, so a quiet ps listing is expected, not a sign the subsystem is off.
  • Reading IO without ANALYZE - the option reports issued requests, which exist only when the plan actually runs.
  • Beta caveat - GUC names and defaults are from the beta documentation.

What this is not for

  • Diagnosing non-I/O waits - IO reports prefetch and request behaviour; lock waits, client waits and CPU time need pg_stat_activity and pg_stat_io, covered in monitoring.
  • Replacing pg_stat_io capacity planning - the per-backend EXPLAIN view is a microscope for one query; the cumulative view is the long-term picture.

PostgreSQL release support

5 supported
ReleaseLatestReleasedEnd of lifeStatus
1818.6Sept 2025Nov 2030Supported
1717.11Sept 2024Nov 2029Supported
1616.15Sept 2023Nov 2028Supported
1515.19Oct 2022Nov 2027Supported
1414.24Sept 2021Nov 2026Supported
1313.23Sept 2020Nov 2025End of life
1212.22Oct 2019Nov 2024End of life
1111.22Oct 2018Nov 2023End of life

Data as of 2026-10-01 from endoflife.date. Refresh with node scripts/fetch-versions.mjs.

What to watch

Specific things that would change what this page concludes.

  • Whether io_method gains a native io_uring method on Linux before or after GA - the release notes document the autoscaling, not a new method

Where PostgreSQL sits

How this component relates to the rest of the stack. Items without a link are not covered here yet.

Depends on

Must be present for this to run

Deployed with

Commonly run alongside

Alternatives

Solves a similar problem differently