io_uring 高性能数据库交互讲解 io_uring for High-Performance DBMSs: When and How to Use It · 分步动画中文解读

Jasny, El-Hindi, Ziegler, Leis, Binnig · PVLDB Vol. 19(1), 2026 · arXiv:2512.04859v2 · 代码与工件

核心结论:直接替换仅 1.06×/1.10×,深度设计可达 2.05×/2.31× Buffer Manager:16.5k → 546.5k tx/s Network Shuffle:饱和 400 Gbit/s PostgreSQL:+11–15%
分步讲解模式:每个动画都支持 播放 / 单步 / 重置;点击“开始全程导览”可自动依次讲完全部 7 课。
/ 单步 空格 播放/暂停 R 重置 17 跳课
数据可信度说明:本页所有数字都带有来源徽标—— 论文原始数据 直接取自论文图/表; 由论文数据计算 由论文数字推算(如加速比); 教学示意 论文未给出量化值,仅为帮助理解而绘制的定性/近似图,不可引用
第 1 课

“直接替换”陷阱:naive io_uring 几乎不提速

论文开篇的核心警告:把 libaio / epoll 原样换成 io_uring,Buffer Manager 只快 1.06×、Network Shuffle 只快 1.10×;只有围绕 io_uring 的能力(批量、异步、Registered Buffers 等)重新设计系统,才能拿到 2.05× / 2.31× 的端到端提升。

传统接口 vs naive io_uring vs 优化后的 io_uring

论文原始数据 图 1(Figure 1):Buffer Manager 173.0 / 183.5 / 376.4 K tx/s;Network Shuffle 12.0 / 13.2 / 30.5 GiB/s;加速比 1.06×、2.05×、1.10×、2.31× 为论文标注。
端到端加速比的定义(论文图 1 的标注方式:相对 naive 替换)
$$S_{\text{BM}}=\frac{376.4}{183.5}\approx 2.05\times \qquad S_{\text{Shuffle}}=\frac{30.5}{13.2}\approx 2.31\times$$
为什么会这样?io_uring 的收益来自摊销(batching 摊销系统调用)与重叠(异步隐藏 I/O 延迟)。如果你的架构仍然是“一次一个 I/O、同步等待”,内核接口换得再新,瓶颈原封不动——这就是后文所有优化的动机。
第 2 课

SQ / CQ 双环形缓冲区:内存映射的零拷贝通道

io_uring 采用完成模型(completion-based,区别于 epoll 的就绪模型):应用把请求写进提交队列 SQ,结果稍后出现在完成队列 CQ。两个环形缓冲区通过 mmap 由用户态与内核共享,提交与完成都不需要额外数据拷贝;完成可以乱序到达,靠用户自定义 ID(user_data)配对。

环形缓冲区状态机(交互动画)

SQ 提交队列(Submission Queue) CQ 完成队列(Completion Queue) tail 尾指针(写入位置) head 头指针(读取位置)

教学示意 动画为机制演示(对应论文图 2、图 3);“共享内存映射、零拷贝、完成可乱序、支持请求链接(request linking)”均为论文 §2.1 原文结论。

批量 = 摊销系统调用

epoll 能一次报告多个就绪事件,但每个 read() 仍需单独系统调用;io_uring 允许先入队多个 SQE,再用一次 io_uring_enter() 提交,完成也可批量收割。论文实测:批量 16 个操作时,每操作 CPU 周期数下降约 5–6×

论文原始数据 §2.1 正文:batch 16 → 每操作周期数降低约 5–6×。

但批量不是越大越好

对 SSD 写延迟的微基准(8 工作线程、1.5 MIOPS 固定吞吐):批量 1 时平均延迟 11.5 µs;批量 128 时冲到 200.9 µs,批量 256 达 317.5 µs。延迟敏感系统必须谨慎调节批量。

论文原始数据 表 2(Table 2),完整数据见数据附录
第 3 课

内核里的三条执行路径:Inline / Poll Set / io_worker

请求进入内核后有三条命运:(2a) Inline Completion 能立刻完成就立即完成(如 socket 已有数据可读);(2b) Poll Set 对可轮询操作挂内部事件处理器 io_async_wake() 非阻塞等待;(2c) io_worker 阻塞回退 对 fsync、大块读等无法异步的操作委派给工作者线程——论文实测该回退平均引入 ~7.3 µs 额外开销。

三条执行路径流程图(点选路径,播放令牌动画)

路径 (2a):最快 路径 (2b):异步等待 路径 (2c):回退,+7.3 µs

论文原始数据 路径语义对应论文图 3;7.3 µs 为 §2.2 微基准(io_worker 处理 NOP 相比 inline 的平均额外开销);SQPoll 线程唤醒延迟 ~30 µs(§2.2 微基准)。流程图为示意画法。

SQPoll:连提交系统调用也省掉

默认模式下应用线程调用 io_uring_enter() 切入内核;SQPoll 模式用一个专用内核线程持续轮询 SQ,提交路径完全没有用户—内核切换。代价:独占一个核;且空闲超时后线程睡眠,唤醒延迟约 30 µs

警惕 io_worker 回退

频繁回退或大量活跃 io_worker 通常意味着 I/O 模式不佳。论文 §3.6 列出三个触发回退的阈值:块大小超过 max_hw_sectors_kb(开启 IOMMU 时可为 128 KiB)、批量请求数超过 nr_requests(裸机 1023 / 云虚拟机 127)、块大小超过 512 KiB(max_segments)。

论文原始数据 §3.6 “Large blocks can backfire”。
第 4 课

Buffer Manager 七步渐进优化:16.5k → 546.5k tx/s

论文在单线程缓冲管理存储引擎(YCSB,100% 均匀更新,约 70% 缺页率,8× Kioxia CM7-R PCIe 5.0 SSD)上逐步叠加优化:批量写回 → 异步 Fiber → 批量读取 → Registered Buffers → NVMe Passthrough → IOPoll → SQPoll,吞吐量从 16.5k 一路爬升到 546.5k tx/s(约 33×)。瓶颈也从 I/O 延迟 转移到 CPU 周期

优化阶梯(分步揭示每一级)

论文原始数据 图 5(Figure 5):16.5 / 16.5 / 19.1 / 173.0(libaio 参照) / 183.5 / 216.6 / 237.8 / 300.5 / 376.4 / 546.5 K tx/s;各步增幅(+14%、+18%、+11%、+20%、+21%、+32%)与 2.05× 标注均出自论文。
① 同步阶段是 I/O 延迟瓶颈 —— 延迟模型(缺页率 rpf=70%,单次读 70 µs + 写 12 µs)论文原始数据
$$T_{\text{sync}}=\frac{1}{r_{pf}\,(t_{r}+t_{w})}=\frac{1}{0.7\times 82\,\mu s}\approx 17.4\,\text{k tx/s}\;\;(\text{实测 }16.5\,\text{k})$$
② 批量写回把写延迟移出关键路径论文原始数据
$$T_{\text{batch}}=\frac{1}{0.7\times 70\,\mu s}\approx 20.4\,\text{k tx/s}\;\;(\text{实测 }19.1\,\text{k})$$
③ 异步 Fiber 之后变成 CPU 瓶颈 —— 周期模型(f=3.7 GHz,ctx=8264,cio=15900 clk)论文原始数据
$$T_{\text{async}}=\frac{f_{clk}}{c_{tx}+r_{pf}\,c_{io}}=\frac{3.7\times10^{9}}{8264+0.7\times15900}\approx190.8\,\text{k}\;\;(\text{实测 }183.5\,\text{k})$$
④ 批量读取把 cio 降到 11100 clk论文原始数据
$$T_{\text{batch-rd}}=\frac{3.7\times10^{9}}{8264+0.7\times11100}\approx 230\,\text{k tx/s}\;\;(\text{实测 }216.6\,\text{k})$$

每一步在做什么?

七步优化的机制与收益(YCSB 单线程;SQPoll 占用第二个核)
步骤机制吞吐 (K tx/s)本步收益
Posix / io_uring 同步基线每次 I/O 都阻塞等待,受设备延迟支配16.5
① +BatchEvict 批量写回淘汰时积攒多个脏页,一次 io_uring_enter 批量写回,摊销写延迟19.1+14%
② +Fibers 异步事务Boost.fiber 协作式调度,缺页时让出 CPU 跑别的事务,隐藏 I/O 延迟183.5≈9.6×
③ +BatchSubmit 批量读取自适应批量:聚合多个 Fiber 的读请求再一次提交,降低每 I/O 周期数216.6+18%
④ +RegBufs 注册缓冲缓冲池一次性注册钉住,内核 DMA 直写用户内存,免逐请求页钉住与拷贝237.8+11%
⑤ +Passthru NVMe 直通OP_URING_CMD 直接下发原生 NVMe 命令,绕过通用存储栈300.5+20%
⑥ +IOPoll 完成轮询直接从 NVMe 队列轮询完成事件,取代中断(仅支持 O_DIRECT 块设备)376.4+21%
⑦ +SQPoll 提交轮询专用内核线程轮询 SQ,提交免系统调用(多花一个核)546.5+32%
论文原始数据 图 5 与 §3.3–3.4 正文;“≈9.6×” 为 183.5/19.1 的算术结果(由论文数据计算)。
工作负载决定上限:同样的优化在 TPC-C 上效果迥异——内存内 TPC-C(计算密集)从 naive 到满配只提升约 4%(60.2k→62.6k),甚至 IOPoll/SQPoll 因空转略有回退;内存外 TPC-C 则提升 55%(21.2k→32.9k),且 naive io_uring 相比 vmcache 高达 12.5×。详见图表总览的热力表。
第 5 课

Network Shuffle:ring-per-thread 与 zero-copy 饱和 400 Gbit/s

第二个案例是 6 节点集群(ConnectX-7,每节点 400 Gbit/s 双向,对分带宽 4.8 Tbit/s)上的分布式哈希连接数据洗牌。论文拒绝“专用 I/O 线程 + 阻塞 I/O”的老架构,采用 ring-per-thread:每个工作线程持有线程本地 io_uring,计算与收发在同一线程内重叠;再用 zero-copy send / receive 消除内核—用户拷贝,最终用 16 个工作线程就饱和 400 Gbit/s 链路

架构与带宽(分步动画)

epoll 基线 naive io_uring io_uring + zero-copy 等优化 链路物理上限

论文原始数据 带宽 12.0 / 13.2 / 30.5 GiB/s(图 1);“zero-copy send+receive 用 16 个工作线程饱和 400 Gbit/s(4 KiB 元组)”、“归一化内存带宽降低约一半(≈2×)”、“相对 naive epoll 最高 2.5×”分别出自 §4.4、图 12、图 13。架构图为示意画法。
链路带宽换算:400 Gbit/s 单向极限由论文数据计算
$$400\,\text{Gbit/s}=\frac{400}{8}\,\text{GB/s}=50\,\text{GB/s}\approx 46.6\,\text{GiB/s}$$
zero-copy 的收益本质:每单位网络吞吐消耗的内存带宽论文原始数据
$$\frac{BW_{\text{mem}}^{(\text{zc})}}{BW_{\text{net}}}\;\approx\;\frac{1}{2}\cdot\frac{BW_{\text{mem}}^{(\text{copy})}}{BW_{\text{net}}}\quad(\text{发送与接收两个方向的拷贝均被消除})$$
小元组是另一回事:64 B 元组时哈希表随机插入主导瓶颈,zero-copy receive 几乎不再带来额外收益——论文强调网络路径优化只在内存带宽成为瓶颈的高吞吐场景收益最大。此外调优网络栈(qdisc、socket buffer)本身就让 100 GiB 双向传输从 6.3 s 降到 4.6 s(−25% 以上);消息大小约 1 KiB 是 zero-copy 是否划算的阈值,超过约 13 KiB 后单发接收反而优于 multishot。
第 6 课

任务调度三种模式:Default / CoopTR / DeferTR 的 IPI 抢占差异

异步操作完成时,内核要运行 task_work 把完成事件放进 CQ。默认模式下任何用户—内核切换都会处理它,线程忙时内核甚至发 IPI(处理器间中断)强行抢占——破坏缓存局部性、增加抖动。CoopTR(COOP_TASKRUN)减少 IPI,但包括 malloc() 在内的任何系统调用仍会触发处理。DeferTR(DEFER_TASKRUN)只在 io_uring_enter() 时运行 task_work,论文推荐并全程使用。

三种模式的抢占行为(时间线动画)

应用正在计算(如 join/scan) IPI 抢占(task_work 强制执行) 系统调用点顺带处理 task_work io_uring_enter 主动收割

教学示意 时间线与下图中断频率为定性示意:论文 §2.2 只给出三种模式的行为描述与“DeferTR 为推荐模式”的结论,未提供 IPI 频率的量化测量

相对 IPI / 抢占频率对比(定性)

教学示意 柱高为定性等级(高 / 低 / ≈0),非测量值。论文依据:§2.2 “CoopTR reduces IPIs”“DeferTR … eliminates unwanted preemptions”。
工程建议(论文 §5.1 GL3):推荐 DeferTR + single issuer 获得可预测的执行与受控的完成收割;SQPoll 在“轮询核的成本可被摊销、延迟或 IOPS 目标值得”时使用;通过保证 fsync 与大 I/O 也能完全异步来避免 io_worker 回退。
第 7 课

PostgreSQL 案例:四条原则换来 11–15% 额外加速

论文把两条案例研究提炼成四条实践原则,应用到 PostgreSQL 18 新加入的 io_uring 后端上做验证。尽管受多进程架构与文件系统依赖的约束,仍在其 io_uring 基线之上获得 11–15% 的扫描吞吐提升(摘要记为 14%)。

四条原则(GL1–GL4)与在 PostgreSQL 中的落地

GL1–GL4:原则 → PostgreSQL 现状 → 采取的改造(点“播放/单步”逐行揭示)
原则内容PostgreSQL 中的应用
GL1 先确认 I/O 是瓶颈I/O 占比小(CPU 密集/常驻内存)时 io_uring 收益有限;用延迟/周期模型预估PG18 的 io_uring 后端在 I/O 密集负载上已比同步设计快至 3× → 瓶颈确认
GL2 架构对齐 io_uring 能力异步重叠计算与 I/O、批量摊销、ring-per-threadPG 后端进程已批量异步读写;但多进程共享 ring 阻碍 DeferTR,文件系统阻碍 Passthrough
GL3 审慎选择执行模式首选 DeferTR+single issuer;SQPoll 视成本;避免 io_worker 回退改用 CoopTR(次优),新增 SQPoll(多后端共享一个内核线程);fsync 走传统调用不进 ring
GL4 启用适配的优化项Registered Buffers、IOPoll、zero-copy 等按负载选择整个缓冲池注册为 fixed buffers(+4–6%);ext4 上开 IOPoll(至 +7.5%);叠加 SQPoll 合计 +11–15%
论文原始数据 §5.1–5.2:3×(PG18 官方结果转述)、+4–6%、至 +7.5%、+11–15%(图 17 及正文);摘要:14%。

PostgreSQL 优化前后对比

工作负载:1–8 个后端进程对 32 GiB 冷表做直接 I/O 扫描(论文未用 TPC-C 测 PostgreSQL,此处为其扫描实验)
配置说明相对上游 PG18 io_uring 基线
上游 PG18 io_uring 后端(基线)异步 I/O + OS readahead;I/O 密集负载下已比旧同步设计快至 3×1.00×
+ Fixed Buffers缓冲池整体注册,替代“4 个在途 I/O 后才 IO_ASYNC”的启发式+4–6%
+ IOPoll(ext4)轮询完成事件取代中断至 +7.5%
+ SQPoll(共享内核线程)多后端共享一个 SQPoll 线程,性能损失可忽略合计 +11–15%(摘要:14%)
论文原始数据 §5.2 与图 17。注意:论文对 PostgreSQL 的评估是表扫描而非 TPC-C;TPC-C 出现在缓冲管理器实验中(见下表)。

(参照)论文缓冲管理器的 TPC-C 结果

TPC-C 单线程吞吐(K tx/s):mostly in-memory(1 仓库)vs mostly out-of-memory(100 仓库)
配置内存内内存外
vmcache(阻塞读基线)35.52.0
libaio + Fibers59.219.2
io_uring + Fibers(naive)60.221.2
+RegBufs60.723.1
+Passthru62.525.0
+IOPoll61.5 (↓)28.3
+SQPoll62.6 (≈)32.9
论文原始数据 图 6(Figure 6)。内存外:naive io_uring 对 vmcache 达 12.5×,优化后再 +55%;内存内:计算密集,I/O 优化空间很小。
图表

总览:批量衰减曲线与端到端加速热力表

批量大小 vs 每 I/O 的 CPU 周期数(衰减曲线)

绿区:系统调用开销已被充分摊销

教学示意 曲线按论文 §2.1 插图的趋势近似重绘(论文未在正文给出逐点数值);“批量 16 → 每操作周期数降低约 5–6×”为 论文原始数据(§2.1 正文)。

端到端 speedup 热力表(优化级别 × 工作负载)

单元格 = 相对该工作负载 naive io_uring 配置 的端到端加速比(YCSB 基线 183.5k tx/s;TPC-C 内存内 60.2k;TPC-C 内存外 21.2k;Shuffle 13.2 GiB/s)。“—” = 论文未报告该组合。

加速比颜色越深越高;悬停查看绝对值
优化级别\工作负载YCSB
更新密集
(存储)
TPC-C
内存内
(计算密集)
TPC-C
内存外
(I/O 密集)
Shuffle
大元组
(网络)
naive io_uring(基线)
+批量 / 异步架构改造
+Registered Buffers
+NVMe Passthrough(存储)
+IOPoll / 轮询
+SQPoll / +zero-copy 收发
由论文数据计算 加速比由图 5、图 6、图 1 的绝对值相除得到;绝对值本身为 论文原始数据。Shuffle 列为图 1 的 30.5/13.2(图 13 在 16 线程内避免链路饱和的公平对比下最高达 2.5×)。
附录

论文原始数据表

表 1:性能建模所用的 I/O 数值

I/O 延迟与 CPU 周期(Table 1)
项目数值
单次读延迟70 µs
单次写延迟12 µs
事务执行(B 树遍历+更新)8 264 clk
单次读 I/O 处理10 200 clk
批量读 I/O 处理5 400 clk
批量写 I/O 处理5 700 clk
论文原始数据 Table 1。

表 2:批量大小对 SSD 写延迟的影响

8 工作线程、1.5 MIOPS 固定吞吐(Table 2)
批量大小平均延迟 (µs)标准差 σ (µs)
111.51±0.95
824.22±1.71
3260.62±3.91
64116.40±12.17
128200.85±7.47
256317.51±33.88
论文原始数据 Table 2。

实验平台(便于复现)

存储:3.7 GHz AMD 服务器(内核 6.15),8× Kioxia CM7-R PCIe 5.0 NVMe SSD(单盘 2.45M IOPS);单线程缓冲管理器 + 1 GB 缓冲池;YCSB 1000 万元组(8 B 键 + 128 B 值,约 70% 缺页率),TPC-C 1 / 100 仓库。网络:6 节点 ConnectX-7(400 Gbit/s 双向,对分 4.8 Tbit/s),内核 6.17(支持 zero-copy receive),1 MiB 传输块,morsel 驱动并行。

论文原始数据 §3.2、§4.2。大块 I/O 下单核可达 90 GiB/s 读 / 50 GiB/s 写(图 8);多线程下优化项带来 3.4–3.5× 提升并分别以 18(读)/ 6(写)核饱和 SSD 阵列(图 7)。