Milvus 存储设计揭秘:从数据写入到 Segment 管理的全链路解析


作为一款云原生向量数据库,Milvus 的高效查询性能有赖于其独特的存储架构设计。然而,在实际使用过程中,许多社区用户常常会遇到以下问题:
为什么频繁调用 flush 后,查询速度会变慢?
数据删除后,磁盘空间为何依旧无法及时释放?
查询延迟有时为何忽高忽低?
这些现象的背后,通常都与 Milvus 的核心存储单元——Segment 的处理机制密切相关。Segment 是 Milvus 数据持久化的最小单位,其生命周期管理直接影响系统性能和资源利用率。本文将深入剖析 Milvus 从数据写入到 Segment 落盘、合并以及建立索引的完整流程,帮助开发者更好地理解系统行为并有效规避常见误区。
01
数据写入:Insert 请求如何拆解为 Segment?
当 Milvus 服务启动后,会在消息队列(Pulsar/Kafka)中创建若干用于接收数据的 “管道”,其数量根据 milvus.yaml 中 rootCoord.dmlChannelNum 进行配置。对于 Pulsar 或 Kafka,每个“管道”即对应一个 topic。
(1)Insert 请求与数据分片
a. insert 请求由 proxy 接收。proxy 会根据每条数据的 primary key 值计算出 hash,并将该 hash 对 collection 的 shards_num 取模。根据这个结果,数据被划分到相应的 shard 中。
b. 当 shards_num 大于 1 时,insert 数据最多会被分为 shards_num 份,分配到不同的 shard。
(2)数据传输
a. 每个 shard 产生的数据通过某个管道进行传输,多个 shard 也可共享同一条管道,以实现负载均衡。
b. data node 订阅各自对应的 shard 数据,并尽量将不同的 shard 分配到多个 data node 上。如果集群中只有一个 data node,则会由该节点订阅所有 shard 的数据。
(3)Growing Segment
a. 在 data node 端,每个partition中的每个 shard 都会对应一个 growing segment。data node 为每个 growing segment 维护一个缓存,用于存放尚未落盘的数据。
简而言之,proxy 会按照 shards_num 对数据进行分片,并将其发送到消息管道 ,然后data node从消息管道中异步接收数据。
下图展示了在 shards_num=2 的情况下,数据经由管道传输至 data node 的流程示意图:

02
数据落盘:Growing Segment 如何持久化?
在 data node 中,来自各个 shard 的数据会被累积在对应 growing segment 的缓存里。该缓存的最大容量由 milvus.yaml 中 dataNode.segment.insertBufSize 控制,默认值为 16MB。一旦缓存中数据超过此阈值,data node 就会将这部分数据写入 S3/MinIO,形成一个 Chunk。Chunk 指的是 segment 中的一小段数据,并且每个字段的数据也会被分别写入独立的文件。因此,每个 Chunk 通常包含多个文件。
此外,dataNode.segment.syncPeriod(默认 600 秒)定义了数据在缓存中允许停留的最长时间。如果在 10 分钟内缓存数据尚未达到 insertBufSize 的上限,data node 同样会将缓存写入 S3/MinIO。
将 segment 数据拆分为多个 Chunk 进行持久化,主要是为了:
(1)减少 data node 内存占用,避免growing segment数据在内存中堆积过多;
(2)当系统发生故障后重启时,无需从消息队列重新拉取growing segment的全部数据,已持久化的数据只需从S3/MinIO读取。

当某个 growing segment 累计写入量达到一定阈值后,data node 会将其转换为 sealed segment,并新建一个新的 growing segment 来继续接收 shard 数据。这个阈值由 dataCoord.segment.maxSize(默认 1024MB)和 dataCoord.segment.sealProportion(默认 0.12)共同决定,也就是说,当 growing segment 大小达到 1024MB × 0.12(约 122MB)时,就会被转为 sealed segment。
在此过程中,如果用户通过 Milvus SDK 调用 flush 接口,则会强制将指定 Collection 的所有 growing segment 缓存落盘并转为 sealed segment,无论它们已写入多少数据。因此,频繁调用 flush() 容易产生大量体量较小的 sealed segment,进而影响查询性能。
下图展示了 growing segment 落盘和转换为 sealed segment 的示意流程:

如果 Collection 存在多个 partition,则每个 partition 的数据也会被相同数量的 shard 分割。下图示例展示了多个 Collection 共用 2 条管道,其中某个 Collection 有 2 个 partition 时的数据分发过程:

数据在 S3/MinIO 中的存储路径由 milvus.yaml 中 minio.bucketName(默认值 a-bucket)以及 minio.rootPath(默认值 files)共同决定。segment 数据的完整路径格式为:
[minio.bucketName]/[minio.rootPath]/insert_log/[collection ID]/[partition ID]/[segment ID] 在 segment 路径下,Milvus 还会根据每个字段的 ID 创建对应子目录来存放该字段的各个 Chunk 文件。下面是某个 segment 在 MinIO 中的存储结构示例,可见 455457303288873052 表示 collection ID,455457303288873053 表示 partition ID,455457303289273082 表示 segment ID,而 0/1/100/101/102 则分别代表该 Collection 的各个字段 ID。该 segment 仅包含一个 Chunk,因此每个字段目录下只有一个数据文件:

03
Segment 合并优化:Compaction 的三种场景
在持续执行 insert 请求时,新数据不断流入,sealed segment 的数量也会随之增加。如果同样规模的数据被拆分成过多小尺寸(如小于 100MB)的 segment,系统的元数据管理和查询都会受到影响。为了优化这一点,data node 会通过 compaction 将若干较小的 sealed segment 合并成更大的 sealed segment。理想状态下,合并后形成的 segment 大小会尽量接近 dataCoord.segment.maxSize(默认 1GB)。
不过,小文件合并仅仅是 compaction 任务的一部分。compaction 还包括其他需求场景,主要分为以下三种:
(1)小文件合并(系统自动)
a.触发条件:存在多个体积较小的 sealed segment,其总大小接近 1GB。
b.优化效果:减少元数据开销,提升批量查询的性能。
(2)删除数据清理(系统自动)
a.触发条件:segment 中的被删除数据占比 ≥ dataCoord.compaction.single.ratio.threshold(默认 20%)。
b.优化效果:释放存储空间,并减少无效数据的重复扫描。
3.按聚类键(Clustering Key)重组(手动触发)
a.使用场景:面向特定查询模式(例如地域或时间范围检索)优化数据分布。
b.调用方式:通过 SDK 调用 compact(),并按照指定的 Clustering Key 对 Segment 进行重组。
04
索引构建:临时索引 vs 持久化索引
对于每个 growing segment,query node 会在内存中为其建立临时索引,这些临时索引并不会持久化。同理,当 query node 加载未建立索引的 sealed segment 时,也会创建临时索引。关于临时索引的相关配置,可在 milvus.yaml 中通过 queryNode.segcore.interimIndex 进行调整。
当 data coordinator 监测到新的 sealed segment 生成后,会指示 index node 为其构建并持久化索引。然而,如果该 sealed segment 的数据量小于 indexCoord.segment.minSegmentNumRowsToEnableIndex(默认 1024 行),index node 将不会为其创建索引。
所有索引数据都被保存在以下路径:
[minio.bucketName]/[minio.rootPath]/index_files 下图展示了在 MinIO 中某个 sealed segment 的索引存储结构。路径中的 455457303289273598 代表了 index node 的任务 ID,用于唯一标识该索引文件;其中的 1 则是索引版本号(index version)。还可见 partition ID(455457303288873053)和 segment ID(455457303289273082)的信息,这有助于排障和定位。而最底层名为 IVF_FLAT 的目录中,才是真正的索引文件:

05
Segment 加载:Query Node 如何管理数据?
当用户调用 load_collection 时:
(1)Query node 会从 S3/MinIO 加载该 Collection 的所有 sealed segment,并订阅相应的 shard 流数据。
(2)系统力求将多个 shard 分配给不同的 query node 以实现负载均衡;如果只有一个 query node,则该节点订阅全部 shard。
(3)对于每个 shard,query node 会同样在内存中维护一个对应的 growing segment,保证与 data node 上的数据保持一致。如果growing segment的部分数据已经以chunk的形式持久化在S3/MinIO中,query node会从S3/MinIO读取这些chunk,未持久化的数据则从Pulsar/Kafka中订阅,从而在内存中组成完整的growing segment。由于query node从Pulsar/Kafka中订阅growing segment的数据是异步行为,当有新的数据插入时,这些新数据对于查询请求不是即时可见,因此用户可在查询请求中设置 consistency level 以控制新数据的可见性。

(4)对于已构建好持久化索引的 sealed segment,query node 会从 S3/MinIO 中加载其索引文件。若某个 sealed segment 尚无索引或是 growing segment,query node 则会在内存中创建临时索引(详见第四节)。
当 compaction 任务将多个 sealed segment 合并为新的 sealed segment 后,query node 只有在新 segment 的索引构建完成后,才会加载其索引,并将旧 segment 的索引与数据从内存中清理掉,这个过程被称为 hand-off。
06
常见问题和 Tips
基于以上对 Milvus 存储与 Segment 处理原理的介绍,以下列出一些常见的注意事项与实用建议,帮助大家更好地使用 Milvus 提升系统性能:
(1)避免频繁调用 flush()
a.每次调用 flush() ,有可能会在每个 partition 的每个 shard 上都产生一个 sealed segment,频繁调用极易产生大量碎片化的小 segment。
b.建议:优先依赖自动落盘机制,只有当用户在插入全量数据后想确认数据全部落盘时(主要用于性能测试时避免用到 temp index),才有必要手动调用一次 flush 操作。
(2)监控 Segment 数量
a.可通过 Milvus 的 GUI 工具 attu 查看各 Segment 的分布状况,小 segment 较多时可能会影响查询性能,无需做额外操作,系统的compaction 机制会在必要时进行合并。
b.如果将 segment_size(默认 1GB)调大,可减少 segment 数量,提升索引查询效率,但同时要注意对系统负载均衡和内存的影响。
c.shards_num 的值直接影响 growing segment 数量,可根据数据量设置合理的 shards_num 来打散写入热点。一般来说,百万行级别数据量建议设置 shards_num=1,千万行级别数据量建议设置 shards_num=2,亿级别以上数据量建议设置 shards_num=4 或 8。
d.partition 的数量也直接影响 growing segment 的数量,此外大量的 partition 也会消耗系统资源导致系统性能下降,因此需要根据业务需求设置合理的 partition 数量。
(3)合理规划 Clustering Key
针对常见的范围查询、批量数据删除等业务场景设计合适的 Clustering Key,可进一步提升 compaction 效率并优化查询表现。
通过理解 Segment 的生命周期管理,开发者可以更高效地设计数据写入策略、调优系统参数,让 Milvus 在复杂场景下依然保持稳定的高性能表现。
如对以上案例感兴趣,或有更多实战经验或疑问,想对Milvus做进一步了解,欢迎扫描文末二维码交流进步。
作者介绍

张粲宇
Zilliz 高级产品经理
推荐阅读



相关文章:
Milvus 存储设计揭秘:从数据写入到 Segment 管理的全链路解析
作为一款云原生向量数据库,Milvus 的高效查询性能有赖于其独特的存储架构设计。然而,在实际使用过程中,许多社区用户常常会遇到以下问题: 为什么频繁调用 flush 后,查询速度会变慢? 数据删除后,…...
Redis双写一致性(数据库与redis数据一致性)
一 什么是双写一致性? 当修改了数据库(MySQL)中的数据,也要同时更新缓存(redis)中的数据,缓存中的数据要和数据库中的数据保持一致 双写一致性,根据业务对时间上的要求,…...
14.PPT:中国注册税务师协会宣传【26】
目录 NO12 NO3/4/5 NO678 【文本框水平/垂直居中】【文本框内容水平/垂直居中】 NO12 坑:注意❗Word文档的PPt素材.docx的标题大纲是混乱的,虽然他设置了,所以我们需要重新设置 设计→主题视图→幻灯片母版→删除版式插入logo NO3/4…...
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
参考文章: 安装protoc、protoc-gen-go、protoc-gen-go-grpc-CSDN博客 一、简单介绍 本文开发环境,均为 windows 环境,mac 环境其实也类似 ~ ① 编译proto文件,相关插件 简单介绍: protoc 是编译器,用于将…...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_write_stderr函数
定义 在ngx_log.h 中 static ngx_inline void ngx_write_stderr(char *text) {(void) ngx_write_fd(ngx_stderr, text, ngx_strlen(text)); } 在开发软件时,尤其是像 Nginx 这样复杂的服务器软件,开发者需要一种方法来输出调试信息或者错误信息。这段代码…...
使用requestAnimationFrame减少浏览器重绘
文章目录 介绍使用使用rAF前使用rAF后 介绍 在屏幕中,浏览器通常都以60FPS(1/60 s)每帧更新屏幕,但是当前端绑定了一些高频事件,如鼠标移动,屏幕滚动、触摸滑动等时,在一帧的周期内,…...
autMan奥特曼机器人-对接deepseek教程
一、安装插件ChatGPT 符合openai api协议的大模型均可使用此插件,包括chatgpt-4/chatgpt-3.5-turbo,可自定义服务地址和模型,指令:gpt,要求Python3.7以上,使用官方库https://github.com/openai/openai-pyt…...
一个精简完整的全栈电商平台采用现代化技术栈和安全架构
以下是一个精简但完整的全栈电商平台实现,采用现代化技术栈和安全架构: 架构设计 前端:SvelteKit + TypeScript (SSR/SSG) 后端:Go 1.21 + Fiber框架 数据库:MySQL 8.0 + Redis 7.0 安全:Cloudflare WAF + JWT+HTTPOnly+Secure Cookie 部署:Docker Swarm + Traefik (自…...
数据分析:企业数字化转型的金钥匙
引言:数字化浪潮下的数据金矿 在数字化浪潮席卷全球的背景下,有研究表明,只有不到30%的企业能够充分利用手中掌握的数据,这是否让人深思?数据已然成为企业最为宝贵的资产之一。然而,企业是否真正准备好从数…...
MySQL中datetime类型23:59:59变成下一天的00:00:00
1. 现象 公司项目中有一个统计的业务需求,需要记录数据的开始和结束时间点,对应的是MySQL表中的datetime类型。结束时间是当天的23:59:59,但是java程序中是对的时间,但是Mybatis-plus入库了之后查看数据库却变成第二天的00:00:00&…...
玩转Docker | 使用Docker部署httpd服务
玩转Docker | 使用Docker部署httpd服务 前言一、准备工作环境确认检查操作系统准备网站目录和配置文件二、拉取httpd镜像三、运行httpd容器运行容器命令检查容器状态四、验证httpd服务浏览器访问测试错误排查五、容器管理与维护查看容器状态停止和启动容器更新网站内容和配置六…...
Vue 双向数据绑定的原理
Vue 的双向数据绑定是其核心特性之一,它可以让视图与数据保持同步,简化了开发者在 DOM 操作上的工作。Vue 的双向数据绑定通过 响应式系统 和 DOM 事件监听 来实现,当数据发生变化时,视图会自动更新;当视图中的元素&am…...
Spring Web MVC项目的创建及使用
一、什么是Spring Web MVC? Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中,通常被称为Spring MVC。 1.1 MVC的定义 MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构…...
32.日常算法
1.最大子数组和 题目来源 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。 示例 1: 输入:nums [-2,1,-3,4,-1,2,1,…...
MySQL的底层原理与架构
前言 了解MySQL的架构和原理对于很多的后续很多的操作会有很大的帮助与理解。并且很多知识都与底层架构相关联。 了解MySQL架构 通过上面的架构图可以得知,Server层中主要由 连接器、查询缓存、解析器/分析器、优化器、执行器 几部分组成的,下面将主要…...
python Excel 表读取合并单元格以及清除空格符
读取合并单元格并保留合并信息 读取合并单元格并保留合并信息清除各单元格的空格和换行符,并去除列名中的空格和换行符 读取合并单元格并保留合并信息 当我们只是使用 pandas 的 read_excel 方法读取 Excel 文件时,我们可能会遇到一个很棘手的问题&…...
Node.js 实现简单爬虫
介绍 爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。 本文将使用 Nodejs 编写一个简单的爬虫脚本,爬取一个美食网站,获取菜品的标题和图片链接,并以表格的形式输出。 准备工作 1、初始化项目 首先࿰…...
【Pytorch】nn.RNN、nn.LSTM 和 nn.GRU的输入和输出形状
nn.RNN、nn.LSTM 和 nn.GRU的输入和输出形状 输入形状通用输入参数特殊情况(LSTM) 输出形状nn.RNN 和 nn.GRUnn.LSTM 代码示例 输入形状 通用输入参数 这三个模块通常接收以下两种形式的输入: 输入序列:形状为 (seq_len, batch…...
代码随想录算法训练营第三十一天| 回溯算法04
491. 递增子序列 题目: 代码随想录 视频讲解:回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列_哔哩哔哩_bilibili 这题需要注意的点: 1. path长度在2以上才放入最终结果 2. 需要记录已经使用过的数字&am…...
2024.1版android studio创建Java语言项目+上传gitee
1.在gitee上创建仓库 Gitee 创建仓库并邀请成员指南_gitee创建仓库邀请成员-CSDN博客 见1 2.新建android studio项目 3.在Android studio配置gitee Android Studio提交代码到gitee仓库_android log in to gitee-CSDN博客 其中的一二步 p.s.添加gitee账户选择password时&a…...
React 打印插件 -- react-to-print
一、安装依赖 npm install react-to-print 二、使用 import { useReactToPrint } from "react-to-print"; import React, { useRef, forwardRef } from react;const Content () > {const contentRef useRef(null);const reactToPrintFn useReactToPrint({ c…...
opentelemetry-collector 配置elasticsearch
一、修改otelcol-config.yaml receivers:otlp:protocols:grpc:endpoint: 0.0.0.0:4317http:endpoint: 0.0.0.0:4318 exporters:debug:verbosity: detailedotlp/jaeger: # Jaeger supports OTLP directlyendpoint: 192.168.31.161:4317tls:insecure: trueotlphttp/prometheus: …...
SQL Server 数据库迁移到 MySQL 的完整指南
文章目录 引言一、迁移前的准备工作1.1 确定迁移范围1.2 评估兼容性1.3 备份数据 二、迁移工具的选择2.1 使用 MySQL Workbench2.2 使用第三方工具2.3 手动迁移 三、迁移步骤3.1 导出 SQL Server 数据库结构3.2 转换数据类型和语法3.3 导入 MySQL 数据库3.4 迁移数据3.5 迁移存…...
C# SQlite使用流程
前言 不是 MySQL 用不起,而是 SQLite 更有性价比,绝大多数的应用 SQLite 都可以满足。 SQLite 是一个用 C 语言编写的开源、轻量级、快速、独立且高可靠性的 SQL 数据库引擎,它提供了功能齐全的数据库解决方案。SQLite 几乎可以在所有的手机…...
MySQL数据库 (三)- 函数/约束/多表查询/事务
目录 一 函数 (一 字符串函数 (二 数值函数 (三 日期函数 (四 流程函数 二 约束 (一 概述 (二 约束演示 (三 外键约束 三 多表查询 (一 多表关系 1 一对多(多对一) 2 多对多 3 一对一 (二 多表查询概述 (三 内连接 1 查询语法 2 代码实…...
【玩转 Postman 接口测试与开发2_018】第14章:利用 Postman 初探 API 安全测试
《API Testing and Development with Postman》最新第二版封面 文章目录 第十四章 API 安全测试1 OWASP API 安全清单1.1 相关背景1.2 OWASP API 安全清单1.3 认证与授权1.4 破防的对象级授权(Broken object-level authorization)1.5 破防的属性级授权&a…...
攻防世界baigeiRSA
打开题目附件 import libnum from Crypto.Util import number from secret import flagsize 128 e 65537 p number.getPrime(size) q number.getPrime(size) n p*qm libnum.s2n(flag) c pow(m, e, n)print(n %d % n) print(c %d % c)n 8850300144784503160345704866…...
12.7 LangChain代理系统Agents深度解析:构建自主决策的智能体应用
LangChain代理系统Agents深度解析:构建自主决策的智能体应用 一、代理系统的核心价值 代理系统是大模型应用的决策中枢,通过动态工具调度和任务分解,突破传统链式流程的三大局限: 动态规划:根据实时反馈调整执行路径工具集成:无缝对接500+外部系统API认知迭代:通过记忆…...
解释一下数据库中的事务隔离级别,在 Java 中如何通过 JDBC设置事务隔离级别?
数据库中的事务隔离级别是用于控制并发事务之间相互影响的一种机制。 它定义了事务之间的可见性和影响范围,常见的隔离级别包括: 读未提交(Read Uncommitted):最低的隔离级别,事务中的修改即使没有提交也…...
[NKU]C++安装环境 VScode
bilibili安装教程 vscode 关于C/C的环境配置全站最简单易懂!!大学生及初学初学C/C进!!!_哔哩哔哩_bilibili 1安装vscode和插件 汉化插件 2安装插件 2.1 C/C 2.2 C/C Compile run 2.3 better C Syntax 查看已…...
