面向可恢复云应用的分布式推测执行(OSDI 2026)
原题:Distributed Speculative Execution for Resilient Cloud Applications
一句话总结:对于延迟主要来自串行持久化的服务链,libDSE 允许独立服务在状态落盘前继续传递消息,用恢复依赖图协调回滚,并在对外输出前等待依赖持久化;长预订工作流的端到端延迟最高降低约一个数量级,代价是故障时丢弃推测工作及改造服务的状态与并发边界。
问题与动机
持久执行(durable execution)让应用在进程故障后恢复执行,避免重复扣款、遗漏后续步骤等异常。传统工作流引擎通常先持久化操作意图,再执行服务调用,随后持久化结果。对于非确定性任务,若直接重放未保存的结果,可能产生另一条执行分支,因此同步持久化承担了正确性职责。
这种实现把服务链的每一层都变成持久化等待点。即使每个服务自身处理很快,工作流延迟仍随链深增长。libDSE 的分布式推测执行(DSE)将中间消息的可用性与持久性分离:内部先继续运行,对外仍只暴露不会被回滚的结果(图 2)。它不取消最终持久化,而是重叠各阶段的持久化等待。
困难在于服务各自拥有私有存储,编排器自身也可能故障。运行时必须把服务状态、控制流依赖和异步执行一起纳入恢复协议,不能仅恢复某个数据库,也不能要求调用方自行处理随时发生的分布式回滚。
关键观察 / 隐含假设
- 观察 1:串行持久化等待会随服务链深累积。 图 8 在每秒 10 个工作流的低负载下增加服务数量,非推测基线延迟持续上升,libDSE 则主要增加 RPC 开销。
- 依赖假设:持久化是主导成本,链内多数服务支持推测执行,故障相对少见。
- 可能失效场景:视频转码、长时间模型推理等计算密集任务,或每一步都必须调用外部支付 API 的服务链。§2.3 明确承认浅层和计算密集负载收益较小。
- 观察 2:恢复依赖包含控制流依赖。 编排器收到服务 A 的确认后才调用 B,即使从未读取 A 的存储,B 的调用仍依赖该确认。§4.2 因而在“消费消息”时记录依赖,而非只追踪数据读写。
- 依赖假设:所有跨参与者的推测影响都经过运行时插桩的消息通道。
- 可能失效场景:未插桩的共享数据库访问、异步线程直接修改父对象,或提前释放外部副作用,都会绕开依赖图。
- 假设 3:组件能在有限时间内重启并恢复到指定持久版本。 §2.2 假设故障后重启、可靠点对点通信、私有持久存储,排除拜占庭故障;§4.4 的活性论证还依赖本地持久化和恢复最终完成。
- 证据强度:中。 Kubernetes 节点终止实验覆盖了一种实际恢复路径,但未系统验证长期网络分区、连续故障和永久存储丢失。
- 假设 4:持久化批次级依赖足以换取可接受的回滚损失。 一个恢复点包含多条消息和状态变化,只要其中一条依赖失效,整个恢复点可能被撤销。
- 边界:回滚有逻辑计数上的界限,不等于工作量、墙钟恢复时间或受影响租户数量都有很小的上界。
核心方法
状态对象与原子执行块
开发者把状态封装为 StateObject,实现 Persist、Restore、ListVersions 等接口。Persist 可以在发出 I/O 后立即返回,再用回调报告完成;Restore 同时支持崩溃恢复和撤销推测状态。
action 是相对于持久化与恢复原子的执行块。运行时保证它不与 Persist、Restore 调用交错,但多个 action 可以并发,应用仍需自行处理请求间的数据竞争。这种原子性不能直接理解为事务隔离。实现使用 action 的共享锁和持久化、恢复的独占锁,并以偏向锁降低常见路径开销(§3.1、§5.1)。
用轻量执行线程承载异步操作
长 RPC 若一直占用 action,会阻碍持久化,甚至造成死锁。sthread 是从状态对象派生、携带推测依赖集合的轻量执行线程。Detach 离开原子块后允许等待或重试;Merge 尝试重新进入父对象,若期间相关状态已回滚,运行时拒绝合并,应用终止这段派生执行。
sthread 不单独持久化,也不成为依赖图顶点。它发送消息时附带依赖集合,接收消息时合并依赖;恢复后的父对象负责重新派生必要的执行。Barrier() 等待其上游依赖不再具有推测性,用于向用户或不可回滚的外部系统发送结果(§3.2)。
恢复依赖图与提交顺序
libDSE 改造分布式前缀恢复(DPR):把原本分离的客户端和存储节点统一为可恢复的消息参与者。图顶点由状态对象 ID、全局故障计数和本地持久化计数组成。后继状态依赖前序状态,消息接收者依赖发送者对应的状态。一个可对外暴露的恢复边界,必须由全部已持久化、且没有指向边界外依赖的顶点组成。
为避免依赖不断追赶未来版本而产生多米诺回滚,接收者的本地持久化计数不得小于发送者的计数。落后者先发起 Persist 推进本地计数,再接收消息,无需等待该 I/O 完成。这保证计数不超过某个值的顶点集合对依赖封闭,但也使频繁通信的参与者被动同步持久化节奏(§4.2)。
协调器与故障恢复
各状态对象把依赖元数据随本地版本持久化,并批量上报协调器。协调器在内存图上搜索恢复边界;由于已持久化顶点的依赖不再变化,正常路径的边界决定可以重算,无需再同步写协调器日志。
“无状态协调器”仅指依赖图视图和正常提交边界可重建。成员变化和回滚决定仍需要持久日志,故障发生时必须先持久化故障序号及回滚方案,再通知参与者。运行时拒绝旧故障代际的消息、延迟接收未来代际的消息,避免恢复前后的状态混用。协调器自身重启时,需要重放日志并等所有参与者报告依赖片段后,才能重新回答恢复边界查询(§4.3)。
设计取舍
- 正常路径少等待,故障路径多撤销。 非确定性执行可以继续推进,但未提交结果及其下游影响都必须能够撤销;运行时不保证保留所有实际上已经落盘的推测工作。
- 粗粒度追踪减少常态记账,扩大回滚范围。 恢复点级追踪避免逐消息因果分析,却可能连带丢弃同一批次中无关的更新。协调器视图陈旧会进一步增加保守撤销。
- 统一恢复协议不等于无侵入接入。 约 4,000 行 C# 实现了承载协议的运行时和协调器,但应用仍需整理状态边界、实现恢复接口,并正确约束后台执行。
- 私有存储保留服务独立性,限制模型兼容性。 单主复制和主备服务较容易映射为状态对象;Dynamo 风格最终一致存储缺少明确的单点恢复状态,不适合直接接入。
- 外部屏障保护可见结果,也截断优化范围。 屏障不能独自解决外部 API 已成功、调用方随即崩溃后的重复调用问题;这类副作用仍需应用原有的幂等或去重安排。
实验与结果
环境。 端到端实验使用 Azure AKS 上 10 台 Standard_D8s_v3 和高级本地冗余 SSD,默认组提交间隔 10 ms;微基准使用两台各有 32 vCPU、128 GB RAM 的 D32s_v3。预订和事件处理主实验各运行 120 秒(§6)。
- 预订工作流:长服务链获得约一个数量级的延迟收益。 工作负载改自 DeathStarBench,仅评估预订写入,以每秒 10 个工作流排除排队影响。图 8 扩展到 10 个服务,libDSE 延迟保持在数十毫秒量级;对照包括关闭推测的同实现,以及 Cassandra/CosmosDB 支撑的 Temporal。正文称统计平均值与 P95,但图例标注 P50/P95,因此不将前者转述为精确平均延迟。
- 事件处理:相同持久化间隔下减少多阶段等待。 三阶段搜索趋势告警以每秒 50,000 个事件运行。10 ms 组提交时,图 9(a) 的 P50 约从 DARQ 基线的 125 ms 降至 35 ms,P95 约从 145 ms 降至 45 ms;均为图读近似值。较长推测窗口还能让已消费的短生命周期中间结果在落盘前被清理,减少写入量。
- 分布式事务:多数事务在 20 ms 内提交。 四分片、100% 分布式事务的修改版 TPC-C 中,libDSE 重叠两阶段提交的消息与 I/O;非推测版本的延迟集中在 10 ms 组提交间隔的多个倍数。Orleans 对照使用相同分片布局并完全驻留内存,仍慢于推测版本,但不属于等持久性配置比较(图 10)。
- 恢复:真实停机主要由容器恢复支配。 事件处理在第 30 秒终止节点,两种版本均约需 10 秒恢复;不重启容器、直接触发回滚的模拟故障产生数百毫秒延迟尖峰。事务模拟故障使总体中止率按作者表述增加 0.3%,原文未明确该数值是相对百分比还是百分点(图 11–12)。
- 插桩开销依赖接入方式。 相对无 DSE 的 FASTER RPC 服务,手动处理协议头的最大吞吐损失小于 5%,自动 gRPC 拦截器版本约损失 25%。因此“运行时开销小”不能无条件用于默认便利接口(图 13)。
- 扩展性证据止于有限规模。 原语微基准到 16 线程无性能退化,每秒可执行数百万次操作;真实协调器接收 8–64 个模拟服务的版本提交时,中位提交延迟低于 5 ms 刷新周期,无依赖快路径约 2 ms。这不是数百个真实服务上的端到端恢复结果(图 14–15)。
论断—证据表
| 论断 | 证据 | 评测边界 | 置信度 |
|---|---|---|---|
| 重叠持久化可压低深服务链延迟 | 图 8:长预订链最高约一个数量级收益;包含同实现关闭推测的对照 | 10 台 Azure 节点,仅写入、低到达率的预订工作流 | 强:支持该场景,不代表任意微服务应用 |
| 推测收益不限于工作流编排 | 图 9:10 ms 组提交时事件处理 P50 约 125→35 ms;图 10:多数事务低于 20 ms | 三阶段事件处理及四分片、全分布式事务 | 中:工作负载均有明显持久化等待 |
| 已对外提交结果不会因回滚失效 | §4.2–4.4 的依赖闭包、提交顺序与故障代际隔离论证 | 完整插桩、正确屏障、可靠持久存储及重启假设 | 中:只有非形式化正确性论证 |
| 回滚成本在所测故障下有限 | 图 11–12:实际重启约 10 秒,模拟回滚尖峰数百毫秒 | 单节点实际终止与有限次模拟故障 | 中:未覆盖分区、故障风暴和协调器失效组合 |
| 协议本身较轻,但集成层可能有实质开销 | 图 13:手动插桩吞吐损失小于 5%,自动拦截器约 25% | FASTER RPC 微基准;不等于所有应用的开销 | 强:存在直接消融对照 |
批判性分析
论证链条
论文把串行持久化瓶颈、允许中间结果提前传播、延迟外部可见性和故障时闭包回滚连接起来。贡献在于跨独立存储服务处理恢复,尤其把编排器控制流纳入协议,并用 action/sthread 隔离应用执行与异步回滚。它与在共享日志内部提前处理记录的 SpecLog-OSDI25 优化层次不同。
不能把“正常路径不等每次落盘”理解为用户请求不等持久化。最终响应仍受最慢依赖的落盘进度和协调器边界发布影响。方法减少的是串行等待次数,持久存储和控制面的尾延迟仍然存在。
假设压力测试
以下是基于设计的推断。 热点服务与大量租户共享恢复点时,粗粒度依赖可能放大一次故障的波及范围。提交顺序规则限制逻辑版本依赖,却可能让慢持久化节点影响大量请求的最终可见性。应同时测量丢弃工作量、受影响请求比例和 P99 恢复时间,不能只以“有界回滚”替代这些指标。
对高外部交互比例的应用,每次不可撤销副作用前的屏障会把长推测链切短。计算成本越高、故障越频繁,提前执行又被丢弃的代价越大。论文未给出故障率、计算时长与组提交间隔共同决定的收益转折点。
实验可信度
关闭推测的同实现对照能比 Temporal 跨栈对照更直接归因;手动头处理与拦截器的消融也揭示了隐藏的集成成本。事件处理和事务扩展了适用机制的证据,但不能视为随机抽样的生产负载:预订排除了读取,TPC-C 被修改为全分布式事务,运行时长也不足以覆盖长期故障和内存回收行为。
论文提供了恢复实验和协调器微基准,但后者用模拟参与者,未展示大规模真实依赖图的内存占用、协调器饱和点和长期 P99。附录给出 Zenodo 研究产物,包含代码、原始结果与绘图脚本;本页未重新运行这些实验。
系统性缺陷
编程模型减少了应用手工编写分布式回滚协议的工作,却没有消除接入错误。sthread 不得直接访问父对象状态;action 之间仍需同步;持久化接口必须保存正确版本。这些约束若无类型系统或运行时检查,很容易在维护时被破坏。
§5.1 明确要求服务实现者防止同一状态对象的两个进程实例同时修改持久状态。运行时处理消息代际,并不替应用提供存储写入隔离。协调器恢复必须等所有参与者回应,也使失联节点影响边界服务恢复时间。论文未系统评测上述运维场景,且明确不提供完整形式化证明。
局限与后续工作
- 适用性局限: 收益依赖持久化等待占比和连续可推测链的长度。可固定服务链结构,分别扫描计算时长、外部屏障比例和持久化延迟,给出端到端收益降至 1× 的边界。
- 恢复局限: 当前证据偏向单次节点故障与快速模拟回滚。应组合注入网络分区、协调器重启、节点连续重启和重复消息,用外部已确认历史验证没有已确认结果被撤销,并统计恢复 P99。
- 隔离局限: 恢复点可能混合多个租户的工作。可比较每服务、每租户和自适应恢复点划分,测量控制流量、图大小、持久化次数与连带撤销比例。
- 工程局限: 表 2 的日志约 200 行、FASTER 约 400 行等包装代码成本建立在清晰日志结构之上,不能代表任意遗留应用。可在一个混合共享数据库和不可回滚外部 API 的真实服务中审计状态访问与屏障覆盖,并量化接入后的吞吐损失。
- 正确性后续: 针对提交顺序、故障代际隔离、协调器日志重放和多实例存储写入约束建立模型检查,覆盖多次重叠故障,而非只验证无故障请求输出。
相关
- 机制线索: Durable-Execution、Distributed-Snapshots;DPR 是恢复协议的直接起点,libDSE 将其推广到对等的消息参与者。
- 同类系统: SpecLog-OSDI25 在共享日志排序层内推测;ExoFlow 借助任务确定性或可回滚标记跳过持久化,libDSE 则依赖跨服务依赖追踪和统一回滚。
- 原始材料: osdi26-li-tianyu、osdi26-li-tianyu.pdf。