05

压缩过去,在不猜测的前提下恢复。

压缩用等价的短层链替换不可变历史。在线重新挂载迁移符合条件的会话;崩溃恢复保留不确定存储,只回收已证实死亡的状态。

合同

一项持久的存储更改。许多可选的实时迁移。

压缩会证明紧凑 S 层与其替代的原始 L 层区段具有相同的合并视图,然后把它提交到活动 manifest。提交后的清理会尝试释放历史 pin;即使清理失败,也无法撤销存储提交。

交易边界

提交第一;经核实后方可回收。

Before

  1. L₄·最新
  2. L₃
  3. L2
  4. B₀·基础

已提交head

  1. L₄·赛车head保留
  2. S₅·L₃+L2
  3. B₀·基础

正确性

合并(B₀,L2,L₃,L₄)=合并(B₀,S₅,L₄)

Reclamation

L2 和 L₃ 仅在每个租约释放后才会消失。

存储交易

昂贵的折叠运行在 写入锁 之外。

租约计划将不可变源层转换为稳定的构建输入。短暂的独占阶段仅重新验证、提升、同步和交换元数据。

  1. 01简短的共享锁

    规划

    读取活跃的manifest,收集每个活跃的租约的最新层作为边界,划分至少两个层的最大原始运行,并获得计划租约。

    没有基础 B*层且没有活动边界进入块。

  2. 02无存储锁

    建造

    将每个选定的最新运行折叠到随机数 暂存 树中。保留 whiteout 和不透明度;硬链接常规文件获胜者并重新创建符号链接。

    发布可能会继续,因为计划租约引脚每个源。

  3. 03独占锁

    提交

    重新读取 head,要求每个原始运行保持连续,重命名暂存的 S层,同步存储文件系统一次,并自动替换 manifest。

    记录替换,然后发布计划租约——该交易的唯一 GC。

竞赛安全 head 保存

由于发布在展平运行时进行前置,因此提交会重新读取最新的 manifest 并仅拼接仍然连续的计划运行。参加比赛的发布仍保留在 head 中。

Space + time

压缩工作根据访问的元数据而不是基础映像副本进行扩展。

这些界限描述了当前实现的主要工作。 L = 活动层、R = 活动租约、E = 已访问条目、V = 压缩输出条目、P/T/F/M = 过程检查输入、N =会话、W = 扫描宽度。

暂存时间额外空间为什么
规划O(L + R log R)O(左+右)扫描L active层并从R live租约订购最新边界。
FlattenO(E log E) 最坏情况O(V) 条目走 E 源条目并在有序地图中选择获胜者。常规有效负载块由硬链接共享; V 是压缩的可见输出。
提交manifest复查+Q重命名O(L+Q)针对最新的 manifest 重新验证并拼接 Q 块; syncfs 的持久性通常主导着 wall time。
租约重写生成×链式扫描奥(大)将追加顺序替换应用于一个 已租借 链,然后验证每个重写的层目录是否仍然存在。
QuiesceO(P+T+F+M)O(P+T)在切换之前扫描 /proc、任务线程、文件描述符和内存映射行。
会话扫频O(N) 功 · ≈⌈N/W⌉ 波在)重新挂载N会话,带边界宽度 W。当前默认 W 为 4;结果排序保持稳定。

硬链接是常规文件有效负载不复制到 S 中的原因。新树仍然需要目录条目、符号链接、whiteout、不透明标记、xattrs 和 暂存 空间,直到提交。

租约替代

在旧链条移动之前,需要获取较短的链条。

每个已提交 S 层都会在按追加顺序记录的内存映射中保存其替代的原始区段。重写时先应用较早的代次,使替换可以跨后续压缩继续组合。

追加订单

第一代S₅ ← [L₃, L₂]
第2代S₈ ← [L₇, S₅]
老款租约[L₇, L₃, L₂, B₀]
rewritten[S₈, B₀]

引脚重叠时间线

不存在未固定的瞬间。

  1. 1旧租约持有原始层OLDNEW
  2. 2获取新的替代品租约OLDNEW
  3. 3静止和开关挂载OLDNEW
  4. 4验证新的,然后释放旧的OLDNEW

缺少替换

身份;保留现有的租约。

缺少层目录

身份;永远不要构建部分链。

没有更短的链条

身份;重新挂载什么也没有。

静止状态证明

停止每个观察者,然后证明它没有工作区引脚。

该交换机在会话闸门内部运行。读取错误是不确定性的,而不是权限:任何子挂载、转义的命名空间、新任务、cwd/root/fd/map pin 或不可分类的状态都会在不返回点之前干净地阻止迁移。

  1. 01

    首先阅读holder mountinfo。工作区必须是 OverlayFS 并且没有子挂载。

  2. 02

    发现 holder挂载命名空间和会话cgroup 中的任务联合。

  3. 03

    SIGSTOP every observable task, then poll every thread until stopped, exited, or safely traced.

  4. 04

    再次发现。任何新任务都意味着成员资格发生变化,因此尝试会停止。

  5. 05

    检查每个冻结线程的挂载命名空间、cwd、根、文件描述符和内存映射。

FrozenTasks 是一个守卫

在干净的跳过和经过验证的开关上,放下防护发送 SIGCONT。在错误的 PONR 后状态下,防护被故意遗忘,因此没有任务观察到部分挂载。

交互式cwd是一个真正的pin

当前目录位于工作区内部的实时 shell 可以预见地阻止重新挂载。会话保持正确,其旧租约仍然具有权威性。

观察到的延迟

我们可以估计重新挂载时间,但还不是确切的应用程序暂停时间。

保留的走线包裹了完整的会话重新挂载操作。它们建立了空闲会话基线,但不隔离从 SIGSTOP 到 SIGCONT 的间隔,并且不是运行服务可用性 SLA。

观察重新挂载跨度

29 次保留运行中 145 次成功测量

空闲基线 · 不暂停 SLA
p50
5毫秒
p95
6毫秒
汇集p99
7.1 毫秒
max
18毫秒

测量了什么

一个在线空闲会话、一个八层压缩区段、每层 4 KiB,清理宽度为 4。由于各报告不发布 p99,汇总 p99 由原始观测值计算。

用户可能会观察到什么

SIGSTOP pauses application work without closing its sockets. An in-flight request may gain latency or hit its client timeout, but the session is not disconnected. Active-service pause percentiles still need a dedicated SIGSTOP-to-SIGCONT benchmark.

安全上限不是延迟目标

默认冻结握手预算为 500 毫秒,重新挂载runner 超时为 30 秒。 PONR 前不确定性恢复任务; PONR 后的不确定性使它们保持冻结状态,直到会话变为 已销毁,而不是暴露部分切换的挂载。

内核事务

舞台新的,将旧的移到一边,将新的移到位。

runner 进入 holder 的用户和挂载命名空间。它在挂载移动之前恢复隐藏路径掩码,在路径更改期间保留 O_PATH 描述符,并且从不使用延迟分离。

启动时内核闸门

在运行中的内核通过同 upperdir、新 workdir 的探测(包括 whiteout 与不透明目录行为)之前,在线重新挂载保持禁用。未探测或失败的闸门会把压缩降级为仅提交维护。

  1. 01

    提升面膜

    暂时公开 holder挂载命名空间内守护程序拥有的路径。

  2. 02

    准备暂存点

    在会话运行目录下创建 暂存、回滚 和新的同级 workdir。

  3. 03

    挂载新款

    重复使用会话upperdir,供应压缩下层,并为暂存、回滚和OLD打开O_PATH句柄。

  4. 04

    Restore + probe

    在任何移动之前恢复掩码,然后证明暂存根是可读的 OverlayFS挂载。

  5. 05不归路

    移动旧版 → 回滚

    第一个 MS_MOVE 分离可见的工作区挂载。它的成功已经到了不归路。

  6. 06

    移动新 →工作区

    重新打开底层工作区路径并将暂存覆盖移到公共安装点上。

  7. 07

    探头可见 新

    在任何任务恢复之前,通过其保留的描述符验证现在可见的挂载。

  8. 08

    严格卸载 OLD

    删除 OLD-root 描述符并卸载 回滚,无需延迟分离。 EBUSY是停放的,不是强制的。

  9. 09

    报告

    返回first_move_succeeded、mount_verified和一个类:确定性分类的详细原因。

Classification + recovery

两个布尔值决定任务是否可以恢复。

父级对 runner 报告进行分类,而不是从进程退出推断成功。如果报告消失,则将holder的工作区挂载ID与切换前的值进行比较。

runner信号分类租约动作Tasks
first_move = false清洁跳过发布新·保留旧Resume
true · verified = false故障会话公园新直到销毁保持冷冻状态
true · true · switchedMigrated安装新的·释放旧的Resume
真·真·回滚 EBUSY已核实,已停车安装新的·停放旧的Resume
真实 · 真实 · 其他细节故障会话公园新直到销毁保持冷冻状态
报告失踪比较挂载ID相同 = 干净 · 否则有缺陷仅在未更改时恢复

manifest更换前

老款manifest仍然具有权威性。进程内失败会删除暂存或升级的 S 目录。

存储后提交

即使每次重新挂载都跳过,压缩仍然持久有效。旧层会继续保持租约,直到会话释放或迁移。

守护进程死亡

仅 RAM 替换和停放租约状态消失。 holder PDEATHSIG和启动收割除死会话; manifest 恢复保持故障关闭状态。

目前的实施真相

自动压缩是内部调度器,不是另一套存储算法。

架构章节的零参数手册操作仍然存在。当配置的 active-层策略匹配时,当前代码还会从单工作后台引擎运行相同的操作。

产品配置

100 active layers

随附的 prd.yml 启用了 squash_at_n_layers: 100。

架构默认值

disabled when omitted

没有策略的自定义配置不会创建队列和工作线程。

比赛规则

active ≥ threshold

B、L 和之前的 S层计数。配置的最小值为 3。

唤醒信号

startup + real commit

压缩创建的 no-op 发布和 S 层不会递归发出通知。

异步收敛

发布从不等待压缩。

Notify

真正的 L-层提交在审核和会话-销毁 订购后会排队一个便宜的信号。

Coalesce + recheck

有效容量为一的队列合并突发。工作线程读取实时 head,等待共享操作闸门,然后再次读取。

复用压缩流程

相同的计划、展平、提交、重新挂载扫描、遥测和故障语义在后台运行。

阈值并不能保证出块

实时租约边界可以对 manifest 进行分区,因此运行两个原始层是不合格的。工人记录 no_squashable_blocks,保持 head 不变,并等待稍后真正的提交或重新启动 - 没有热重试循环。

重启协议

恢复持久的真理;收获运行时无法存活的状态。

该守护进程不会尝试复活工作区holder 或重建仅 RAM 的租约。父死亡信号使持久的会话可以证明已死亡,而活动的 manifest 仍然具有存储清理的权限。

上菜前一次
  1. 01

    删除导出线轴

    删除 RAM 登记表 消失的导出拥有的假脱机目录。

  2. 02

    探测内核层

    断言所需的内核行为并设置 live-重新挂载功能闸门。

  3. 03

    收获会话

    读取 manager.json,删除包含的运行目录,并重置持久的句柄。

  4. 04

    扫存储

    保持活跃manifest层;删除 暂存 和未引用的非基础工件。

什么幸存下来

原子活动 manifest 和每个引用的不可变层保持持久。它们足以在没有实时工作区或租约登记表 的情况下重建当前的层栈视图。

故意丢失什么

实时 命令 登记表、命名空间 holder、内存中租约、替换和停放重新挂载状态不会恢复。他们拥有的会话收获了;客户用耐用的 head 打造出新鲜的会话。

存储删除失败关闭

丢失、不可读、空或预版本的 manifest 不会删除任何内容。通过有效的保持设置,基本层ID 仍然不会被引导扫描删除。