Finding 1: Identity no longer address-derived - ReplicaAddr.ServerID field added (stable server identity from registry) - BlockVolumeAssignment.ReplicaServerID field added (scalar RF=2 path) - ControlBridge uses ServerID, NOT address, for ReplicaID - Missing ServerID → replica skipped (fail closed), logged Finding 2: Wired into real ProcessAssignments - BlockService.v2Bridge field initialized in StartBlockService - ProcessAssignments converts each assignment via v2Bridge.ConvertAssignment BEFORE existing V1 processing (parallel, not replacing yet) - Logged at glog V(1) Finding 3: Fail-closed on missing identity - Empty ServerID in ReplicaAddrs → replica skipped with log - Empty ReplicaServerID in scalar path → no replica created - Test: MissingServerID_FailsClosed verifies both paths 7 tests: StableServerID, AddressChange_IdentityPreserved, MultiReplica_StableServerIDs, MissingServerID_FailsClosed, EpochFencing_IntegratedPath, RebuildAssignment, ReplicaAssignment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
28 KiB
V2 详细算法设计
日期:2026-03-27 状态:详细算法草案 读者:架构设计、simulator、prototype、实现负责人
1. 文档目的
这份文档不是 CEO 综述,也不是 phase 汇报。
它的目标是把 sw-block V2 的核心算法写成一份更接近“协议规格”的设计文档,回答下面几个问题:
- 系统里的正式状态对象是什么
- 写路径如何推进
- 不同
sync mode如何决定是否可以返回成功 - replica 掉队后如何决定
catch-up还是rebuild - primary crash / failover / epoch bump 后哪些状态仍然有效
- 什么叫做“允许的 WAL-first 可见性”,什么叫做不允许的幽灵状态
本文默认接受一个核心前提:
- 已 durable 的 WAL 是系统正式状态的一部分
因此:
visible state可以领先于checkpoint- 只要该状态仍然有合法的恢复依据,它就不是 bug
真正的错误是:
acked state > recoverable state- 或
visible state > recoverable state
2. 设计目标
V2 的目标不是把所有事情都交给 WAL。
V2 的目标是:
- 用
WAL提供严格顺序、短间隙恢复和明确的 durable history - 用
extent + checkpoint/snapshot提供稳定读镜像和长距离恢复基线 - 用显式的
epoch + sender + RecoverySession管住恢复 authority - 用显式的
CommittedLSN管住对外承诺边界 - 用显式的
catch-up/rebuild分类避免长期模糊状态
3. 核心对象
3.1 LSN 边界
-
HeadLSNprimary 当前已分配并写入本地 WAL 的最高 LSN -
CommittedLSN当前对外可承诺、可用于 failover / recovery 目标的 lineage-safe 边界 -
ReplicaReceivedLSNreplica 已收到并追加的最高 LSN,不代表 durable -
ReplicaFlushedLSNreplica 已 durable 的最高 LSN,是 sync/barrier 判断的正式依据 -
CheckpointLSN当前 checkpoint / base snapshot 所代表的稳定物化边界 -
RecoverableLSN某节点 crash 之后,仍可由checkpoint + retained WAL或等价机制恢复出的最高边界
3.2 存储层对象
-
Active WAL当前保留的 WAL 历史,用于:- 顺序写入
- crash recovery
- short-gap catch-up
-
Extent当前运行中的块视图,可以比 checkpoint 更新 -
Checkpoint / Snapshot一个真实历史点的稳定镜像,用于:- rebuild base
- 长距离恢复
- GC / retention 的正确边界
3.3 协议层对象
-
Epochprimary lineage / fencing 边界。旧 epoch 的消息和恢复结果不能修改当前系统。 -
Senderprimary 上对每个 replica 的唯一发送 authority。 -
RecoverySession对一个 replica 的一次有界恢复尝试。它必须被:- 一个 sender 拥有
- 一个 epoch 约束
- 一个 session ID 唯一标识
-
AssignmentIntentorchestrator 对 sender group 的意图输入。它决定:- 哪些 replica 被保留
- 哪些 replica 要恢复
- 恢复目标和 epoch 是什么
4. 全局结构图
flowchart TD
client[Client]
primary[Primary]
wal[Active WAL]
extent[Live Extent]
flusher[Flusher]
cp[Checkpoint Snapshot]
senderGroup[SenderGroup]
r1[Replica Sender R1]
r2[Replica Sender R2]
repl1[Replica 1]
repl2[Replica 2]
orchestrator[Volume Orchestrator]
client --> primary
primary --> wal
primary --> senderGroup
primary --> extent
senderGroup --> r1
senderGroup --> r2
r1 --> repl1
r2 --> repl2
wal --> flusher
flusher --> extent
flusher --> cp
orchestrator --> primary
orchestrator --> senderGroup
这个结构的关键点是:
- 前台写路径只负责产生顺序和推进正式边界
- 每个 replica 的恢复执行由自己的 sender/session 管理
- flusher / checkpoint 负责物化和长期恢复基线
- orchestrator 负责 volume 级 admission、epoch 和 failover
5. 数据真相与可见性规则
V2 必须明确区分 5 种状态:
visible stateWAL durable statereplica durable statecheckpointed statecommitted / acked state
它们不是同一个概念。
5.1 允许的情况
下列情况是允许的:
visible state > CheckpointLSNWAL durable state > CheckpointLSN- replica 在后台追赶,extent 已经更“新”
只要:
- crash 后这些状态仍有恢复依据
- client 所收到的 ACK 语义没有被夸大
5.2 禁止的情况
下列情况是 V2 必须阻止的:
AckedLSN > RecoverableLSNVisibleLSN > RecoverableLSN- 根据 socket/send progress 而不是 durable progress 给出 sync 成功
- replica 实际已不可能 catch-up,却长期停留在
CatchingUp
5.3 可执行 invariant
V2 的 simulator / prototype 至少应围绕下面三条 invariant 展开:
-
RecoverabilityInvariant所有已 ACK 的边界在 crash / restart / failover 后仍必须可恢复 -
VisibilityInvariant所有向用户暴露的状态都必须有合法恢复来源 -
CatchUpLivenessInvariantreplica 要么收敛,要么显式升级为NeedsRebuild
6. 写路径算法
6.1 写路径目标
写路径要满足两件事:
- 维持 primary 本地严格顺序
- 不把复杂恢复逻辑塞进前台热路径
6.2 写入步骤
对一次逻辑写入 Write(block, value),V2 的基本步骤是:
- primary 检查自己是否拥有当前
epoch的 serving authority - 分配下一个单调递增的
LSN - 生成
WALRecord{LSN, Epoch, Block, Value, RecoveryClass} - 本地 durable append 到
Active WAL - 更新 primary 的运行期可见状态
- 把该记录放入每个 replica 的 sender queue
- 根据 volume 的
sync mode决定是否需要 barrier / durable quorum / all replicas - 在满足对应 mode 条件后返回成功,否则阻塞或失败
6.3 写路径图
sequenceDiagram
participant C as Client
participant P as Primary
participant W as Local WAL
participant SG as SenderGroup
participant R as Replica Sender
participant X as Replica
C->>P: Write(block, value)
P->>P: allocate LSN
P->>W: durable append WALRecord
P->>P: update live visible state
P->>SG: enqueue record for each replica
SG->>R: ordered per-replica send
R->>X: replicate WALRecord
X->>X: append and later flush
P->>P: evaluate sync mode
P-->>C: ACK or block/fail
6.4 写路径伪算法
OnWrite(req):
require PrimaryState == Serving
require LocalEpoch == VolumeEpoch
lsn = AllocateNextLSN()
rec = BuildWALRecord(lsn, req, epoch)
DurableAppendLocalWAL(rec)
ApplyToLiveVisibleState(rec)
EnqueueToReplicaSenders(rec)
if Mode == best_effort:
return success after local durable WAL
if Mode == sync_all:
wait until every required replica reports durable progress >= lsn
else timeout/fail
if Mode == sync_quorum:
wait until true durable quorum reports progress >= lsn
else timeout/fail
AdvanceCommittedLSN(lsn) only at the correct lineage-safe boundary
return success
7. 三种 sync mode
7.1 best_effort
语义:
- 只要求 primary 本地达到 durability point
- replica 可以异步恢复
- 不应对 client 承诺多副本 durable
适合:
- 后台恢复优先
- 临时 degraded 仍继续服务
7.2 sync_all
语义:
- 所有 required replica 都必须在目标
LSNdurable - 不能因为“看起来网络还活着”而提前 ACK
- 一旦达不到条件,应阻塞或失败,不能偷偷降级
7.3 sync_quorum
语义:
- 必须形成真实 durable quorum
- 只统计满足当前 epoch 和 state 资格的 replica
- 不能只数 healthy socket 或 sender 已发送
7.4 sync 决策图
flowchart TD
start[Write at LSN L]
local[Primary local WAL durable]
mode{Sync Mode}
best[Return success]
allCheck{All required replicas durable >= L}
quorumCheck{Durable quorum >= L}
success[Return success]
block[Block or fail]
start --> local
local --> mode
mode -->|best_effort| best
mode -->|sync_all| allCheck
mode -->|sync_quorum| quorumCheck
allCheck -->|yes| success
allCheck -->|no| block
quorumCheck -->|yes| success
quorumCheck -->|no| block
7.5 sync mode 的正式原则
所有 sync mode 都必须遵守:
- durable truth 只来自
ReplicaFlushedLSN - 不来自
ReplicaReceivedLSN - 不来自 send queue
- 不来自 transport 连接存活
8. 本地 WAL / extent / checkpoint 算法
V2 必须把本地状态推进拆成三个动作:
WAL appendextent materializationcheckpoint advancement
8.1 本地生命周期
stateDiagram-v2
[*] --> WALDurable
WALDurable --> VisibleApplied
VisibleApplied --> ReplicaDurableEligible
ReplicaDurableEligible --> CheckpointMaterialized
CheckpointMaterialized --> CheckpointPublished
note right of WALDurable
Local ordered truth exists
and can participate in recovery
end note
note right of VisibleApplied
New data may be visible
before checkpoint catches up
end note
note right of CheckpointPublished
Stable base image advances
and old WAL may become recyclable
end note
8.2 关键规则
extent可以比checkpoint更新- 但 crash 后真正可恢复的是:
checkpoint- 加上仍被保留、可 replay 的 WAL
所以:
VisibleLSN > CheckpointLSN可以合法- 但
VisibleLSN > RecoverableLSN绝不合法
8.3 flusher / checkpoint 职责
flusher 不负责决定 ACK。
flusher 负责:
- 将 WAL-backed dirty state 物化到 extent
- 产生新的 checkpoint / snapshot
- 在有了新的稳定基线后,帮助推进 WAL retention / GC 边界
- 保证被对外承诺的数据仍然可恢复
9. Replica 正常复制算法
9.1 steady-state
每个 replica 有一个稳定 sender。
sender 负责:
- 顺序发 WAL record
- 发 barrier
- 处理 reconnect / handshake
- 执行 catch-up / rebuild 尾部 replay
- 拒绝旧 session 的结果
9.2 正常复制步骤
- sender 从 queue 取出下一个 record
- 按顺序发给 replica
- replica 验证 epoch 和顺序
- replica 先 append 到本地 WAL 或等价 durable log
- replica 更新
receivedLSN - 若收到 barrier,则等待本地 durable progress 达到目标
- replica 更新
flushedLSN - 返回
BarrierResp
9.3 steady-state 图
sequenceDiagram
participant P as Primary Sender
participant R as Replica
P->>R: WALRecord(LSN=n)
R->>R: validate epoch and order
R->>R: append local log
R->>R: receivedLSN = n
P->>R: BarrierReq(LSN=n)
R->>R: wait until durable >= n
R->>R: flushedLSN = n
R-->>P: BarrierResp(flushedLSN=n)
10. 恢复总算法
10.1 恢复的正式入口
当 replica 不再能作为正常 InSync 复制对象时,系统不能直接“猜测”怎么修。
必须走明确的恢复入口:
- orchestrator 识别该 replica 已失去 sync eligibility
- 对该 replica 发出新的
AssignmentIntent - sender 建立或 supersede 一个新的
RecoverySession - 通过 handshake 获得该 replica 的正式 durable 点
- 对恢复路径做显式分类
10.2 handshake 输入
一次恢复决策至少需要:
ReplicaFlushedLSNCommittedLSNRetentionStartLSN- 当前
epoch - endpoint/version 视图
10.3 恢复分类
V2 把恢复明确分成三类:
-
ZeroGapReplicaFlushedLSN == CommittedLSN -
CatchUpgap 在 recoverable window 内,或 replica 需要先 truncate divergent tail -
NeedsRebuildgap 超过 retention / payload / snapshot 可恢复边界
10.4 恢复决策图
flowchart TD
hs[HandshakeResult]
zero{ReplicaFlushedLSN == CommittedLSN}
ahead{ReplicaFlushedLSN > CommittedLSN}
recoverable{Gap provably recoverable under retention/reservation}
zeroGap[ZeroGap]
truncate[CatchUp with truncation]
catchup[CatchUp]
rebuild[NeedsRebuild]
hs --> zero
zero -->|yes| zeroGap
zero -->|no| ahead
ahead -->|yes| truncate
ahead -->|no| recoverable
recoverable -->|yes| catchup
recoverable -->|no| rebuild
10.5 为什么用 CommittedLSN
恢复目标必须是 CommittedLSN,而不是 HeadLSN。
原因是:
HeadLSN可能包含还未形成正式外部承诺的尾部- failover / promotion 的安全边界必须围绕 committed prefix
- 否则会把“primary 看起来更新”误当成“lineage-safe truth”
11. Catch-up 算法
11.1 进入条件
只有当下面条件同时满足时,才允许进入 CatchUp:
- session authority 有效
- 当前 epoch 未失效
- endpoint/version 未变化
- gap
(ReplicaFlushedLSN, CommittedLSN]可恢复 - 对应恢复窗口已被 reservation pin 住
11.2 执行步骤
- session 进入
Connecting - handshake 后进入
Handshake - classifier 返回
CatchUp - session 设置:
StartLSNTargetLSN- 如有需要,
TruncateRequired
- sender 开始按顺序回放 WAL records
- replica durably 应用并持续汇报进展
- sender 更新
RecoveredTo - 若达到
TargetLSN且 barrier 条件满足,则 session 完成 - replica 进入
InSync或进入短暂PromotionHold
11.3 catch-up 状态图
stateDiagram-v2
[*] --> Connecting
Connecting --> Handshake
Handshake --> ZeroGap
Handshake --> CatchingUp
Handshake --> NeedsRebuild
Handshake --> Truncating
Truncating --> CatchingUp
CatchingUp --> PromotionHold
PromotionHold --> InSync
CatchingUp --> NeedsRebuild
11.4 catch-up 失败规则
以下任何情况都必须终止当前 catch-up:
- reservation 失效
- payload / WAL 保留条件失效
- epoch bump
- endpoint change
- session 被 supersede
- 长时间无净进展
一旦终止,必须:
- 拒绝旧 session 的后续结果
- 根据原因进入
NeedsRebuild或等待新的 assignment
12. Rebuild 算法
12.1 何时进入 rebuild
下列情况进入 NeedsRebuild:
ReplicaFlushedLSN + 1 < RetentionStartLSN- 历史 payload 不再可解析
- 对应 snapshot / base image 不存在或无法 pin 住
- catch-up 期间 recoverability 条件丢失
12.2 rebuild 步骤
- orchestrator 为该 replica 发出 rebuild assignment
- sender 建立新的 rebuild session
- primary 选择一个真实
snapshotCpLSN - pin 住:
- snapshot/base image
snapshotCpLSN之后的 tail replay window
- replica 安装 base image
- sender 从
snapshotCpLSN + 1开始 replay 到目标TargetLSN - barrier 确认 durable reach
- replica 进入
PromotionHold/InSync
12.3 rebuild 图
sequenceDiagram
participant O as Orchestrator
participant P as Primary
participant S as Sender
participant R as Replica
O->>S: AssignmentIntent(rebuild)
S->>P: choose snapshotCpLSN
P->>P: pin snapshot and tail replay window
S->>R: install base snapshot
R->>R: load snapshot(cpLSN)
S->>R: replay WAL tail (cpLSN, target]
R->>R: durable apply
R-->>S: barrier/progress reached
S->>O: rebuild completed
13. RecoverySession 与 authority 算法
13.1 为什么要有 RecoverySession
块设备前端看起来没有“session”概念,但恢复执行内部必须有一个 bounded object。
否则无法明确回答:
- 谁拥有这次恢复尝试
- 哪个结果是新的,哪个是晚到的旧结果
- endpoint 变了之后旧连接还能不能继续生效
- epoch bump 后旧 catch-up 结果还能不能落地
13.2 authority 规则
一个恢复 API 调用只有同时满足下面条件才有效:
- sender 当前仍存在
- sender 未 stopped
- sender 当前 session 不为空
sessionID与当前 active session 一致- session 仍处于 active phase
- sender 的 epoch 与 volume epoch 一致
- endpoint/version 未变化
13.3 authority 图
flowchart TD
op[Recovery Operation]
stopped{Sender stopped?}
hasSession{Active session exists?}
idMatch{Session ID matches?}
phaseOk{Phase valid?}
epochOk{Epoch still current?}
endpointOk{Endpoint still current?}
allow[Apply mutation]
reject[Reject as stale/invalid]
op --> stopped
stopped -->|yes| reject
stopped -->|no| hasSession
hasSession -->|no| reject
hasSession -->|yes| idMatch
idMatch -->|no| reject
idMatch -->|yes| phaseOk
phaseOk -->|no| reject
phaseOk -->|yes| epochOk
epochOk -->|no| reject
epochOk -->|yes| endpointOk
endpointOk -->|no| reject
endpointOk -->|yes| allow
14. Failover / promotion 算法
14.1 触发条件
当 primary lease 丢失、节点 crash 或被明确 demote 时,需要 volume 级 failover。
这不是单个 replica 的本地状态迁移,而是:
- 整个 volume lineage 重新定根
14.2 failover 步骤
- 旧 primary 丧失 authority
- volume
Epoch++ - 选择 promotion candidate
- candidate 必须满足:
- running
- epoch 可对齐
- state 允许提升
FlushedLSN >= CommittedLSN
- 新 primary 开始 serving
- 旧 primary 相关的 recovery sessions 全部失效
- 其余 replicas 相对新 primary 重新做 handshake / classify
14.3 failover 图
flowchart TD
old[Old Primary loses lease]
bump[Epoch++]
choose[Choose promotion candidate]
eligible{Candidate has committed prefix?}
promote[Promote to new primary]
invalidate[Invalidate old sessions/messages]
reclassify[Reclassify all replicas]
fail[No safe candidate]
old --> bump
bump --> choose
choose --> eligible
eligible -->|yes| promote
eligible -->|no| fail
promote --> invalidate
invalidate --> reclassify
14.4 promotion 的原则
默认规则应当是保守的:
- 宁可没有 candidate,也不要提升一个不具备 committed prefix 的节点
否则最危险的错误就是:
- 用户以为已 durable / 已 ACK 的数据,在 failover 后找不到
15. Crash recovery 语义
15.1 primary 本地 crash
primary restart 后必须能够根据:
- 最近 checkpoint
- retained WAL
重建出新的运行状态。
15.2 重要边界
必须允许:
visible state > checkpoint
但必须保证:
- 所有已 visible 的状态都有合法恢复来源,或者 crash 后不会再被当作正式状态
15.3 crash 语义图
flowchart TD
run[Running state]
cp[CheckpointLSN = C]
wal[Retained WAL covers (C, R]]
crash[Crash]
restart[Restart]
replay[Replay retained WAL]
recover[Recoverable state up to R]
illegal[Illegal: visible/acked beyond recoverable]
run --> cp
run --> wal
cp --> crash
wal --> crash
crash --> restart
restart --> replay
replay --> recover
run --> illegal
16. Simulator 应重点验证的算法义务
V2 如果要进入更真实实现,simulator 至少要系统证明以下几类事情:
16.1 ACK 可恢复性
flush/sync ACK返回成功后- crash / restart / failover 后仍可恢复到该边界
16.2 可见性合法性
- 运行期看到的新数据
- 必须来自 WAL durable 或 checkpoint lineage
- 不能出现 visible-but-unrecoverable state
16.3 Catch-up 收敛性
- replica 不能无限期
CatchingUp - 要么收敛,要么显式
NeedsRebuild
16.4 历史正确性
- 对目标
LSN的恢复结果必须匹配 reference state - 不能拿 current extent 伪造旧历史
16.5 stale authority fencing
- epoch 变化
- endpoint 变化
- session supersede
- late barrier / late catch-up result
都不能修改当前 truth
17. 方向微调:第一性思考与 Mayastor 启发
这一节不是推翻 V2,而是回答一个更关键的问题:
- 在确认
V2大方向正确之后,是否还需要收紧目标、减少复杂恢复逻辑?
当前判断是:
- 需要微调,但不需要换方向
17.1 思维过程:先看 block 的第一性问题
判断 V2 是否该微调,不能先从“现有代码已经写了什么”出发,而应先问 block 产品最不可回避的本质是什么。
从第一性原理看,block 的核心不是:
- volume 编排
- 控制面外形
- 接口包装
而是下面四件事:
write在什么时候算成立flush/fsync ACK到底承诺了什么- failover 后用户已收到 ACK 的边界是否仍然成立
- replica 永远不完全同步时,系统如何定义真实可承诺边界
这四件事如果没有被做硬,那么无论产品外形多完整,都还不能算真正可信的 block 产品。
因此,V2 最值得坚持的主轴仍然是:
CommittedLSN- durable progress
RecoverySession- stale fencing
CatchUp / NeedsRebuild / Rebuild
17.2 为什么还要微调
虽然主轴正确,但 V2 仍然存在一种风险:
- 为了尽量避免
rebuild,把catch-up做得越来越聪明
这会带来新的债:
- recovery session 生命周期过长
- target 跟着 live head 漂移
- 一个 lagging replica 长期消耗 primary 的 WAL retention
- recover 与 live WAL 并存时形成双流复杂度
- 系统长期停留在
CatchingUp,却没有真正恢复
也就是说,V2 的风险不在于方向错,而在于:
- 可能在正确方向上走得过深,重新长出不必要的复杂 transmission
17.3 Mayastor 的第一性启发
Mayastor 给 sw-block 的最大启发,不是某个具体的 WAL 算法,而是另一种产品化思维:
- block 产品不必把所有恢复复杂度都压在增量追赶上
rebuild不是羞耻路径,而是正式主路径- volume / replica / target / control plane 应该是明确对象
- 系统要接受“某些副本不值得继续低成本追赶”的现实
从这个角度看,Mayastor 更接近:
- block 产品的工程外形
- volume 服务的组织方式
- 明确的 replica lifecycle
但 Mayastor 并没有替代 V2 的核心语义问题:
flush ACK到底何时成立- failover 后 committed truth 如何保住
- stale authority 如何 fencing
所以正确的吸收方式不是“改走 Mayastor 路线”,而是:
- 保留
V2的语义内核 - 吸收
Mayastor对正式 rebuild 路径和产品组织的启发
17.4 微调结论:用正式 rebuild 替换过度复杂的 catch-up
因此,当前最合理的方向微调是:
- 不把
CatchUp当作“尽量避免 rebuild 的万能恢复手段” - 而把它收紧为:
- 短 gap
- 有界 target
- 有时间预算
- 有进展预算
- 有 recoverability/reservation 预算
一旦超出这些边界,就应该:
- 明确终止当前
CatchUp - 进入
NeedsRebuild - 再走正式
Rebuild
这不是保守,而是更接近成熟 block 产品的现实:
CatchUp是便宜路径Rebuild是正式路径- 不能为了少做 rebuild,而把系统拖进长期复杂恢复状态
17.4A catch-up 与 rebuild 的职责划分
这里需要进一步把 CatchUp 与 Rebuild 的职责说清楚,否则实现很容易再次滑回“尽量避免 rebuild,所以不断扩大 catch-up 能力”的旧习惯。
CatchUp 不应被理解为一个与 Rebuild 对等、且可以无限扩展的恢复体系。更准确地说:
CatchUp是KeepUp的放松态- 它只负责短 gap、短期、有界、可证明可恢复的 WAL replay
- 它依赖 replica 当前 base 仍然可信
- 它依赖 primary 仍保留
(ReplicaFlushedLSN, TargetLSN]所需历史 - 它的价值在于成本明显低于
Rebuild
一旦这些前提不再成立,系统不应继续把复杂度堆入 CatchUp,而应显式进入 NeedsRebuild,再走正式 Rebuild。
Rebuild 则应被视为更 general 的恢复框架。它不假设 target replica 当前状态仍可直接追赶,而是通过一个可信 base 把 replica 带回某个明确目标点:
- 冻结
TargetLSN - 选择并 pin 一个可信
base - 将 replica 恢复到该
base - 如有需要,补齐
(BaseLSN, TargetLSN]的 tail - 通过 durable barrier 确认 replica 已达到
TargetLSN - 再接回
KeepUp / InSync
因此,full rebuild 与 partial rebuild 不应被理解为两套不同协议,而应被理解为同一 Rebuild 合同下对 base 和传输量的不同选择:
full rebuild- 下载完整 pinned snapshot / base image
- 必要时再补 tail
partial rebuild- replica 已有较老但可信的 base
- 通过
bitmap/diff/snapshot + tail只补足达到 target 所需的数据
两者共同的正确性前提都是:
- 恢复目标必须是冻结的
TargetLSN - 恢复依赖的 snapshot / base 必须被 pin 住
- 不允许直接用持续变化的 live extent 作为历史目标点数据来源
这一定义意味着:
CatchUp应继续收紧为短 gap、低成本、强约束路径Rebuild应被当作正式主恢复路径,而不是失败后的羞耻 fallback- 后续优化(例如
bitmap/ range rebuild)应优先被建模为Rebuild的优化分支,而不是继续把复杂度堆入CatchUp
17.5 建议收紧的具体点
1. 收紧 CatchUp
CatchUp 应只覆盖:
- 短 outage
- 短 gap
- recoverability 清楚
- 成本明显低于 rebuild
不应覆盖:
- 长时间追 moving head
- 长时间阻塞 WAL GC
- 长时间无净进展
2. 恢复 contract 只追 bounded target
一个 recovery session 只对 (R, H0] 负责:
R = ReplicaFlushedLSNH0 = 本次 primary 分配的目标边界
> H0 的 live WAL 不应让当前 session 的完成条件漂移。
3. recover -> keepup 必须有明确 handoff
session 完成后:
- 释放 reservation 和历史恢复债
- 经过
PromotionHold或等价稳定条件 - 再回
KeepUp / InSync
而不是让 recovery session 无限延长为长期 keepup。
4. Rebuild 升格为一级路径
Rebuild 不应只被视为:
- catch-up 失败后的被动补丁
而应被视为:
- 长 gap
- 高成本恢复
- recoverability 不稳定
- 持续 tail-chasing
时的正式恢复选择。
17.6 微调后的核心判断
微调后的 V2 不应再被理解成:
- “把 WAL 恢复做得越来越聪明”
而应理解成:
- 把 block 的真实同步边界做硬
- 把
CatchUp收紧成短 gap、低成本、有限时间的 contract - 把
Rebuild升格成正式主路径 - 把 Smart WAL 等更高复杂度扩展延后到基础复制契约稳定之后
一句话总结就是:
V2不换方向,但要从“雄心更大”微调为“边界更硬、目标更窄、恢复更有预算”。
18. 推荐的实现切片
为了让实现顺序和算法风险一致,推荐切片如下:
Slice 1: Sender / RecoverySession authority
先解决:
- 每 replica 一个 sender
- 一次只允许一个 active recovery session
- stale session result rejection
Slice 2: Outcome classification + assignment orchestration
再解决:
ZeroGap / CatchUp / NeedsRebuildAssignmentIntent- sender group reconcile
Slice 3: Historical recoverability model
再把:
CommittedLSN- WAL retention
- checkpoint/snapshot base
- recoverability proof
做成可执行模型
Slice 4: Crash-consistency simulator
最后重点加强:
visible staterecoverable stateacked state- flusher / checkpoint / replay 之间的边界
19. 总结
V2 的真正算法核心,不是“有一个 WAL”这么简单。
它真正要建立的是一整套明确边界:
- 用
WAL表示顺序与近期历史 - 用
CommittedLSN表示外部承诺边界 - 用
RecoverySession表示恢复 authority - 用
catch-up/rebuild表示恢复分类 - 用
checkpoint + replay表示 crash 后正式可恢复状态
因此 V2 可以允许:
WAL-first visibility
但绝不能允许:
ACK-first illusionvisible-but-unrecoverable statestale authority mutates current lineage
如果这几个边界都被 simulator、prototype 和真实 runner 分层证明,那么 V2 才有资格从“架构方向”进入“真实引擎实现”。