在这里插入图片描述

先花 30 秒对齐一个背景:所谓「存算分离」,就是把数据库的计算节点(处理 SQL 的部分)和存储节点(持久化数据的部分)部署在不同的物理机器上,通过高速网络互联。计算节点可以随时加减(这就是 Serverless 弹缩),但数据始终留在存储节点组成的共享池里。好处是弹性好、资源利用率高;代价是每一次 I/O 都要走网络,存储层要同时服务多个计算节点——怎么保证每个租户拿到承诺的 I/O 配额,就成了一个绕不开的治理问题。

Oracle 7 月份在 arXiv 上发了一篇论文 Hardware-Transparent I/O Governance in Disaggregated Heterogeneous Storage,讲的是他们在 Exadata(Oracle 的企业级数据库一体机)的分布式存储层 Exascale 上跑了很多年的一套 I/O 调度方案,叫做 IORM(I/O Resource Management,I/O 资源管理器)。论文本身不新——三个机制拆开看都不算前沿——但有意思的地方在于:它把"为什么这么设计"和"踩过什么坑"写得非常实在,不是那种纯秀肌肉的工程论文。

更关键的是,它回答了一个做存算分离架构的人迟早会撞上的问题:计算侧可以秒级弹缩,存储层的 I/O 治理跟不跟得上?


一个 4% 的性能差异,揭开了存算分离的结构性难题

论文给出了一个很具体的数字。

在 Exadata Exascale 的生产集群里,不同代的存储服务器共存于同一个存储池——这是因为硬件升级是逐台滚动进行的(rolling upgrade),不可能一次性把所有磁盘都换掉,所以新旧硬件会长期共存。具体来说,X9M 这一代(PCIe 4.0 接口,约 230 万 8KB Read IOPS,IOPS 即每秒 I/O 操作次数)和更新的 X10M(PCIe 5.0,约 280 万 IOPS)混在一起。PCIe 是 NVMe SSD 与 CPU 之间的连接标准,每一代带宽大约翻倍,所以同一块 SSD 在新代机器上完成 I/O 的速度明显更快。

给一个租户分配 500K IOPS 的配额(大致相当于一个中大型 OLTP 数据库——在线事务处理,如订单系统——的繁忙工作负载),在 X9M 上消耗了 22% 的设备容量(500K / 2.3M ≈ 22%),在 X10M 上只消耗 18%(500K / 2.8M ≈ 18%)。

差 4 个百分点。在一个有数千租户的大集群里,每个租户 4% 的偏差会累积——一些租户持续多拿、另一些持续少拿,时间长了就是 SLA(对用户承诺的服务水平)投诉。

如果你用的是 Linux 内核中的传统 I/O 调度算法(如 CFQ、BFQ),它们按"设备忙碌的实际时间"(wall-clock time)来分配 I/O 配额,同一份逻辑配额在不同代硬件上会买到不同的实际吞吐——新硬件上的租户"白嫖"了更多 IOPS。这不是 bug,而是时间基调度器的结构性缺陷:它把"这个设备忙了多久"等同于"这个 I/O 花了多少代价",但同样的 I/O 在不同代设备上完成的快慢不同。

这个问题不是 Oracle 独有的。任何做了存算分离并且还在做滚动升级的系统都会碰上——你的存储池里必然长期共存多代 NVMe SSD。

论文把这个叫做「Hardware Gap」。还有第二个问题叫「Distributed Access Skew」——顺序工作负载(比如软件安装、大量虚拟桌面同时开机产生的 VDI 启动风暴)把 I/O 全打到少数几个存储节点上,其余节点空转。最朴素的做法是把全局限额均分到每个节点(论文管这叫 static slicing,静态切片),但这样该租户在热节点上只能拿到 1/N 的配额,其余的闲置在冷节点上。

第三个问题藏在配额执行本身:数据库的 I/O 是突发性的,事务提交时要在亚毫秒内刷一批日志,如果限流器严格按长期配额逐个拦截,这些合法的微突发就会被截断、直接拖慢事务;但如果放任突发,长期配额又形同虚设。怎么在"严格执行长期 SLO"和"放行合法微突发"之间找平衡,是第三个挑战。

这三个问题——硬件异构、访问倾斜、突发容忍——就是这篇论文要解决的,分别对应下面的三层机制。


背景:Serverless 数据库的计算侧弹性已经很好了,存储层呢?

各家的弹性做到什么程度了

先看看 Serverless 数据库在计算侧做到什么程度了。

Aurora Serverless v2 以 0.5 ACU(Aurora Capacity Unit,Aurora 的计算容量单位,包含 CPU、内存和网络资源)为粒度做增量调整,文档原话是 “doesn’t wait for a quiet point”,可以在有开放连接、进行中事务、锁定表的情况下完成 Scale-Up。每秒采集一次指标并做扩缩决策,频繁且细粒度地调整。

Neon 走的是另一条路——每个租户一个独立的 Postgres 实例,空闲时整个计算节点下线,连接进来时从持久化状态重启。他们保留备用虚拟机做快速分配,把冷启动的等待时间压短。

TiDB Serverless 把 S3 作为持久化层,EBS 作为低延迟层,PD(Placement Driver,集群调度器)层用「Request Units」做 per-tenant 的 CPU/IO 节流。

阿里云的几款 Serverless 数据库产品也普遍做到了 5 秒级检测、亚秒级 Scale-Up,按秒计费。

计算侧弹性已经是标配了。下面的所有讨论都集中在存储层——尤其是一个云厂商把多个客户(租户)的数据放在同一批物理存储机器上时,存储层的 I/O 队列和带宽怎么分。不要和计算层混为一谈:Aurora、Neon、TiDB、CockroachDB 的计算侧都已经做了租户隔离,区别只在于存储层怎么隔离。

存储层 I/O 治理远远没有跟上

Neon 的 Noisy Neighbor(吵闹的邻居:一个租户的 I/O 风暴拖累所有共享同一存储的邻居)问题很说明问题。Neon 的架构是多个租户共享同一个 Pageserver(负责存储和提供数据页面的组件),他们的博客里描述过这种场景:一个租户的重写入会导致 “disk I/O queues grow, causing increased latency for all tenants”。计算层隔离了(每个租户一个 Postgres 实例),但存储层一个租户打满 I/O 队列,其他人跟着遭殃。

Aurora 的 I/O 调度对用户完全黑盒。SIGMOD 2017 论文描述了共享存储卷架构(6 副本分布在 3 个可用区的 6 个存储节点上),但所有 I/O 调度(段管理、压缩、复制流量)都封装在存储服务内部。用户能做的只有选 I/O Optimized 还是不选——而根据 AWS 文档,这个选项的差异主要体现在计费口径上,底层调度逻辑对用户不透明。

很多云原生数据库的多租户隔离目前也只做 CPU 层(thread pool 绑定或 cgroup,后者是 Linux 内核给一组进程设定 CPU/内存上限的机制),I/O 隔离依赖底层存储的隐式机制——比如共识协议要求所有副本按相同顺序执行操作,这天然形成了一个全局队列。但队列只保证 I/O 的先后顺序,并不保证每个租户分到多少配额。

IORM 这篇论文恰好填补的就是这个缺口:存储层的 I/O 治理。它不在数据库引擎里,而是在存储节点上运行,解决的是"多个计算节点共享一个分布式存储池时,怎么让每个租户拿到承诺的 I/O 配额"。


多租户 I/O 隔离:Serverless 数据库的四种活法

这个问题值得单独展开,因为它和 Serverless 的关系比看上去更紧密。

Serverless 数据库的成本优势来自"多租户共享基础设施"——把成百上千个小租户的计算和存储塞到同一批物理机器上,按需分配、按需释放。但"共享"的另一面就是"干扰":一个租户的大查询或批量写入,可能把共享存储的 I/O 队列打满,导致其他租户的延迟飙升。这就是 Noisy Neighbor 问题。

CPU 层的隔离相对成熟——cgroup、thread pool 绑定、Request Units 节流,各家都有自己的方案。但 I/O 层的隔离要难得多,因为 I/O 请求从计算节点出发,穿过网络,到达存储节点,路径上的每一跳都可能成为瓶颈,而没有任何一个单点能看到全局视图。

目前市面上的 Serverless 数据库,在 I/O 隔离上大致走了四条路。再次强调:这四条路都是存储层的隔离方案,不是计算层——Aurora、Neon、TiDB、CockroachDB 的计算侧都已经各自独立。

在这里插入图片描述

路线一:卷级隔离——“每户一套独立房产(但地基是共享的)”

Aurora Serverless 是这个路线的代表。它的计算层和 Neon 一样,每个客户有自己的数据库 cluster(writer + reader),不和别人共享进程。关键差别在存储层:每个 Serverless 集群拿到一个独占的逻辑存储卷——数据 6 副本(每个可用区 2 份)分布在 6 个存储节点上,写要 4/6 仲裁、读要 3/6 仲裁。从租户视角看,这个卷是只属于这个客户的,卷级的 I/O 队列不与其他租户共享,租户 A 的 I/O 再大,也不会直接挤占租户 B 的卷。

但要诚实说明:这个"独立存储卷"是逻辑卷。Aurora 自己的 SIGMOD 论文原话是 “a multi-tenant scale-out storage service”——底层存储集群(物理磁盘、网络)是在多个客户之间共享的,跨租户的干扰由 AWS 在存储服务内部用一套不公开的 QoS 机制兜底。所以更准确的说法是"卷级隔离"而非"物理隔离":租户感知到的隔离很强,但物理层的共享是真实存在的,只是对用户黑盒。

这条路的代价是容量利用率低——每个集群都要预留一整套卷的容量,小租户的大量空间是闲置的。AWS 能这么做,是因为规模足够大、闲置成本可以被海量客户摊薄;对体量小一些的云厂商,这条路走不通。

路线二:计算隔离、存储共享——“每户有独立房间,但共用电梯和水管”

Neon 走的是这条路。它的计算层也是每个租户独立的 Postgres 实例(隔离 CPU 和内存),和 Aurora 的"每个客户独占数据库 cluster"在隔离强度上没有本质区别。真正的差别仍在存储层:Neon 不给每个客户一个独占卷,而是多个租户共享同一个 Pageserver。Pageserver 负责从 WAL(Write-Ahead Log,数据库先把修改写入日志、再写入数据文件的机制,用于崩溃恢复)重建数据页面,它的本地 NVMe 作为缓存层,冷数据放到 S3。

这个架构的好处是计算侧做到了强隔离,用他们博客的话说是 “a heavy reporting workload from Tenant A has zero impact on Tenant B”。但存储侧,当一个租户的大量写入导致 Pageserver 的 I/O 队列增长时,共享同一个 Pageserver 的其他租户延迟会跟着上升。Neon 自己也在博客里承认了这个问题。

他们的解法是存储分片(sharding)——把大数据集按 256 MiB 为单位条带化(把数据切成固定大小的条带、交错分布到多台机器上,类似 RAID 0)分散到多个 Pageserver 上,让单个租户的 I/O 压力分散开。这确实缓解了问题,但本质上是把"一个大热点"打散成"多个小热点",并没有在存储层做显式的 per-tenant 配额管理。如果一个租户的热点恰好集中在某个分片上,Noisy Neighbor 仍然会出现。

阿里云的一些 Serverless 数据库产品也在这个光谱上——计算节点通过 thread pool 做 CPU 隔离,但存储层(比如 PolarFS)的 I/O 隔离依赖底层分布式文件系统的隐式机制。ParallelRaft 的有序队列保证了 I/O 顺序的正确性,但"保证顺序"和"保证配额公平"是两码事。

路线三:逻辑隔离——“大家住同一层楼,每人有独立电表,超额就跳闸”

TiDB Serverless 走的是这条路。TiDB 用「Request Units」(RU)统一度量每个租户消耗的 CPU 和 I/O 资源。配额架构是"集中分配、分布执行":PD(Placement Driver)上的全局资源管理器负责给每个租户分配配额,但真正的 per-request RU 记账和限流发生在 TiDB 计算节点的本地令牌桶里,TiKV 存储侧再用优先级调度保证资源组之间的公平。所以 PD 挂了,限流不会立刻失效,只是配额无法再动态重分配。

这个方案的好处是 PD 拥有全局视图——它知道每个租户的资源消耗,可以做出全局最优的配额分配。代价是 PD 本身成为可用性上的关键依赖,而且配额调整要经过"PD 分配 → 本地令牌桶执行"两层,链路比纯分布式方案长。

此外,TiDB 还有 Placement Rules(数据放置规则),可以让运维人员把热点数据分区固定到低延迟节点、把冷数据放到廉价存储上。这是一种静态的、人工驱动的隔离方式,适合已知热点的场景,但对 Serverless 环境中不可预测的负载变化就显得不够灵活了。

路线四:存储层显式 I/O 治理——“共用电梯,但电梯里有智能限流器”

IORM 走的正是这条路——多个租户共享同一个存储池(没有物理隔离),但存储层的每个节点都运行 I/O 调度器,通过代价模型 + 速率限制 + 反馈控制器的组合,显式地保证每个租户拿到承诺的 I/O 配额。

CockroachDB Serverless 是另一个走到这条路上的产品,在"共享存储 + 每租户显式配额"这一点上和 IORM 思路最接近。它把数据库节点拆成两层:每个租户跑独立的、无状态 SQL Pod(计算隔离),但所有租户共享同一个多租户 KV 存储层,数据靠给每个 key 加 tenant-ID 前缀做隔离(论文原话:“Implementing multi-tenancy is as simple as prepending a Tenant ID to the key”)。关键在于公平性保证放在存储层:每个存储节点跑 admission control(准入控制)+ 每租户的 Request Unit 令牌桶(令牌桶是一种经典限流算法:以固定速率生成"令牌",每个请求消耗一个令牌才能通过),每秒重估 1000 次 CPU 容量、每 15 秒重估一次磁盘带宽,给每个租户分配大致均等的 CPU 时间和存储 I/O——论文管这叫 “soft isolation in a shared KV layer”(共享 KV 层上的软隔离)。

但 CockroachDB 和 IORM 仍有两处本质差别。一是治理目标:CockroachDB 是「公平共享」(大致均分、允许租户占用空闲容量),IORM 是「承诺配额」(保证每个租户达到自己的 Limit_pct)。二是代价模型:CockroachDB 每 15 秒动态重估设备容量,这恰恰是 IORM 明确拒绝的做法——IORM 用静态 datasheet 代价(理由见下文 Layer 1)。所以可以说 CockroachDB 证明了"存储层显式治理可行",但 IORM 在硬件异构和访问倾斜这两个问题上走得更远。

这条路线的资源利用率最高(所有租户共享同一批硬件),但工程复杂度也最高。论文在测试中模拟了 100 个租户同时运行不同工作负载(数据库扫描、软件安装、VDI 启动风暴、流式读写),每个租户的 I/O 配额都能独立收敛到目标值,没有出现租户间干扰。

四条路线的本质权衡

把这四条路线放在一起看,核心权衡是:租户感知到的隔离强度 vs 资源利用率,四条路线大致排成一个谱系。

卷级隔离(路线一)租户感知最强,但要为每个集群预留整卷容量,利用率最低。计算隔离、存储共享(路线二)把利用率提上来了,但存储层没有显式治理,Noisy Neighbor 只能靠分片打散来缓解。逻辑隔离(路线三)和存储层显式治理(路线四)都建立在共享存储上、利用率都高,差别在治理的执行架构:前者靠一个拥有全局视图的集中调度器(TiDB 的 PD),后者靠存储节点各自分布式决策(IORM、CockroachDB)。

对 Serverless 数据库来说,这个权衡更加尖锐——商业模型是"按用付费",资源利用率直接决定毛利率。卷级隔离在 Serverless 场景下成本太高(你不可能为一个月用几块钱的租户预留一整卷容量),所以路线二、三、四是更现实的选择。而路线四里,真正做到"承诺配额 + 硬件异构处理"的目前只有 IORM 这类垂直整合系统,CockroachDB 做到了存储层治理但停在公平共享,这也是为什么 IORM 这篇论文值得细读。


论文核心:三层机制,每层解决一个问题

IORM 的设计可以拆成三层,恰好对应前面提的三个挑战:Layer 1 的代价模型解决硬件异构,Layer 2 的速率限制器解决突发容忍,Layer 3 的反馈控制器解决访问倾斜和分布式协调。每层有明确的时间尺度和职责边界,上层吸收下层的不精确——论文管这叫"分层吸收"。拆开看每一层都不算复杂,叠在一起的工程考量才是有意思的部分。

在这里插入图片描述

Layer 1 · 硬件感知代价模型:“你在哪条车道,交同样的过路费”

想象一条高速公路有不同限速车道。按"在这条车道上开了几秒"来收费,快车道的司机明明跑更远却交更少的钱。IORM 的做法是按"你消耗了多少道路容量的百分比"来统一计费。

为什么要把 I/O 分类?因为小 I/O(如索引查找,每次 8KB)的瓶颈在操作次数,大 I/O(如全表扫描,每次 128KB)的瓶颈在传输带宽。用同一个维度衡量两者不公平,所以分开计数。具体来说,IORM 以 64KB 为界(Oracle 根据其数据库中典型数据块大小选定的经验阈值),将所有 I/O 请求分成三类:

类别阈值计费维度独立计数器
Small Read< 64KBIOPS(每秒操作次数)N_small_rd
Small Write< 64KBIOPS(仅计主副本,镜像复制不算)N_small_wr
Large I/O≥ 64KB吞吐量(带宽)N_large

每个类别的「代价」从该代硬件的 datasheet 额定最大值算出来,是一个固定值。比如 X9M 的 Cost_small_rd = 1 / 2,300,000,X10M 的 Cost_small_rd = 1 / 2,800,000。同一笔 I/O 在高配硬件上自动计更少的 utilization,允许通过更多操作——恰好匹配了硬件更强的物理能力。

论文在这里做了一个刻意的选择:用静态 datasheet 代价,不用动态校准。

他们试过动态方案——根据实时 I/O 延迟推断代价。结果发现引入了一个双向反馈循环:设备进入 GC(SSD 内部的垃圾回收,整理空闲空间的后台操作,期间设备会暂时变慢)时变慢 → 系统误判为"代价升高" → 限流更激进 → 负载下降 → 设备看起来恢复 → 代价回落。实际发出的 I/O 负载和系统计算出的限额之间出现了逆相关振荡。

静态代价绕开了这个问题。论文总结出一句话我觉得挺值得记住的:

在分层调度器中,最底层的代价模型应当追求 stable 而非 accurate;上层可以吸收适度的不准确,但无法吸收不稳定。

代价是 datasheet 代价忽略了一些非线性因素——写放大(SSD 实际写入的物理数据量远大于主机请求写入的逻辑数据量)、SLC 缓存(用高速单层单元做写缓存,耗尽后写入性能骤降)以及设备老化。但论文认为这些误差可以由 Layer 2 的 carry-forward credit(跨量子吸收)和 Layer 3 的反馈控制器(跨节点吸收)来处理。

Layer 2 · 量子化速率限制器:“信用卡额度可以结转,但最多攒一个月”

数据库工作负载有两个互相矛盾的需求。举个例子:一个租户的配额是每秒 1000 次 I/O,但某一毫秒内事务提交需要瞬间刷写 50 次日志——如果严格按配额限流,这 50 次就得排队等待,直接影响事务响应时间。所以长期 SLO(Service Level Objective,服务等级目标)要严格执行(不能超配额),但事务日志刷写这种亚毫秒微突发不能被截断。

标准的令牌桶(token bucket,一种经典限流算法——以固定速率生成"令牌",每个 I/O 消耗一个令牌才能通过)做不好这件事——深桶允许违规的持续突发,浅桶会截断合法的 transaction commit。

IORM 用一个 200ms(大约相当于人眨眼一次的时间)的固定控制量子来调和。核心思想是:在 200ms 的时间窗口内部,I/O 直接放行不拦截;只在窗口结束时重新算一次配额、决定下一个窗口的额度。

每个量子内,先算出这个租户的 Util_measured(用 Layer 1 的代价模型),然后和 Util_allowed = Δt × Limit_pct 比较。如果没用完配额,剩余额度存入结转信用(carry-forward credit);如果超了,先从 credit 里扣。只要 credit 还有余额,超配的 I/O 照样放行;只有 credit 扣到零以下,才开始拦截新的 I/O 请求。

关键的抗积分饱和(anti-windup,防止"积攒的额度无限增长"的机制)设计:credit 最多积累 1 秒的量(Carry_max = 1 second × Limit_pct)。

这个 1 秒上限来自一个反复发生的生产事故。论文描述得很直白:空闲数据库在夜间积累了几个小时的 credit,凌晨维护任务启动时一次性消费完所有 credit,垄断磁盘好几分钟,触发共存租户的级联 p99 告警。无界 credit 把一个小概率事件变成了必现故障。
在这里插入图片描述

1 秒 cap 之后,突发行为变成了一个显式的 SLA 制品——“你有最多 1 秒的 100% 容量突发权限”,而不是 credit 池的涌现行为。论文说了一句我觉得很有洞察的话:anti-windup 在经典控制理论中只是控制器稳定性问题,但在调度器记账中它直接定义了租户可见的 burst SLA。

为什么选 200ms 而不是更短?实测了三个值:100ms 让浮点运算 CPU 开销翻倍(数千 consumer group 的记账成本),但 SLO 精度没有可度量的改善;500ms 降低了 CPU 成本,但允许交互式数据库出现可感知的延迟漂移。论文的关键洞见是:真正影响尾延迟的限流决策发生在 quantum 内部的 admission time(亚毫秒级),而不是 quantum boundary。200ms 只是"重新计算全局配额"的周期,不是"决定是否放行这个 I/O"的周期。

Layer 3 · 分布式反馈控制器:“智能通风系统”

前两层都在单个存储节点上运行。但一个租户的全局 IOPS 限额要跨整个集群执行。前面提到的 static slicing(静态切片)——Limit_local = Limit_global / N——当顺序工作负载只打到一个节点时,该租户只能拿到 1/N 的配额。这正是 Layer 3 要解决的问题。

IORM 的方案是每个存储节点独立运行一个反馈控制器,通过三步流水线来动态重分配未使用的配额。想象一栋写字楼有 8 个会议室共享空调,今天只有 1 号有人开会——系统缓慢地把 2-8 号的风量借给 1 号,但每次只多给 5%(不能一次全开,否则管道啸叫):

Stage 1 · EMA 平滑(α = 0.2):EMA 是指数移动平均——越近的数据权重越大、越远的数据权重越小,α=0.2 意味着每次更新时新观测值占 20% 权重、历史值占 80%。这层过滤掉 SSD GC 引起的延迟毛刺和 I/O 噪声。论文实测 α > 0.35 时控制器会追逐 GC 微突发,导致重分配限额产生可见抖动。

Stage 2 · 比例增益(Kp = 0.5):计算当前利用率与目标之间的误差,每 tick 只关闭一半的误差间隙(比如还差 20% 没到位,这次只调 10%)。为什么不设更高?论文解释 Kp = 1.0 在 ≥3 节点时引发环形振荡——就像三个人同时调音量旋钮,每个人都觉得对方太大声于是调低自己,结果三个人同时调低、又同时调高,音量忽大忽小。

Stage 3 · 变化速率限幅(Slew Clamp,±5%):不管算出来的调整量多大,每 tick 最多调 5%。这是最关键的一步。

论文给了量化证据(Figure 2c):在同步开门效应的压力场景下,slew bound 从 5% 放到 20%,outlier fraction(配额偏离目标超过 5% 的时间窗口占比)从 54% 飙到 92%。也就是说,即便默认的 5% slew,这种恶劣场景下仍有过半时间窗口在偏离目标——同步开门效应就是这么难压——放到 20% 后更是几乎全程偏离。原因是同步开门效应——N 个节点同时观察到"peer 空闲,我可以借",同时提高限额,流量涌入同一条 RDMA(远程直接内存访问,一种绕过 CPU 的高速网络协议)网络,产生惊群效应(大量请求同时涌向同一目标),稳态立刻打回原形。5% 的阻尼确保单调递增式爬升,而不是多节点同步"开门"。

控制器还有一个迟滞状态机:利用率归零时进入 Hold 状态(保持当前限额 5 秒),持续空闲后才进入 Decay 模式渐进释放。5 秒 grace period 是为了避免突发暂停后的冷启动惩罚——万一只是工作负载中场休息呢。

论文总结出一条反直觉的原则:

分布式重分配必须慢。N 个独立运行的小信号本地控制器,实质上构成了一个大信号分布式控制器,其参数必须按最坏情况下的关联决策而非典型本地场景来调整。

翻译成白话:每个节点自己看只是在"微调",但 N 个节点同时微调,叠加起来就是一次"剧变"。所以参数不能按"只有我在动"来设计,必须按"所有节点同时动"的最坏情况来设计——这就是为什么要慢。


五个 Lessons Learned:从生产事故里长出来的设计

论文的 Section VI 是我最推荐读的部分——五条经验都不是推导出来的,而是被生产事故逼出来的。前三条上面已经展开过,这里只点结论:静态优于动态(动态校准会和 GC 形成振荡)、200ms 不是粗而是恰好(更细的粒度只增加 CPU 开销、不改善 SLO)、分布式必须慢(单节点该快的直觉,到集群级就是网络不稳定的头号来源)。另外两条值得单独记一下:

“Anti-windup 是 SLA,不只是稳定性细节”:1 秒 credit cap 把突发行为从 credit 池的涌现特征,变成了一个租户可见、可沟通的边界。论文原话——“把这个边界清楚地告诉应用方,和执行它同样重要”。

“硬件透明需要持续付费”:datasheet 代价每代硬件都要更新一次查找表,这是内部评审时最常被反对的一点。但换来的是"租户的配额在第一天和五年后的硬件刷新日含义相同"——论文认为这种可预测性本身就是 feature。


横向对比:Aurora / Neon / TiDB / CockroachDB 各自怎么处理这个问题?

维度Aurora Serverless v2NeonTiDB ServerlessCockroachDB ServerlessOracle IORM
架构共享存储(6 副本跨 3 AZ),计算原地扩缩Pageserver + Safekeeper + S3,计算独立实例S3 + EBS,计算 shared-nothing无状态 SQL Pod + 共享多租户 KV 层共享存储池 + RDMA
多租户隔离卷级隔离(独占逻辑卷,物理层共享黑盒)计算独立实例 + 存储共享 PageserverPD 分配配额 + 本地令牌桶限流tenant-ID 前缀 + 存储层准入控制三层显式 I/O 治理
存储层 I/O 治理对用户完全黑盒256 MiB 条带化分散,无显式 per-tenant 配额RU 配额(PD 分配、本地执行)每租户 RU 令牌桶 + 准入控制(公平共享)代价模型 + 速率限制 + 反馈控制器(承诺配额)
硬件异构处理未公开用分片数量对冲Placement Rules 固定热点分区每 15 秒动态重估带宽Datasheet 固定代价跨代归一化
Burst 机制不暴露,ACU 频繁细粒度调整无显式 burst SLA无显式 burst SLA无显式 burst SLA1 秒 carry-forward cap,显式 SLA

几个值得注意的差异:

Aurora 和 Neon 在计算层都是独立的(Aurora 是独占 cluster,Neon 是独立 Postgres 实例),差别在存储层。两者某种程度上都在"回避"存储层的显式治理,只是方式不同:Aurora 给每个客户一个独占逻辑卷,把 I/O 治理彻底封装进内部;Neon 则让多个客户共享 Pageserver,存储层的 Noisy Neighbor 他们自己也承认存在,解法是把大数据集分片打散——更像是稀释问题而非治理问题。

TiDB、CockroachDB 和 IORM 才是真正在做显式治理,区别在集中式还是分布式。TiDB 的 PD 是一个拥有全局视图的集中式配额分配器,重分配快,代价是 PD 自身的可用性成为关键依赖;CockroachDB 和 IORM 都走松耦合分布式路线,治理逻辑下沉到存储节点。IORM 的收敛速度是这条路线的短板:反馈控制器从 0 爬到满配额,理论下限约 4 秒(20 个 tick),论文实测倾斜场景约 10 秒内收敛——对后台分析无所谓,对刚 Scale-Up 的在线事务节点则是可感知的性能爬坡。


争议与开放问题

静态代价 vs. 动态自适应:Meta IOCost 走了另一条路

IORM 选了静态 datasheet 代价,Meta 的 IOCost(ASPLOS 2022,部署在数百万台机器上)走了相反方向——同样从静态线性模型起步,但会基于观测到的设备饱和度动态调整虚拟时间速率(vrate),理由是"简单线性建模抓不住现代 SSD 的复杂性"。IOCost 的实测也够硬:优先级比例测试精确命中 2:1(BFQ 会跑成 10:1),ZooKeeper 隔离测试里只有 2 次轻微 SLO 违规,blk-throttle 有 78 次。

两边都对,只是赌注不同:IORM 赌"stable > accurate"(动态调整会和 GC 打架),IOCost 赌"纯静态在某些设备上确实不准"。Sandook(NSDI 2026)提出的折中——“静态基座 + 慢时间尺度动态微调”——正在获得关注。对做存算分离产品的人,一个可落地的思路是:如果后台 I/O 是可预测的(比如 LSM-tree——多数 KV/数据库引擎的存储结构——定期在后台合并数据文件的 compaction 操作,时机和量级相对可预期),就用前馈(feed-forward,提前预留配额)而不是等延迟涨上来再反馈(feedback)。

存储收敛要数秒 vs 计算亚秒级弹缩:谁等谁?

IORM 的反馈控制器从 0 爬到满配额,理论下限约 20 个 tick(4 秒),论文实测倾斜场景约 10 秒内收敛;而 Serverless 的 Scale-Up 是亚秒级。这不冲突——新节点能在已有配额内立即 I/O,只是前几秒到不了满配额,但用户确实会感知到"弹上来了却没立刻变快"。化解方式无非几种:Proxy 层过渡期缓冲、存储层按历史访问模式预分配 credit、或干脆像 Aurora 那样不承诺即刻满性能。再往下就是"Burst SLA 该给多少"的产品取舍:IORM 的 1 秒 cap 够事务日志刷写,但数据库 checkpoint 或大规模 ETL 可能不够。给更长突发(如 EBS gp2 的信用制)要防信用耗尽后的性能悬崖;完全回避信用机制(如 GCP PD 的线性缩放)又牺牲小盘性能——没有通用最优解。


对我们的启示

读这篇论文不是为了抄 Oracle 的方案——Exadata Exascale 是一个垂直整合的系统,它的全栈共设计(数据库内核 + 存储调度 + 硬件)是其他架构很难复制的。但有三个东西我觉得是可以带走的:

存储层 I/O 治理是 Serverless 弹性的"最后一公里"。 计算侧弹性已经是标配了,但多个租户共享同一个存储池时,I/O 配额的公平性和可预测性还没有被显式设计。IORM 用生产落地证明了这件事在工程上是可行的。

弹性伸缩 = 不可预测的访问倾斜 = 存储层治理的最大挑战。 Scale-Up 的新节点内存数据缓存(Buffer Pool)是空的,所有数据都得从远程存储读取,大量远程读涌向存储层,而这些读请求的分布由数据条带化决定,极可能倾斜到少数存储节点。IORM 的反馈控制器正是针对这种"热节点借用冷节点闲置配额"的场景设计的。

"底层 stable → 中层弹性 → 顶层均衡"的分层吸收思想。 这个设计哲学超越了 I/O 调度本身。任何分层系统都面临类似的选择:每一层应该追求精确还是稳定?误差应该在哪一层被吸收?IORM 的答案是:底层故意不精确但稳定,中层用短期弹性吸收底层误差,顶层用长期均衡吸收结构性偏差。三层的时间尺度差一个数量级,互不干扰。


论文信息与参考资料

  • 论文:Hardware-Transparent I/O Governance in Disaggregated Heterogeneous Storage,arXiv:2607.16578,2026-07-18
  • 作者:Rajarshi Chowdhury, Akshay Shah, Sue K. Lee(Oracle)
  • 部署系统:Oracle Exadata Exascale 生产集群
  • 关键引用:
    • mClock (OSDI 2010) — 单设备比例公平调度,IORM 的前置工作之一
    • PARDA (FAST 2009) — 跨 hypervisor 的分布式令牌桶,IORM 的前置工作之一
    • IOFlow (SOSP 2013) — SDN 风格集中式存储 QoS,IORM 的前置工作之一
    • IOCost (ASPLOS 2022) — Meta 的块 I/O 控制,走动态自适应路线
    • Sandook (NSDI 2026) — 在键值存储中处理 SSD 异构性,提出混合方案
    • CockroachDB Serverless (SIGMOD 2025) — cluster virtualization,共享 KV 层上的软隔离
    • Dean & Barroso, “The Tail at Scale” (CACM 2013) — 尾延迟放大效应的经典论证

更多推荐