Kiwi Raft Apply Correctness:父 Issue 与子 Issue 拆分方案 #330
AlexStocks
started this conversation in
General
Replies: 1 comment
P0 Raft Apply Correctness Issue 树已根据本 Discussion 的父/子 Issue 方案创建 GitHub Issues:
执行顺序:先 #333;接口冻结后 #334 与 #336 可并行;随后 #335、#337;再推进 #338/#339;#340 测试框架可提前开始,但最终验收依赖 #333~#339。 所有 Issue 均带 |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
1. 为什么必须从 VectorSet 方案中拆出
VectorSet 依赖以下通用能力:
这些能力不是 VectorSet 专属。String、Hash、Set、List、ZSet、TTL、DEL 和后续新增数据类型都依赖同一套状态机正确性。
FLUSHDB/FLUSHALL因为跨多个 RocksDB instance,单独使用 cluster feature gate 管理,在 database epoch 方案完成前不进入普通 logical mutation apply。因此:
2. 当前源码证据
以下结论来自当前仓库源码,不是对未来实现的推测。
2.1
last_applied在业务写入前推进当前状态机在处理每条 entry 时,先更新内存字段:
然后才执行
apply_binlog():src/raft/src/state_machine.rs:237-245如果 RocksDB 写入失败,当前代码把错误转换成
BinlogResponse::error,但仍继续处理后续日志:src/raft/src/state_machine.rs:243-257这不满足“只有业务状态成功提交后才能推进 applied frontier”的状态机不变量。
2.2
last_applied目前只存在于状态机内存KiwiStateMachine的last_applied是普通内存字段:src/raft/src/state_machine.rs:181-188snapshot 会携带 last log id,但普通 apply 路径没有定义 durable state machine metadata 与业务数据之间的恢复协议。
2.3 当前 Binlog 只表达物理写入
当前日志类型只有:
src/conf/src/raft_type.rs:23-29Binlog只包含一个slot_idx和一组物理 CF entry:src/conf/src/raft_type.rs:31-46当前 storage apply 只支持 CF 0~5,并且没有使用传入的
_raft_log_index:src/storage/src/storage.rs:485-5232.4 RESP 命令到 Raft 的执行链尚未闭环
当前
Cmdtrait 暴露:src/cmd/src/lib.rs:235-241当前只有
SET标记CmdFlags::RAFT并实现to_binlog():src/cmd/src/set.rs:34-95DEL和EXPIRE仍是普通本地写命令:src/cmd/src/del.rs:32-78src/cmd/src/expire.rs:32-86全仓搜索没有发现 RESP 命令执行路径实际调用
Cmd::to_binlog()或Cmd::needs_raft()的闭环。2.5 多 RocksDB instance checkpoint 当前依次创建
Storage::create_checkpoint()依次为每个 RocksDB instance 创建 checkpoint:src/storage/src/storage.rs:407-425当前 snapshot builder 捕获
last_applied后直接调用该方法,没有与 state machine apply 共享构建 barrier:src/raft/src/state_machine.rs:427-4773. 目标不变量
本改造完成后,必须能够证明以下不变量。
I1. Applied-after-commit
I2. Fatal errors stop apply
I3. Business rejection is deterministic
I4. Crash replay is safe
I5. Slot and instance are revalidated
I6. One mutation does not silently cross RocksDB instances
I7. Snapshot represents one applied frontier
4. 已确认的目标模型
4.1 Apply 结果分类
Applied和BusinessRejected都表示该 committed entry 已被确定性处理,可以在持久化对应结果后推进 applied frontier。StateMachineFatalError只能通过Result::Err返回,不能作为普通 outcome 持久化后继续下一条日志。成功结果至少包括:
业务错误使用稳定 code,而不是把 RESP 文本直接放进 Raft 层:
Raft 层不依赖
RespData。命令层负责把 typed result 转成 RESP2/RESP3。持久化结果选择 bounded、versioned、RESP-independent 的 typed outcome,而不是只保存一个无法恢复成功返回值的 result code:
要求:
format_version,decoder 拒绝未知未来版本;VADD/VREM的整数结果;4.2 ApplyContext
所有 logical mutation apply 接收统一上下文:
其中
leader_time_ms只用于需要确定性时间输入的 mutation,例如相对 TTL 转绝对 TTL 后的校验;状态机不能自行用本地当前时间决定副本相关的业务分支。4.3 目标 instance 内的 apply marker
由于 Kiwi 使用多个独立 RocksDB instance,全局
last_applied无法天然与任意目标 instance 的业务数据放入同一个 WriteBatch。每个 RocksDB instance 新增内部
ApplyMarkerCF。每条 mutation 在目标 instance 的同一个 WriteBatch 中写入:记录:
要求:
ApplyMarkerCF必须位于目标业务 RocksDB instance,不能用独立 Raft state store 代替,因为后者无法与业务 mutation 组成同一个 RocksDB WriteBatch。独立 Raft state store 只保存 durable global frontier、membership 和 snapshot metadata。4.4 Durable global applied frontier
全局状态机 metadata 至少包含:
提交顺序必须是:
禁止:
允许的崩溃窗口是:
重启后通过 apply marker 安全重放并补齐 global frontier。
5. 父 Issue 草案
标题
背景
Kiwi 当前 OpenRaft state machine 在业务写入前更新内存
last_applied,并把 storage apply 错误包装成普通BinlogResponse后继续处理后续日志。同时,RESP 命令到 Raft proposal 的通用执行链、durable applied frontier、多 RocksDB instance 下的 crash replay 协议和 typed business result 尚未形成闭环。这些问题会影响所有需要跨 CF 原子 mutation、状态相关返回值、TTL 和多实例 snapshot 的数据类型。VectorSet 只是第一个集中暴露这些基础能力缺口的复杂类型,因此本 Issue 独立于 VectorSet 跟踪。
目标
非目标
FLUSHDB/FLUSHALL;完成标准
last_applied不在业务 commit 前推进;6. 子 Issue 拆分
子 Issue 1:修正 apply 顺序与 fatal error 传播
建议标题:
范围:
KiwiStateMachine::apply()顺序;StorageError;验收:
last_applied不推进;子 Issue 2:持久化 state machine metadata
建议标题:
范围:
DurableStateMachineMetaformat;last_applied和 membership;验收:
last_applied;子 Issue 3:实现 per-log apply marker 与结果重放
建议标题:
范围:
ApplyMarkerCF;验收:
子 Issue 4:闭环 RESP 命令到 Raft proposal 和 typed response
建议标题:
范围:
CmdFlags::RAFT和Cmd::to_binlog()建立实际调用方,或用统一 logical mutation API 替代;验收:
子 Issue 5:强化 slot 与多 RocksDB instance apply 不变量
建议标题:
范围:
验收:
子 Issue 6:定义 TTL 与通用 key 生命周期的确定性 mutation 契约
建议标题:
范围:
DEL、EXPIRE/PEXPIRE/EXPIREAT、PERSIST的 logical mutation;FLUSHDB/FLUSHALL在 proposal 前返回稳定的UnsupportedInCluster,不得退化为本地物理 flush;验收:
FLUSHDB/FLUSHALL在独立 feature gate 打开前确定性拒绝,且不修改任何 instance;子 Issue 7:建立多实例 snapshot build barrier 与 replay 契约
建议标题:
范围:
last_applied=L;验收:
子 Issue 8:补齐故障注入、三节点恢复与可观测性
建议标题:
范围:
验收测试至少覆盖:
7. 子 Issue 依赖关系
允许并行:
8. 建议的实现 PR 分组
不建议机械地做到“一子 Issue 一个 PR”。可以按可验证边界分组:
PR0-A:Apply error semantics
覆盖子 Issue 1:
PR0-B:Durable recovery
覆盖子 Issue 2、3:
PR0-C:Command routing and deterministic mutations
覆盖子 Issue 4、5、6:
PR0-D:Snapshot and fault-injection gates
覆盖子 Issue 7、8:
9. VectorSet 可以依赖的 PR0 能力契约
VectorSet 设计不需要重复实现本文件内部机制,只依赖以下稳定接口。
9.1 写入接口
保证:
9.2 Apply 接口
保证:
ApplyMarkerCF;FLUSHDB/FLUSHALL不使用该接口,直到独立的 database epoch 设计与 cluster feature gate 验收完成。9.3 读屏障
VectorSet 只负责从同一个 RocksDB snapshot 读取 MetaCF 和 VectorDataCF。
9.4 Snapshot 契约
VectorSet 只负责:
storage_incarnation;10. VectorSet 与 PR0 的边界
VectorSet 一侧的完整存储、命令和索引设计见 Kiwi Redis 8 Vector Set 存储设计。两篇文档以本节和第 9 节的能力契约为唯一衔接点,不在各自文档中复制另一侧的内部实现。
属于 PR0:
FLUSHDB/FLUSHALL的显式禁用门禁;属于 VectorSet:
11. 父 Issue 关闭门禁
父 Issue 只有在以下条件全部满足后才能关闭:
cargo fmt --check通过;FLUSHDB/FLUSHALLfeature gate 保持关闭,并验证命令在 proposal 前确定性拒绝且不修改任何 instance。12. 明天创建 Issue 时的建议顺序
Raft Global Flush Semantics后续 Issue,以 database epoch 为优先方向;该 Issue 完成前不开放 clusterFLUSHDB/FLUSHALL。13. 关键决策状态
13.1 已确认
ApplyMarkerCF,不使用独立 Raft state store 代替;FLUSHDB/FLUSHALL的优先方向是 database epoch;在独立设计和验收完成前,cluster feature gate 保持关闭并确定性拒绝命令。13.2 仍待确认
剩余决策应在父 Issue Review 阶段确认,不能留到各子 PR 中分别作出互相冲突的选择。前三项属于正确性/兼容性约束,最后一项属于交付边界,不改变已确认的恢复模型。
All reactions