Milvus 的 Segment 与 Compaction:后台合并时的查询抖动排查
2026/9/4 22:22:49 网站建设 项目流程

Milvus 的 Segment 与 Compaction:后台合并时的查询抖动排查

在高并发生产环境中运维 Milvus 向量数据库时,很多团队经常遇到一种诡异的“周期性性能毛刺”:
平时在线检索耗时非常稳定(P99 维持在 5ms 左右),但每隔十几分钟或半小时,P99 延迟就会突然剧烈抖动,瞬间飙升到 80ms 甚至 200ms,持续一两分钟后又自动恢复正常。

查看系统监控,此时在线查询 QPS 并没有明显突增,但磁盘 I/O 读写和 CPU 利用率却出现了一波剧烈的脉冲式高峰。

去查 Milvus 的内部运行日志,真相通常非常聚焦:这是后台正在执行 Segment Compaction(分片数据压缩与合并)带来的资源争抢!

深入搞懂 Milvus 的 Segment 生命周期与 Compaction 底层机制,是彻底根除线上查询抖动、保障 SLA 稳定的关键一步。

Milvus 的数据组织:Growing 与 Sealed Segment

在 Milvus 架构中,数据是以Segment(数据段)为物理单位进行管理的。每个 Segment 的生命周期经历两个阶段:

[新写入数据流] | v [ Growing Segment ] (纯内存,写入缓冲,支持暴力搜索,不建索引) | 达到 512MB / 定时触发 Flush v [ Sealed Segment ] (不可变数据段,落盘持久化至 MinIO/S3,后台异步构建 HNSW 索引) | v 后台异步触发 [ Compaction 合并 ] (将多个小 Segment 或清理已删除数据的 Segment 合并为大 Segment)
  1. Growing Segment(生长中段)
    新插入(Insert)的向量数据首先进入 Growing Segment。它直接驻留在内存中,为了保证写入速度,它不构建复杂的 HNSW 图索引,而是采用内存暴力搜索(Brute-force Scan)。
  2. Sealed Segment(封存段)
    当 Growing Segment 的大小达到阈值(默认 512 MB)或应用显式调用了collection.flush()时,该段会被封存为不可变(Immutable)的 Sealed Segment 并落盘存储到对象存储(MinIO/S3)。随后,IndexNode 节点会拉取该段数据,为其构建 HNSW 或 IVF 索引。

为什么 Compaction 会引发查询抖动?

随着业务频繁地进行数据插入(Insert)、更新(Upsert)和删除(Delete),系统内会产生大量包含墓碑标记(Delete Bitset)的碎片化小 Segment

为了提升检索效率和回收磁盘空间,Milvus 的 DataCoord 节点会定期调度后台Compaction(压缩合并任务)

  1. 磁盘与网络 I/O 巨额争抢
    Compaction 任务需要从对象存储拉取 5~10 个小 Segment,在 DataNode 节点上读取全量向量数据、应用 Delete 位图剔除已删除数据,重新重组为一个完整的 512 MB 大 Segment,并重新上传对象存储。
  2. CPU 与内存带宽打满
    合并完成后,IndexNode 需要为这个新合并出的大 Segment重新全量构建 HNSW 索引!建图过程中的多线程高维向量距离计算会瞬间吃满物理机的 CPU 核心与内存总线。
  3. QueryNode 热替换时的瞬时卡顿
    新索引建好后,QueryNode 必须将新 Segment 加载进内存,并将旧的几个小 Segment 从内存中卸载(Release)。在原子切换(Atomic Swap)的短暂瞬间,查询线程需要获取读写锁,导致正在并发执行的在线检索请求发生毫秒级的排队等待。

彻底根除查询抖动的四大生产实操策略

1. 严禁在线业务代码中高频调用collection.flush()

很多初学者写完一段插入代码后,顺手就写一句collection.flush()。这会导致每次只插入几百条数据就强行封存一个微小的 Segment,系统在几分钟内产生上百个碎片 Segment,直接逼迫后台 Compaction 频繁满负荷运转。

  • 最佳做法:依赖 Milvus 自身的后台自动 Flush 机制(默认每秒批量提交),或者只在离线大批量灌库结束后显式调用一次flush()
2. 在物理拓扑上实现读写与计算节点隔离

在 Kubernetes 生产部署中,通过节点亲和性(Node Affinity)和资源隔离,将负责在线查询的QueryNode与负责建索引/合并的IndexNode / DataNode分离部署在不同的物理机上

# Kubernetes Pod 亲和性隔离配置示例 spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/milvus-query operator: In values: ["true"]

这样无论 IndexNode 在后台合并时 CPU 烧到多少,QueryNode 所在的机器 CPU 和内存总线始终保持 100% 清爽,绝不影响在线查询。

3. 调优 Compaction 触发阈值与执行时间窗口

milvus.yaml中,合理配置 Compaction 参数,避免在白天业务高峰期频繁合并:

dataCoord: compaction: enable: true # 限制单次合并的最大 Segment 数量,降低单次任务的瞬时压力 maxSegmentRun: 10 # 设定最小 Segment 尺寸,避免微小合并 minSegmentSizeToCompact: 268435456 # 256 MB
4. 控制并发 Compaction 任务数

限制 DataNode 与 IndexNode 允许并发执行的 Compaction 线程数(如compaction.maxParallelTask: 2),防止合并任务占用全部 CPU 时间片。

总结

了解了 Segment 从 Growing、Sealed 到 Compaction 的生命周期,你就掌握了 Milvus 性能调优的底牌:管住业务端的滥用 Flush,在基础设施层拆分 Query 与 Index 物理资源,微调后台合并阈值。三管齐下,就能彻底抹平周期性性能毛刺,让向量检索服务的 P99 曲线平稳如镜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询