Milvus 源码精读版路线图
目标读者:已经熟悉数据库 / 存储系统概念,希望从架构实现角度系统理解 Milvus 的人。
总体策略
不要从 API 教程开始读,也不要一上来钻 ANN 算法。先看控制面和物理抽象,再看写路径和查询路径,最后下潜到 C++ segcore。
推荐分成 6 个阶段。
阶段 0:建立全局地图(半天)
必读文件
/home/zouhaipeng/work/db/milvus/README.md/home/zouhaipeng/work/db/milvus/cmd/main.go/home/zouhaipeng/work/db/milvus/internal/types/types.go/home/zouhaipeng/work/db/milvus/configs/milvus.yaml
要回答的问题
- 当前 Milvus 到底有哪些主要角色?
- 默认是否启用了 streaming?
- metadata / object store / mq 的外部依赖是什么?
- Go 层的组件接口边界是什么?
读完后你应该得到的图
- 入口层:
cmd/ - 组件边界:
internal/types/types.go - 系统依赖:
configs/milvus.yaml - 当前演进信号:streaming 默认开启
阶段 1:先看控制面骨架(1 天)
必读文件 / 目录
internal/coordinator/mix_coord.gointernal/distributed/mixcoord/service.gointernal/rootcoord/internal/datacoord/internal/querycoordv2/internal/streamingcoord/
阅读顺序建议
- 先读
mix_coord.go看启动和聚合关系 - 再看
rootcoord、datacoord、querycoordv2各自负责什么 - 最后看
streamingcoord在当前架构里的位置
阅读重点
- 为什么 MixCoord 聚合了多个 coord
- RootCoord 和 DataCoord / QueryCoord 的依赖关系
- QueryCoordV2 管的到底是 segment、channel 还是 replica
- streamingCoord 是否只是附属模块,还是控制面一等公民
阶段产出
你应该能画出“当前控制面真实拓扑图”。
阶段 2:吃透写路径(1~2 天)
主线问题
写入请求从客户端进入 Milvus 后,如何一步步变成 segment 和 binlog?
必读文件
internal/proxy/task_insert.gointernal/proxy/task_insert_streaming.gointernal/datanode/internal/flushcommon/writebuffer/write_buffer.gointernal/storage/docs/agent_guides/streaming-system/streaming-system.md
推荐读法
第一步:Proxy insert
看 task_insert.go 的 PreExecute():
- collection/schema 获取
- request 校验
- rowID 自动分配
- timestamp 填充
- schema version 检查
- dynamic field 处理
要理解:Proxy 在写路径里不是简单转发,而是 mutation normalization 层。
第二步:streaming / wal
看 streaming 文档,理解:
- DML append 路径
- PChannel / VChannel / CChannel
- StreamingClient / StreamingNode / StreamingCoord 的职责
第三步:WriteBuffer
重点读 write_buffer.go:
CreateNewGrowingSegmentBufferDataSealSegmentsGetCheckpointEvictBuffer
这是理解 DataNode 数据组织的关键。
第四步:存储格式
读 internal/storage/:
- binlog writer
- data codec
- chunk manager
这一阶段要回答的问题
- growing segment 何时创建、何时 seal
- flush checkpoint 怎么推进
- mutation 是如何从 WAL 物化到对象存储的
- DataNode 和 DataCoord 的职责边界在哪里
阶段 3:吃透查询路径(2 天)
主线问题
Search/query 请求如何从 Proxy 一路走到 QueryNodeV2,并同时覆盖 sealed/growing 数据?
必读文件
internal/proxy/task_search.gointernal/querycoordv2/server.gointernal/querycoordv2/services.gointernal/querycoordv2/meta/internal/querynodev2/server.gointernal/querynodev2/services.gointernal/querynodev2/delegator/delegator.gointernal/querynodev2/segments/
推荐读法
第一步:Proxy search
读 task_search.go 的 PreExecute():
- collection / schema 绑定
- partition / partition key 逻辑
- output fields 翻译
- consistency / guarantee timestamp
- advanced search 初始化
第二步:QueryCoordV2
重点看:
- load/release collection
- replica 管理
- segment/channel 分配
- 资源组与均衡
第三步:QueryNodeV2
重点看:
- node 服务接口
- segment manager
- watch channel / load segment 的流程
第四步:ShardDelegator
这是本阶段最重要的文件:delegator.go
读这个文件时重点标记:
SearchQueryProcessInsertProcessDeleteLoadGrowingLoadL0SyncDistributionUpdateTSafecatchingUpStreamingData
这一阶段要回答的问题
- QueryCoord 决定什么,QueryNode 决定什么
- growing 和 sealed 数据怎么一起查
- delete buffer、partition stats、tsafe 如何影响可见性
- shard 级运行时是怎么维护的
阶段 4:下潜到执行内核(2~3 天)
主线问题
Milvus 真正的数据执行、segment load 和 search 内核在哪里?
必读目录
internal/core/src/segcore/internal/core/src/index/internal/core/src/query/internal/core/src/storage/internal/core/src/segcore/segment_c.h
推荐读法
第一步:看 C 接口头文件
从 segment_c.h 入手,先建立边界:
NewSegmentSegmentLoadAsyncSegmentLoadAsyncSearchAsyncRetrieveInsert
这一步的目的是看清:Go 层到底把什么交给 C++。
第二步:segcore 内部对象
顺着 segment/load/search 相关类型往下追,看:
- growing vs sealed segment
- field / index load
- memory accounting
- search plan 执行
第三步:index 子系统
看 internal/core/src/index/,理解:
- 向量索引封装
- 标量索引
- 倒排 / text match / bitmap / json 等能力
这一阶段要回答的问题
- segment 在 C++ 层的内部表示是什么
- search / retrieve 的执行入口在哪里
- 索引和原始 field data 在执行时如何配合
- Go 和 C++ 的真正边界在哪里
阶段 5:后台维护与长期演化(1~2 天)
主线问题
Milvus 如何维持长期稳定,而不是只跑通基础搜索?
必读文件
internal/datacoord/compaction_trigger_v2.gointernal/datacoord/compaction_task_*.goconfigs/milvus.yamldocs/agent_guides/streaming-system/下各子文档
阅读重点
- 不同 compaction 类型和触发源
- clustering / sort / backfill / storage version upgrade
- woodpecker / kafka / pulsar / rocksmq 的位置
- streaming 当前是否已成为主路径
这一阶段要回答的问题
- compaction 为什么不是“简单 merge”
- 后台维护如何影响查询和存储布局
- 系统的长期演化方向是什么
阶段 6:对照官方资料,校正文档与现实(半天)
推荐对照材料
docs/developer_guides/chap01_system_overview.md- 官方文档
architecture_overview.md - GitHub README
- release notes / blog
做什么
- 哪些旧文档描述仍然成立
- 哪些只代表历史设计
- 当前源码已经演化到了哪里
最终产出
建议你自己写一张表:
| 主题 | 旧文档说法 | 当前源码事实 | 备注 |
|---|---|---|---|
| 控制面 | 独立 coord | MixCoord 聚合 | 当前实现优先 |
| WAL | hash ring 叙事 | streaming/PChannel 叙事更关键 | 两者有历史承接 |
| 查询节点 | query node | querynodev2 + delegator | 当前复杂度显著提升 |
精读时的 12 个关键问题
- Milvus 为什么需要 MixCoord?
- RootCoord 的 timestamp / ID 分配语义是什么?
- DataCoord 如何维护 segment 生命周期?
- mutation 到 WriteBuffer 的关键状态转换是什么?
- flush checkpoint 的推进条件是什么?
- QueryCoordV2 的 replica / distribution 模型是什么?
- QueryNodeV2 为什么需要 ShardDelegator?
- growing / sealed / delete / tsafe 如何共同决定查询可见性?
- C++ segcore 和 Go 层如何分工?
- index build 为什么要异步化?
- compaction 有哪些系统级目标?
- streaming 为何被提升到 single source of truth 的地位?
最终推荐阅读顺序(压缩版)
README.mdcmd/main.gointernal/types/types.gointernal/coordinator/mix_coord.gointernal/proxy/task_insert.gointernal/flushcommon/writebuffer/write_buffer.gointernal/proxy/task_search.gointernal/querycoordv2/internal/querynodev2/delegator/delegator.gointernal/core/src/segcore/segment_c.hinternal/core/src/segcore/internal/datacoord/compaction_trigger_v2.godocs/agent_guides/streaming-system/streaming-system.mdconfigs/milvus.yaml
一句话建议
先把 Milvus 当成数据库系统读,再把它当成向量检索系统读;不要反过来。