Kiwi Redis 8 Vector Set 存储设计(Phase 1 FLAT / Phase 2 HNSW) #331
AlexStocks
started this conversation in
General
Replies: 2 comments
VectorSet cluster 的 P0 Raft 前置 Issue本 Discussion 的 cluster 能力依赖以下父 Issue: 其 8 个原生 sub-issues 为 #333~#340,覆盖 apply 顺序、durable metadata、ApplyMarkerCF/typed outcome、RESP→Raft 路由、slot/多 RocksDB instance、TTL/key lifecycle、单一 snapshot frontier,以及三节点故障注入验收。 Raft 设计和完整依赖图见 Discussion #330。除独立关闭的 cluster |
0 replies
|
已按确认意见更新 Discussion 正文的 27.1 RocksDB / FAISS IVF 的非阻塞预研策略:
|
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. 背景与结论
Kiwi 需要增加原生向量能力。此前 Discussion #301 和 PR #326 采用的是 Redis Query Engine / RediSearch 路线:在 HASH 字段上挂载向量,通过
FT.CREATE定义 schema,通过FT.SEARCH执行 KNN,并使用 SearchCF 维护二级索引。该路线适合全文检索、JSON、标量过滤和向量混合查询,但会引入全局 schema、文档前缀匹配、二级索引一致性、跨 RocksDB instance 路由和跨分片查询聚合。对于当前目标“给 Kiwi 增加原生向量接口”,成本过高。
本设计改为兼容 Redis 8 原生 Vector Set:
Vector Set 是一个真正的 Redis key,不依赖 HASH、JSON、
FT.CREATE或全局二级索引。本设计的核心结论:
MetaCF + 类型专属 CF + version + slot 路由 + RocksDB 多实例。VectorSet数据类型,不嵌入 Pika/Floyd C++ 引擎。MetaCF + VectorDataCF为权威数据,使用 FLAT 精确搜索。VectorDataCF。DEL只删除 MetaCF,成员数据通过storage_incarnation + generation隔离并由 compaction 后台回收。1.1 与 Raft PR0 的边界
本文不重复设计 Kiwi 的通用 Raft apply、crash replay、typed result、slot/instance 二次校验、确定性 TTL、snapshot barrier 和 ReadIndex 机制。这些能力统一由 Raft Apply Correctness 方案 提供。
VectorSet cluster 模式只依赖该文第 9 节定义的能力契约,并在以下两类门禁都完成后才能开放:
Standalone VectorSet 可以先行开发,但单机验证不能替代 PR0,也不能据此打开 cluster feature gate。
Cluster
FLUSHDB/FLUSHALL使用独立 feature gate。其优先设计方向是 database epoch;在该全局语义完成前,命令必须在 proposal 前确定性拒绝,不能执行本地物理 flush。该门禁只约束 cluster FLUSH,不自动阻断已经通过 PR0 与 VectorSet 门禁的其他 VectorSet 命令。2. 设计目标
2.1 功能目标
Phase 1 支持以下核心命令:
支持的参数:
Phase 1 同时完成:
2.2 一致性目标
写命令
VADD、VREM:读命令
VSIM、VCARD、VDIM、VEMB、VISMEMBER、VINFO:ensure_linearizable()建立读屏障;2.3 非目标
Phase 1 不实现:
Phase 1 只支持 Redis Vector Set 的
NOQUANT数据格式,并要求VADD显式指定NOQUANT。Redis 8 省略量化选项时默认使用 Q8;Kiwi Phase 1 不得把省略选项静默解释为 NOQUANT,而是返回:3. 架构概览
架构不变量:
4. Redis 8 接口兼容范围
兼容性判断以 2026-07-17 检查的 Redis
unstable提交a4d0e84a47717e751cc218bababf02df0b0b5c17中 Vector Set 命令实现为基线。Phase 1 明确记录偏差,不使用“内部同为 FP32”替代命令语义兼容。4.1 VADD
支持:
返回:
规则:
NOQUANT;Q8或BIN返回明确的 unsupported 错误;示例错误:
4.2 VSIM
支持:
默认:
Phase 1:
两者返回相同的精确结果,但代码中仍保留
Approximate与Truth搜索模式。Phase 2 中普通模式切换到 HNSW,TRUTH 永远使用 FLAT。ELE的 element 不存在时返回:key 不存在时返回空数组,key 类型错误返回 WRONGTYPE。
4.3 VREM
返回:
删除最后一个 element 后,整个 key 消失。
4.4 VCARD
4.5 VDIM
ERR key does not exist;4.6 VEMB
Phase 1 不支持:
RAW不是普通 FP32VEMB的别名。Phase 1 必须返回明确的 unsupported 错误,不能用普通 double array 冒充 Redis RAW 结构;正确的 RAW 编码留到 Phase 2 单独实现。普通 VEMB 从 value 中读取 normalized FP32 与 original L2,返回反标准化后的 double array:
4.7 VISMEMBER
4.8 VINFO
Phase 1 保持 Redis 当前九个字段:
quant-typefp32hnsw-m0,表示当前没有 HNSWvector-dimprojection-input-dim0sizemax-level0,FLAT sentinelattributes-count0vset-uidhnsw-max-node-uid0,FLAT sentinel缺失 key 返回 Redis 兼容的 null array,类型错误返回 WRONGTYPE。
Kiwi 私有状态不加入 VINFO,改由
INFO VECTOR暴露,其中必须包含index-kind=flat。上述命令形状和九个字段与 Redis 8 对齐,但
hnsw-m=0、max-level=0和hnsw-max-node-uid=0是 Kiwi Phase 1 对 FLAT 的明确 sentinel,不表示已经具备 Redis HNSW 的完整语义兼容性。4.9 RESP2/RESP3
VSIM WITHSCORES:VINFO:如果 Kiwi 当前 RESP 层缺少 Map,需要增加正式的 RESP3 Map 类型,不能在 RESP3 模式继续返回 RESP2 扁平数组后宣称完全兼容。
5. DataType 与 Column Family
5.1 DataType
保留已有值并追加:
禁止重排已有数字。
补充:
TYPE key返回vectorset,SCAN TYPE vectorset能识别该类型。5.2 Phase 1 CF
新增:
如果实现时现有最后一个 CF index 是 5,则 VectorDataCF 追加为 6;如果仓库已经增加其他 CF,则继续追加,禁止重排。
Phase 1 不创建空的 VectorIndexCF。FLAT 直接扫描 VectorDataCF。
5.3 Phase 2 CF
Phase 2 追加:
它是派生状态,不是权威数据。
6. MetaCF 编码
VectorSet 保留 Floyd/Kiwi 统一 metadata envelope:
16 字节 reserve 解释为:
Phase 1 固定:
Phase 1 的 metric 只允许
COSINE。其他 metric 必须返回明确的 unsupported 错误,不能写入 MetaCF。version字段在 VectorSet 中解释为 per-keygeneration sequence,标识该 key 的一次生命周期;data_revision标识当前 generation 内的权威成员数据版本。两者职责不同:全局持久化的
storage_incarnation: u64存在 schema/database manifest 中,不塞入 16 字节 reserve。它与 generation sequence 共同形成完整 generation identity:集群模式的 generation sequence 使用创建该 key 的 Raft log index;standalone 使用持久化单调生成器。Snapshot 必须保留 storage incarnation。复用旧数据目录或改变 cluster identity 时必须显式校验或迁移,不能静默重新解释旧 generation。
HNSW 的 index kind、index generation、M、quant type、build state、data revision 和构建来源 log id 都是 Phase 2 的本地派生状态,放入
VectorIndexCFmanifest,不进入权威 VectorSet MetaCF。Meta 包含:
7. VectorDataCF 编码
7.1 Key
冻结的 V1 物理布局:
规则:
codec_version = 1;key_len使用u32big-endian,拒绝不能表示的 key 长度;element占剩余全部字节,因此不需要单独长度字段,长度可以为 0;建议定义独立类型:
支持:
访问方式:
encode_prefix正好结束在 generation sequence 之后;encode_full只追加原始 element。使用默认 RocksDB bytewise comparator,不需要自定义数值 comparator。迭代器必须同时设置 lower bound 和由prefix_upper_bound计算的 exclusive upper bound,不能只依赖遇到非 prefix key 后退出。7.2 Value V1
建议:
读取时校验:
8. Canonical Vector
8.1 写入转换
Raft 只复制 canonical bytes 与 original L2,不让各副本重新解析字符串或重新标准化。
8.2 Score
内部 normalized cosine:
距离累加使用 f64,最终 RESP 返回 double。
8.3 稳定排序
9. 多 RocksDB Instance 路由
所有 VectorSet 操作只按 user key 路由一次:
禁止:
多实例扩展单位是不同 VectorSet key,不是同一 VectorSet 的成员。
Phase 1 不支持在线修改
db_instance_num。磁盘元信息必须记录 instance 数,启动时配置不一致应拒绝打开。10. 原子状态转换
10.1 新建 VectorSet
10.2 新增 element
10.3 更新 element
10.4 VREM
最后一个成员删除时,该 generation 的生命周期结束;逻辑 mutation 仍对应下一 data revision,但 MetaCF 已在同一 batch 中删除,无需为已死亡 generation 单独保留 revision。后续同名重建必须取得新的 generation sequence,并从
data_revision=1开始。DEL、TTL 过期和 standaloneFLUSH*同样结束当前 generation。ClusterFLUSHDB/FLUSHALL在 database epoch 方案完成前不进入 mutation apply。所有输入和格式错误必须在创建 WriteBatch 前返回。11. FLAT 查询引擎
接口:
Phase 1:
流程:
复杂度:
禁止把全部向量或全部 hit 收集到 Vec 后全排序。查询 deadline 从进入 FLAT 队列时开始计算,包含 semaphore/线程池排队时间;超时、取消、budget 超限或 decode 错误都必须通过 RAII 释放 snapshot、iterator、permit 和临时 heap,并返回完整错误,不返回部分结果。
12. 并发与 RocksDB Snapshot
VADD/VREM 使用一个 WriteBatch 提交 Meta/member。VSIM 使用同一 RocksDB snapshot 读取 Meta/member,因此只能看到完整的提交前状态或提交后状态。
VSIM 不应在全量扫描期间持有 key 写锁。
13. VectorSet Logical Mutation 契约
本节只定义 VectorSet 专属 payload 和状态转换。通用 proposal、apply marker、durable frontier、fatal/business error 分类、结果重放、slot/instance 校验及 crash recovery 由 Raft PR0 能力契约 提供。
13.1 复制逻辑 mutation,而不是最终物理状态
并发请求可能同时读取相同旧 count。如果 leader 在 proposal 前生成物理 MetaCF count,顺序 apply 后会发生成员数与 count 不一致。Raft 日志只复制:
状态机按日志顺序读取当前 Meta/member,决定新增、更新、删除、count、generation 和 data revision,再在目标 RocksDB instance 内构造一个原子 WriteBatch。HNSW 图和本地派生状态不进入日志。
13.2 VectorSet mutation V1
VectorSet logical mutation 必须有显式版本,并封装在 PR0 的通用
LogicalMutation中:mutation 中不包含最终 count、最终 data revision、HNSW node/edge 或本地 index generation。空
element是合法 payload;decoder 必须区分空 bytes 和字段缺失。13.3 Proposal 前校验
不依赖数据库状态的错误不进入 Raft:
解析成功后只传 canonical FP32 + original L2,所有副本不得重复执行可能产生浮点差异的输入归一化。
13.4 Apply 阶段的 VectorSet 决策
依赖当前状态的结果由状态机决定:
首次创建的
generation_sequence使用ApplyContext.log_id.index;已有 key 的 VADD/VREM 保持 generation 不变。Standalone 走同一 mutation 函数,但从持久化单调生成器取得首次创建的 generation sequence。13.5 唯一 mutation API
Standalone 和 Raft apply 必须复用此入口。Cluster 模式禁止先本地写再 proposal;leader 也只能通过 state machine apply 修改权威数据。
返回值只表达命令所需的 typed result,例如
VADD的新增/更新和VREM的删除/未命中;RESP 编码留在 cmd 层。通用层负责把 typed business error 与 fatal storage/protocol error 分开,VectorSet 不定义第二套错误与重放机制。14. 读一致性
Phase 1 所有 VectorSet 读默认 leader 线性一致:
PR0 的 ReadIndex/linearizable barrier 保证本地状态机追到 committed 点,RocksDB snapshot 保证扫描期间读取稳定本地视图。
Phase 1 不开放默认 follower read。未来可显式增加 eventual consistency 模式。
15. 错误分类
15.1 用户状态错误
例如 WRONGTYPE、dimension mismatch:
15.2 协议/日志损坏
例如:
节点停止正常 apply,并通过 snapshot 或人工恢复。禁止跳过日志。
15.3 RocksDB I/O 错误
WriteBatch commit、CF、磁盘或 checksum 错误:
16. 大 Key 删除与 Compaction
16.1 DEL
VectorSet DEL 前台只删除 MetaCF:
禁止同步扫描全部 VectorDataCF 成员。
删除后旧 generation 成员不可见,新建同名 key 使用新的 generation sequence。
16.2 类型化 del_key
通用 del_key 改为按 metadata type dispatch。VectorSet 使用 O(1) 逻辑删除,不能继续扫描所有 CF。
16.3 Compaction Filter
VectorDataCF 接入 DataCompactionFilter。以下情况删除 member:
无法读取/证明 stale 时保留,不删除。
16.4 后台清理
DEL 后轻量 enqueue:
enqueue 失败不回滚 DEL,由自然 compaction 兜底。
Engine 已有
DeleteRange能力,但它只是可基准验证的性能优化,不是正确性前提。Phase 1 默认仍采用 Meta 删除 + targeted compact + natural compaction;只有证明 range tombstone 对目标 workload、snapshot 和 compaction amplification 有净收益后,才能单独引入DeleteRange。16.5 VREM
VREM 写单个 delete tombstone;最后成员时同时删除 MetaCF。物理 SST 空间由 RocksDB compaction 回收。
17. Snapshot
17.1 构建 barrier
多个 RocksDB instance 的单一 applied frontier、checkpoint barrier、失败回滚和 durable snapshot metadata 全部由 PR0 snapshot 契约 提供。VectorSet 不在自身模块重新实现暂停 apply 或协调 checkpoint 的逻辑。
17.2 Snapshot Meta V2
拒绝不支持的未来版本、storage incarnation/cluster identity 不合法、instance 数不一致、缺失 CF 或未知 value format。
17.3 恢复
不在恢复路径全量扫描所有向量。
18. Leader 切换
Phase 1 没有派生 HNSW。新 leader 通过 PR0 恢复并追到 committed/applied frontier 后,即可通过 FLAT 提供正确查询。
Phase 2:
leader 切换不能因为 HNSW 未准备好而让 VSIM 不可用。
19. 滚动升级门禁
新增 VectorDataCF 和 LogicalCommand 后,旧 follower 无法安全 apply 新日志。
升级分两阶段:
vector.enabled=false。一旦集群写入 VectorSet,不能直接降级到不识别新 CF/日志格式的版本。
20. 数据损坏处理
MetaCF 格式损坏:
FLAT 扫描发现单个 VectorValue 损坏:
21. 资源控制
建议配置:
硬上限:
FLAT 查询运行在独立 blocking/CPU 执行面,使用 semaphore 限制并发。deadline 包含排队时间;扫描每 N 个 entries 或 bytes 协作检查取消、timeout 和 budget。任何中止都通过 RAII 清理资源并返回完整错误,不返回部分结果。
22. INFO VECTOR 与指标
Redis 兼容 VINFO 不增加私有字段。Kiwi 运维信息通过:
Phase 1 至少暴露:
建议指标:
Phase 2 增加 HNSW READY/BUILDING/DIRTY 和 FLAT fallback 指标。
23. 模块边界
23.1 storage
23.2 cmd
23.3 职责
命令层:argv、选项、无状态校验、RESP 编解码、构造 mutation。
存储层:类型、路由、锁、Meta/member、WriteBatch、snapshot、FLAT。
Raft 层:日志、共识、apply 顺序、ReadIndex、typed response、snapshot index。
禁止 cmd 直接拼 RocksDB key,禁止 storage 创建 RespData,禁止 Raft 实现 cosine。
24. 测试门禁
24.1 Codec 与 Property Tests
0x00、key/element 前缀关系、最大 key 长度边界、畸形长度/codec version、prefix upper bound、full/prefix round trip;24.2 单实例 Storage
24.3 多实例
使用至少 3 个 RocksDB instance,生成分别路由到不同 instance 的 VectorSet key,验证 Meta/member 只存在目标 instance,element 不参与路由,snapshot/restore 后映射不变。
24.4 三节点 Raft
24.5 Redis Differential
同一测试发送给 Redis 8 和 Kiwi,双方都显式使用
NOQUANT,比较 RESP2/RESP3、返回类型、错误类别、空/binary element、element 顺序、score/VEMB 容差。Redis 默认 Q8 的成功路径不伪装成 Phase 1 已兼容;Kiwi 对省略选项的 Phase 1 错误单独做契约测试。24.6 故障注入
覆盖 Meta read、member read、batch put/commit、Raft proposal/commit、snapshot 每个 instance、snapshot meta write、cleanup enqueue、compaction Meta lookup,以及长时间 FLAT 扫描中的排队超时、协作取消和 RAII 清理。
25. 性能门禁
建议 benchmark:
普通 CI 使用 10K,定时 benchmark 运行 100K/1M。
26. Phase 1 完成标准
只有同时满足以下条件才宣布正式支持:
FLUSHDB/FLUSHALL在 database epoch 设计完成前由独立 feature gate 确定性拒绝;只有命令能运行和单机测试通过,不代表 Phase 1 完成。
27. Phase 2:HNSW 派生索引
新增 VectorIndexCF,状态:
每个 VectorSet 的
VectorIndexCFmanifest 至少包含:HNSW node/edge/element map 归属于一个 immutable
index_generation。普通 VSIM 只有同时满足以下条件才能使用 HNSW:任一条件不满足都回退 FLAT。
source_applied_log_id只用于构建来源、诊断和可观测性,不能替代 per-set generation/data_revision 新鲜度判断。构建流程从权威
VectorDataCFsnapshot 生成新的 immutable index generation;发布前重新读取 Meta,确认 generation 和 data revision 未变化,再原子切换为 READY。旧 index generation 延迟清理,不能在仍有查询 pin 时删除。TRUTH:永远扫描 VectorDataCF。
Raft:不复制 node/edge/index generation。
Q8/BIN:由 canonical FP32 + original L2 本地构建。
27.1 RocksDB / FAISS IVF 的非阻塞预研策略
RocksDB 从 10.0 起通过实验性的 SecondaryIndex 框架提供
FaissIVFIndex。它包装的是预训练的 FAISSIndexIVF,使用 coarse list + inverted lists 做 KNN,并依赖TransactionDBOptions.secondary_indices维护主列与二级列的一致性。它是 IVF 能力,不是 HNSW,也不是 Kiwi 当前 Phase 2 的直接依赖。已确认的定位:
VectorDataCF+ 精确 FLAT;Phase 2 的当前主线仍然是本地派生 HNSW。本节不改变两阶段范围、完成门禁或 PR 顺序。arana-db/rust-rocksdb暂未暴露上游 SecondaryIndex /FaissIVFIndex接口,不视为预研阻断项。Kiwi 可以借鉴 RocksDB C++ 实现的算法分层、RocksDB inverted-list 布局和 iterator adapter,在 Rust 存储层实现自己的磁盘 IVF 适配器;这里是设计复刻,不要求逐行移植,也不提前冻结持久化 codec。arana-db/rust-rocksdb增加最小 Rust/C++ bridge。bridge 可以只暴露 FAISS 训练/量化/搜索原语,也可以评估直接绑定 SecondaryIndex /FaissIVFIndex;但不得因为复用该实验接口,就默认把 Kiwi 现有普通 RocksDBDB全面迁移到TransactionDB。PoC 至少要回答:
TransactionDB,是否影响现有 WriteBatch、snapshot、compaction、backup/restore 和普通数据类型;vector_generation + data_revision + immutable index_generation;重启、snapshot restore、leader 切换和旧 generation 清理后仍能检测 stale 并回退 FLAT;arana-db/rust-rocksdb长期维护成本。如后续引入该能力,建议把它建模为独立的
VectorIndexKind::IvfFlat/VectorIndexKind::IvfPq,与 HNSW 共享派生索引 manifest、generation 校验和 FLAT fallback,不把 FAISS 类型泄漏到 Redis 命令协议或权威 VectorSet codec。28. PR 拆分建议
Standalone 可以按 1~4 逐步开放。其他 VectorSet cluster 命令只有在 PR0 全部关闭且 VectorSet 1~6 的 cluster 门禁完成后才能开放;cluster
FLUSHDB/FLUSHALL保持独立关闭,直到 database epoch 方案完成。29. 当前 FT 原型的定位
PR #326 验证了 FP32、距离计算、RocksDB prefix scan、命令解析和多 instance 路由问题,但产品模型是 HASH + SearchCF 二级索引,与原生 VectorSet 不同。
建议:
30. 待 Review 的关键决策清单
31. 参考资料
FaissIVFIndexAPI(experimental)SecondaryIndexAPI(experimental)All reactions