news 2026/9/9 23:03:15

LoadDiff 机制详解:Milvus Segment 自管理加载与 Reopen 增量更新架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LoadDiff 机制详解:Milvus Segment 自管理加载与 Reopen 增量更新架构

LoadDiff 机制详解:Milvus Segment 自管理加载与 Reopen 增量更新架构

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

本文基于 Milvus 查询节点(QueryNode)与 segcore 的 LoadDiff 设计文档,系统讲解 Milvus 如何把 sealed segment 的加载编排从 Go 层下沉到 C++ segcore 层,使 segment 自治管理自身的加载过程,并通过 diff 计算支持不重载整段的增量更新(Reopen)。读完你将对SegmentLoadInfoLoadDiffApplyLoadDiff、Storage V1/V2/V3 存储模式适配以及 QueryCoord 侧的触发链路形成完整的工程认知。

背景与动机:segment 加载为什么需要"自己管理自己"

Milvus 的查询节点需要把位于对象存储上的 sealed segment(含字段数据、向量/标量索引、删除记录、文本/JSON 统计等)加载进内存以服务查询。在引入 LoadDiff 之前,整条加载链路的编排职责全部集中在 Go 层

旧架构:Go 层大包大揽,segcore 被动接收

根据设计文档(docs/design-docs/design_docs/segcore/20260204-loaddiff_based_segment_load.md),旧设计中 Go 层segment_loader.go需要负责:

  • 资源估算与申请(按 segment 大小与字段配置申请内存/磁盘配额);
  • 并发控制
  • 加载编排:走Load → LoadSegment → loadSealedSegment/LoadMultiFieldData这条较长调用链;
  • 等待与同步

而 C++ segcore 层完全被动:

  • 被动接收数据(LoadFieldDataLoadIndexLoadDeletedRecord);
  • 不具备自主调度能力。

由此带来的典型问题是:加载相关状态(已加载哪些字段、哪些索引、索引是否包含原始数据 raw data)散落在 Go 侧的对象中,Go 与 C++ 之间围绕"下一步该传什么"需要大量跨语言调用与状态同步;一旦目标数据源发生局部变化(例如新增一个字段、索引从"仅有向量"变为"携带原始数据"、manifest 更新),往往只能整体卸载并重载整段,开销大、链路长。

新架构的三大目标

  1. segment 自己管理加载(对应 issue #45060);
  2. 支持在 QueryNode 上Reopen(增量重开)segment(对应 issue #46358);
  3. 把"从目标状态推导需要做哪些变更"抽象为统一的LoadDiff 差异计算,让初始加载(Load)与增量更新(Reopen)走同一套执行引擎。

说明:以下关键实现均可在仓库中找到实证,核心路径位于internal/core/src/segcore/(C++ segcore)与internal/querycoordv2/internal/querynodev2/(Go 层)。

新架构总览:三层协作 + C API 桥接

设计文档给出了清晰的端到端分层结构:

┌─────────────────────────────────────────────────────────────────────────────┐ │ QueryCoord │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ SegmentChecker │ │ │ │ - Detects segment state changes (lacks, redundancies, updates) │ │ │ │ - Creates Load/Reopen/Reduce tasks based on diff with target │ │ │ │ - getSealedSegmentDiff() checks ManifestPath changes │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ SegmentLoadInfo ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ QueryNode (Go) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ segmentLoader │ │ │ │ - Resource estimation and protection │ │ │ │ - Delegates actual loading to segcore via C API │ │ │ │ - ReopenSegments() for incremental updates │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ LocalSegment │ │ │ │ - Load() / Reopen() pass SegmentLoadInfo to C++ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ C API (SegmentLoad / ReopenSegment) ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Segcore (C++) │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ SegmentLoadInfo │ │ │ │ - Wraps protobuf SegmentLoadInfo with caching │ │ │ │ - ComputeDiff(new_info) → LoadDiff │ │ │ │ - GetLoadDiff() → LoadDiff (for initial load from empty state) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ ChunkedSegmentSealedImpl │ │ │ │ - SetLoadInfo(proto) stores segment load configuration │ │ │ │ - Load() calls GetLoadDiff() + ApplyLoadDiff() │ │ │ │ - Reopen(new_info) calls ComputeDiff() + ApplyLoadDiff() │ │ │ │ - ApplyLoadDiff() executes incremental changes │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘

各层职责如下:

  • QueryCoord(调度中枢)SegmentChecker周期性对照"分布式目标(target)"与"节点当前分布(dist)",发现缺失(lacks)、冗余(redundancies)与需要更新的 segment(updates),据此产出 Load / Reopen / Reduce 任务,并把SegmentLoadInfo下发到 QueryNode。实际实现见 internal/querycoordv2/checkers/segment_checker.go。
  • QueryNode(Go 协调层)segmentLoader只负责资源估算与保护,真正的加载通过 C API 委托给 segcore;对增量场景调用ReopenSegments()。入口可参考 internal/querynodev2/handlers.go 中由LoadSegmentsRequest触发的reopenSegments逻辑,其接口定义位于 internal/querynodev2/segments/segment_loader.go。Go 侧LocalSegmentLoad()/Reopen()负责把SegmentLoadInfo透传给 C++。
  • Segcore(C++ 执行层)SegmentLoadInfo包装 protobuf 消息并提供差异计算能力;ChunkedSegmentSealedImpl是 sealed segment 的核心实现,负责SetLoadInfoLoadReopenApplyLoadDiff的具体执行。

值得一提:这条下沉链路的落地对应一批已合入的 PR(#45061 新增NewSegmentWithLoadInfoAPI、#45488 把加载逻辑从 Go 层迁入 segcore、#46359 支持因数据/schema 变化触发的 reopen、#46394 让 QueryCoord 在 manifest 路径变化时触发 reopen、#46536 统一 Load 与 Reopen、#46598 处理 v1 旧 binlog 格式、#47061 支持 index-含-raw-data 字段的懒加载、#47412 把默认值填充纳入 LoadDiff)。

核心组件一:SegmentLoadInfo 类(加载信息包装与缓存)

设计文档中该类位于 internal/core/src/segcore/SegmentLoadInfo.h 与同目录SegmentLoadInfo.cpp,是一个对 protobufSegmentLoadInfo消息的 C++ 包装层,能力包括:

  • 访问器GetSegmentID()GetNumOfRows()GetIndexInfos()GetBinlogPaths()等;
  • 缓存:内部通过BuildCache()构建按字段快速查找的结构(文档注释称其 cache 为"对可变计算的 memoization",见 SegmentLoadInfo.h 中GetColumnGroups()的可重入说明);
  • 索引转换:把FieldIndexInfo转换为带 mmap 配置的LoadIndexInfo
  • 差异计算ComputeDiff()GetLoadDiff()

设计文档给出的类骨架如下:

class SegmentLoadInfo { public: explicit SegmentLoadInfo(const ProtoType& info, SchemaPtr schema); // Compute diff between current and new load info (for Reopen) LoadDiff ComputeDiff(SegmentLoadInfo& new_info); // Compute diff from empty state (for initial Load) LoadDiff GetLoadDiff(); // Column groups support (Storage V3) std::shared_ptr<milvus_storage::api::ColumnGroups> GetColumnGroups(); private: void BuildCache(); void ComputeDiffIndexes(LoadDiff& diff, SegmentLoadInfo& new_info); void ComputeDiffBinlogs(LoadDiff& diff, SegmentLoadInfo& new_info); void ComputeDiffColumnGroups(LoadDiff& diff, SegmentLoadInfo& new_info); void ComputeDiffReloadFields(LoadDiff& diff, SegmentLoadInfo& new_info); };

从实际源码(SegmentLoadInfo.h)看,私有 diff 计算函数比文档骨架更丰富,还包括ComputeDiffDefaultFields(默认值字段差异)、ComputeDiffTextIndexes(文本索引差异)与ComputeDiffJsonKeyStats(JSON key 统计差异),可见"差异类型"会随功能演进持续扩展。

核心组件二:LoadDiff 结构体("变更清单")

LoadDiff表示两个 segment 加载状态之间的差异,是整个机制的"变更清单"。设计文档给出基础版:

struct LoadDiff { // Indexes to load (field_id -> list of LoadIndexInfo) std::unordered_map<FieldId, std::vector<LoadIndexInfo>> indexes_to_load; // Binlogs to load (Storage V1/V2) std::vector<std::pair<std::vector<FieldId>, proto::segcore::FieldBinlog>> binlogs_to_load; // Column groups to load (Storage V3 manifest mode) std::vector<std::pair<int, std::vector<FieldId>>> column_groups_to_load; // Column groups for lazy loading (index has raw data) std::vector<std::pair<int, std::vector<FieldId>>> column_groups_to_lazyload; // Fields to reload (when index raw data availability changes) std::vector<FieldId> fields_to_reload; // Fields to fill with default values (schema evolution) std::vector<FieldId> fields_to_fill_default; // Indexes to drop std::set<FieldId> indexes_to_drop; // Field data to drop std::unordered_set<FieldId> field_data_to_drop; // Manifest update flag bool manifest_updated = false; std::string new_manifest_path; bool HasChanges() const; std::string ToString() const; // For debugging };

实际实现(SegmentLoadInfo.h)在文档基础上还引入了大量"replace 类"与"细分统计类"条目,每个条目的语义注释都值得精读:

  • indexes_to_loadvsindexes_to_replace:前者用于"字段尚无索引"的新增加载,后者用于"字段已有索引"时的替换加载;
  • binlogs_to_load/binlogs_to_replace:仅在 current 与 new 都处于 binlog 模式时填充;
  • column_groups_to_load/column_groups_to_replace:对应字段新建/字段在列组间移动或列组数据变化的场景;同一个 group 可因不同配置重复出现;
  • column_groups_to_lazyload/column_groups_to_lazyreplace:懒加载路径(含"index 携带 raw data"的字段);
  • json_indexes_to_drop:由于 JSON 字段可含多个嵌套路径索引,字段级 drop 会丢失"保留兄弟路径"所需的标识,故按field_id -> 嵌套路径集合精确下发;
  • 文本/JSON 统计系列text_indexes_to_load(从预构建文件装载)、text_indexes_to_create(从 raw data 现场创建,服务于 enable_match 字段)、json_stats_to_load/replace/drop
  • load_external_manifest:外部集合(external collection)的列名是 parquet 字段名而非数字 field_id,应绕过ComputeDiffColumnGroups,在ApplyLoadDiff中直接LoadColumnGroups(manifest_path)

HasChanges()会聚合上述全部条目(含manifest_updatedload_external_manifest),ToString()则为每个条目输出人类可读摘要,方便定位"这个 reopen 到底改了什么"。

一个需要特别留意的约束(代码注释中明确写出):binlog 模式与 manifest 模式之间不支持跨类别变更——SegmentLoadInfo只能在同一类别内演进,即 binlog → binlog,或 manifest → manifest。

核心组件三:ApplyLoadDiff —— 统一执行增量变更

设计文档给出的执行骨架位于 internal/core/src/segcore/ChunkedSegmentSealedImpl.cpp:

void ChunkedSegmentSealedImpl::ApplyLoadDiff( SegmentLoadInfo& segment_load_info, LoadDiff& diff, milvus::OpContext* op_ctx) { // 1. Load new indexes if (!diff.indexes_to_load.empty()) { LoadBatchIndexes(trace_ctx, diff.indexes_to_load, op_ctx); } // 2. Reload fields (MUST before drop index) if (!diff.fields_to_reload.empty()) { ReloadColumns(diff.fields_to_reload, op_ctx); } // 3. Drop indexes (MUST after reload to maintain data availability) if (!diff.indexes_to_drop.empty()) { for (auto field_id : diff.indexes_to_drop) { DropIndex(field_id); } } // 4. Load column groups (eager + lazy) if (!diff.column_groups_to_load.empty()) { LoadColumnGroups(..., diff.column_groups_to_load, true, op_ctx); } if (!diff.column_groups_to_lazyload.empty()) { LoadColumnGroups(..., diff.column_groups_to_lazyload, false, op_ctx); } // 5. Load field binlogs (Storage V1/V2) if (!diff.binlogs_to_load.empty()) { LoadBatchFieldData(trace_ctx, diff.binlogs_to_load, op_ctx); } // 6. Fill default values for schema evolution if (!diff.fields_to_fill_default.empty()) { FillDefaultValueFields(diff.fields_to_fill_default); } // 7. Drop field data if (!diff.field_data_to_drop.empty()) { for (auto field_id : diff.field_data_to_drop) { DropFieldData(field_id); } } }

实现层面(ChunkedSegmentSealedImpl.cpp)有两点超出文档骨架、需要补充的事实:

  1. 每个阶段都做了可取消性(cancellation)检查。源码在第 7243、7253、7268、7347 行等多处调用CheckCancellation(op_ctx, id_, "ChunkedSegmentSealedImpl::ApplyLoadDiff()"),与 Go 侧的资源保护配合,保证加载过程可被中止回收,避免长时间占用资源。
  2. 内部存在"已发布状态"(PublishedState)快照与写时复制(COW)提交机制。例如Load()开头会CapturePublishedState()再构造可变副本,文档注释说明"ApplyLoadDiff 产生的运行时更新在数据加载完成后通过 COW helper 提交",保证并发读(search/retrieve)在加载过程中始终看到一致状态。索引/字段的 drop 也有DropIndexFromState(...)DropFieldData(..., &runtime)这类带状态快照的重载版本,具体可参见 ChunkedSegmentSealedImpl.cpp 中DropFieldDataDropIndex的定义。

Load 与 Reopen:同一 diff 机制的两个入口

这是本次设计的核心统一点:初始 Load 与增量 Reopen 共用ApplyLoadDiff()执行管线,差异只在于 diff 的来源。

初始 Load:从空状态取 diff

void ChunkedSegmentSealedImpl::Load( milvus::tracer::TraceContext& trace_ctx, milvus::OpContext* op_ctx) { auto diff = segment_load_info_.GetLoadDiff(); ApplyLoadDiff(segment_load_info_, diff, op_ctx); }

实际的Load()实现(ChunkedSegmentSealedImpl.cpp)与 Reopen 共享reopen_mutex_以保证串行化,并通过CapturePublishedState()捕获快照后,把"已用默认值填充的字段、已创建的文本索引"等运行时状态标记进可变副本,再GetLoadDiff()计算"从空 → 目标"的完整 diff 并打日志(如Loading segment {id} with {rows} rows),最后交给ApplyLoadDiff

Reopen:对当前状态计算增量 diff

void ChunkedSegmentSealedImpl::Reopen( const milvus::proto::segcore::SegmentLoadInfo& new_load_info, SchemaPtr new_schema) { auto current = std::atomic_load(&segment_load_info_); SegmentLoadInfo current_mutable(*current); SegmentLoadInfo new_seg_load_info(new_load_info, new_schema); auto diff = current_mutable.ComputeDiff(new_seg_load_info); std::atomic_store(&segment_load_info_, std::make_shared<const SegmentLoadInfo>(new_seg_load_info)); ApplySchemaForReopen(new_schema); ApplyLoadDiff(new_seg_load_info, diff); }

从实现可以看到segment_load_info_是一个原子共享指针std::atomic_load/std::atomic_store),当前加载配置会被整体替换成新配置,同时ApplySchemaForReopen(new_schema)更新段内 schema,最后ApplyLoadDiff只执行差异部分。源码中Reopen系列包含多个重载(见 ChunkedSegmentSealedImpl.cpp),分别覆盖"仅 schema 变化""携带 OpContext 的 schema 变化"以及"携带完整新 load info + schema"的路径。

Schema 感知的 Reopen

文档特别强调:schema 变更不再走"直接填充缺失字段"的独立 sealed-segment 路径。QueryNode 会把最新集合 schema 连同SegmentLoadInfo一起传给 segcore,于是ComputeDiff()在计算 load / 默认值填充 / drop 动作时能"看见"新增或删除的字段。

对于仅由 query/retrieve 计划触发的"schema-only 懒检查",Reopen(SchemaPtr)会复用当前 load info,与较新的 schema 做差异计算;默认值填充、字段数据删除、索引删除因此都被建模进LoadDiff,统一由ApplyLoadDiff()执行。这也让段内字段与查询计划所需字段的一致性维护完全收敛到 C++ 层。

三种存储模式的 diff 适配

Milvus 的对象存储布局历史上存在多种格式,LoadDiff 机制对它们做了分层适配:

存储模式数据组织特征diff 计算入口说明
Storage V1(遗留 binlog)每个字段拥有独立 binlog 文件;child_fields为空,field_id == group_idComputeDiffBinlogs()文档中称为 legacy binlog,另有 PR #46598 专门处理 v1 格式
Storage V2(列组 Column Groups)多个字段可共享同一列组;child_fields列出组内字段 IDComputeDiffBinlogs()仍以 binlog 路径集合表达变更
Storage V3(manifest 模式)使用Loon manifest描述列组元数据;支持"index 携带 raw data"字段的懒加载ComputeDiffColumnGroups()ManifestPath变化会触发 segment reopen

设计文档明确指出 V3 的 diff 类型划分为"加载/替换列组"与"懒加载/懒替换列组"两对动作,且该能力由 SegmentLoadInfo.cpp 中GetColumnGroups()milvus_storage::api::ColumnGroups支撑。

Proto 定义与字段演进

SegmentLoadInfo的线格式定义于 pkg/proto/segcore.proto(QueryCoord 调度侧还有一份同名的querypb.SegmentLoadInfo,见 pkg/proto/query_coord.proto,两者在跨层转发时互转)。设计文档收录的消息体:

message SegmentLoadInfo { int64 segmentID = 1; int64 partitionID = 2; int64 collectionID = 3; int64 dbID = 4; int64 flush_time = 5; repeated FieldBinlog binlog_paths = 6; int64 num_of_rows = 7; repeated FieldBinlog statslogs = 8; repeated FieldBinlog deltalogs = 9; repeated int64 compactionFrom = 10; repeated FieldIndexInfo index_infos = 11; string insert_channel = 13; int64 readableVersion = 14; int64 storageVersion = 15; bool is_sorted = 16; map<int64, TextIndexStats> textStatsLogs = 17; repeated FieldBinlog bm25logs = 18; map<int64, JsonKeyStats> jsonKeyStatsLogs = 19; common.LoadPriority priority = 20; string manifest_path = 21; // Storage V3 manifest path }

对照仓库实际文件(pkg/proto/segcore.proto),该消息已进一步演进,后续新增字段包括:

  • use_take_for_output(字段 22):为 true 时输出字段检索改用take()API 而非bulk_subscript
  • estimated_bytes_per_row(字段 23):来自 Take API 的每行平均内存字节采样值,在格式元数据缺乏 size stats 时作为回退估算;
  • commit_timestamp(字段 24):import / CDC segment 的事务时间戳。非零时 segcore 会覆写内存中的时间戳列,使 MVCC / TTL / 删除逻辑统一按 commit 时间而非行内原始时间戳生效;
  • 原文档中compactionFrom(字段 10)注释为 "segmentIDs compacted from",即该 segment 由哪些 compaction 源段合并而来。

FieldIndexInfo(同文件 L100-L115)则描述了每个索引的index_name/indexID/buildIDindex_paramsindex_file_pathsindex_sizeindex_versionnum_rowscurrent_index_version与标量索引版本等信息,是 diff 中"索引加载/替换/删除"判定的数据基础。

QueryCoord 集成:谁来决定何时 Reopen

SegmentChecker 与差异检测

Go 侧负责发现更新需求的是 internal/querycoordv2/checkers/segment_checker.go 中的SegmentChecker。设计文档给出了关键逻辑:

func (c *SegmentChecker) getSealedSegmentDiff(...) (toLoad, loadPriorities, toRelease, toUpdate) { isSegmentUpdate := func(segment *datapb.SegmentInfo) bool { segInDist, existInDist := distMap[segment.ID] return existInDist && segInDist.ManifestPath != segment.GetManifestPath() } // ... detect updated segments and create reopen tasks } func (c *SegmentChecker) createSegmentReopenTasks(...) []task.Task { // Creates ActionTypeReopen tasks for updated segments }

实际实现印证了这一描述:getSealedSegmentDiff(segment_checker.go)在判断是否需要 update 时调用了packed.CompareManifestPath(segInDist.ManifestPath, segment.GetManifestPath())(segment_checker.go),对"dist 中段"与"目标段"的 manifest 路径做语义比较,并在日志中同时输出 distManifest 与 targetManifest;而 reopen 任务最终通过task.NewSegmentAction(s.Node, task.ActionTypeReopen, s.GetInsertChannel(), s.GetID())创建(segment_checker.go)。

新增 ActionType:Reopen

为承载增量更新,任务动作类型在既有基础上新增了一类:

const ( ActionTypeGrow ActionType = iota + 1 ActionTypeReduce ActionTypeUpdate ActionTypeReopen // New action type for segment reopen )

任务到达 QueryNode 后,由 handler 层路由到 loader 的ReopenSegments(internal/querynodev2/handlers.go),该能力在 loader 接口ReopenSegments(ctx context.Context, loadInfos ...)中暴露(internal/querynodev2/segments/segment_loader.go),最终把SegmentLoadInfo交给 segcore 执行 C++ 侧的Reopen

执行顺序:保证"查询永远有可用数据源"

ApplyLoadDiff()内的动作顺序是正确性关键。设计文档的权威顺序如下:

  1. 加载/替换索引—— 新索引或替换索引最先就位;
  2. 重载列(ReloadColumns)—— 恢复因"索引不再携带 raw data"而失去 raw data 覆盖的字段;
  3. 加载/替换列组—— Storage V3 的 eager 与 lazy 两路;
  4. 加载/替换字段 binlog—— Storage V1/V2 数据面;
  5. 丢弃索引—— 只有在新数据/新索引可用后才 drop 旧索引;
  6. 加载文本/JSON stats 索引—— 应用预构建的 text 与 JSON 统计变更;
  7. 填充默认值—— 面向无数据源的 schema 演进字段;
  8. 从 raw data 现场创建文本索引—— 服务于没有预构建索引文件的 enable_match 字段;
  9. 丢弃字段数据—— 清理被删除的字段。

设计文档特别强调:"当前实现总是先加载/替换 raw 字段数据、再 drop 旧索引,因此在索引切换过程中,查询永远可以回退到某个合法数据源。"这个可用性保证是第 2、3、4 步必须排在 drop 动作(第 5、9 步)之前的根本原因。

当前能力矩阵

设计文档以表格形式总结了 LoadDiff 机制已覆盖的能力(均标注为已实现,可在 SegmentLoadInfoTest.cpp 等测试中找到对应用例):

FeatureStatusDescription
Initial Load✅ Implemented从空状态经GetLoadDiff()加载 segment
Index Load✅ Implemented加载新索引(向量/标量)
Index Drop✅ Implemented目标中移除的索引被 drop
Binlog Load✅ Implemented加载字段 binlog(Storage V1/V2)
Column Group Load✅ Implemented加载列组(Storage V3)
Lazy Loading✅ Implemented跳过携带 raw data 的索引字段的加载
Field Reload✅ Implemented索引 raw data 可用性变化时重载字段
Schema Evolution✅ Implemented为新增字段填充默认值
Manifest Update✅ Implementedmanifest 路径变化时执行 reopen
Legacy Format✅ Implemented兼容 V1 binlog 格式

单元测试覆盖

差异计算的单元测试集中在 internal/core/src/segcore/SegmentLoadInfoTest.cpp(仓库中约 3955 行),设计文档概括的测试维度包括:

  • 索引的 diff 计算;
  • binlog 的 diff 计算(覆盖 V1 与 V4 两种格式);
  • 列组的 diff 计算;
  • 混合格式处理(V1/V2 binlog 与 V3 manifest 并存场景下的模式约束);
  • schema 演进场景(新增带默认值字段、字段数据 drop、索引 drop 的组合)。

与之配套的段级测试还有ChunkedSegmentSealedStorageV2Test.cppChunkedSegmentSealedTest.cppChunkedSegmentSealedBinlogIndexTest.cppLoadCancellationTest.cpp等,读者可沿 internal/core/src/segcore/ 目录继续深入。

收益总结

设计文档归纳的架构收益可概括为七点:

  1. 职责清晰:加载逻辑完全由 segment 自己管理,Go 层退化为"资源看门人";
  2. 性能优化:显著减少 Go ↔ C++ 的跨语言调用次数(编排不再逐字段往返);
  3. 资源管理更精准:C++ 层直接处理资源分配,避免中间层重复估算;
  4. 缓存深度整合:segment 可直接与缓存层交互;
  5. schema 演进更主动:segment 掌握自身全部信息,能自主决策填充/删除;
  6. 增量更新:局部变化(新增字段、索引 raw data 状态翻转、manifest 更新)无需整体卸载重载;
  7. API 统一:单一ApplyLoadDiff()覆盖所有加载场景,Load 与 Reopen 共享一条执行管线。

未来演进方向

文档明确该机制的终极目标是:让所有加载状态变更都走 LoadDiff。已规划的扩展方向包括:

  1. 在线索引构建:检测到某字段新增索引 → reopen 段并加载新索引 → 若索引携带 raw data 则 drop 字段数据;
  2. 在线 schema 变更:新增带默认值字段、drop 字段(删除字段数据)、修改字段属性;
  3. 索引类型变更:在不同索引类型之间切换并处理参数变化;
  4. Compaction 集成:compaction 之后更新 segment、处理段合并;
  5. 分层存储(Tiered Storage):数据在不同存储层级间移动、更新存储位置。

如何继续阅读源码

建议按以下路径沿"决策 → 编排 → 执行"的调用链阅读:

  • 调度与差异检测:internal/querycoordv2/checkers/segment_checker.go(getSealedSegmentDiffActionTypeReopen任务创建);
  • 线格式定义:pkg/proto/segcore.proto(SegmentLoadInfoFieldIndexInfo)与 pkg/proto/query_coord.proto;
  • Go 编排层:internal/querynodev2/segments/segment_loader.go(ReopenSegments)、internal/querynodev2/handlers.go;
  • C++ 差异计算:internal/core/src/segcore/SegmentLoadInfo.h 与同目录SegmentLoadInfo.cppComputeDiff*系列、GetLoadDiff);
  • C++ 执行主体:internal/core/src/segcore/ChunkedSegmentSealedImpl.cpp(Load/Reopen/ApplyLoadDiff/DropIndex/DropFieldData);
  • 测试印证:internal/core/src/segcore/SegmentLoadInfoTest.cpp。

提示:LoadDiff 机制面向 sealed segment 的加载编排,其行为与对象存储格式版本(Storage V1/V2/V3)、索引是否携带 raw data、schema 演进状态强相关,跨 binlog/manifest 类别的变更当前不受支持,阅读代码与设计时需留意上述前提。

【免费下载链接】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/9 23:03:00

GC0308摄像头驱动开发实战:从DVP接口到图像采集的完整指南

简介&#xff1a;摄像头GC0308资料包面向嵌入式系统与物联网开发者&#xff0c;聚焦该CMOS图像传感器的寄存器初始化配置难题。压缩包共3个文件&#xff0c;包含GC0308数据手册PDF与配套C语言驱动源码&#xff08;头文件和源文件&#xff09;&#xff0c;头文件定义寄存器映射与…

作者头像 李华
网站建设 2026/9/9 23:01:48

ESP32 OpenOCD调试实战:Windows环境下的安装配置与问题排查全指南

简介&#xff1a;OpenOCD ESP32 Win32 是一份面向 Windows 平台 ESP-IDF 开发者的调试与烧录工具包&#xff0c;定位在连接常用 JTAG/SWD 调试器与 ESP32 目标芯片之间&#xff0c;配合 GDB 完成从固件烧录到源码级调试的完整流程。压缩包体积约 2.01MB&#xff0c;内置可执行程…

作者头像 李华
网站建设 2026/9/9 23:01:09

zx-du99d4-1.12通用鸡血BIOS:破解功耗墙,解锁老主板隐藏性能

简介&#xff1a;ZX-DU99D4主板的第三方优化版BIOS固件1.12版&#xff0c;主要面向希望通过调整底层设置充分释放硬件潜力的DIY玩家。压缩包共56个文件、约62.2MB&#xff0c;主要包含BIOS ROM镜像、AMI刷写工具、CPU-Z/AIDA64等硬件检测软件&#xff0c;以及dll运行库、sql数据…

作者头像 李华
网站建设 2026/9/9 23:00:21

Python Fabric部署自动化:从SSH远程命令到CI/CD全流程实战

每次部署上线&#xff0c;你是不是也有过这样的体会&#xff1a;本地测试全绿&#xff0c;代码提交完&#xff0c;打开终端&#xff0c;ssh 连上服务器&#xff0c;备份、拉代码、改配置、重启服务&#xff0c;一连串命令全靠手敲&#xff0c;哪一步稍微分神&#xff0c;线上就…

作者头像 李华