PostgreSQL 18 brings a significant advancement in how the database handles disk operations: asynchronous I/O (AIO).
Until now, PostgreSQL relied on synchronous reads, meaning each backend process would issue a disk request and then wait until the operating system returned the result. On fast SSDs, cloud block storage, or large-scale analytic workloads, this model wastes CPU cycles and limits throughput.
With PostgreSQL 18, reads can now be done asynchronously — the database issues requests, continues execution, and consumes data as it arrives. This change enables parallelism at the I/O layer, making queries, scans, and VACUUM operations faster and more efficient.
In this post, we’ll cover:
- The new configuration parameters that control async I/O.
- SQL examples to observe and tune these parameters.
- Performance scenarios where async I/O shines.
New Parameters for Asynchronous I/O
PostgreSQL 18 introduces several new parameters (and adjusts defaults of existing ones) to give you control over the new I/O subsystem. Let’s look at each one.
1. io_method
Purpose: Selects how PostgreSQL executes I/O.
- Options:
- sync - Legacy synchronous I/O (no async benefits).
- worker - Uses PostgreSQL I/O worker processes.
- io_uring - Uses Linux io_uring API (if available).
- Default: worker
SHOW io_method;
-- Switch to synchronous I/O (requires restart)
ALTER SYSTEM SET io_method = 'sync';
If your system supports it, try io_uring for lower latency. Otherwise, worker is the default choice.
2. io_workers
Purpose: Number of worker processes used when io_method = worker.
SHOW io_workers;
-- Increase to 6 workers
ALTER SYSTEM SET io_workers = 6;
SELECT pg_reload_conf();
More workers can help on busy systems with many concurrent scans.
3. io_combine_limit
Purpose: Maximum size of adjacent blocks combined into one I/O request.
- Unit: 8kB blocks
- Range: 1–128
- Default: 16 (128kB)
SHOW io_combine_limit;
-- Increase to 32 blocks (256kB)
ALTER SYSTEM SET io_combine_limit = 32;
Larger values improve sequential scans by batching reads, but may waste resources on small queries.
4. io_max_combine_limit
Purpose: Server-wide ceiling for io_combine_limit.
- Default: 16 (128kB)
- Context: Requires restart.
SHOW io_max_combine_limit;
-- Clamp limit at 64 blocks
ALTER SYSTEM SET io_max_combine_limit = 64;
Prevents sessions from setting overly high combine limits.
5. io_max_concurrency
Purpose: Maximum number of outstanding I/O requests a single process can issue at once.
- Range: -1 (auto) to 1024
- Default: -1 (based on shared_buffers)
SHOW io_max_concurrency;
-- Manually set concurrency to 128
ALTER SYSTEM SET io_max_concurrency = 128;
Higher values allow more overlap of reads but can overwhelm the storage system.
6. Related Parameters
PostgreSQL 18 also raised defaults for concurrency:
- effective_io_concurrency - Used in queries (bitmap scans, parallel reads).
Default: 16.
- maintenance_io_concurrency - Used in maintenance tasks (VACUUM).
Default: 16.
SHOW effective_io_concurrency;
SHOW maintenance_io_concurrency;
Performance Examples
Now let’s see how these parameters translate into performance benefits.
Example 1: Large Sequential Scan
-- Create a large test table (1M rows)
CREATE TABLE big_table AS
SELECT i, md5(random()::text)
FROM generate_series(1, 1000000) s(i);
CHECKPOINT;
DISCARD ALL;
-- Run scan with synchronous I/O
ALTER SYSTEM SET io_method = 'sync';
– edit the postgres.conf and restart the postgres server
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM big_table;
-- Run scan with worker async I/O
ALTER SYSTEM SET io_method = 'worker';
– edit the postgres.conf and restart the postgres server
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM big_table;
We can get 5 – 15% faster runtimes with async I/O, especially on SSDs.
Example 2: VACUUM on Large Table
-- Run with sync I/O
ALTER SYSTEM SET io_method = 'sync';
– edit the postgres.conf and restart the postgres server
VACUUM VERBOSE big_table;
-- Run with worker I/O
ALTER SYSTEM SET io_method = 'worker';
– edit the postgres.conf and restart the postgres server
VACUUM VERBOSE big_table;
Async I/O shortens VACUUM duration by overlapping page reads.
Tuning Tips
- Stick with defaults first (io_method = worker, io_workers = 3).
- Try io_uring if your Linux system supports it.
- Increase io_combine_limit for sequential workloads, but avoid extremes.
- Keep io_max_concurrency at -1 unless careful testing proves otherwise.
- Monitor via pg_stat_io and new views (pg_aios) to see I/O behavior.
Conclusion
Asynchronous I/O in PostgreSQL 18 is a game-changer for workloads that depend on disk performance. By letting the database overlap reads with query execution, it delivers faster scans, shorter VACUUM times, and better utilization of modern SSDs and cloud storage.
With the new parameters — io_method, io_workers, io_combine_limit, io_max_combine_limit, and io_max_concurrency — you now have fine-grained control over how PostgreSQL performs I/O. Combine that with practical tuning and benchmarking, and you can unlock significant performance gains in your database.
PostgreSQL 18 isn’t just faster — it’s smarter about I/O.