跳到主要内容

Force Merge Compaction:让你的 Milvus 性能原地翻倍

预计阅读 9 分钟
原文来源:Zilliz

做向量检索性能优化时,很多人第一反应是调索引参数、加机器、换检索参数。但在 Milvus 里,还有一个容易被忽略的变量:Segment 的形态。

Segment 是 Milvus 组织数据、加载索引和执行搜索的基本单位。随着数据写入、增量更新、删除和 flush,系统会不断生成新的 Segment。为了保证写入效率和在线稳定性,Milvus 不会假设“这批数据已经彻底写完了”,也不会在业务高峰期贸然把多个 Segment 合并成更大的 Segment。

数据库不会做这个假设,但业务侧往往知道数据的“写完”时间点。比如一次离线导入已经完成,一批知识库数据已经更新完毕,或者某个 Collection 接下来主要承担读取和检索,不再频繁写入。这时,继续保留多个较小的 sealed segment,就可能让每次查询都打到多个 Segment 上,产生更多查询扇出(fan-out)、调度和 TopK 结果归并开销。

我们在Milvus中推出的Force Merge Compaction的价值就在这里:它让业务可以在数据相对稳定后,主动给系统信号,触发一次更明确的 Segment 整理,把多个较小的 sealed segment 合并成更少、更大的 Segment。

接下来,本文会通过一个实验验证这个问题:当业务明确知道数据已经进入相对静态阶段后,通过 Force Merge 主动整理 Segment,搜索吞吐能提升多少?

先说结论,在本次实验里,我们使用的是100 万条 768 维向量使用 HNSW 类图索引,在同等资源条件下,Force Merge 之后,搜索 QPS 从约 3000 提升到约 5600–6000。

01 为什么会有小 Segment

Force Merge 不是凭空出现的性能开关。它解决的是一个常见的工程问题:系统只能根据当前配置和资源状态做自动 compaction,但它不知道业务数据是否已经进入稳定阶段。

在持续写入、增量更新、删除和 flush 的过程中,Collection 里可能会留下多个 sealed segment。对系统来说,这些 Segment 仍然是合法的数据组织形态。系统不能默认把它们全部合并,因为合并会消耗 I/O、内存和计算资源,还会触发索引重建。

但业务知道更多上下文。一次离线导入是否已经结束,一批知识库是否已经更新完,后续是否主要是查询流量,这些信息通常只有业务侧最清楚。

Force Merge 就是业务给系统的一个明确提示:这批数据已经相对稳定,可以用一次后台整理,换取后续更高效的查询路径。

02 Force Merge 优化的是什么:更少 Segment,更少查询开销

Segment 是 Milvus 组织数据、加载索引和执行搜索的基本单位。一次查询如果需要命中多个 sealed segment,Milvus 通常需要在多个 Segment 对应的索引上分别搜索,再把各自返回的结果合并成最终 TopK。

这个过程可以理解为查询扇出(fan-out):一个查询被分发到多个 Segment 上执行,最后再做结果归并。

Segment 数量越多,查询路径上的额外工作就越多,包括多个索引实例上的重复搜索、Segment 级调度、结果归并和排序。

对于 HNSW 这类图索引来说,单个 Segment 变大并不一定让搜索成本按比例线性增加;但 Segment 从 3 个变成 1 个,确实会减少重复搜索、调度和归并的额外开销。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 1

03 Force Merge Compaction 怎么执行

普通 compaction 更像系统的日常清理。它会在配置的 Segment 大小约束内,把明显的小碎片合并掉。但如果几个 Segment 已经接近上限,继续合并就可能超过 dataCoord.segment.maxSize,普通 compaction 无法继续减少 Segment 数量。

Force Merge 则更像一次由业务触发的 Collection 级别重排。它通过 target_size 告诉系统:这批数据已经适合重新整理,可以围绕一个更大的目标 Segment 大小重新规划合并。

简单说,普通 compaction 关注的是“系统在默认规则下能不能顺手清理”;Force Merge 关注的是“业务明确知道数据已经稳定后,能不能把 Segment 形态整理得更适合后续查询”。

官方文档中也把 Force Merge 描述为对现有 compaction API 的扩展:调用 compact() 时传入 target_size 参数即可触发。target_size 省略或为 0 时相当于普通 compaction;显式设置时单位为 MB,且需要大于或等于 dataCoord.segment.maxSize;也可以使用 max_int64 让 Milvus 根据当前 Segment 分布和节点资源自动计算目标大小。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 2

04 实验设计:只改变是否 Force Merge

本次实验使用了一组相对干净的对照设计。数据集、索引类型、查询负载和并发阶梯保持一致,只比较执行 Force Merge 前后的搜索表现。

实验环境如下:

  • Milvus 部署:Docker Compose 单机部署

  • Milvus 版本:2.6.17

  • Milvus 服务器:16 核 / 64GB 虚拟机

  • 发压机:32 核 / 32GB 虚拟机

  • 测试工具:VDBBench

  • 数据集:VDBBench Cohere 100 万向量数据集

  • 向量维度:768 维

  • 数据规模:约 3GB

  • 索引类型:HNSW 类图索引

  • 并发组:80、120、160、200、240

  • 监控:Grafana / Prometheus,统计窗口调整为 30 秒

  • Segment 配置:segment max size 约 1024 MB,seal proportion 设置为 1.0

这个设置的好处是变量少。Baseline 和 Force Merge 两条路径只改变 Segment 合并策略,因此更容易观察 Segment 形态对搜索吞吐的影响。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 3

Baseline:3 个 sealed segment,QPS 3000

第一轮测试跳过 Force Merge。写入 100 万条数据后,可以在 Attu 中看到 3 个 sealed segment,每个 Segment 大约 30 多万行。等待 HNSW 索引全部建完后,开始执行多组并发搜索测试。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 4

Baseline 的结果大致稳定在 3000 QPS 左右。80 并发时 QPS 已经达到 3000 多;继续提高并发后,整体峰值没有明显突破。通过服务器监控可以看到,查询阶段 Milvus 服务端 CPU 基本打满,高并发阶段发压机也可能逐渐成为瓶颈。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 5

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 6

这组结果说明,在当前硬件、数据集和索引配置下,系统瓶颈已经出现在搜索执行路径和资源使用上。接下来就可以看 Force Merge 是否能通过减少 Segment 数量降低这部分开销。

开启 Force Merge:3 个 Segment 合并成 1 个大 Segment

第二轮测试开启 Force Merge。通过 compaction 接口传入 target_size,让 Milvus 在后台异步执行合并。

合并完成后,原先 3 个 30 多万行的 sealed segment 被整理成 1 个 100 万行的大 Segment。随后系统需要为这个新的大 Segment 重新构建索引。实验视频里可以看到,重建索引阶段 16 核 CPU 被打满,这也印证了前面的判断:Force Merge 的收益发生在后续查询阶段,成本发生在合并和重建索引阶段。

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 7

如果是集群部署,可以通过资源隔离降低影响。例如让 DataNode、QueryNode 相关资源解耦,或者在适合的环境中使用 GPU 加速索引构建。单机环境里,这些任务会更容易互相抢 CPU 和 I/O。

测试结果:搜索 QPS 从 3000 提升到约 6000

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 8

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 9

索引重建完成后,使用同样的并发阶梯再次测试。结果很直观:Baseline 的典型 QPS 约 3000;Force Merge 后,典型 QPS 提升到约 5600–6000。具体看并发组,120 并发时 Force Merge 后 QPS 约 5600;160 和 200 并发时接近 5700–5800。Grafana 监控和 VDBBench 汇总结果基本一致:Force Merge 后的吞吐曲线明显高 Baseline。

05 什么时候应该使用 Force Merge

并不是所有业务场景都适合使用 Force Merge,下表列了一些 Force Merge 的适合和不适合场景,大家可以根据自己的实际情况进行评估:

如何通过修改Segment 形态,让你的 Milvus 性能原地翻倍 - 图 10

结语

本次实验里,在 100 万条 768 维向量、HNSW 类图索引、单机 Docker Compose Milvus 环境下,Force Merge 后 QPS 从 3000 提升到约 5600–6000。这个结果足够说明:当集合偏静态、读压力明显、Segment 数量又比较分散时,Segment 形态本身就是一个值得优化的性能变量。

Force Merge 是异步执行,不会阻塞查询,但执行期间会消耗 I/O 和内存,可能影响查询延迟,建议低流量时段执行,并监控合并前后的 Segment 数量。另外,我们也建议把 Force Merge 当成一次可观测、可回滚、可复测的维护动作,用自己的数据集和查询负载验证收益,再决定是否纳入长期流程。