土法炼钢兴趣小组的算法知识备份

【向量检索引擎】Qdrant 对照:单库路径、payload 过滤与分片副本

文章导航

分类入口
databasestorage
标签入口
#qdrant#milvus#payload-filter#sharding#raft#hnsw#segment#vector-engine

目录

Milvus 2.6.x 主线强调存算分离与 Streaming/Query/Data 三分(第 1–14 篇)。Qdrant 走另一条常见生产路径:Rust 实现的向量库,单节点即可完整服务,分布式用 分片 + 副本 扩展。本文按官方 StorageOptimizerDistributed deploymentFiltering 文档做对照,重点回答两个容易被「谁的 HNSW 更快」掩盖的问题:单进程栈里数据到底怎么落盘、集群里哪些操作真正需要共识。不写成 Qdrant 教程。

本文是「向量检索引擎」系列第 15 篇(共 19 篇)。→ 系列目录

版本锚定:Qdrant 官方文档(Storage、Optimizer、Distributed deployment、Filtering);与 Milvus 对照时机制以本系列已钉的 2.6.x 为准。


一、产品形态对照

维度 Milvus 2.6.x(本系列) Qdrant(官方文档)
典型部署 Proxy + Coordinator + 多类 Worker + 对象存储 + WAL 单二进制可跑全功能;集群为对等 peer
写日志 Woodpecker / Kafka / Pulsar(第 5 篇) 每个 collection 先写 WAL,按序号落到对应 segment
历史数据 Sealed 在对象存储,Query Node 加载 segment 本地(及副本)持有,appendable/non-appendable 两态
过滤 表达式 → bitset → Knowhere(第 11 篇) payload 条件 + 建议建 payload index
水平扩展 Channel/shard + Worker 扩缩 Collection 拆 shardreplication_factor 控制副本
集群共识 Coordinator 单活跃,无 Raft 式投票 Raft 管拓扑与 collection 结构,不管点操作

核心差异不是「谁的 HNSW 更好」,而是 状态放哪、组件拆多细、共识覆盖到什么粒度、运维面多大


二、最小故事:同一条 insert,两套系统怎么落地

设想客户端对同一批 100 条向量分别发 insert 给 Milvus 集群和单节点 Qdrant,都立刻返回成功。这条请求接下来发生的事完全不同:

Milvus 侧(第 3、5 篇已钉):Proxy 按 shard 规则拆包,送到负责该 vchannel 的 Streaming Node;Streaming Node 给这批操作分配 TSO,写入 Woodpecker/Kafka/Pulsar 一类 WAL;提交后应用到 Growing Segment,才真正可搜。整条链路跨越 Proxy、Streaming Node、WAL 存储三个独立进程/组件。

Qdrant 侧Storage 文档):数据变更分两步——先写入 Write-Ahead-Log,WAL 给每个操作分配一个全局有序的序号;然后变更进入 segment。每个 segment 记录「最后应用的变更版本」以及「每个点的版本」:如果新变更的序号小于该点当前版本,更新器直接忽略这条变更。这条规则本身就是一种幂等保护——重放 WAL 时,旧序号的变更不会覆盖已经更新到更新版本的点。

两边都遵循「先日志、后数据」的经典模式(ARIES 谱系),但作用域不同:Milvus 的 WAL 提交是跨进程网络往返(Proxy → Streaming Node → WAL 存储),Qdrant 单节点场景下 WAL 与 segment 更新都在同一进程内,网络往返只发生在分布式部署时的副本复制路径上。这解释了单节点 Qdrant 为何常被描述为「写入延迟更低」——不是算法更快,而是少了跨进程组件


三、单节点甜区

Qdrant 官方 Distributed deployment 明确:若最看重成本,可跑 单节点——同时写明不适合生产高可用(重启即停服、无副本则难自愈)。

工程含义:

Milvus 也有单机/精简部署形态,但本系列内核叙事默认云原生四分;选型时比较的是你真正要运维的拓扑,不是营销页上的「也能 Docker 一键起」。


四、Qdrant 单进程栈:Segment 里装的是什么

官方 Storage 文档把一个 collection 内的数据切成多个 segment,每个 segment 独立持有:

组成 官方定义 与 Milvus 的对照落点
向量存储 In-memory(全放 RAM,速度最高)或 memmap(映射磁盘文件,靠 page cache)二选一 对应 Query Node 段内向量数据是否常驻内存(第 8 篇 Knowhere 假设)
payload 存储 InMemory(启动即加载进 RAM)或 OnDisk(经 Gridstore 读写,省内存但访问延迟高) 对应 Milvus 标量字段与索引的存放(第 11 篇)
id mapper 维护内部 id 与外部 id 的映射 对应 Milvus 主键索引(第 13 篇)
向量/payload 索引 HNSW、payload index 等 对应 Knowhere VecIndex + 标量索引(第 8、11 篇)

segment 有 appendable(可读写)与 non-appendable(只读,只能删除)两种状态;collection 内至少要保留一个 appendable segment。这个二分与 Milvus 的 Growing/Sealed(第 3 篇)目标一致——可变的实时结构 + 不可变的优化结构——但落点不同:Qdrant 的 appendable/non-appendable 是同一节点内、由后台 optimizer 决定的存储形态切换,Milvus 的 Growing/Sealed 则是分别绑定到不同 Worker 角色(Streaming Node vs Query Node)、需要跨进程 handoff 的状态迁移。

把两套系统的整体分层画在一张图上,能更直接看出「单进程栈」与「四层拆分」的差别:

flowchart TB
  subgraph qdrant ["Qdrant single-process stack"]
    qcoll["Collection"]
    qshard["Shard"]
    qseg["Segment"]
    qvec["Vector storage<br/>in-memory or memmap"]
    qpay["Payload storage<br/>InMemory or OnDisk + Gridstore"]
    qidx["HNSW index + payload index"]
    qidmap["Id mapper"]
    qopt["Background optimizer<br/>merge / indexing / vacuum"]
    qcoll --> qshard --> qseg
    qseg --> qvec
    qseg --> qpay
    qseg --> qidx
    qseg --> qidmap
    qopt -.->|"rebuild via proxy segment"| qseg
  end
  subgraph milvus ["Milvus four layers"]
    access["Access: Proxy"]
    coord["Coordinator: DDL/TSO/handoff"]
    sn["Streaming Node: WAL + Growing"]
    qn["Query Node: Sealed search"]
    dn["Data Node: index build/compaction"]
    storage["Storage: etcd + object store + WAL"]
    access --> coord
    access --> sn
    coord --> sn
    coord --> qn
    coord --> dn
    sn --> storage
    qn --> storage
    dn --> storage
  end

图中最值得注意的差异是 optimizer 与 segment 的关系:Qdrant 的 merge / indexing / vacuum 是同一进程内针对 segment 的后台任务,直接改写 SegmentHolder 里的引用;Milvus 的 Data Node 建索与 compaction 是独立 Worker 角色,要经 Coordinator 调度、经对象存储中转,再由 handoff 缝合到 Query Node(第 10 篇)。两者做的事同构(后台整理 + 索引构建),但组件边界差一整层网络与协调。

4.1 后台 optimizer 四类任务

官方 Optimizer 文档列出四种优化器,各自的触发条件与目的不同:

Optimizer 目的 触发条件(官方)
Merge 合并过多的小 segment segment 数超过 default_segment_number(默认等于 CPU 数),至少取三个最小 segment 合并
Indexing 给普通 segment 建 HNSW 索引、启用 memmap segment 大小超过 indexing_threshold_kb
Vacuum 回收软删占用的空间,修复因软删产生的「索引碎片」 删除比例超过 deleted_threshold
Config mismatch segment 的 HNSW/量化配置与当前 collection 配置不一致时重建 collection 配置变更后

优化过程中,被优化的 segment 通过 proxy segment(写时复制)保持可读:变更先落到一个新的可写 segment,优化完成后原地替换。这与 Milvus compaction 完成后由 Coordinator 发起 handoff(第 10 篇)目标一致——都是「后台重写不能阻塞前台服务」,Qdrant 用进程内的 copy-on-write 包装,Milvus 用跨 Worker 的视图切换。

把 Merge optimizer 的触发条件代入一个具体节点规格,能看出这个默认值背后的直觉:一台 16 核的节点,default_segment_number 默认等于 16,意味着单个 collection 的 segment 数量长期维持在 16 左右——这个数字既不是任意选的,也不是”越多越好”:segment 数太少(例如强行合并成 1 个),单次写入触发的重建成本会很高,且失去了并行处理多个 segment 的能力;segment 数太多,每次 search 需要扇出到更多 segment 再合并,单次查询的调度开销上升。用 CPU 数作为默认阈值,隐含的假设是”segment 数大致对齐可并行处理的核数”——这和 Milvus 里 segment 大小策略要在”堆积风险”与”过度分裂”之间取平衡(第 3、10 篇)是同一类工程直觉的两种参数化方式:一个按 CPU 数定数量,一个按字节数定大小。


五、分布式:Raft 管拓扑、不管点数据

Distributed deployment(A 级要点)中最容易被忽视的一句话是:Qdrant 用 Raft 共识协议维护集群拓扑与 collection 结构的一致性,但对点(point)的操作不经过共识基础设施。换句话说,Raft 管的是「collection 有哪些、shard 怎么分、副本在哪个 peer」,不管「这一条 insert 具体写没写成功」——那部分靠副本集上的复制路径(write_consistency_factor,见第六节)解决。

这条边界画出了两套完全不同的一致性代价:

操作类别 是否经过 Raft 一致性保证来源
创建/删除 collection、shard 迁移、加/减 peer Raft 日志复制 + 多数派确认
insert / update / delete 某个点 副本集复制 + write_consistency_factor

官方给出的高可用建议:至少运行三个投票节点——两节点集群无法在任一节点不可用时形成多数派,Raft 就无法选出或确认 leader,直到两节点重新可通信。这与经典分布式共识的多数派下限完全一致:Raft 论文(Ongaro & Ousterhout, USENIX ATC 2014)证明的正是「多数派存活即可继续服务」这一性质,\(n\) 节点集群最多容忍 \(\lfloor (n-1)/2 \rfloor\) 节点故障。

flowchart TB
  subgraph control ["Control plane: goes through Raft"]
    createColl["create/drop collection"]
    shardOp["shard transfer / rebalance"]
    peerOp["add/remove peer"]
  end
  subgraph data ["Data plane: bypasses Raft"]
    insertOp["insert/update/delete a point"]
  end
  control --> raftLog["Raft log<br/>majority ack required"]
  data --> replSet["Replica set on shard<br/>write_consistency_factor ack"]
  raftLog --> topology["Cluster topology + collection metadata"]
  replSet --> pointData["Point vectors + payload in segments"]

其余分布式要点(官方 Distributed deployment):

与 Milvus Channel / vchannel(第 3 篇)对照:两者都把集合切成可并行单元,但 Qdrant shard 是 带完整点存储的对等分片,扩缩与恢复走统一的 Raft + 副本机制;Milvus channel 绑定 Streaming Node,历史段另由 Query Node 从对象存储加载,协调靠单活跃 Coordinator 而非多数派投票。Milvus 没有 Raft 式的集群拓扑共识——Coordinator 本身的高可用依赖外部 etcd 选主,不是本文对照的重点,感兴趣可回读第 4 篇。

5.1 写一致性:write_consistency_factor

write_consistency_factor(默认 1)定义一次写操作需要多少个副本确认才能向客户端返回成功。调大它可以让写操作更能容忍网络分区(因为已确认的副本数更多),但代价是需要更多副本处于活跃状态才能完成写入;若设成等于 replication_factor,未完全复制的更新会被直接拒绝,从而省去后续的额外同步。这与 Milvus 的一致性级别(第 12 篇的 Strong/Bounded/Session/Eventually)是两个不同轴上的旋钮:Milvus 调的是「查询愿意等多旧的可见性水位」,Qdrant 的 write_consistency_factor 调的是「写入愿意等多少个副本确认」——前者作用于读路径,后者作用于写路径,不能直接类比谁更强。


六、Payload 过滤

Filtering:搜索/检索时可对 payload 与点 id 设条件;业务属性(库存、地域、价格带)往往无法全部进入 embedding。

官方强调:要性能,应对过滤字段建 payload index,且 最好在摄入数据前 建好;一旦建了字段索引,无论 payload 存储类型是 InMemory 还是 OnDisk,该字段的全部取值都会常驻内存,避免过滤时触发磁盘访问。条件可用 must / should / must_not 等子句嵌套,表达任意布尔组合。

对照 Milvus:

Milvus Qdrant
标量载体 字段 + 布尔表达式 JSON-like payload
加速结构 标量索引 + bitset 进 Knowhere payload index(建索后强制常驻内存)
存储位置可调 无独立「payload 落盘」开关 on_disk_payload 可选择整体落盘节省内存
选择度陷阱 同第 11 篇 / frontier/09 同样存在;索引只降过滤成本,不改变选择度本身

不要假设「Qdrant 过滤一定比 Milvus 快」——取决于索引是否建、选择度、以及是否在图遍历中有效剪枝(同第 8、11 篇的 bitset 讨论)。


七、何时选 Qdrant 而不是上 Milvus 集群

更偏向 Qdrant 更偏向 Milvus 集群
团队要少组件、快上线 已有对象存储/K8s 平台,要存算分离弹性
数据中小、可垂直扩展一阵 十亿级、索引构建要独立扩 Data/Index 类负载
payload 过滤模型贴业务 JSON 要强绑定多一致性级别产品语义(第 12 篇)
分片副本模型可接受、能接受 Raft 3 节点门槛 需要 Woodpecker/零盘 WAL 等云原生写路径
建索/compaction 与在线查询共用同一批 segment 即可 需要把离线建索独立成资源池(第 10 篇)

第 18 篇给出决策树;本篇只钉对照坐标。


八、常见误解

误解一:Qdrant 是「轻量版 Milvus」,功能是 Milvus 的子集。 两者是不同的架构选择,不是能力包含关系。Qdrant 的 segment 级 optimizer(merge/indexing/vacuum/config mismatch)、Raft 拓扑管理、write_consistency_factor 等机制在 Milvus 里没有直接对应物;Milvus 的存算分离、多一致性级别、离线 Data Node 资源池在 Qdrant 里也没有直接对应物。两边解决问题的方式不同,不是谁功能更全。

误解二:Qdrant 集群里所有操作都要走 Raft,所以写入吞吐受共识协议限制。 第五节已明确:点操作(insert/update/delete)绕过共识基础设施,走的是副本集复制路径,受 write_consistency_factor 约束,不是 Raft 日志复制。真正受 Raft 限制的是拓扑变更类操作(建 collection、shard 迁移、增减 peer),这类操作频率远低于点写入。

误解三:只要副本数(replication_factor)设得高,集群就能容忍任意比例的节点故障。 副本数只影响数据面的冗余;控制面的拓扑变更与故障恢复仍然要求 超过半数 Raft 投票节点健康。一个 3 节点集群里即使某个 collection 的副本数设成 3,一旦两个节点同时故障,剩下一个节点也无法独自完成需要共识的拓扑级恢复操作。

误解四:Qdrant 的 segment 数量应该尽量少,合并成一个大 segment 性能最好。 第 4.1 节已经说明 default_segment_number 默认按 CPU 数设置,不是越少越好:过少的 segment 数会让单次写入触发的重建成本过高,也失去了跨 segment 并行处理的能力。Merge optimizer 的目标是”合并过多的小 segment”,不是”最终只剩一个 segment”——把 segment 数量当成一个需要归零的指标,是对 optimizer 设计目标的误读。


九、学术谱系与开放问题

9.1 谱系

代表 在本篇的落点
共识协议 Ongaro, D., Ousterhout, J., In Search of an Understandable Consensus Algorithm, USENIX ATC 2014(Raft) Qdrant 拓扑与 collection 结构一致性的直接来源
WAL + 版本号幂等 经典「日志序号去重」思路(ARIES 谱系的工程变体) Qdrant segment 按点版本号丢弃过期变更(第二节)
后台整理不阻塞前台 与 LSM/列存 merge、Milvus compaction 同属一类工程分形 proxy segment 写时复制(第四节)
段式存储引擎 类似 Bleve/Lucene 的 segment 抽象(不可变段 + 后台合并) Qdrant segment 的 appendable/non-appendable 二态

Milvus 没有采用 Raft 式多数派共识来管理集群拓扑,而是让单活跃 Coordinator 持有权威视图(第 4 篇),高可用靠外部 etcd 选主;Qdrant 把这一层显式做成了 Raft。这不是谁的设计更先进,而是两种不同的工程取舍:单活跃 Coordinator 减少了共识协议的实现与运维复杂度,代价是 Coordinator 故障切换依赖外部组件;Raft 把拓扑一致性收进自己的二进制里,代价是必须接受多数派下限(至少 3 节点才能自愈)这一约束。

9.2 开放问题

  1. Qdrant 自定义 shard key 与 Milvus partition 在多租户下的运维成本比较——两者都能按业务键切分数据,但 shard key 影响的是物理分片,partition 更接近逻辑过滤域(第 3 篇),语义不完全对齐。
  2. 两边 HNSW 参数默认值不同导致的「同数据不同召回」评测规范:Qdrant 的 indexing/memmap 阈值与 Milvus 的 segment 大小策略都会间接影响单次索引训练看到的数据分布(对照第 8 篇 Knowhere 的「同段训练同段搜索」假设)。
  3. 混合云(厂商托管)下,write_consistency_factor 与 Milvus 一致性级别的产品化程度差异,是否应纳入 SLO 对照而非只比开源内核。

十、小结

三句话小结:Qdrant 用单进程内的 segment(向量存储 + payload 存储 + id mapper + 索引)与四类后台 optimizer 换取部署简单,分布式扩展靠 Raft 管拓扑、副本集管数据两条独立路径;Milvus 用跨进程的 Access/Coordinator/Worker/Storage 四层与单活跃 Coordinator 换取存算分离弹性,一致性靠 GuaranteeTs 而非多数派投票;两边的过滤都是「标量条件进检索」,载体分别是 payload index 与 bitset/表达式,选型的关键是你愿意在哪一层付出运维复杂度,而不是哪家 HNSW 实现更快。

下一篇:Lance / LanceDB 对照


参考资料

  1. Qdrant Documentation, Storage(segment 组成:vector storage、payload storage、id mapper、appendable/non-appendable、WAL + 版本号)。
  2. Qdrant Documentation, Optimizer(merge/indexing/vacuum/config mismatch 四类 optimizer、proxy segment 写时复制、阈值参数)。
  3. Qdrant Documentation, Distributed deployment(Raft 管拓扑不管点操作、replication_factorwrite_consistency_factor、shard transfer、故障恢复的多数派下限、Consensus Checkpointing)。
  4. Qdrant Documentation, Filtering(payload、clauses、payload index、on_disk_payload)。
  5. Ongaro, D., Ousterhout, J., In Search of an Understandable Consensus Algorithm, USENIX ATC 2014(Raft 论文;多数派容错的理论来源)。
  6. 本系列第 1、3、4、8、10、11、12、13、14 篇;系列 index

返回 系列目录 | 上一篇:副本与故障恢复 | 下一篇:Lance 对照

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-12 · database / storage

【向量检索引擎】Knowhere:向量索引执行引擎与插件契约

按官方 Knowhere 文档说明其在 Milvus 中的位置、相对 Faiss 的扩展(bitset、SIMD 选择、二进制度量)、VecIndex 类层次与 IDMAP/IVF/HNSW 等类型,用插件注册、CPU/GPU 分发与 bitset 进查询三张图钉住工程契约,并与 db-frontier/08 的算法细节分工。


By .