PostgreSQL 18: 异步 I/O 子系统

John Doe 九月 9, 2025

PostgreSQL 18 正式版发布在即。那么,你知道该版本最大的亮点在哪吗?

image

异步 I/O 子系统

新增异步 I/O 子系统(Asynchronous I/O,简称 AIO),是 PostgreSQL 18 中一项重要的性能特性。引入异步 I/O 特性的目的是提升 I/O 吞吐量,尤其针对顺序扫描、位图堆扫描与VACUUM操作。在 Linux 系统上使用io_uring时,通过将磁盘访问与数据处理并行执行,该特性可带来 2 至 3 倍的性能提升。

为 PostgreSQL 新增异步 I/O 的主要原因如下:

  1. 提前发起 I/O 请求,减少等待时间:过去,PostgreSQL 的读写操作严重依赖阻塞式 I/O(blocking I/O)。这意味着后端进程发起一个 I/O 调用后,会进入空闲状态,等待操作系统或磁盘响应后才能继续执行其他操作。而异步 I/O 的设计思路是,在实际需要数据之前就提前启动 I/O 操作,让 PostgreSQL 能将 I/O 操作与有效工作(如计算或其他 I/O 请求)并行执行,从而避免空闲时间的浪费。
  2. 支持直接 I/O(Direct I/O,简称 DIO):直接 I/O 指绕过操作系统内核的页面缓存,直接在应用程序与存储设备之间执行 I/O 操作。直接 I/O 可将大部分 I/O 工作转移到硬件层面完成,从而提升吞吐量、降低 CPU 使用率,并减少延迟。此外,该特性还支持通过 GUC 参数设置来配置直接 I/O,下文将详细介绍 GUC 参数io_method

异步 I/O 基础设施支持通过多种方式实现异步 I/O 功能。该特性引入了一个名为io_method的新 GUC 参数,用于控制异步 I/O 实现方式的选择。io_method参数需在postgresql.conf文件中设置,并在服务器启动时生效;若不重启服务器,无法修改该参数;若尝试通过ALTER SYSTEM命令修改,服务器会返回如下错误:

ERROR: parameter "io_method" cannot be changed without restarting the server

io_method参数可设置为以下三个值:

  • sync(同步模式):这是异步 I/O 基础设施刚引入时的默认设置,即传统的阻塞式同步 I/O,行为与 PostgreSQL 17 完全一致。将该参数设为sync时,不会执行任何异步 I/O 操作,仅会跳过为该特性新增的代码。
  • worker(工作进程模式):这是 PostgreSQL 18 的默认设置。该模式使用后台 I/O 工作进程,后端进程将 I/O 请求加入队列,由后台工作进程异步处理读写操作。后台 I/O 工作进程的数量由io_workers参数控制,在操作系统的进程列表中,这些后台工作进程会显示为独立进程。

示例如下:

SHOW io_method;
 io_method 
-----------
 worker
(1 row)

后台 I/O 工作进程的数量由io_workers参数控制,在操作系统的进程列表中可看到这些独立的后台进程:

$ ps ux | grep 'io worker'
redrock    352900  352899  0 16:53 ?        00:00:01 postgres: io worker 0
redrock    352901  352899  0 16:53 ?        00:00:00 postgres: io worker 1
redrock    352902  352899  0 16:53 ?        00:00:00 postgres: io worker 2
SHOW io_workers;
 io_workers
------------
 3
(1 row)
  • io_uring(Linux 专用异步 I/O 模式)io_uring是 Linux 系统特有的现代高性能接口,支持真正的异步 I/O。使用该模式时,PostgreSQL 需在编译时添加--with-liburing选项,且需运行在兼容的内核与文件系统上。io_uring模式无需工作进程,而是通过 PostgreSQL 与内核之间的共享环形缓冲区(shared ring buffer)高效地加入和分发 I/O 请求。

该异步 I/O 模式的系统调用开销极低,尤其在高延迟存储系统上,能显著提升性能,是 Linux 系统上性能最快的异步 I/O 模式。

使用io_uring模式需先配置如下内核参数:

$ echo 0 | sudo tee /proc/sys/kernel/io_uring_disabled

下面,我们来针对三种受支持的io_methodsyncworkerio_uring)进行异步 I/O 测试,并比较下测试结果。测试表明,从syncworker再到io_uring,随着io_method模式的切换,性能呈现出明显的提升趋势。

以下示例展示了如何在三种受支持的io_method模式下使用异步 I/O 特性:

  1. 创建测试表并插入数据
CREATE TABLE aio_test AS
  SELECT generate_series(1, 10000000) AS id,
         repeat('x', 100) AS filler;
  1. 创建索引
CREATE INDEX ON aio_test(id);
  1. 开启 I/O 时间跟踪
ALTER SYSTEM SET track_io_timing = on;
  1. 使用 sync 模式测试
SHOW io_method;
 io_method 
-----------
 sync
(1 row)

EXPLAIN (ANALYZE, TIMING) SELECT count(*) FROM aio_test;
                                                                    QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=225499.55..225499.56 rows=1 width=8) (actual time=573.824..577.110 rows=1.00 loops=1)
   Buffers: shared hit=15927 read=156489
   ->  Gather  (cost=225499.33..225499.54 rows=2 width=8) (actual time=573.674..577.104 rows=3.00 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=15927 read=156489
         ->  Partial Aggregate  (cost=224499.33..224499.34 rows=1 width=8) (actual time=568.825..568.825 rows=1.00 loops=3)
               Buffers: shared hit=15927 read=156489
               ->  Parallel Seq Scan on aio_test  (cost=0.00..214082.67 rows=4166667 width=0) (actual time=0.092..444.480 rows=3333333.33 loops=3)
                     Buffers: shared hit=15927 read=156489
 Planning:
   Buffers: shared hit=16 read=6
 Planning Time: 2.263 ms
 Execution Time: 577.863 ms
  1. 使用 worker 模式测试
SHOW io_method;
 io_method 
-----------
 worker
(1 row)

EXPLAIN (ANALYZE, TIMING) SELECT count(*) FROM aio_test;
                                                                                   QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=212777.59..212777.60 rows=1 width=8) (actual time=431.819..431.967 rows=1.00 loops=1)
   Buffers: shared hit=12 read=27331 written=1
   I/O Timings: shared read=120.329 write=0.017
   ->  Gather  (cost=212777.37..212777.58 rows=2 width=8) (actual time=431.814..431.963 rows=3.00 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=12 read=27331 written=1
         I/O Timings: shared read=120.329 write=0.017
         ->  Partial Aggregate  (cost=211777.37..211777.38 rows=1 width=8) (actual time=426.209..426.209 rows=1.00 loops=3)
               Buffers: shared hit=12 read=27331 written=1
               I/O Timings: shared read=120.329 write=0.017
               ->  Parallel Index Only Scan using aio_test_id_idx on aio_test  (cost=0.43..201360.64 rows=4166691 width=0) (actual time=0.396..333.324 rows=3333333.33 loops=3)
                     Heap Fetches: 0
                    Index Searches: 1
                     Buffers: shared hit=12 read=27331 written=1
                     I/O Timings: shared read=120.329 write=0.017
 Planning:
   Buffers: shared hit=36 read=20 dirtied=1
   I/O Timings: shared read=1.706
 Planning Time: 2.186 ms
 Execution Time: 432.178 ms
  1. 使用 io_uring 模式测试
SHOW io_method;
 io_method 
-----------
 io_uring
(1 row)

EXPLAIN (ANALYZE, TIMING) SELECT count(*) FROM aio_test;
                                                                                   QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Finalize Aggregate  (cost=212777.59..212777.60 rows=1 width=8) (actual time=389.182..389.555 rows=1.00 loops=1)
   Buffers: shared hit=13 read=27331
   I/O Timings: shared read=86.715
   ->  Gather  (cost=212777.37..212777.58 rows=2 width=8) (actual time=388.204..389.549 rows=3.00 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         Buffers: shared hit=13 read=27331
         I/O Timings: shared read=86.715
         ->  Partial Aggregate  (cost=211777.37..211777.38 rows=1 width=8) (actual time=377.915..377.916 rows=1.00 loops=3)
               Buffers: shared hit=13 read=27331
               I/O Timings: shared read=86.715
               ->  Parallel Index Only Scan using aio_test_id_idx on aio_test  (cost=0.43..201360.64 rows=4166691 width=0) (actual time=0.083..276.593 rows=3333333.33 loops=3)
                     Heap Fetches: 0
                     Index Searches: 1
                     Buffers: shared hit=13 read=27331
                     I/O Timings: shared read=86.715
 Planning:
   Buffers: shared hit=36 read=20
   I/O Timings: shared read=0.352
 Planning Time: 0.840 ms
 Execution Time: 389.683 ms

执行计划中的 “I/O Timings: shared” 字段表明,该查询计划已触发异步 I/O 操作。

总结

PostgreSQL 18 中异步 I/O 带来的性能提升,无疑是数据库 I/O 性能领域的一大步飞跃。该特性并非通过简单调整来模拟异步 I/O 功能,而是真正新增了一套异步 I/O 子系统。借助为支持该特性而新增的基础设施,可在 PostgreSQL 的多个模块中实现异步 I/O。该特性允许数据库同时发起多个读取请求,突破了以往的 I/O 瓶颈,使 PostgreSQL 能更充分地利用 CPU 与 I/O 带宽。

新的io_method参数为切换异步 I/O 模式提供了灵活的方式,同时支持通过 GUC 设置配置直接 I/O。在 Linux 系统上,io_uring模式能带来更高的效率,但需注意的是,要使用该模式,需在编译 PostgreSQL 时添加--with-liburing选项。

上面对比三种受支持的io_method模式性能的测试结果表明,worker模式与io_uring模式均能带来性能提升。针对该特性的基准测试显示,对于典型工作负载,磁盘读取吞吐量可提升 2 至 3 倍。此外还需注意,当前异步 I/O 仅支持读取操作,暂不支持写入操作与 WAL(预写日志)操作。

总而言之,PostgreSQL 18 中的异步 I/O 特性为读取性能带来了革命性的飞跃。数据库管理员与开发者通过合理利用workerio_uring选项,并相应地调整可观测性配置与性能调优策略,可实现显著的效率提升。不过,由于该特性目前仍处于测试版阶段,在投入生产环境前,务必在自身环境中进行全面测试。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/da7226993fd4b73d8b40abb7167d124eada97f2e