1 背景与创新点
EBS 的 paid IOPS 通常被理解为吞吐上限,Calcspar 关注的是另一个更直接的问题:请求速率一旦超过 paid IOPS,单次 I/O 延迟会大幅上升,时延性能显著下降,而且高延迟不会在请求速率回落后立即消失。1
论文先在 gp2、gp3、io1 和 io2 上发送 4 KiB 随机读,每种 EBS 都配置 3000 paid IOPS。Submit IOPS 没有超过配额时,四种盘的 P99 都低于 270 μs;超过配额后,请求延迟进入 1000---11000 μs 区间,是未超限时的 5 倍以上。这个变化不是几个离群点,延迟分布会整体向右移动。1

图 1:gp2、gp3、io1 和 io2 在 I/O 压力低于或超过 paid IOPS 时的延迟 CDF。灰线整体位于黑线右侧,其中 io2 的变化最明显。原图来自论文 Figure 1。1
Figure 3 对 1000-IOPS io2 做了更严格的单线程控制。第 1 秒提交 1000 个请求,延迟低于 200 μs;第 2 秒提交 1600 个请求,前 1000 个请求仍维持低延迟,后 600 个请求升到约 1000 μs,也就是 1/1000 秒。第 5 秒提交 2000 个请求时,同样是前 1000 个快速返回、后 1000 个进入高延迟区间。更关键的是,后续 Submit IOPS 即使回落到 1000,高延迟仍然持续;直到第 14 秒暂停请求、第 15 秒恢复,延迟才重新回到低位。1

图 2:1000-IOPS io2 在不同提交压力下的单请求延迟。黑色方块表示每秒提交的 IOPS,红色圆点表示返回的 IOPS;第 14 秒暂停、第 15 秒恢复请求后,延迟回到低位。原图来自论文 Figure 3。1
RocksDB 恰好会持续制造这种 IOPS 峰值。写入可以在 Memtable 中聚合后批量落盘,读请求却可能逐层查找多个 SSTable。Figure 8 中,客户端 Submit IOPS 的峰值约为 6 Kops/s,RocksDB 实际发往 EBS 的 I/O 峰值超过 13 Kops/s;论文统计实际 IOPS 约为 Submit IOPS 的 3 倍。L0 和 L1 的数据量很小,两层合计仍消耗了接近三分之一的读 I/O;LSM 的逐层查询将客户端负载进一步放大。1

图 3:RocksDB 的 Submit IOPS、实际 EBS IOPS、各 Level IOPS 与数据量。实际 EBS IOPS 约为 Submit IOPS 的 3 倍。原图来自论文 Figure 8。1
Compaction 又会集中读取和写回 SSTable。Figure 10 将客户端 QPS、各类 RocksDB I/O 和延迟放在同一时间轴上:高负载阶段,P99 延迟已经升到约 10 ms;第 70 秒附近的 Compaction I/O 进一步把 P99 推到接近 40 ms。第 300 秒附近即使客户端处在低负载阶段,RocksDB 内部启动的后台操作仍然制造了一次明显的延迟尖峰。1

图 4:10 线程混合读写负载下的客户端 QPS、用户读 I/O、Flush/Compaction I/O 和延迟。原图来自论文 Figure 10。1
因此,Calcspar 的创新点很集中:把 paid IOPS 从云盘计费参数变成 LSM Store 内部可执行的调度边界。系统在 I/O 进入 EBS 以前限速和分类,尽量不让 EBS 进入超限后的高延迟区间;负载低时再利用空闲 IOPS 做预取,Compaction 则按 Level 决定紧急程度。论文的成本实验还显示,直接购买更多 IOPS 虽然能降低延迟,RocksDB 吞吐没有得到对应增长。Calcspar 要解决的是相同 paid IOPS 下的尾延迟问题。1
2 Modeling Cloud Storage Performance:EBS 延迟模型
论文根据上述黑盒实验提出了一个 EBS 延迟模型。这里必须先划清证据边界:I/O domain、regular token、overdraft token 和 borrowing pool 都是论文为解释实验现象建立的推测,论文原文将其标成 Speculative Reason,AWS 并未公开确认这一内部实现。1
模型假设 EBS 内存在一个长度等于 paid IOPS 的请求缓冲区,论文称为 I/O domain。请求到达后先占据一个空槽,EBS 再从中取出请求交给设备执行。当一秒内已经返回的请求数达到 paid IOPS,EBS 停止继续取请求,剩余请求留在 I/O domain 中等待。多线程调度的瞬时偏差也可能让请求在一个较小时间窗口内集中到达,即使整秒总量看起来没有超过预算,队列仍然可能发生拥塞。1

图 5:论文推测的 EBS IOPS 限速机制,包括 I/O domain、regular token、overdraft token 与 borrowing pool。原图来自论文 Figure 5。1
论文用 Overdraft Rule 解释请求的返回速度。每块 EBS Volume 拥有一个 regular token bucket 和一个 borrowing pool,两者的 token 数量都与 paid IOPS 相同。请求优先取得 regular token 并快速返回;regular token 耗尽后,超额请求从 borrowing pool 取得 overdraft token,以较慢速度返回。下一秒刷新 token 时,系统优先补偿 borrowing pool,因此上一秒形成的超额请求会继续占用后续预算。Figure 3 中第 2 秒多出的 600 个请求,按这个模型借走了第 3 秒的 600 个 IOPS,下一秒只有剩余 400 个请求可以快速返回。暂停负载以后债务才被清空。1
线程数带来的延迟变化被论文写成排队公式:1
W = L A W=\frac{L}{A} W=AL
W W W 是超出配额后的响应时间,单位为秒; L L L 是线程数; A A A 是 paid IOPS。Submit IOPS 未超过 paid IOPS 时,实验平均延迟约为 120 μs,线程数增加不会明显改变结果。Submit IOPS 超限以后,单线程延迟先进入约 1 / A 1/A 1/A 秒的区间,再随线程数近似线性增长。1
这个模型最终解释了四个实验现象:1
-
Submit IOPS 未超限时,请求取得 regular token,延迟主要由 EBS 类型决定。
-
Submit IOPS 超限时,部分请求取得 overdraft token,延迟进入约 1 / A 1/A 1/A 秒的区间。
-
返回速度低于到达速度时,未处理请求填满
I/O domain,后续 token 又优先偿还 borrowing pool,高延迟会跨时间窗口持续。 -
多线程请求在
I/O domain中相互阻塞,用户看到的尾延迟继续放大。
Calcspar 后续所有设计都建立在这个边界上:EBS 一旦开始用高延迟处理超额请求,上层已经失去重新排序的机会;最合适的控制点在请求进入 EBS 之前。1
3 Calcspar 的问题分解与实验验证
Calcspar 基于 RocksDB 实现。四项设计分别对应一种具体问题:1
| 论文识别的问题 | Calcspar 组件 | 控制对象 | 直接目标 |
|---|---|---|---|
| IOPS 超限 | IOPS Stabilizer | 提交速率 | 避免 overdraft |
| 前后台 I/O 竞争 | Congestion-Aware IOPS Allocator | token 顺序 | 前台请求优先 |
| 负载周期性波动 | Fluctuation-Aware Cache | 缓存策略 | 跨时间复用 IOPS |
| Compaction 突发 I/O | Opportunistic Compaction | Level 优先级 | 降低读放大干扰 |

图 6:Calcspar 由 Congestion-Aware IOPS Allocating、IOPS Stabilizer、Fluctuation-Aware Caching 和 Opportunistic Compaction 组成。原图来自论文 Figure 12。1
3.1 IOPS Stabilizer 的合同限速
IOPS Stabilizer 按 paid IOPS 每秒刷新 token,每个访问 EBS 的请求都要先取得 token。它直接解决 Figure 3 暴露的问题:只要提交量不跨过 paid IOPS,EBS 就不会进入论文观测到的 overdraft 高延迟区间。等待发生在 RocksDB 一侧以后,系统还知道某个请求属于前台读、Compaction 还是 Prefetch,可以继续调整顺序;请求进入 EBS 的 I/O domain 后,这个机会就消失了。1
3.2 Congestion-Aware IOPS Allocator 的动态时间窗口
单纯限速会让所有请求排在同一条应用层队列中,后台任务仍可能提前拿走 token。Allocator 将直接影响用户请求的 I/O 放进最高优先级队列,Compaction 等后台任务进入中、低优先级队列,Prefetch 使用最低优先级。这里解决的是有限 token 下的优先级倒置问题。1

图 7:多优先级队列先通过动态时间窗口分配 token,IOPS Stabilizer 再按 paid IOPS 控制提交速率。原图来自论文 Figure 13。1
时间窗口决定一类请求从一秒中的哪个时刻开始有资格竞争 token。论文给出的例子为:高优先级使用 [0,1),中优先级使用 [0.7,1),低优先级使用 [0.9,1)。高优先级请求可以在整秒内取得 token,中、低优先级只能在前台请求留下额度以后参与竞争。中、低优先级窗口根据上一秒实际取得的 token 动态调整,窗口比例取 Allocated_IOPS / Paid_IOPS。固定分区会在某类队列空闲时浪费 IOPS,动态窗口保留了低负载期的复用能力。1
3.3 Fluctuation-Aware Cache 的负载相位切换
IOPS Stabilizer 能压平超过配额的峰值,也会在低负载阶段留下没有使用的 token。Fluctuation-Aware Cache 用两种粒度处理这个时间差。1
低负载时,Calcspar 用空闲 token 读取 256 KiB EBS Block,这是论文采用的单次 EBS I/O 最大数据量。系统通过指数平滑记录 Block 热度,周期性预热 L0、L1 中访问频繁的数据,一次 I/O 尽量搬回更多数据。高负载时,缓存切换到 4 KiB 粒度的被动 LRU,减少缓存维护本身对 EBS 的访问。当最高优先级队列消耗超过 95% token,系统启用被动缓存;低于该阈值时恢复主动预取。两种策略共享同一块缓存,热点跟踪表每 1 GB 数据约占 64 KiB 内存。1
这项设计解决的不是单一时刻的 Cache Hit Ratio。低负载期无法结转的 IOPS 被转换成 Cache Entry,高峰期再通过 Cache Hit 减少云盘请求,相当于把一部分 IOPS 使用能力搬到了后续时间窗口。1
3.4 Opportunistic Compaction 的 Level 优先级
一次 Compaction 至少读取两个 SSTable,并写出一个新 SSTable,容易在短时间内与前台请求竞争大量 IOPS。Calcspar 优先处理 L0 Compaction;L1、L2 的 Compaction I/O 进入中优先级队列;L2 以下的深层 Compaction 进入最低优先级队列。L0 SSTable 之间可能重叠,对当前读放大的影响更直接;深层 Level 的短期推迟对前台读路径影响较小。1
Calcspar 没有修改 RocksDB 的 Compaction Candidate Selection,调度对象是候选任务产生的 I/O。这一做法降低了 Compaction 突发对前台读的干扰,也留下一个论文没有长期量化的问题:深层任务延后以后,Compaction Debt、Space Amplification 和 Write Stall 风险如何变化。1
3.5 实验过程与结果
默认实验使用一台 m5d.2xlarge EC2,配置 8 vCPU、32 GB 内存,以及 100 GB、1000 paid IOPS 的 io2。系统先写入 1 亿条 KV,初始数据约 25 GB;Key、Value 与 SSTable 大小分别为 16 B、256 B 和 8 MB。各系统使用 500 MB Cache、4 个 Compaction 线程和 4 个 Flush 线程,SILK 因不支持多线程而例外。对照包括 RocksDB、Autotuned RocksDB、SILK 和 CruiseDB。1
测试先使用 Mixgraph 改变写请求数量,将读比例从 0.2 提高到 1.0,比较五个系统的吞吐、平均延迟和 P99;随后使用 YCSB A---F 与 Uniform 分布核对热点局部性变化;最后分别测试时间窗口、缓存、线程数、写放大、IOPS 压力与跨云适配,依次覆盖端到端结果、组件贡献和适用范围。1

图 8:不同读请求比例的 Mixgraph 负载下,五个系统的吞吐、平均延迟与 P99 延迟。原图来自论文 Figure 14。1
Calcspar 在各组 Mixgraph 测试中的平均延迟不超过 200 μs,比其他系统低 45%---66%;P99 稳定在约 0.55 ms,离群点也更少。其中一部分吞吐收益来自 Mixgraph 的空间局部性与预取命中。1
YCSB A---F 的结果更接近 Calcspar 的目标边界:吞吐不低于 RocksDB,P99 约为其他方案的一半,但受 paid IOPS 限制,吞吐没有继续增长。换成 Uniform 分布以后,主动预取的收益下降,Calcspar 仍能依靠 I/O 限速保持较低延迟;CruiseDB 在该实验中的 Tail Latency 上升到 20 ms。1
组件实验里,动态时间窗口策略 TWA 相对不做分配的 NA 将 P99 降低 50%;使用静态 6:3:1 配额会浪费 IOPS,只按最高优先级队列用量动态分配的 DA 虽然平均延迟更低,P99 仍高于 TWA。Fluctuation-Aware Cache 在 YCSB Zipfian 与 Mixgraph 下都得到最高命中率,缓存小于数据量 5% 时命中率最高约 60%;使用 1% 缓存时,平均延迟已经低于 200 μs。用户线程从 1 增加到 20 时,Calcspar 的平均延迟维持在约 175 μs,P99 约为 500 μs;其他系统在 20 线程下的 P99 增幅最高达到 60 倍。1
这些读延迟收益没有完全转嫁给写入。1 亿次随机写实验中,Calcspar 吞吐比 RocksDB 低 1.2%,Write Amplification 从 RocksDB 的 7.7 降到 5.6。论文还把 Mixgraph 平均读请求压力提高到 paid IOPS 的两倍,Calcspar 的平均延迟仍约为 200 μs,P99 仍是五个方案中最低。1
论文在 AWS 四种 EBS 上做了敏感性测试,并在 Alibaba Cloud ESSD PL1、Azure Managed Disk Premium SSD v2 上与原生 RocksDB 对比。Aliyun 实验中,P99 从 9.6 ms 降到 509 μs,平均延迟降低 69.5%。其他云厂商实验只对比了 RocksDB,证据强度低于 AWS 主实验。1
论文的实验对象始终是云块存储,不包括对象存储。默认数据规模约 25 GB,主实验使用 500 MB Cache 和能够被 Mixgraph 利用的热点局部性;Uniform 结果已经显示 Cache 收益会随局部性下降。Opportunistic Compaction 也没有长期量化深层任务被推迟后的 Compaction Debt、Space Amplification、Write Stall 与最坏等待时间。1
4 总结
我认为 Calcspar 解决的问题确实比较小。它聚焦于按 paid IOPS 计费、超限后出现显著延迟惩罚的 EBS 类云块存储,目标可以压成一句话:在相同 paid IOPS、也就是相同云盘成本下,降低 RocksDB 的平均延迟和尾延迟。论文没有给出一套通用的 LSM Compaction 算法,也没有覆盖对象存储、共享 Volume 或带宽受限设备。它处理的是一个很窄的工程切口。1
这个小问题做得很漂亮。论文先用受控的 fio 实验找出一个反直觉现象:IOPS 超限会让超额请求进入稳定的高延迟区间,并把惩罚带到后续时间窗口。作者随后用 I/O domain、regular token 和 overdraft token 建模,再让 IOPS Stabilizer、优先级队列、缓存切换和 Compaction 调度逐一对应模型暴露的问题,最后用端到端对比、组件实验和跨云测试核对贡献。文章从现象、模型、设计走到实验,证据链很完整,没有把一个小问题硬包装成云存储的通用答案。1
我读完以后留下的重点,是 Calcspar 将排队点从 EBS 的黑盒队列搬回了 RocksDB。云盘只看到一串 I/O,无法判断哪一个请求正在阻塞前台查询,哪一个只是深层 Compaction。请求跨过云盘边界以前仍然带着完整语义,调度器才有机会把有限 IOPS 给到更紧急的请求。缓存也承担了资源搬运:低谷期无法结转的 IOPS 被换成 Cache Entry,高峰期再消费。
我认为这里还有一个更普遍的启发:数据库和存储的 co-design 很关键。Calcspar 绕了一圈做黑盒测试、推测 EBS 的限速模型,原因在于 RocksDB 拿不到 EBS 内部的队列、token 和拥塞状态。类似 PolarDB 与 PolarFS 这种上下层都可控的体系,具备做联合优化的条件:数据库可以把前台读、Flush、Compaction 的优先级传递给存储层,存储层也可以暴露实时配额、排队和拥塞状态,双方不用再通过延迟反推内部发生了什么。用别人的存储,能做的通常只有基于某一种盘型、某个可观测版本进行适配;底层策略一变,模型和参数就需要重新校准。
边界同样明确。论文的 EBS token bucket、borrowing pool 与 overdraft token 都来自实验推测,AWS 没有确认这套内部实现。1 Calcspar 还需要看到并约束发往该 Volume 的主要 I/O;深层 Compaction 的延期也可能积累 Compaction Debt,最终以 Space Amplification 或 Write Stall 的形式回来。Calcspar 的四个模块未必需要整套照搬,把 paid IOPS 放进 LSM 调度面这个视角值得留下。
5 参考资料
1 Calcspar: A Contract-Aware LSM Store for Cloud Storage with Low Latency Spikes