如何配置 Dragonfly 把 HNSW 向量索引复制到副本(序列化与重建两条路径)
【免费下载链接】dragonflyA modern replacement for Redis and Memcached项目地址: https://gitcode.com/GitHub_Trending/dr/dragonfly
在 Dragonfly 里用FT.CREATE ... VECTOR HNSW建了向量索引之后,如果给主节点挂副本,副本上的 HNSW 图不会“自动变出来”:HNSW 图是每个(index, field)一张、跨所有 shard 共享的全局结构,而全同步 RDB 流是逐 shard 发送的,副本侧必须把“图”和“各 shard 的键空间”两块状态重新拼起来。Dragonfly 为此提供了两条互斥的落地路径:
- 序列化路径:主节点把图结构序列化成 RDB opcode 随全同步流出,副本直接恢复(shard 数不同时还会重映射
GlobalDocId); - 重建路径:副本跳过图数据,仅凭键流把索引从头重建一遍。
下面按“建索引 → 配副本 → 验证”的顺序给出两条路径的完整配置和核对方法。机制细节以 docs/hnsw-index-replication.md 为准,复制协议以 docs/replication.md 为准。
先弄清两条路径由什么条件决定
复制线路上涉及三类数据,理解路径选择前先看它们各自的门槛:
| 数据 | 载体 | 门槛 |
|---|---|---|
索引定义(FT.CREATE参数) | AUX 字段search-index | 无条件写入 summary flow;副本幂等地执行FT.CREATE |
| 图结构(节点、层级、邻居链接表) | RDB_OPCODE_VECTOR_INDEX(opcode 222),仅 shard 0 发送、仅非空图发送 | 主节点--serialize_hnsw_index+ 副本能力VER6 |
| 各 shard 的 key→DocId 映射 | RDB_OPCODE_SHARD_DOC_INDEX(opcode 223),每个 shard 发送 | 同上 |
路径判定(依据 docs/hnsw-index-replication.md §4、§6、§7):
| 条件 | 副本实际走的路径 |
|---|---|
主节点--serialize_hnsw_index关闭(默认即关闭) | 重建:跳过步骤 2、3,副本从键流重建所有索引 |
主节点开启,但副本--deserialize_hnsw_index关闭 | 重建:副本到达时直接跳过两个 opcode |
双方都开启,且副本握手时上报的能力达到VER6 | 序列化恢复 |
副本能力低于VER6 | 重建:副本只通过 summary flow 收到FT.CREATE定义,索引完全靠键流重建 |
| 序列化开启但主/副本 shard 数不一致,且重映射失败或该索引没有收到映射 | 该索引回退到重建 |
两个 flag 都默认false(见 snapshot.cc 与 rdb_load.cc 中的 flag 定义),所以不显式配置时走的就是重建路径。序列化路径必须主、副本两侧同时打开。
第一步:启动主节点并创建 HNSW 索引
按 docs/quick-start/README.md 的 Docker 方式起一个实例,并加上序列化 flag(--serialize_hnsw_index=true)。Linux 与 macOS 的网络参数不同,下面用带端口映射的写法,两种系统都适用;两个实例各占一个宿主机端口:
# 主节点(宿主机端口 6379) docker run -p 6379:6379 --ulimit memlock=-1 \ docker.dragonflydb.io/dragonflydb/dragonfly --serialize_hnsw_index=true连上主节点,建一个带 HNSW 向量字段的索引,再写入文档。以下命令取自仓库测试 search_test.py 中test_replicate_all_index_types的真实用例:
FT.CREATE all_types_idx ON HASH PREFIX 1 item: \ SCHEMA name TEXT price NUMERIC SORTABLE category TAG location GEO \ embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 2 DISTANCE_METRIC L2# embedding 字段的值是 DIM 个 float32 的原始字节(本例 DIM=2,即 8 字节)。 # 仓库测试用 numpy 生成:np.array([x, y], dtype=np.float32).tobytes() HSET item:0 name "Product 0" price 0 category electronics location "-122.0,37.0" embedding <2 个 float32 的原始字节>两个占位符需要你自己替换:<2 个 float32 的原始字节>是向量原始字节,由客户端侧用 numpy 之类的库生成后写入。注意一个明确限制:单个索引目前不支持多个 HNSW 向量字段(见 hnsw-index-replication.md 的 Open Issues)。
第二步:启动副本并挂到主节点
路径 A:序列化恢复(主路径)
副本启动参数加--deserialize_hnsw_index=true,它镜像主节点 flag 的作用:关闭时副本对两个 opcode 一律跳过:
# 副本(宿主机端口 6380,容器内仍是 6379) docker run -p 6380:6379 --ulimit memlock=-1 \ docker.dragonflydb.io/dragonflydb/dragonfly --deserialize_hnsw_index=true然后在副本上挂主。仓库测试用的是运行期命令,等价于通过 redis-cli 执行:
redis-cli -h <副本地址> -p 6380 REPLICAOF <主节点地址> 6379测试中也有用启动参数replicaof=localhost:<主节点端口>在副本启动时就挂主节点的写法,两种方式等价,任选其一即可。
路径 B:从键流重建(替代路径)
如果不想传图(比如索引很小、或想规避 shard 数不一致带来的重映射),就不开 flag:副本只带--replicaof或执行REPLICAOF,主节点也可以不开--serialize_hnsw_index。副本收完键流后会自己把整个 HNSW 索引建出来。
关于 shard 数:副本与主节点 shard 数相同时,映射按 shard id 原位恢复;不一致时副本会先建重映射表、改写图里所有global_id再恢复(因为GlobalDocId高 32 位编码的是主节点的 shard id)。某索引无法完整重映射时,该索引的图被丢弃、回退重建——这是文档明确给出的行为,不是需要规避的错误。
第三步:验证副本上的索引与 KNN 结果
按下面的顺序核对,每一步的判断标准都来自仓库文档与测试:
确认复制进入稳定状态。主节点
INFO REPLICATION会把会话状态报成preparation、full_sync、stable_sync之一;副本侧INFO的相位会经历FULL_SYNC_IN_PROGRESS/INITIAL_SYNC再到STABLE_SYNC(见 replication.md 的 Observability 一节)。等到主节点显示stable_sync再继续。确认索引定义已落到副本。在副本上执行:
FT._LIST返回里应包含
all_types_idx(测试即以此判断索引存在)。用同一条 KNN 查询对比主、副本结果。测试用的查询形式(
$vec传入 2 个 float32 的原始字节,如[50.0, 50.0]):FT.SEARCH all_types_idx "*=>[KNN 10 @embedding $vec]" PARAMS 2 vec <查询向量原始字节>在
STABLE_SYNC后对主、副本各跑一次。判断标准参照仓库测试:副本应返回同样数量的结果(KNN 10 对应计数 10),且两侧结果键集合一致。测试特意把两侧键排序后再比较,因为浮点距离并列时返回顺序可能有细微差异——所以核对时应比较键集合,而不是逐位比返回顺序。(可选)确认副本实际走了哪条路径。副本日志里有明确的路径标记,仓库测试正是靠这几个字符串断言的(示例字符串,非固定预期日志):
Restored HNSW index—— 同 shard 数下从序列化图恢复;global_ids remapped—— 不同 shard 数下经重映射恢复;Will rebuild from scratch(伴随 HNSW 字样)—— 走了重建路径。
测试里用
logbuflevel=-1强制 glog 每行立即刷盘,这样在进程日志文件里才能读到 INFO 行;如果你也要从日志文件核对,副本加这个启动参数。
边界与限制
- 空图不传输:主节点不发送空索引的图块,副本从(空的)键流直接重建,属正常行为。
- 磁盘 RDB 不含索引数据:
search-index数据只出现在复制用的 RDB 流里,RDB 保存到磁盘时会整体省略——向量索引是“复制态”的概念,不要把“RDB 文件里没有索引”当成数据丢失。 - 重建路径的语义差异:全重建时,加载期间缓冲的操作会被丢弃而不是回放(重建本来就会从键流重索引所有文档);而序列化恢复路径下这些操作会在全部分片完成水合后统一 drain 回放。这解释了为什么两条路径的加载耗时特征不同:重建时间与文档量成正比。
- 版本门槛:低于
VER6能力的副本只收到FT.CREATE定义,无论 flag 如何配置都走重建。 - 更底层的线格式(
HnswNodeData结构、MRMW 锁、借用向量不变量、状态机kProhibit/kRestoring/kBuilding/kSerializing)都在 hnsw-index-replication.md 中,日常配置不需要接触;复制握手、全同步/部分同步的完整机制见 replication.md。
【免费下载链接】dragonflyA modern replacement for Redis and Memcached项目地址: https://gitcode.com/GitHub_Trending/dr/dragonfly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考