Milvus架构与核心原理文章目录Milvus架构与核心原理1 整体认识2 三种部署方式3 分清逻辑数据模型和物理处理模型3.1 Database3.2 Collection3.3 Schema3.4 Field3.5 Entity3.6 Partition3.7 Shard3.8 Segment3.8.1.1 Growing Segment3.8.1.2 Sealed Segment4 分布式 Milvus 的四层架构4.1 Client SDK4.2 Proxy 无状态访问层4.3 Coordinator 控制平面的“大脑”4.4 Streaming Node 实时数据负责人4.5 Query Node 历史数据查询引擎4.6 Data Node 历史数据后台处理节点5 三类持久化存储5.1 Meta Storage etcd5.2 WALWrite\-Ahead Log5.3 Object Storage6 VChannelPChannelShard6.1 VChannel6.2 PChannel6.3 为什么需要两层 Channel6.4 主键散列与写入路由7 一次insert内部发生了什么7.1 Insert返回不等于索引已经建立好7.2 TSO8 Flush、Index、Load、Handoff8.1 Flush8.2 Index Building8.3 Load8.4 Handoff9 一次Search内部发发生了什么9.1 TopK9.2 Search 与 Query10 标量过滤与BitSet10.1 BitSet10.1.1 BitSet是什么10.1.2 必须先定义1的含义10.1.3 BitSet如何参数属性过滤10.1.4 BitSet如何参与删除10.2 案例10.2.1 查询时间ts15010.2.2 查询时间ts25010.2.3 查询时间ts35010.3 删除时不会立即释放对象存储空间11 时间戳与数据可见性11.1 Guarantee Timestamp11.2 Service Timestamp11.3 Graceful Time12 四种一致性级别12.1 Strong12.2 Bounded Staleness12.3 Session12.4 Eventually13 Compaction1 整体认识Milvus 不是“把向量写进一个文件然后从这个文件里搜索”的简单程序。分布式 Milvus 把以下工作拆给不同组件接收、校验和路由请求协调集群中的任务和数据分布处理实时写入及实时数据查询查询已经持久化的历史数据构建索引、压缩数据段持久化元数据、操作日志、原始数据和索引。数据和索引的持久副本放在共享存储中计算节点可以按需加载数据。计算节点发生故障时系统可以重新调度节点并从 WAL、元数据存储和对象存储恢复而不是把某台计算节点的本地磁盘当作唯一数据来源。来源Milvus 架构概述2 三种部署方式Milvus官网给出了三种部署方式部署模式形态适用场景官网给出的规模参考Milvus Lite嵌入 Python 应用本地文件持久化学习、Notebook、原型、边缘设备数百万向量以内Milvus Standalone所有服务组件打包在单机 Docker 部署中中小规模生产环境可扩展到约 1 亿向量Milvus Distributed组件运行在 Kubernetes 集群中大规模生产、高可用、独立扩缩容约 1 亿到数百亿向量Milvus LiteMilvusClient(milvus_demo.db)会创建一个嵌入应用进程的本地向量数据库。它适合学习 API但不能据此认为生产集群也只有一个.db文件。三种部署共享主要客户端 API因此从 Lite 迁移到服务端部署时业务代码通常不需要重写但底层组件数量、可用功能和一致性能力并不完全相同。3 分清逻辑数据模型和物理处理模型3.1 DatabaseDatabase 命名空间Database用于组织多个collection他不是向量数据的直接处理单位可以类比关系型数据库中的数据库Database ├── Collection: documents ├── Collection: images └── Collection: users3.2 CollectionCollection逻辑数据表Collection 是存储和管理实体的主要逻辑对象。官网将它类比成关系数据库的表列是 Field行是 Entity。例如一个rag知识库可以设计为idtextsourcecategoryvector1“Milvus 是向量数据库……”milvus.mddatabase[0.12, ...]2“LangChain 是……”langchain.mdframework[0.38, ...]3.3 SchemaSchema Collection的结构定义Schema描述Collection中有哪些字段字段类型主键向量维度以及其他字段属性一个字段可以是主键字段例如id标量字段例如text、price、category向量字段例如FLOAT_VECTOR、SPARSE_FLOAT_VECTOR动态字段中保存的额外属性。向量字段通常还需要声明维度例如dim768表示每个向量必须包含 768个分量3.4 FieldFieldCollection 的一列标量字段保存结构化属性向量字段保存 Embedding。标量字段参与输出结果标量查询元数据过滤分区键标量索引。向量字段参与ANN 相似性搜索范围搜索混合搜索向量索引。3.5 EntityEntity Collection中的一行记录同一行所有字段的值共同组成一个 Entity每个 Entity 通过主键识别。例如{ id: 1, vector: [0.12, 0.08, ...], text: Alan Turing..., subject: history, }3.6 PartitionPartitionCollection 的逻辑子集Partition 与父 Collection 使用同一个 Schema但只包含部分实体。指定 Partition 搜索时可以跳过其他 Partition。例如按业务隔离documents ├── partition: tenant_a ├── partition: tenant_b └── partition: tenant_cPartition 主要解决缩小读取范围按业务或数据生命周期隔离数据避免搜索无关数据。3.7 ShardShardCollection 的写入分片Shard 用于把写入负载分散到多个流处理通道。每个 Shard 对应一个 VChannel。Shard与Partition不同概念主要目的Partition缩小读取范围、逻辑隔离数据Shard分散写入负载、提高写入并行度不要把shard_num4理解为创建了四个供业务直接选择的 Partition。3.8 SegmentSegmentMilvus 内部存储和处理数据的基本物理单位一个 Collection 可以包含多个 Segment。搜索时Milvus 会在相关 Segment 上执行查询并归并结果。Segment 分为两种状态。3.8.1.1 Growing Segment正在接收新数据尚未全部预存到对象存储由 Streaming Node 维护可以参与实时搜索通常不能像稳定的历史段一样使用完整的持久化索引。3.8.1.2 Sealed Segment数据已经持久化到对象存储不再接受新写入内容不可变可以由 Data Node 构建正式索引可由 Query Node 加载并查询。FlushGrowing Segment 转换为 Sealed Segment 的过程称为 Flush。Flush 影响数据从实时路径进入历史数据路径但“数据是否能被查询到”还受一致性级别控制不能简单理解成“不 Flush 就查不到”。4 分布式 Milvus 的四层架构官网把分布式 Milvus 分为Access Layer访问层Coordinator协调层Worker Nodes工作节点Storage存储层。4.1 Client SDKClient SDK 包括 Python、Java、Go、Node.js、C# 等客户端。4.2 Proxy 无状态访问层ProxyAPI 网关、请求校验器、路由器和结果归并器Proxy 主要负责接收 SDK 请求认证和权限检查校验 Schema、字段类型和向量维度将请求路由到正确的处理节点聚合中间查询结果将最终结果返回客户端。Proxy 自身不保存向量和索引的权威持久副本因此可以横向部署多个实例┌── Proxy 1 Client ─ LB ─┼── Proxy 2 └── Proxy 3Milvus 使用 MPPMassively Parallel Processing大规模并行处理模式。一个搜索可能在多个 Shard、Segment 和节点上并行执行再经过多级 Reduce 得到全局结果。Reduce结果归并每个执行节点先返回自己的局部候选结果上层节点合并、去重或重新排序最终保留全局 TopK。4.3 Coordinator 控制平面的“大脑”Coordinator维护集群拓扑、调度任务、管理数据分布和集群级一致性它负责的任务包括DDL 和 DCL 管理Collection、Partition、索引等元数据管理TSO 与时间刻度管理WAL 与 Streaming Node 的绑定和服务发现Query Node 拓扑、负载均衡和查询视图Segment 拓扑和数据视图将索引、Compaction 等离线任务分配给 Data Node节点故障后的重新调度。Coordinator 不是向量搜索或索引构建的主要执行者。它更像调度中心Coordinator将 Segment A 加载到 Query Node 2。 Query Node 2从对象存储加载 Segment A。 Coordinator为 Segment B 创建索引。 Data Node读取 Segment B构建索引并写回对象存储。官网说明任一时刻集群中有一个活动 Coordinator 负责协调工作。高可用部署中的备用实例不应理解成多个 Coordinator 同时独立修改同一份集群状态。4.4 Streaming Node 实时数据负责人Streaming Node分片级实时处理节点它基于 WAL 提供分片级一致性和故障恢复管理 Growing Segment并参与实时数据查询。主要职责接收insert、delete、upsert将操作追加到 WAL为数据包分配 TSO维护 Growing Segment搜索本地 Growing Segment生成分片级查询计划协调 Query Node 获取 Sealed Segment 的历史结果Flush Growing Segment崩溃后重放 WAL。为什么 Streaming Node 既参与写入又参与查询因为刚写入的数据可能还没有Flush 到对象存储构建完整的持久化索引Handoff 到 Query Node。如果只查 Query Node就可能漏掉实时数据。因此当前官方架构描述的搜索路径是Streaming Node搜索 Growing Segment Query Node搜索 Sealed Segment ↓ 合并实时与历史结果4.5 Query Node 历史数据查询引擎Query Node加载并查询 Sealed SegmentQuery Node 负责从对象存储加载 Sealed Segment加载向量索引和标量索引执行 Segment 级向量搜索执行标量过滤返回局部候选结果。Query Node 的主要资源是内存CPU可选 GPU本地缓存对象存储带宽。Query Node 的本地数据不是唯一持久副本。节点故障后系统可以让其他 Query Node 从对象存储重新加载相关 Segment。4.6 Data Node 历史数据后台处理节点Data Node处理历史数据的离线计算节点主要职责构建向量索引构建标量索引执行 Compaction合并或重组 Segment将处理结果写回对象存储。Data Node 通常不直接承接在线搜索。这样可以避免索引构建和 Compaction 的 CPU、内存开销干扰查询延迟。5 三类持久化存储5.1 Meta Storage etcd元数据 描述数据和集群状态的数据etcd 主要保存Collection SchemaSegment 状态消息消费检查点服务注册和健康信息集群拓扑及任务状态。5.2 WALWrite-Ahead LogWAL先写日志在提交数据变化前先将操作写入日志。节点发生故障后可以重放日志恢复尚未完成的操作。写入路径不是:insert → 直接修改 Query Node 内存 → 完成而是insert ↓ Streaming Node ↓ WAL 持久化 ↓ Growing Segment ↓ 异步 Flush 到对象存储WAL 中记录的是有顺序的操作例如TSO 1001Insert PK1 TSO 1002Insert PK2 TSO 1003Delete PK1Milvus 架构文档列出的常见 WAL 实现包括 Kafka、Pulsar 和 Woodpecker。Woodpecker 使用面向云对象存储的设计目的是降低本地磁盘管理成本5.3 Object Storage对象存储用于保存大体量持久数据例如标量和向量数据日志快照或 BinlogSealed Segment 的数据向量和标量索引文件Compaction 产生的新 Segment部分中间结果。常见实现包括MinIOAmazon S3Azure Blob Storage。对象存储延迟通常高于内存因此正常在线查询路径是Object Storage ↓ Load Query Node 内存或缓存 ↓ 执行在线搜索6 VChannelPChannelShard6.1 VChannelVChannel 逻辑写入通道每个Collection的一个Shard对应一个VChannel它代表一个Collection内的一条逻辑写入流6.2 PChannelPChannel物理 WAL 通道每个 PChannel 对应一条由底层 WAL 管理的物理日志流并绑定到一个 Streaming Node。逻辑关系可以表示为Collection A / Shard 1 → VChannel A1 ┐ Collection A / Shard 2 → VChannel A2 ├→ PChannel 1 → Streaming Node 1 Collection B / Shard 1 → VChannel B1 ┘6.3 为什么需要两层 Channel如果业务逻辑直接绑定到底层物理日志节点扩缩容和故障转移会非常僵硬VChannel 与 PChannel 分离后Collection 看到的是稳定的逻辑 Shard系统可以调整逻辑通道到物理 WAL 的映射PChannel 可以重新绑定到其他 Streaming Node故障节点负责的日志可由新节点重放。6.4 主键散列与写入路由Milvus 使用基于主键散列的 Shard 路由将写入分散到不同 Shard从而利用多个节点并行写入。这不等于相同语义的向量一定进入同一 Shard同一 Partition 只有一个 Shard查询只需要访问一个 Shard。如果查询没有可用于裁剪范围的条件Proxy 可能需要请求所有相关 Shard再做全局结果归并。7 一次insert内部发生了什么如果执行以下代码client.insert( collection_namedemo_collection, data[ { id: 1, vector: [0.12, 0.08, ...], text: Milvus architecture, subject: database, } ], )内部流程可以理解为7.1 Insert返回不等于索引已经建立好insert()返回时不应该推断HNSW、IVF 等索引已经完成Segment 已经 Flush数据已经加载进 Query Node。数据是否立即对搜索可见主要由一致性级别和服务时间推进情况决定而不是简单由 Flush 决定。7.2 TSOTSOTimestamp OracleTSO 为 DML 操作建立可比较的时间顺序用于数据可见性、一致性判断和恢复。同一个 DML 批次中的实体共享同一时间戳。时间戳不是业务字段而是 Milvus 内部用于排列数据变化顺序的逻辑依据。8 Flush、Index、Load、Handoff8.1 FlushGrowing Segment ↓ Flush 对象存储中的持久化数据 ↓ Sealed SegmentFlush 解决的是实时数据向历史持久数据转换的问题。8.2 Index Building索引构建由Data Node 执行对象存储中的 Segment 数据 ↓ Data Node 下载并反序列化 ↓ 构建向量或标量索引 ↓ 序列化索引 ↓ 写回对象存储向量索引向量索引是从原始向量衍生出的搜索数据结构用于减少搜索时需要比较的候选向量数量。例如FLAT近似理解为穷举比较IVF先把向量划分到多个聚类桶再搜索部分桶HNSW构建多层近邻图通过图遍历寻找候选DISKANN面向磁盘的大规模 ANN 检索。索引通常是在 Segment 级别构建而不是整个 Collection 只生成一个不可分割的大索引。8.3 LoadLoad把搜索所需的数据和索引加载到 Query NodeObject Storage ↓ Query Node 内存 / 缓存8.4 HandoffHandoff把查询责任从实时路径交接给历史数据路径当 Growing Segment 被 Flush或者 Data Node 完成 Compaction 后Coordinator 会协调 Sealed Segment 在 Query Node 上的分配并释放冗余 Segment。9 一次Search内部发发生了什么client.search( collection_namedemo_collection, data[query_vector], filtersubject biology, limit10, output_fields[text, subject], )9.1 TopKTopK按距离或相似度排序后保留最优的 K 个候选如果limit10并不表示每个 Segment 只产生一个候选。底层节点可能先产生局部候选再经过多级 Reduce 得到全局前 10。9.2 Search 与 QueryAPI主要用途search()使用查询向量执行相似性搜索可附加标量过滤query()按主键或过滤表达式检索实体主要是标量查询10 标量过滤与BitSetclient.search( collection_namedemo_collection, dataembedding_fn.encode_queries( [tell me AI related information] ), filtersubject biology, limit2, output_fields[text, subject], )Milvus 不应该对所有向量完成距离计算后才逐条检查subject。对于大规模数据这会浪费大量计算。典型过程是解析过滤表达式 ↓ 计算哪些行满足标量条件 ↓ 形成 Bitset ↓ 与删除、时间可见性等掩码组合 ↓ 向量搜索跳过不可参与计算的行10.1 BitSet10.1.1 BitSet是什么Bitset由 0 和 1 组成的紧凑数组每个 bit 可以对应 Segment 中的一行用来表示这一行是否满足某种条件。假设 Segment 有 8 行行位置 1 2 3 4 5 6 7 8 Bitset 1 0 1 0 1 0 1 0Bitset 比为每一行存储完整整数或对象更紧凑也适合执行按位布尔运算。10.1.2 必须先定义1的含义1没有脱离上下文的永恒语义在“条件命中 Bitset”中1可以表示该行满足过滤条件在“删除 Bitset”中1表示该行已删除应被跳过在最终“排除 Bitset”中1表示该行不参与后续搜索。10.1.3 BitSet如何参数属性过滤假设过滤条件是PK ∈ {1, 3, 5, 7}首先的到“命中bitset”match [1, 0, 1, 0, 1, 0, 1, 0]如果后续搜索组件使用的是“1 表示跳过”的排除掩码就需要翻转filter_exclude NOT match [0, 1, 0, 1, 0, 1, 0, 1]于是向量搜索就会跳过 2468只计算 135710.1.4 BitSet如何参与删除假设实体 7 和 8 已删除那么delete_exclude [0, 0, 0, 0, 0, 0, 1, 1]10.2 案例假设有下面的时间顺序ts100插入 PK 1、2、3、4ts200插入 PK 5、6、7、8ts300删除 PK 7、8属性条件只匹配 PK 1、3、5、7。10.2.1 查询时间ts150此时只有1234 属性条件在该时间点存在的只有13最终的排除掩码[0,1,0,1,1,1,1,1]只有13参与搜索10.2.2 查询时间ts250此时有1-8的数据最终的排除掩码[0,1,0,1,0,1,0,1]10.2.3 查询时间ts350此时1-8都曾被插入但是78被删除10.3 删除时不会立即释放对象存储空间删除首先使实体在查询结果中不可见但底层旧 Segment 占用的空间通常不会立即释放。过程如下删除被作为逻辑删除处理后台 Compaction 合并 Segment并去掉逻辑删除或过期实体旧 Segment 被标记为 DroppedGarbage Collection 最终清理旧 Segment释放存储空间。11 时间戳与数据可见性11.1 Guarantee TimestampGuarantee Timestamp搜索或查询执行前Milvus 必须保证该时间戳之前的 DML 更新对查询可见。例如15:00 插入 A 17:00 插入 B Guarantee Timestamp 18:00执行查询时A 和 B 都应该进入查询所依据的数据视图。通常用户不需要直接计算内部 TSO而是通过一致性级别表达需求。11.2 Service TimestampService Timestamp表示查询服务已经处理完成并能保证可见的数据时间点。查询执行时会比较Guarantee Timestamp和Service Timestamp如果Guarantee Timestamp Service Timestamp:说明查询服务尚未追上所需数据强一致性请求需要等待时间推进。否则说明数据变化已经可见可以执行查询11.3 Graceful TimeGraceful Time一个允许数据视图落后于最新写入的时间窗口它是时长而不是时间戳。有界滞后一致性可以接受一定时间窗口内的数据暂时不可见以换取更低查询等待时间。12 四种一致性级别分布式系统通常需要在一致性、可用性和延迟之间权衡。Milvus 支持四种一致性级别。12.1 Strong强一致性查询必须看到最新数据视图Milvus 将 GuaranteeTs 对齐到最新系统时间戳。查询节点需要等到能看到要求时间点之前的所有操作。特点最新写入可见性最强可能产生等待查询延迟通常更高。12.2 Bounded Staleness有界滞后允许数据视图在规定时间窗口内落后特点默认一致性级别在可控的数据新鲜度损失下减少等待适合推荐、检索等允许极少量新数据暂不可见的场景。12.3 Session会话一致性同一客户端会话至少能读到自己已经完成的写入客户端使用最近一次写入的时间戳作为 GuaranteeTs从而实现 Read Your Writes。12.4 Eventually最终一致性查询立即使用当前可用的数据视图特点一致性约束最弱查询等待最少新写入可能暂时不可见不再写入后各副本最终收敛。13 CompactionCompaction将多个 Segment 合并或重组为新的 Segment持续写入和删除后系统可能出现许多小 Segment删除记录Segment 碎片查询需要访问过多 Segment存储空间中存在已被替代的旧 Segment。Compaction 可以合并小 Segment清理满足回收条件的逻辑删除数据减少查询扇出重组数据布局生成新的 Segment。示例Segment A1000 行 Segment B 800 行 Segment C 500 行 逻辑删除 300 行 ↓ Compaction 新 Segment约 2000 行有效数据Compaction 不是原地修改旧 SegmentData Node 读取旧 Segment生成新 Segment新 Segment 写回对象存储Coordinator 调度 Query Node 加载新 Segment旧 Segment 被标记为 DroppedGC 后续清理旧数据。
网站建设
高端定制
企业官网