Milvus 查询路径时序图版

目标:把一次 search/query 请求如何从 Proxy 走到 QueryCoordV2、QueryNodeV2 和 C++ segcore 讲清楚,并解释 sealed/growing 数据如何同时参与查询。


1. 一句话版本

Milvus 查询路径不是“请求到了就查一个 ANN 索引”,而是:

Proxy 做请求语义整理 → QueryCoordV2 决定数据放置与副本 → QueryNodeV2/ShardDelegator 在 shard 上统一 sealed/growing/delete/tsafe 状态 → C++ segcore 执行真正的 search/retrieve。


2. 查询路径中的关键角色

  • Client / SDK:发起 search / query / hybrid request
  • Proxy:绑定 collection schema,处理一致性、输出字段、分区和参数
  • QueryCoordV2:决定 collection/segment/channel 应由哪些 QueryNode 提供服务
  • QueryNodeV2:查询执行节点
  • ShardDelegator:QueryNode 内的 per-shard 运行时协调器
  • segcore (C++):真正执行 segment load / search / retrieve 的内核

3. 总时序图

Client
  ↓ search/query
Proxy
  ↓ 参数整理、schema 绑定、consistency 计算
QueryCoordV2
  ↓ 确认 replica / channel / segment 分布
QueryNodeV2

ShardDelegator (per vchannel/shard)

sealed segments + growing segments + delete buffer + tsafe

segcore AsyncSearch / AsyncRetrieve

局部结果 reduce / merge

Proxy 聚合返回

4. Proxy 阶段:请求从用户语义变成系统语义

4.1 关键源码

  • internal/proxy/task_search.go
  • internal/proxy/task_query.go

4.2 Proxy 在 searchTask.PreExecute() 里做什么

从源码可以看到它会做:

  • 获取 collection ID
  • 拉 collection schema 和 collection info
  • 判断 partition key mode
  • 解析 partition names → partition IDs
  • 校验 namespace
  • 翻译 output fields
  • 初始化 search request / advanced search request
  • 计算 guaranteeTimestamp / consistency 相关逻辑
  • 处理 IgnoreGrowing、pipeline trace、topK 等参数

4.3 为什么这一步很关键

因为用户说的是“在这个 collection 上搜 topK,并返回这些字段”,但系统真正需要的是:

  • 绑定到哪个 collection ID
  • 查哪些 partition
  • 需要哪些 output field IDs
  • 是否允许忽略 growing
  • snapshot 时间点是什么

所以 Proxy 实际上是查询路径中的 binding + request planning 层。


5. QueryCoordV2 阶段:决定谁来服务查询

5.1 QueryCoordV2 的核心职责

它主要处理:

  • collection 的 load / release
  • replica 管理
  • segment / channel 分布
  • rebalance 和节点故障恢复
  • resource group / placement policy

5.2 它不执行查询,但决定查询是否能正确分布

很多人把向量数据库想成“所有节点都知道所有数据在哪”,但实际生产系统里,placement 才是核心复杂度之一。

QueryCoordV2 要回答:

  • 这个 collection 现在 load 到哪些 QueryNode 上?
  • 哪些 replica 可用?
  • 这些 segment / channel 当前的 authoritative 分布是什么?
  • 节点上下线后,如何恢复和均衡?

5.3 这一步的系统价值

ANN 算法不是这里的重点。这里最难的是:

  • 资源管理
  • fault tolerance
  • 恢复速度
  • 负载均衡

这也是为什么 QueryCoordV2 是 Milvus 查询面成熟化的关键模块。


6. QueryNodeV2 阶段:真正进入 shard 级查询运行时

6.1 QueryNodeV2 做什么

QueryNodeV2 负责:

  • load sealed segments 和索引
  • 保持 growing segment 的实时可查
  • 处理 delete 信息和增量可见性
  • 执行 search/query/get statistics/analyzer 等操作

6.2 但它真正的核心不是 Server,而是 ShardDelegator

对应文件:

  • internal/querynodev2/delegator/delegator.go

这个文件暴露出来的接口包括:

  • Search
  • Query
  • ProcessInsert
  • ProcessDelete
  • LoadGrowing
  • LoadL0
  • LoadSegments
  • ReleaseSegments
  • SyncDistribution
  • SyncPartitionStats
  • UpdateTSafe

这表明 QueryNode 里的“查询核心”不是简单的一组 RPC handler,而是:

一个持续维护 shard 状态并对外提供搜索能力的 per-shard runtime。


7. sealed + growing 为什么可以一起查

这是 Milvus 查询模型里最重要的点之一。

7.1 sealed segment

特点:

  • 通常已经 flush
  • 往往已 build index
  • 适合高效 ANN 检索
  • 可由 QueryNode 从对象存储 / 本地缓存加载

7.2 growing segment

特点:

  • 尚未 flush 或尚未完全固化
  • 但必须尽快对查询可见
  • 常常不具备和 sealed segment 同等级的索引结构
  • 更偏近实时增量路径

7.3 为什么需要两条路径并存

如果所有数据都必须先进入 sealed/indexed 态才能查,系统 freshness 会很差。 如果所有数据都只按 growing 态查,系统成本和吞吐会失控。

Milvus 的答案是:

  • 新数据靠 growing path 保证可见性
  • 老数据靠 sealed/indexed path 提供效率

ShardDelegator 的价值就在于统一这两条路径的回答。


8. delete buffer、tsafe、partition stats 在查询里为什么重要

8.1 delete buffer

QueryNode 上不仅要知道“哪些行存在”,还要知道“哪些逻辑上已经被删掉但物理上尚未 compaction”。

因此查询执行时不仅是查 segment,还要叠加 delete buffer 的可见性判断。

8.2 tsafe / MVCC 时间推进

Query 不只是“查最新”,而是要 obey consistency/guarantee timestamp。ShardDelegator 里有:

  • UpdateTSafe
  • GetTSafe
  • latestRequiredMVCCTimeTick

这说明 QueryNode 维护的是 一个随时间推进的可见性边界

8.3 partition stats

随着 clustering compaction、segment prune 等能力增强,partition stats 对查询优化越来越重要:

  • 可帮助做 segment prune
  • 减少无效 segment 扫描
  • 改善 query fan-out 的代价

9. segcore:真正执行 Search / Retrieve 的地方

9.1 关键文件

  • internal/core/src/segcore/segment_c.h

9.2 暴露的关键能力

从 C 接口头文件可直接看到:

  • SegmentLoad
  • AsyncSegmentLoad
  • AsyncSearch
  • AsyncRetrieve
  • Insert
  • GetRowCount
  • GetDeletedCount

9.3 这说明了什么

Go 层更多负责:

  • orchestration
  • request preparation
  • shard state management
  • RPC and scheduling

C++ segcore 负责:

  • 真正的数据装载
  • plan 执行
  • ANN 检索
  • retrieve
  • 内存统计和 segment 内部状态

所以理解查询路径,不能只停在 task_search.godelegator.go,最终必须下潜到 segcore。


10. 局部结果如何归并

一次 search 往往不是在单个 segment 上完成,而是:

  • 多个 segment
  • 可能多个 QueryNode
  • 可能 sealed + growing 双路径

因此结果需要:

  • shard 内 reduce
  • 节点间 merge
  • Proxy 侧再做最终组织和 output fields 补齐

task_search.go 中存在 requery threshold 等逻辑,也说明有些场景下会采用“两阶段结果取回”的思路:

  • 第一阶段先确定 candidate / topK
  • 第二阶段再补取更多字段

11. 一个更贴近源码的时序版本

1. Client 发起 SearchRequest
2. Proxy 在 task_search.go 中:
   - 绑定 collection/schema
   - 解析分区 / output fields / consistency
   - 形成内部 SearchRequest
3. QueryCoordV2 提供当前 replica / distribution 视图
4. Proxy / QueryNode 路由到相应 shard
5. QueryNodeV2 中每个 shard 的 ShardDelegator:
   - 检查自身 serviceable 状态
   - 结合 tsafe / delete buffer / distribution
   - 在 sealed segments 上发起索引搜索
   - 在 growing segments 上补实时搜索
6. segcore 执行 AsyncSearch / AsyncRetrieve
7. 各段结果局部 reduce
8. Proxy 汇总并返回用户结果

12. 查询路径最值得精读的文件顺序

快速主线

  1. internal/proxy/task_search.go
  2. internal/querycoordv2/server.go
  3. internal/querycoordv2/services.go
  4. internal/querynodev2/services.go
  5. internal/querynodev2/delegator/delegator.go
  6. internal/core/src/segcore/segment_c.h

更深入

  1. internal/querynodev2/segments/
  2. internal/core/src/segcore/
  3. internal/core/src/query/
  4. internal/core/src/index/

13. 用一句话压缩查询路径理解

Milvus 查询路径本质上是在 QueryCoordV2 提供的数据放置视图下,由 QueryNodeV2 的 ShardDelegator 统一 sealed/growing/delete/tsafe 等状态,再调用 C++ segcore 执行真正的检索与取回。