news 2026/9/10 19:28:02

Milvus Query 体系源码深度剖析:QueryCoordinator、QueryNode 接口契约与 Collection Replica 设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Milvus Query 体系源码深度剖析:QueryCoordinator、QueryNode 接口契约与 Collection Replica 设计

Milvus Query 体系源码深度剖析:QueryCoordinator、QueryNode 接口契约与 Collection Replica 设计

【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus

本文是 Milvus 2.0 开发指南的专题续写,围绕归档文档 developer_guides/chap07_query_coordinator.md 展开,面向希望深入理解 Milvus 查询链路(数据加载、segment 分发、查询执行、内存副本一致性)的研发工程师。读完本文,你将掌握 QueryCoord 与 QueryNode 的完整接口契约、查询/检索消息在 channel 中的流转方式、Collection Replica 在查询节点内部的数据组织模型,并能在当前仓库源码(pkg/proto/querypbinternal/querycoordv2internal/querynodev2)中一一对照印证。

说明:chap07 描述的是 Milvus 2.0 时代的查询架构。时至今日,仓库中查询协调与执行已演进为internal/querycoordv2/internal/querynodev2/两套实现,但"协调者下发任务、节点持有内存副本、消息通道驱动状态同步"的核心思想一脉相承,读者可借此理解架构演进的来龙去脉。

1. 查询体系在 Milvus 整体架构中的定位

在 chap01_system_overview.md 中,Milvus 把数据组织为 collection(表)→ partition(可选的分片逻辑单元)→ segment group → segment 的层级:collection/partition 是查询的基本执行范围;segment group 是数据到节点映射、以及副本调度的基本单位;segment 是数据与索引真正驻留的最细粒度单元,其内部按列(column-based)布局以便利用 SIMD 降低查询内存占用。

在这种模型下,一套独立的"查询子系统"负责回答两类问题:

  1. 数据该被谁持有:一个 collection/partition 的 segment 数据与索引分布在哪些查询节点上、何时加载、何时释放;
  2. 查询如何被执行:用户发起的向量搜索(search)与按主键/标量条件取行(retrieve/query)如何被路由到持有数据的节点上执行,并把结果收敛回 Proxy。

上述第 1 类问题的"大脑"是Query Coordinator(QueryCoord),第 2 类问题的"执行体"是QueryNode。文档 chap07 正是围绕这两个组件的接口契约、两者之间的消息通道,以及 QueryNode 内部用于承接数据的内存副本结构(Collection Replica)展开的。

2. QueryCoord 总览与核心职责

文档 7.1 Overview 给出 QueryCoord 的系统定位:它是查询侧的协调者,负责元数据获取、segment 归属决策与生命周期管理,是查询数据从"已落盘"到"可被检索"的关键枢纽。

从仓库当前实现看,这一职责被延续并进一步拆分。internal/querycoordv2/下的目录与文件(如 segment 分配、collection 顶层编排、负载均衡相关的segmentsdistbalancer等子包)承担了 2.0 时代 QueryCoord 演进后的调度逻辑;而归档文档中的 QueryCoord 更强调其对外的控制面接口与通过消息流建立的数据面通道。

QueryCoord 向下要与两类基础服务打交道(见上文架构图):

  • RootCoord:获取 collection/partition 的 Schema 与 segment 描述(图中Schema / Seg Description);
  • IndexCoord:获取索引描述(Index Description),从而得知每个 segment 是否已建好可用索引;
  • TxnKV(etcd)/ KV(minIO/S3/内存 KV):持久化查询侧的元数据、读取索引文件(IndexFiles);
  • MsgStream(如 Pulsar):通过消息流收发DdRequest / DmRequest / DqRequest / SegInfo等控制事件与TimeTick / Stats / DqResult等状态/结果信号。

3. QueryCoord 接口契约(文档 7.2)

QueryCoord 以 gRPC 接口对外提供服务。文档给出的核心 Go 接口如下,其语义直接映射到查询控制面的每一条关键路径:

type QueryCoord interface { Component TimeTickProvider // ShowCollections notifies RootCoord to list all collection names and other info in database at specified timestamp ShowCollections(ctx context.Context, req *querypb.ShowCollectionsRequest) (*querypb.ShowCollectionsResponse, error) // LoadCollection notifies Proxy to load a collection's data LoadCollection(ctx context.Context, req *querypb.LoadCollectionRequest) (*commonpb.Status, error) // ReleaseCollection notifies Proxy to release a collection's data ReleaseCollection(ctx context.Context, req *querypb.ReleaseCollectionRequest) (*commonpb.Status, error) // ShowPartitions notifies RootCoord to list all partition names and other info in the collection ShowPartitions(ctx context.Context, req *querypb.ShowPartitionsRequest) (*querypb.ShowPartitionsResponse, error) // LoadPartitions notifies Proxy to load partition's data LoadPartitions(ctx context.Context, req *querypb.LoadPartitionsRequest) (*commonpb.Status, error) // ReleasePartitions notifies Proxy to release collection's data ReleasePartitions(ctx context.Context, req *querypb.ReleasePartitionsRequest) (*commonpb.Status, error) // CreateQueryChannel creates the channels for querying in QueryCoord. CreateQueryChannel(ctx context.Context) (*querypb.CreateQueryChannelResponse, error) GetPartitionStates(ctx context.Context, req *querypb.GetPartitionStatesRequest) (*querypb.GetPartitionStatesResponse, error) // GetSegmentInfo requests segment info GetSegmentInfo(ctx context.Context, req *querypb.GetSegmentInfoRequest) (*querypb.GetSegmentInfoResponse, error) // GetMetrics gets the metrics about QueryCoord. GetMetrics(ctx context.Context, req *milvuspb.GetMetricsRequest) (*milvuspb.GetMetricsResponse, error) }

接口继承了Component(组件生命周期)与TimeTickProvider(提供查询侧时间推进),这是 Milvus 流式时间体系在查询协调层的落地。逐个方法看其作用:

  • ShowCollections / ShowPartitions:查询当前可见的 collection/partition 及其"内存化进度"(InMemoryPercentages);
  • LoadCollection / LoadPartitions / ReleaseCollection / ReleasePartitions:加载/释放数据,是用户执行load/release的底层语义;
  • CreateQueryChannel:为一个 collection 建立查询消息通道(见第 4 节);
  • GetPartitionStates:轮询 partition 的加载状态机;
  • GetSegmentInfo:返回指定 segment 的内存驻留、行数、索引等信息,供观测与调试。

3.1 消息公共头 MsgBase

所有请求都携带公共消息头,用于在分布式与消息流环境中标识消息来源与顺序:

type MsgBase struct { MsgType MsgType MsgID UniqueID Timestamp Timestamp SourceID UniqueID }
  • MsgType:消息类别,决定它走哪条处理分支;
  • Timestamp:消息的(逻辑)时间戳,Milvus 依赖它对"何时可见"做一致性判定;
  • SourceID:产生方节点标识,便于接收方溯源与去重。

3.2 ShowCollections 与 ShowPartitions

type ShowCollectionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionIDs []int64 } type ShowCollectionResponse struct { Status *commonpb.Status CollectionIDs []UniqueID InMemoryPercentages []int64 }

注意接口注释里"notifies RootCoord to list all collection names",即查询协调者需要借助根协调者的元数据回答"系统里有哪些 collection、各有多少比例已加载进内存"。InMemoryPercentages与第 7 节的 segment/partition 状态机是一体两面:百分比由 QueryCoord 依据各 partition 的 segment 加载情况汇总而来。

ShowPartitions 的请求/响应结构与之对称,只是把粒度下沉到 collection 内的 partition:

type ShowPartitionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID PartitionIDs []int64 } type ShowPartitionResponse struct { Status *commonpb.Status PartitionIDs []UniqueID InMemoryPercentages []int64 }

3.3 Load / Release:集合与分区的加载生命周期

加载类请求的核心载荷是"要加载谁的 schema、哪些分区":

type LoadCollectionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID schema *schemapb.CollectionSchema } type LoadPartitionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID PartitionIDs []UniqueID Schema *schemapb.CollectionSchema }

携带schema是因为加载动作的本质是"把该集合的全部/部分 partition 的 segment 元数据与索引描述取回,并调度各 QueryNode 把数据搬进内存",schema 用于 QueryNode 侧反序列化与段加载。释放类请求则只需身份信息即可:

type ReleaseCollectionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID } type ReleasePartitionRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID PartitionIDs []UniqueID }

3.4 GetPartitionStates:分区加载状态机

加载不是一蹴而就的,尤其在大数据量下要经历"搬运 + 建索引 + 服务"的多个阶段。文档定义了如下状态枚举:

type PartitionState = int const ( PartitionState_NotExist PartitionState = 0 // 分区不存在 PartitionState_NotPresent PartitionState = 1 // 尚未加载(未驻留内存) PartitionState_OnDisk PartitionState = 2 // 数据在磁盘上(如 disk 索引,尚未进入内存服务) PartitionState_PartialInMemory PartitionState = 3 // 部分 segment 已加载进内存 PartitionState_InMemory PartitionState = 4 // 全部进入内存,可服务查询 PartitionState_PartialInGPU PartitionState = 5 // 部分 segment 驻留 GPU 显存 PartitionState_InGPU PartitionState = 6 // 全部驻留 GPU 显存 )

该枚举同样存在于当前仓库的查询协议定义中,见 pkg/proto/querypb/query_coord.pb.go(PartitionState_NotExist等常量)——这为"该设计在后续版本被继承"提供了直接证据。状态值按资源层级递增:从"不存在/未驻留",经"磁盘 → 内存"两段,再到 GPU 加速形态。PartitionState_PartialInMemory等中间态意味着:查询服务可以在分区部分加载完成时就开始提供尽力而为的服务,而非全有全无。

type PartitionStatesRequest struct { Base *commonpb.MsgBase DbID UniqueID CollectionID UniqueID PartitionIDs []UniqueID } type PartitionStates struct { PartitionID UniqueID State PartitionState } type PartitionStatesResponse struct { Status *commonpb.Status PartitionDescriptions []*PartitionStates }

3.5 CreateQueryChannel 与 GetSegmentInfo

CreateQueryChannel的返回揭示了查询消息通道的"请求 / 结果"二元命名模型:

type CreateQueryChannelResponse struct { Status *commonpb.Status RequestChannelName string ResultChannelName string }

GetSegmentInfo(文档以\*标注为进阶内容)用于细粒度观测 segment 级状态:

type GetSegmentInfoRequest struct { Base *commonpb.MsgBase SegmentIDs []UniqueID } type SegmentInfo struct { SegmentID UniqueID CollectionID UniqueID PartitionID UniqueID MemSize UniqueID NumRows UniqueID IndexName string IndexID UniqueID } type GetSegmentInfoResponse struct { Status *commonpb.Status Infos []*SegmentInfo }

4. Query Channel:查询请求与结果的消息载体(文档 7.3)

在 2.0 的流式架构中,用户查询不是像传统 RPC 那样"直连查询节点",而是经过请求通道 / 结果通道两级消息流中转。CreateQueryChannel一次性返回一对通道名:

  • RequestChannelName:Proxy 把用户查询打包成消息发布到该通道;
  • ResultChannelName:执行查询的 QueryNode 把搜索结果发布到该通道,Proxy 订阅取回。

通道上流动的两类核心消息是SearchMsgRetrieveMsg

4.1 SearchMsg:向量搜索请求

type SearchRequest struct { Base *commonpb.MsgBase ResultChannelID string DbID int64 CollectionID int64 PartitionIDs []int64 Dsl string PlaceholderGroup []byte DslType commonpb.DslType SerializedExprPlan []byte OutputFieldsId []int64 TravelTimestamp uint64 GuaranteeTimestamp uint64 } type SearchMsg struct { BaseMsg SearchRequest }

几个关键字段说明:

  • Dsl + DslType + PlaceholderGroup:查询 DSL 与序列化的占位符组(query vectors 经 protobuf 打包),DslType决定 DSL 解释方式;
  • SerializedExprPlan:由 DSL 编译得到的表达式执行计划,查询节点直接执行;
  • PartitionIDs:限定在哪些分区内搜索;OutputFieldsId:需要随结果返回的标量字段;
  • TravelTimestamp(时间旅行)与GuaranteeTimestamp(一致性保证水位)是 Milvus 时间语义的两个端点:查询需要看到TravelTimestamp之前的快照,且要求数据服务水位至少推进到GuaranteeTimestamp。若数据还不够新,查询会被挂起等待(见 chap01_system_overview.md 中"异步状态同步"的论述)。这一机制的现代版解析可参考 how-guarantee-ts-works.md。

4.2 RetrieveMsg:按表达式取行请求

type RetrieveRequest struct { Base *commonpb.MsgBase ResultChannelID string DbID int64 CollectionID int64 PartitionIDs []int64 SerializedExprPlan []byte OutputFieldsId []int64 TravelTimestamp uint64 GuaranteeTimestamp uint64 } type RetrieveMsg struct { BaseMsg RetrieveRequest }

Retrieve 与 Search 的差别在于没有向量相关字段,其执行计划通常是"主键/标量过滤"表达式,返回满足条件行的原始数据(point query / filter query)。两者共享同一套ResultChannelID回传模型与时间戳语义。

5. QueryNode:查询的执行节点接口(文档 7.4)

如果说 QueryCoord 决定"加载什么、谁来加载、何时加载",QueryNode 则负责"把加载任务落成内存数据、把查询消息消费成结果"。文档给出的接口:

type QueryNode interface { Component TimeTickProvider // AddQueryChannel notifies QueryNode to subscribe a query channel and be a producer of a query result channel. AddQueryChannel(ctx context.Context, req *querypb.AddQueryChannelRequest) (*commonpb.Status, error) // RemoveQueryChannel removes the query channel for QueryNode component. RemoveQueryChannel(ctx context.Context, req *querypb.RemoveQueryChannelRequest) (*commonpb.Status, error) // WatchDmChannels watches the channels about data manipulation. WatchDmChannels(ctx context.Context, req *querypb.WatchDmChannelsRequest) (*commonpb.Status, error) // LoadSegments notifies QueryNode to load the sealed segments from storage. The load tasks are sync to this // rpc, QueryNode will return after all the sealed segments are loaded. LoadSegments(ctx context.Context, req *querypb.LoadSegmentsRequest) (*commonpb.Status, error) // ReleaseCollection notifies Proxy to release a collection's data ReleaseCollection(ctx context.Context, req *querypb.ReleaseCollectionRequest) (*commonpb.Status, error) // ReleasePartitions notifies Proxy to release partitions' data ReleasePartitions(ctx context.Context, req *querypb.ReleasePartitionsRequest) (*commonpb.Status, error) // ReleaseSegments releases the data of the specified segments in QueryNode. ReleaseSegments(ctx context.Context, req *querypb.ReleaseSegmentsRequest) (*commonpb.Status, error) // GetSegmentInfo requests segment info GetSegmentInfo(ctx context.Context, req *querypb.GetSegmentInfoRequest) (*querypb.GetSegmentInfoResponse, error) // GetMetrics gets the metrics about QueryNode. GetMetrics(ctx context.Context, in *milvuspb.GetMetricsRequest, opts ...grpc.CallOption) (*milvuspb.GetMetricsResponse, error) }

这些方法把查询节点的工作拆成三类:

  1. 通道订阅类AddQueryChannel让节点订阅某 collection 的 request 通道、并成为其 result 通道的生产者;RemoveQueryChannel反向注销。请求结构仅需指明节点与两侧通道:
type AddQueryChannelRequest struct { Base *commonpb.MsgBase NodeID int64 CollectionID int64 RequestChannelID string ResultChannelID string } type RemoveQueryChannelRequest struct { Base *commonpb.MsgBase NodeID int64 CollectionID int64 RequestChannelID string ResultChannelID string }
  1. 增量数据订阅类WatchDmChannels让节点开始消费某个 vchannel(数据流通道)的增量写入,从而把 growing 数据实时搬到查询侧:
type WatchDmChannelsRequest struct { Base *commonpb.MsgBase NodeID int64 CollectionID int64 PartitionID int64 Infos []*datapb.VchannelInfo Schema *schemapb.CollectionSchema ExcludeInfos []*datapb.SegmentInfo }

Infos(要 watch 的 vchannel 信息)与ExcludeInfos(需要排除的 segment,通常是已转为 sealed、改走加载路径的部分)的组合,保证了增量订阅与存量加载之间不重不漏。

  1. 存量加载/释放类LoadSegments把已封口(sealed)的 segment 从对象存储中拉入内存建立可查询结构。注释特别强调该 RPC 是同步语义——只有当所有指定 segment 全部加载完成后才返回,这简化了 QueryCoord 对"加载完成"的判定:
type LoadSegmentsRequest struct { Base *commonpb.MsgBase NodeID int64 Infos []*SegmentLoadInfo Schema *schemapb.CollectionSchema LoadCondition TriggerCondition }

ReleaseSegments / ReleasePartitions / ReleaseCollection则是释放路径,支持从最细粒度(segment)到整体(collection)的逐级回收:

type ReleaseSegmentsRequest struct { Base *commonpb.MsgBase NodeID int64 DbID UniqueID CollectionID UniqueID PartitionIDs []UniqueID SegmentIDs []UniqueID }

6. Collection Replica:查询节点内的内存副本模型(文档 7.5)

文档 7.5 是本章最"硬核"的部分,它揭示 QueryNode 内部如何组织数据。核心概念是collectionReplica:一个 collection/partition 的持久数据在查询节点内存中的本地副本

系统通常有多个查询节点,一个 collection 的数据会被打散分布到所有可用查询节点上,每个节点的collectionReplica只维护自己承担的那一份(collection 的部分数据)——这对应第 1 节"segment group 是数据到节点映射的基本单元"的调度语义。

每个 replica 维护一个名为tSafe的水位值——replica 数据"最新推进到的时间戳上限"。这是查询一致性的基石:当 SearchMsg/RetrieveMsg 携带的GuaranteeTimestamp ≤ tSafe时,本地查询可安全执行,无需等待。

6.1 collectionReplica 主结构

type collectionReplica struct { tSafes map[UniqueID]tSafer // map[collectionID]tSafer mu sync.RWMutex // guards all collections map[UniqueID]*Collection partitions map[UniqueID]*Partition segments map[UniqueID]*Segment excludedSegments map[UniqueID][]*datapb.SegmentInfo // map[collectionID]segmentIDs }

四个 map 分别按 collectionID / partitionID / segmentID 索引三类对象,构成"collection → partitions → segments"的自上而下持有链;excludedSegments记录需要从增量订阅中剔除的 segment;一把sync.RWMutex保护整体并发访问,因为查询读取与 WatchDmChannels/LoadSegments 带来的结构变更会同时发生。

6.2 Collection:一次加载的顶层单元

type FieldSchema struct { FieldID int64 Name string IsPrimaryKey bool Description string DataType DataType TypeParams []*commonpb.KeyValuePair IndexParams []*commonpb.KeyValuePair } type CollectionSchema struct { Name string Description string AutoID bool Fields []*FieldSchema }

注意Collection.collectionPtr C.CCollection:schema 描述由 Go 持有,而实际的可查询数据载体由 C++ 侧(Segcore,见 docs/archive/milvus-2.0/segcore 下的 segment 文档)承载,C.CCollection正是 Go ↔ C++ 的桥接指针——Milvus 的查询执行核心在 C++ 层以列存结构完成向量计算。

type Collection struct { collectionPtr C.CCollection id UniqueID partitionIDs []UniqueID schema *schemapb.CollectionSchema vChannels []Channel pChannels []Channel loadType loadType releaseMu sync.RWMutex releasedPartitions map[UniqueID]struct{} releaseTime Timestamp }

字段含义:

  • partitionIDs:本节点持有该 collection 的哪些 partition;
  • vChannels / pChannels:虚拟数据通道与物理通道,pChannels对应该节点消费的 DmChannel 上游,vChannels是对外暴露的逻辑通道视图;
  • loadType:本次加载的类型(整个 collection 还是指定 partition,用于释放语义的判定);
  • releasedPartitionsreleaseTime:记录"已执行释放的分区"与释放时间,防止释放请求乱序到达导致的误删。

6.3 Partition 与 Segment:持有链的中间层与最小单元

Partition 非常轻量,只是把 segment 归属关系串起来:

type Partition struct { collectionID UniqueID partitionID UniqueID segmentIDs []UniqueID }

Segment 则是结构最丰富的对象:

type segmentType int32 const ( segmentTypeInvalid segmentType = iota segmentTypeGrowing segmentTypeSealed segmentTypeIndexing ) type indexParam = map[string]string type Segment struct { segmentPtr C.CSegmentInterface segmentID UniqueID partitionID UniqueID collectionID UniqueID onService bool vChannelID Channel lastMemSize int64 lastRowCount int64 once sync.Once // guards enableIndex enableIndex bool rmMutex sync.Mutex // guards recentlyModified recentlyModified bool typeMu sync.Mutex // guards builtIndex segmentType segmentType paramMutex sync.RWMutex // guards index indexInfos map[FieldID]*indexInfo idBinlogRowSizes []int64 vectorFieldMutex sync.RWMutex // guards vectorFieldInfos vectorFieldInfos map[UniqueID]*VectorFieldInfo pkFilter *bloom.BloomFilter // bloom filter of pk inside a segment }

对 Segment 结构做几点工程解读:

  • 生命周期与类型segmentTypegrowing(增量写入中)→ sealed(已封口)→ indexing(已就索引)间迁移;文档注释中标出的segmentTypeInvalid = iota说明该常量组自 0 开始按声明顺序编号,Go 的 iota 语义保证了类型值稳定且紧凑;
  • 内存汇报lastMemSize / lastRowCount是节点周期性向 QueryCoord 汇报的资源快照,供其做负载均衡与副本调度(呼应第 1 节"节点过载时把部分 segment group 迁移到低载节点");
  • 索引相关enableIndex / indexInfos决定该 segment 查询走暴力扫描还是索引检索;vectorFieldInfos记录向量字段的维度等元数据;
  • 去重与状态追踪pkFilter是段内主键的 Bloom Filter,用于WatchDmChannels场景下的主键去重判断——同一主键的重复写入需按序处理;
  • 并发控制:结构上散布了once / rmMutex / typeMu / paramMutex / vectorFieldMutex等多把细粒度锁,各自保护独立的易变状态(是否启用索引、最近是否被修改、segment 类型、索引参数、向量字段信息),尽量减少读写互斥——这是高并发查询路径上的典型锁设计。

6.4 Data Sync Service:流驱动的数据同步骨架

把上面的结构串起来的执行者是dataSyncService

type dataSyncService struct { ctx context.Context mu sync.Mutex // guards FlowGraphs collectionFlowGraphs map[UniqueID]map[Channel]*queryNodeFlowGraph // map[collectionID]flowGraphs partitionFlowGraphs map[UniqueID]map[Channel]*queryNodeFlowGraph // map[partitionID]flowGraphs streamingReplica ReplicaInterface tSafeReplica TSafeReplicaInterface msFactory msgstream.Factory }

其本质是一个按 collection/partition + channel 维度组织的数据流图(FlowGraph)管理器

  • 每个queryNodeFlowGraph对应一个(collection/partition, channel)对的消费流水线:从 MsgStream 拉取插入/删除日志 → 更新 growing segment → 推进 tSafe;
  • streamingReplica提供对第 6.1 节 replica 的并发安全读写接口;tSafeReplica管理每个 collection 的 tSafe 水位;
  • msFactory(msgstream 工厂)为节点创建底层消息流客户端(对应消息中间件如 Pulsar/Kafka 的抽象,其总体设计见 chap04_message_stream.md)。

tSafe 的推进正是由 FlowGraph 在每个消费周期末尾完成的:只有某 channel 在该时间戳之前的日志都被处理完,tSafe 才能越过该时间戳。这为第 4 节所述GuaranteeTimestamp语义提供了执行端支撑——查询节点用 tSafe 回答"我能否立即响应这次查询"。

7. 从 2.0 到当前仓库:查询架构的演进对照

chap07 是理解 Milvus 查询体系的极佳"入门口",但当前仓库(master 分支)的实现已经历显著演进,读者在对照源码时应注意以下对应关系:

  • 协调层internal/querycoordv2/取代了早期 QueryCoord 的单体编排逻辑。该目录中包含的子包与文件体现了新架构的关注点:dist(数据分布视图)、segment(segment 调度)、collection(集合级协调)与负载均衡、channel 管理等,整体以"目标分布 + 实际分布 + 校正动作"的 controller 循环取代了 2.0 中偏命令式的加载/释放逻辑;
  • 执行层internal/querynodev2/取代了早期 QueryNode。其顶层文件(server.goservices.gohandlers.go)仍在实现与 chap07 一脉相承的LoadSegments / WatchDmChannels / AddQueryChannel等职责,但内部组织改为delegator/(查询委托与结果汇集)、segments/(segment 生命周期)、pipeline/(增量消费流水线)、qnview/pkoracle/等子包。其中pkoracle/正是pkFilter(主键 Bloom Filter 去重)思想的延续与强化;
  • 协议层:文档中的querypb各消息与PartitionState枚举仍可在此仓库的 pkg/proto/querypb/query_coord.pb.go 中找到,证明接口契约的总体形态具备跨版本的稳定性;
  • 消息通道模型:2.0 中"request/result 双通道 + MsgStream 中转"的查询分发形态,在后继版本中逐步收敛为 Proxy 直连查询协调者/节点的查询调度与 delegator 汇聚机制,以降低长链路延迟。

需要强调的是:本文对演进部分的描述是基于当前仓库目录结构的观察与推断,不构成对新版内部行为的断言;若需深入新架构,建议直接阅读internal/querycoordv2/internal/querynodev2/下对应源码,并结合 developer_guides/README.md 中其余章节(如数据流、时间同步机制)拼出全貌。

8. 小结:理解查询子系统的三把钥匙

读完 chap07 及本文的展开,可以用三句话概括 Milvus 查询子系统的设计精髓:

  1. 分层协调:QueryCoord 掌握"数据分布与加载状态",通过LoadSegments / WatchDmChannels / ReleaseSegments等控制 RPC 指挥各 QueryNode,通过GetPartitionStates / GetSegmentInfo观测执行结果;
  2. 通道解耦:SearchMsg / RetrieveMsg 经 request 通道分发、经 result 通道回传,查询语义(GuaranteeTimestamp)与数据新鲜度(QueryNode 侧 tSafe)通过时间戳对齐,让流式数据系统能提供可预期的一致性;
  3. 副本即状态:collectionReplica 把"分布式数据布局"落地为节点内可直接查询的内存结构,而 segment 状态机、索引/字段元数据与多把细粒度锁的设计,是支撑高并发查询路径工程质量的具体体现。

沿文档脉络继续深入,可参阅同一归档目录下的 chap05_proxy.md(请求入口如何把查询写入通道)、chap08_binlog.md 与 chap09_data_coord.md(数据侧如何产出 segment),以及 how-guarantee-ts-works.md(时间戳一致性机制的完整说明)。

【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 19:27:23

CANN/ge离线图编译执行Python示例指南

Sample Usage Guide 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、Tensor…

作者头像 李华
网站建设 2026/9/10 19:27:17

NX后处理获取当前刀具信息:UF_MOM_ask_mom与ask_string详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 19:26:17

LIMS系统如何推动实验室数字化转型与效率提升

1. 实验室数字化转型的必然选择(开场白直接切入主题)上周刚帮本地一家三甲医院检验科部署完优检云系统,他们的实验室主任老张拉着我感慨:"这套系统上线后,我们科室的样本流转效率提升了40%,报告差错率…

作者头像 李华
网站建设 2026/9/10 19:22:04

极值分布:攻克质量管理中极端失效预测的利器

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 19:21:51

Java面向对象核心详解:从类与对象到多态实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华