Short answer: sometimes, and only for reads. PostgreSQL 18 added an asynchronous I/O subsystem that sequential scans, bitmap heap scans and vacuum use to read ahead. The default, io_method = worker, is already on after an upgrade. In this lab it made a cold sequential scan about a third faster, did nothing measurable for a bitmap heap scan, and io_uring did the opposite. Which one helps depends on the query and the storage, so measure before you change it.
The lab used PostgreSQL 18.6 in Docker on a laptop with a local SSD, with parallel query off and the page cache dropped before every run. The script and full output are public.
What changed in PostgreSQL 18
Before 18, a backend asked the operating system for one 8 kB page at a time and waited for each read, with some prefetch hints for bitmap scans. From 18, eligible reads are issued ahead of time and the backend only waits if the data has not arrived when it needs it. Three settings control it:
io_method:worker(default) hands reads to background I/O worker processes;io_uringuses the Linux kernel interface and needs a build with liburing;syncbehaves like PostgreSQL 17. Changing it needs a restart.io_workers: number of I/O workers whenio_method = worker. Default 3.effective_io_concurrency: how many reads a session keeps in flight. The default rose from 1 to 16 in 18.
Writes, index scans and WAL are still synchronous in 18. If your slow statements are index lookups or commits, async I/O will not change them.
Results: three methods, two query shapes
An events table of 8 million rows (1.2 GB) and a 256 MB shared_buffers, so every run read from storage. Five cold runs per setting, execution time in milliseconds. sync ran first and again last to check the baseline did not drift:
| io_method | Sequential scan, 5 runs | Median | Bitmap heap scan, 5 runs | Median |
|---|---|---|---|---|
sync | 402 404 378 375 465 | 402 | 348 345 346 514 459 | 348 |
worker | 263 287 267 267 259 | 267 | 365 345 325 330 334 | 334 |
io_uring | 329 330 328 398 327 | 329 | 271 259 268 292 259 | 268 |
sync again | 445 496 383 373 466 | 445 | 338 366 333 346 339 | 339 |
For the sequential scan, worker was 34–40% faster than sync and io_uring 18–26%. For the bitmap heap scan, worker was within noise of sync and io_uring was about 22% faster. The plans show where the time went: I/O Timings is time the backend spent waiting for reads.
-- Sequential scan, 153,847 pages read
io_method=sync I/O Timings: shared read=160.178 Execution Time: 392.861 ms
io_method=worker I/O Timings: shared read=24.159 Execution Time: 254.596 ms
io_method=io_uring I/O Timings: shared read=36.071 Execution Time: 328.108 ms
-- Bitmap heap scan, 151,644 pages read
io_method=sync I/O Timings: shared read=184.557 Execution Time: 350.437 ms
io_method=worker I/O Timings: shared read=218.860 Execution Time: 336.057 ms
io_method=io_uring I/O Timings: shared read=86.969 Execution Time: 285.155 msWith worker, the sequential scan waited for reads for 24 ms instead of 160 ms because the reads were already done when it reached each page. The bitmap scan's wait barely changed with worker but halved with io_uring. I did not investigate why the two methods split this way; the point is that they did, on the same data and the same machine.
Raising effective_io_concurrency from 16 to 64 or 256 made no consistent difference to the bitmap scan here. That query read 98% of the table's pages in physical order, which is close to a sequential read. A bitmap scan that jumps across a large table on network storage is where prefetch depth should matter more.
Why your numbers will differ
This laptop is close to the worst case for async I/O. Docker Desktop runs Linux in a VM, and dropping the Linux page cache does not clear the macOS cache underneath, so "cold" reads still returned in microseconds. Async I/O helps most when each read takes long, which is the normal case on network-attached storage such as EBS. So the gain on a real server can be larger, smaller or zero, and the only number that matters is yours. On RDS and Aurora, check which of these parameters your parameter group exposes before planning around them; I have not tested them there.
How to see async I/O working
I/O workers are ordinary backend processes, and in-flight reads are visible in the new pg_aios view. Sampled during a cold sequential scan with io_method = worker:
SELECT count(*), string_agg(DISTINCT operation || '/' || state, ', ') FROM pg_aios;
11 | readv/COMPLETED_SHARED, readv/SUBMITTED
SELECT backend_type, count(*) FROM pg_stat_activity GROUP BY 1;
io worker | 3
client backend | 1
...Around ten reads were in flight at any moment. If pg_aios stays empty while your slow query runs, that query is not using async reads and io_method is not your lever.
Should you change io_method?
- Leave the default first.
workerworks everywhere and gave the largest sequential-scan gain here. - Find out whether reads are your problem. Rank statements in
pg_stat_statementsby total time, then compareshared_blk_read_timewith total execution time. If read wait is a small share, no I/O setting will help much. - Check the plan shape. Only sequential scans, bitmap heap scans and vacuum use async reads in 18. Look for them with
EXPLAIN (ANALYZE, BUFFERS), which in 18 shows buffers by default. - Test
io_uringon a copy. It needs Linux, a liburing build and a kernel and container runtime that allow it. Docker's default seccomp profile blocks it; this lab used--privileged. - Compare on the real storage at real concurrency. Same statements, same data, cold and warm, at each setting. Under load, 3 I/O workers may become the queue; raise
io_workersand measure again.
PostgreSQL 19 reached release candidate 1 on 15 October 2026. Check its release notes before tuning io_workers by hand on a new cluster. For the measurement method itself, see how to profile slow PostgreSQL queries, and if you are upgrading from 14, the PostgreSQL 14 upgrade checklist.
Frequently asked questions
What is io_method in PostgreSQL 18?
It selects how PostgreSQL 18 performs asynchronous reads: worker hands them to background I/O worker processes, io_uring uses the Linux io_uring interface, and sync performs them synchronously as PostgreSQL 17 did.
What is the default io_method?
worker, with io_workers set to 3. A cluster upgraded to PostgreSQL 18 uses asynchronous reads without any configuration change.
Does asynchronous I/O make every query faster?
No. In PostgreSQL 18 it applies to reads by sequential scans, bitmap heap scans and vacuum. Index scans, writes and WAL are still synchronous. In the lab, worker cut a cold sequential scan by 34 to 40 percent but did not measurably change a bitmap heap scan.
Should I use io_uring instead of worker?
Only after measuring. io_uring needs Linux, a PostgreSQL build with liburing, and a kernel and container runtime that allow it; Docker's default seccomp profile blocks it. In the lab it was about 22 percent faster than sync on a bitmap heap scan but slower than worker on a sequential scan.
Does changing io_method need a restart?
Yes. io_method can only be set at server start. effective_io_concurrency can be changed per session.
What is the default effective_io_concurrency in PostgreSQL 18?
16, up from 1 in earlier versions. In the lab, raising it to 64 or 256 made no consistent difference to a bitmap scan that read 98 percent of the table in physical order.
How can I tell whether a query uses asynchronous I/O?
Query the pg_aios view while the statement runs; in-flight reads appear there. With io_method set to worker, pg_stat_activity also shows io worker processes. If pg_aios stays empty during your slow query, io_method is not the setting that will help it.
Can I change io_method on RDS or Aurora?
Check whether your parameter group exposes it before planning around it. This guide's lab ran self-managed PostgreSQL 18.6 and did not test RDS or Aurora.