给高性能查询引擎换上更先进的底层 I/O 接口,往往会带来令人尴尬的翻车现场。
分析引擎 Conviva 在一台 192 核、750 GB 内存的高规格物理机上,处理数万亿条时序事件。原先架构基于 Rust、DataFusion 和 Arrow IPC 构建,用 Linux 原生内存映射机制 mmap 负责磁盘数据读取。随着并发查询拉高,这套组合出现了严重的延迟雪崩,团队决定换上在业界备受追捧的 Linux 异步 I/O 新宠 io_uring。
测试结果却给团队泼了一盆冷水。在 12 并发真实查询中,未配置足量缓存的 io_uring 方案,平均查询耗时从 4<容器 mmap 基准的 38.7 秒恶化到 87.8 秒,p95 延迟更是直接飙到了 148 秒。
技术选型中迷信先进接口,往往会掩盖真实瓶颈所在。
隐式共享之困:mmap 是如何拖垮 192 核服务器的
Arrow IPC 格式的物理排布与内存完全一致,天生支持指针级反序列化,通过 mmap 映射文件看似是最优雅的零拷贝方案。但在重度高并发的 OLAP 分析场景下,mmap 依赖的是 Linux 内核全局托管的虚拟内存与 Page Cache。
当并发查询压满内存时,单机常驻内存(RSS)一路飙升至 734 GB,占整机内存的 98.91%。内核为了自保被迫启动激进的全局页面驱逐。这一驱逐立刻引发连锁反应,被驱逐的数据页瞬间又被并发线程访问,导致每秒出现高达 1,352 次从磁盘重读的 major faults,以及每秒超 235 万次 minor faults。
频繁的缺页异常伴随着海量的原子操作和 TLB shootdown,直接冲垮了 CPU 的 L1 与 L2 缓存。在 192 个核心上,各线程陷入严重的内核锁与虚拟内存结构(VMA)竞争。
底层由 32 块 NVMe 固态硬盘组装的 RAID-0 存储阵列,其实际 fio 带宽极限高达 21.7 GB/s。但在 mmap 的层层锁互斥与缺页阻滞下,峰值读带宽仅能挤出 3.44 GB/s,硬件潜能被浪费了 84%。
击穿系统的往往不是存储物理上限,而是操作系统黑盒代管的隐式状态。
换装翻车:io_uring 并非开箱即用的银弹
眼见内核 Page Cache 成为瓶颈,工程师自然想到绕过内核页缓存。但把 mmap 换成 io_uring 后遭遇性能腰斩,原因在于混淆了系统架构的边界。
根据 Linux 规范,io_uring 解决的是系统调用的异步排队与提交机制,而绕过 Page Cache 靠的是底层打开文件时的 O_DIRECT 标志。如果只引入 io_uring 与 O_DIRECT,却缺乏精密的应用层缓存机制,每次数据读取都必须落到物理磁盘上。在 Arrow 列式存储的多列查询中,失去预读与页缓存的磁盘随机小块读取,会迅速引发严重的 I/O 放大。
没有内存缓冲池支撑的 Direct I/O,无异于拆除水库直接挑水灌溉。
从初期的 87.8 秒惨败,到最终缩短至 19.8 秒,中间隔着极重的系统级工程重构:
- 采用 O_DIRECT 旁路掉全局 Page Cache,杜绝内核级缺页异常;
- 搭建严格满足硬件物理要求的 4 KiB 对齐内存池,避免数据从内核向用户空间反复拷贝;
- 构建专属的 I/O 调度线程架构,将异步 I/O 提交与 CPU 密集型的 Arrow 列数据反序列化解耦;
- 在应用层实现大容量显式缓存与精准的主动预取,将驱逐权从操作系统收回。
这一套重构完成之后,平均查询延迟降至 19.8 秒(降幅约 49%),p95 延迟从 118 秒压至 48.3 秒(降幅约 59%),原本沉睡的 32 盘 NVMe 阵列吞吐终于被榨干到近 20 GB/s。
- 结论.单纯更换系统调用无法解决架构层面的争用,必须重构包括内存对齐、线程调度与应用层缓冲池在内的整套上下文。
边界与审视:重写操作系统不是通用解法
Conviva 的逆风翻盘是一次底层工程的标杆级实践,但这类成功往往带有极其严苛的先决条件。
在通用 DataFusion 或开源 OLAP 查询引擎中,计算瓶颈往往集中在 Parquet 文件的元数据解析、行组过滤、字典解码以及谓词计算上,底层块设备的 I/O 速率很少成为核心短板。而在基于云原生对象存储的架构里,网络传输开销与 HTTP 范围请求构成了主要耗时,下沉到底层去折腾 Direct I/O 与内存对齐无异于缘木求鱼。
- 风险.如果文件格式非预先排布或运行于云存储之上,绕过内核 Page Cache 会立刻失去操作系统的预读红利,徒增巨额的用户态维护负担。
当硬件存储带宽突破 20 GB/s 的临界点后,通用操作系统的通用分页策略,已经无法满足极端算力对数据流动的控制精度。剥离操作系统代管的隐式特权,在应用内部亲手实现一套严苛的缓冲池管理器,正在成为现代计算引擎重铸性能边界的必经代价。
