在前几章中,我们探索了如何在 Milvus 中写入和读取数据。虽然这些流程对于实时操作非常高效,但它们也会引入底层挑战,例如 segment fragmentation,以及过时文件的积累。
本章将探索两个关键的自动化后台流程,它们负责确保 Milvus 的长期健康和效率:compaction 和 garbage collection(GC)。如果没有这些机制持续清理和组织数据,Milvus 的性能会逐渐衰退,查询会变慢,存储成本也会因为未清理数据不断上升。我们将探索 mix compaction、L0 compaction 和 clustering compaction 等策略,这些策略在底层负责合并 segments,并永久移除已删除数据,以确保数据一致性、降低系统开销并提升查询性能。与此同时,GC 在清理过时数据、释放存储空间和维持资源效率方面发挥关键作用。本章面向希望深入理解 Milvus 内部数据管理策略,并针对真实工作负载优化集群的 Milvus 管理员、开发者和系统架构师。
本章将覆盖以下主题:
- 为什么 compaction 在 Milvus 中很重要
- Compaction 详解
- 使用 GC 管理过时数据
技术要求
在深入本章之前,读者应该具备以下基础理解:
- Collections、partitions 和 clustering keys,第 5 章已覆盖
- Flushed segments 和 L0 segments 的基础概念,第 6 章已覆盖
让我们先从理解 compaction 在 Milvus 中的重要性,以及它所解决的挑战开始。
为什么 compaction 在 Milvus 中很重要?
Compaction 是 Milvus 读写分离架构中的组成部分,第 5 章已介绍该架构。它专门用于解决数据管理中的几个关键挑战:segment fragmentation、低效查询,以及过时数据或 soft-deleted data 的积累。通过持续优化数据组织方式,compaction 可以提升查询性能、降低资源开销,并确保系统能够随着工作负载增长而有效扩展。
Compaction 通过将较小 segments 合并成较大 segments,并永久移除逻辑删除记录来解决这些问题。这样做之后,Milvus 可以减少 QueryNodes 在搜索过程中需要扫描的 segments 数量,从而提高查询效率并降低延迟。它还通过消除冗余或过时数据来优化存储使用。本质上,compaction 让系统保持干净、高效和高性能,确保即使数据集不断增长和演化,向量搜索仍然保持快速。
下面几节将探索 compaction 如何应对这些挑战。
Segment fragmentation
正如第 6 章所述,当一个 growing segment 达到目标大小 dataCoord.segment.maxSize 的 15% 时,Milvus 就会 seal 并 flush 它。这种提前 sealing 策略有明确的运维优势:
控制内存压力:通过限制 growing segments 的大小,它可以减轻负责处理 growing segments 的 Shard Delegator 的内存负担。Fragmentation 会增加内存开销,并可能导致资源利用效率低下。Compaction 通过将较小 segments 合并成较大 segments,有助于降低内存压力,减少需要加载进内存的 active segments 数量。
加速索引构建:碎片化 segments 会减慢索引构建速度,因为可能需要为许多小 segments 分别构建索引。通过 compaction 合并 segments,Milvus 可以在更大的、整合后的数据集上构建索引。数据能更快持久化,从而支持索引构建并提升查询性能。不过,这种策略也带来了一个显著取舍:在 15% 阈值处频繁 sealing 会产生许多小 segments,导致严重 segment fragmentation。
Fragmentation 如何影响查询性能
Segment fragmentation 会以多种方式影响查询性能。我们从 query path 角度理解这一点。回想第 7 章中的 scatter-gather query pattern:queries 必须被分发到所有 segments,然后结果会被合并并返回。Segment 数量直接决定这一过程的开销。考虑一个 shard 中有 100 个小 segments,而不是 10 个大 segments。在这种情况下,query engine 必须执行显著更多 scatter-gather 操作。前者需要:
- 10 倍数量的 per-segment query invocations
- 10 倍更多 intermediate result sets 需要合并
- 10 倍更多 metadata overhead 需要处理
第二,从 index efficiency 角度看,小 segments 上的 indexes 表现弱于大 segments 上的 indexes。以 hierarchical navigable small world(HNSW)indexes 为例,它们通过构建多层 graph structures 加速 nearest neighbor search。在大 segments 上,HNSW 可以构建更深、更优化的 graph hierarchies,具备更短搜索路径和更高查询效率。在这种情况下,query engine 必须执行显著更多 scatter-gather 操作。
最后,QueryNode 中的每个 segment 都必须包含独立 metadata 和 memory state,包括 bloom filters、statistical information 等。当 segments 过多时,这些 metadata 的 memory footprint 和管理开销会增加,进一步挤压可用于 query caching 的内存。
Mix compaction 的性能优化
Mix compaction 会将多个小 segments 合并成较大 segments,从根本上减少 segment 数量。具体来说,当 mix compaction 将 10 个小 segments 合并成 1 个大 segment 时,会发生以下情况:
简化 query path:query 和 merge 操作从 10 次降低到 1 次,大幅降低延迟和开销。
提升 index quality:在更大数据集上构建的 indexes 更加优化。对于 HNSW,大 segments 上的 graph structures 具有更深层级和更密连接,从而带来更短搜索路径和更高查询效率。类似地,在更大数据集上构建的 IVF indexes 可以生成更有代表性的 clusters,同时提升 recall 和查询效率。
在不改变底层数据内容的情况下,mix compaction 通过优化数据组织,显著提升查询性能和系统资源利用率。
Compaction 解决的下一个挑战是低效的 intra-partition queries。当你应用类似 partition_key == '2025-02-11' 的过滤条件时,Milvus 可以在 collection 层面高效执行 partition pruning,通过排除整个 partitions 来减少扫描范围。这种 collection-wise pruning 是一种强大的优化,可以减少不必要的数据扫描,但 Milvus 的 partition key mapping 机制也引入了一个挑战。
Partition key mapping logic
在 Milvus 中,partition keys 并不会直接映射到物理 partitions。相反,它们会经过 hash,并分布到有限数量的 partitions 中,通常是 32 个。这意味着多个逻辑 partition key values 可能映射到同一个物理 partition。参见图 8.1:
图 8.1:Partition keys 如何映射到 partitions
虽然这支持高效 partition-level pruning,但也带来了挑战。即使 query 只针对某个特定 partition key value,也必须扫描选中 partition 内的所有 segments。因为该 partition 可能包含其他 partition key values 的数据,而这些 values 也映射到了同一 partition,所以 query 仍然必须扫描包含与目标 key value 无关数据的 segments。如果一个 partition 包含大量数据,而 entities 又随机分布在 segments 中,就会导致过高的 read amplification 和不必要计算。
例如,当查询 partition_key == "2025-02-11" 时,系统会定位到 partition 0,但该 partition 可能存储多个日期的数据,见图 8.2:
图 8.2:partition_key == "2025-02-11" 查询会扫描 partition 0 中的所有 segments
因此,实际访问的数据远大于所需数据,并且 partition 中每个 segment 都必须被访问和搜索。
Clustering compaction 支持 intra-partition segment pruning
Clustering compaction 正是为解决这个问题而设计的。其核心思想是围绕 clustering key 重新组织物理 partitions 内部的数据。这个 clustering key 通常是 queries 中最常用的过滤字段,以确保数据按照该字段值进行排序和聚集。
具体来说,clustering compaction 会把具有相同或相似 clustering key values 的 entities 重组到同一 segments 中。重组后,过去分散在多个 segments 中的同一天数据,现在会紧密聚集在少数几个 segments 中。与此同时,系统会为每个 segment 维护 clustering key value range metadata,记录该 segment 包含的最小和最大 clustering key values。
这种重组最大的好处,是支持 segment-level data pruning。当 query 指定 partition_key == "2025-02-11" 时,系统首先通过 hashing 定位到物理 partition。然后,它不会盲目扫描该 partition 中所有 segments,而是检查每个 segment 的 clustering key value range metadata。只有 value range 包含 "2025-02-11" 的 segments 才会被真正访问和搜索,其他 segments 可以安全跳过。这种 segment-level pruning 会大幅减少访问数据量,显著降低查询延迟和计算开销,尤其是在 segments 很多、partition keys 基数很高的场景中,性能提升会特别明显。
现在,我们来看 compaction 面对的第三个挑战:soft deletes 带来的存储和性能开销。
Soft deletions 的存储与性能开销
Milvus 使用 soft deletion 处理 delete operations。然而,随着 deletions 累积,soft deletes 会逐渐成为性能负担,主要体现在两个方面。
第一个问题是已删除 entities 持续占用资源。虽然这些 entities 在逻辑上已被删除,但它们的数据在物理上仍然留在 segments 中,继续占用存储,并且当 segments 被加载时,也可能继续占用 QueryNode 内存。在删除频繁的应用中,这类 zombie data 的积累会导致内存利用率持续下降,存储成本持续上升。
更严重的是对查询性能的影响。执行 search operations 时,相比没有 deletions 的 query,在搜索相同 top-k 结果时,携带 deletions 的 query 总是需要生成 bitmaps、执行额外检查,并遍历更多数据。这种过滤过程会增加额外计算开销,并且随着 soft-deleted data 比例上升,这种开销会越来越显著。在极端情况下,如果一个 segment 中大多数数据已被删除,query engine 可能需要扫描大量无效数据,才能找到足够有效结果,从而严重拖慢查询速度。
第二个问题来自 orphan deletes 的处理机制,第 6 章和第 7 章已介绍。当 delete requests 到达时,系统会将 delete messages 写入 vChannel,随后写入 L0 segments。这些 delete messages 被称为 orphan deletes,因为它们只包含已删除 entities 的 primary keys,而不知道哪个具体 segment 包含该 entity。
这种不确定性会给 shard delegator 带来沉重路由负担。对于每条 orphan delete message,delegator 必须执行一个 lookup process:它需要遍历 shard 中所有 segments 的 bloom filters,逐个检查哪个 segment 可能包含该 primary key 对应的 entity。一旦定位到目标 segment,delegator 会将 delete information 转发给相应 QueryNode 中的对应 segment。
这个 lookup process 的计算复杂度会随 shard 中 segment 数量线性增长。在高删除率场景下,大量 orphan delete messages 会持续触发全局 bloom filter 查询,产生显著 CPU 开销和网络通信成本。随着 collection 规模扩展和 segment 数量增加,这项路由工作会成为严重性能瓶颈,尤其是在需要大规模动态数据集处理的应用中。
L0 和 mix compaction 解决 soft delete 开销
Milvus compaction 会以多种方式减轻 soft deletes 的负担。第一,L0 compaction 会将 orphan deletes 转换为明确的 in-segment delete markers。第二,在 mix compaction 的 segment merging 过程中,compaction 会物理删除所有标记为 deleted 的 entities,将它们彻底从 segments 中移除。这不仅会立即释放内存和存储空间,也会消除查询期间过滤这些无效数据的开销。
接下来,我们将深入 Milvus 中的具体 compaction types,并理解这些优化在实际系统中如何实现。
Compaction 详解
Milvus 中的 compaction 是一组流程的集合,分别针对系统中的具体挑战而设计。本节将探索 compaction 的基础方面,拆解其通用流程,并介绍三种主要类型:mix compaction、L0 compaction 和 clustering compaction。
先从所有 compaction types 共同依赖的通用流程开始。
通用流程
虽然 Milvus 有三种不同类型的 compactions,用于不同场景,但它们都遵循同一套执行框架。该框架由 DataCoord 和 DataNodes 共同处理,包含三个核心阶段:triggering、scheduling 和 execution。其工作方式如下:
Triggering(在 DataCoord 中) :DataCoord 会定期检查所有 flushed segments 的 metadata。基于每种 compaction type 特定的一组预定义规则,它识别符合条件的 segments,并将它们组合成一个 compaction task。随后,该 task 被提交到 compaction task queue,等待调度。下一节会覆盖每种 compaction type 的具体规则。
Scheduling(在 DataCoord 中) :tasks 进入 queue 后,compaction scheduler 会根据 task priority 和 DataNodes 的 resource availability,也就是 slots,决定何时以及在哪里运行该 task。随后,scheduler 会将选中的 compaction task 分配给某个具体 DataNode。
Execution(在 DataNode 中) :被分配的 DataNode 会执行从 DataCoord 收到的 compaction task。虽然不同 compaction types 的具体逻辑不同,但基本原则相同:compaction 是一种 immutable operation。它总是将数据写入新的 segments,而不会修改或删除已有 files。
我们将在后续小节探索每种类型的 execution details。
深入理解 scheduling process
由于所有 compaction types 共享 scheduling algorithm,这里将详细介绍,避免后面重复。DataCoord 中的 scheduler 依赖一套稳健的 priorities 和基于 slots 的 resource allocation 系统:
Priority-based scheduling:DataCoord 的 scheduler 总是优先派发 queue 中最高优先级 task。默认情况下,priority policy 是 first-in-first-out(FIFO),也就是说,较早生成的 tasks 优先级高于较晚生成的 tasks,scheduler 会基于创建时间进行调度。Priority policy 通过 dataCoord.compaction.taskPrioritizer 配置,提供三个选项:level、mix 和 default。default 选项代表标准 FIFO 策略,并会优先处理 L0 compaction tasks;而 mix 选项会将 mix compaction tasks 设置为最高优先级。
对于删除频繁或 L0 compaction backlog 的 workloads,将配置设置为 level 可以优先调度 L0 compaction tasks,以缓解积压并防止 deletion accumulation。对于写入高、fragmentation 严重的场景,使用 mix 会优先调度 MixCompaction tasks,加快小 segments 合并。
Slot-based resource management:选择最高优先级 task 后,scheduler 必须选择一个 DataNode 来执行它。Slot mechanism 是 scheduler 资源管理的核心。Slots 是衡量 DataNode 可用于并发 task execution 的资源单位。每个 DataNode 的 slot capacity 决定它可以同时处理多少 tasks,由配置参数 dataNode.slot.slotCap 控制,默认值为 16。
不同 compaction task types 消耗不同 slot counts,这些也可以配置。选择执行 compaction task 的 node 时,scheduler 会将 tasks 分配给 available slots 最多的 DataNode,从而实现 load balancing。
通过调整相关配置参数,你可以精确控制 compaction 的资源使用。
下表列出了管理 compaction resource allocation 的关键配置参数:
| Configuration parameter | Default value | Description |
|---|---|---|
dataNode.slot.slotCap | 16 | 每个 DataNode 的总 slots 数 |
dataCoord.slot.clusteringCompactionUsage | 16 | 每个 clustering compaction task 的 slot usage |
dataCoord.slot.mixCompactionUsage | 8 | 每个 mix compaction task 的 slot usage |
dataCoord.slot.l0DeleteCompactionUsage | 8 | L0 compaction task 的 slot usage |
表 8.1:Compaction resource configuration parameters
如果你有足够内存和 CPU 资源,可以提高 slotCap,增加单个 DataNode 可并发执行的 compaction tasks 数量。对于资源密集型操作,增加每个 task 的 slot consumption 可以防止 DataNode overload。反过来,在资源受限 nodes 上,降低每个 task 的 slot consumption,可以允许更多 tasks 并发执行。例如,如果将 mix compaction 配置为在一个总共 16 slots 的 DataNode 上消耗 4 slots,那么四个 mix compaction tasks 可以并行执行。通过合理调优这些参数,你可以优化资源利用率,有效扩展 DataNode cluster,并确保 compaction tasks 在各种系统负载下高效、稳定运行。接下来几节会更详细讨论 mix compaction、L0 compaction 和 clustering compaction。请注意,scheduling phase 是三种 compactions 的共同阶段。
Mix compaction
Mix compaction 会将多个 flushed L1 segments 合并为更少、更大的 segments,同时清除 expired 和 marked-for-deletion data。这种合并不仅解决 segment fragmentation,也通过物理移除 invalid data 来释放存储空间并提升查询性能。
Expired data 指超过其定义 time-to-live(TTL)的 entities。你可以在创建 collection 时,通过配置 collection.ttl.seconds 属性来为 collection 设置 TTL。一旦 collection 设置了 TTL,collection 中任何超过指定 duration 的 entity 都会被标记为 expired,并在 compaction 过程中被永久移除。
Mix compaction 会以多个 flushed segments 作为输入,并将其 valid data 合并成一个或多个输出 segments,这些输出 segments 接近预定义目标大小,以避免创建过小或过大的 segments。例如,如果输入三个 segments,大小分别为 200 MB、300 MB 和 500 MB,并且每个都有 10% deleted data,那么 mix compaction 最终会生成一个新的 900 MB segment。值得注意的是,单个 compaction task 可以合并多个 L1 segments。不过,对于 clustering compaction 产生的 L2 segments,由于其数据分布具有特定性且相对稳定,compaction tasks 只会在 deleted 或 expired data 过多时触发,并且每个 task 只处理一个 L2 segment,以避免频繁扰乱 L2 segments 的数据分布。
Mix compaction 可以自动触发,也可以按需手动执行。自动触发基于以下几类条件:
Deletion ratio:当某个 segment 中 soft-deleted data 比例超过配置阈值时,默认 20%,会触发 compaction。该阈值可以根据 delete entries 比例设置,即 dataCoord.compaction.single.ratio,默认 20%;也可以根据 deleted data 总大小设置,即 dataCoord.compaction.single.deltalog.maxsize,默认 16 MB;或者根据 delta log files 数量设置,即 dataCoord.compaction.single.deltalog.maxnum,默认 200 files。类似地,如果 segment 中 expired data 大小超过可配置阈值,即 dataCoord.compaction.single.expirelog.maxsize,默认 10 MB,也会触发 compaction。
Fragmentation:当一组小 segments 的总大小,落在目标最大 segment size 的 85%,即 maxSize * dataCoord.segment.compactableProportion,和 115%,即 maxSize * dataCoord.segment.expansionRate,之间时,系统会触发 compaction task,其中 maxSize 是配置的目标 segment size。这个范围设计可以避免创建过小 segments,也防止生成过大 segments,从而给内存带来压力。
在需要更高性能的场景中,你可以通过提高 dataCoord.segment.maxSize,例如提高到 2 GB,从源头减少 fragmentation,使 compaction 合并出更大、更少的 segments。不过需要注意,提高 maxSize 也会相应增加 segments 被 sealed 时的大小,要求 growing segments 和 shard delegators 有更多内存。另一种方式是同步降低 dataCoord.segment.sealProportion 到更小比例,例如 8%,从而降低系统中 growing data 的整体大小。需要注意的是,对于带 DiskANN indexes 的 segments,Milvus 提供了额外、更大的 size 配置,专门用于 compaction selection:dataCoord.segment.diskSegmentMaxSize。如果你使用 DiskANN indexes,应调整该参数以生成更大的 segments。DiskANN indexes 利用磁盘存储进行数据和索引创建,因此在同等机器条件下,相比纯内存 indexes,它们可以支持更大的 segment sizes。
对于删除频繁的场景,默认配置会最多保留 20% 的 obsolete data。在这种情况下,应降低 delete ratio threshold,例如降低到 10–15%,让系统更积极地清理 deleted data,防止 invalid data 长期占用资源。
接下来,我们把重点转向 L0 compaction。
L0 compaction
L0 compaction 是一个专门流程,用于处理 L0 segments 中的 anonymous deletions。它的主要功能是将这些 deletions 应用到对应的 L1 和 L2 segments。这样可以确保更高层 segments 通过自己的 delta logs 准确反映 deletion,同时降低 shard delegator 的计算和路由负担。
下图展示了 L0 segments 如何在这一过程中与 flushed segments 合并:
图 8.3:L0 compaction process:将 delete records 与 data segments 合并
L0 compaction 的输入包括多个包含 anonymous delta logs 的 L0 segments,以及相关 L1 和 L2 segments 的 bloom filters。输出则是受影响 L1 和 L2 segments 的更新 delta logs,用于反映从 L0 segments 传播过来的 deletion records。
L0 compaction 使用 L1 和 L2 segment bloom filters 来筛选 orphan deletes。如果 bloom filter 识别出某些 orphan-deleted entities 的 primary keys 存在于 Segment A 中,则说明这些 orphan deletes 可能删除了 Segment A 中的数据。此时,L0 compaction 会为该 segment 写入一个新的 delta log,记录这些 deleted entities。L0 compaction 会在经过精心定义的条件下触发,以平衡数据准确性和资源效率:
Older timestamps:符合条件的 L0 segments,是 timestamps 早于该 channel 中 growing segments 最早数据的 L0 segments。
Activity-based triggers:在 deletion 高峰期,只要符合条件的 L0 segments 的总大小或行数超过配置阈值,就会触发 L0 compaction。这可以主动管理大量 deletes。
Inactivity-based triggers:在 deletion 活动较低或没有 deletion 活动时,time-based trigger 会充当 housekeeping 机制。它确保即使小批量 stale delete records,也会被周期性清理,不会无限期滞留系统中。
Safety limits:单个 L0 compaction task 中 L0 segments 的总大小和数量,都受最大阈值限制,以确保操作保持高效且资源友好。
一般来说,L0 compaction 的触发机制已经覆盖所有场景,并且已充分优化,不需要额外配置调整。
现在,我们转向 clustering compaction。
Clustering compaction
Clustering compaction 是一个根据 clustering keys 重组数据的过程。该过程通过支持高效 segment pruning 提升查询性能,使 queries 可以跳过无关 segments。
Milvus 中的 clustering key 并不是由系统自动决定的。相反,用户必须在创建 collection 时显式指定。这是一个刻意设计选择,将优化查询模式的责任交给最了解数据访问模式的开发者。
创建 collection 时,用户必须通过 collection schema 配置声明哪个 field 作为 clustering key。该 field 会成为 collection metadata 的永久组成部分,创建后无法修改。Clustering key selection 是一次性决策,会对查询性能产生长期影响。
理想的 clustering key 应满足几个标准。第一,它应该是 query filters 中频繁出现的 field,这样才能最大化 segment pruning 的收益。第二,它应具有合适 cardinality,既不能高到每个 segment 只包含少量 records,也不能低到 clustering 无法提供有意义的分离。第三,该 field 应具有自然 ordering 或 grouping patterns,与数据典型访问方式一致。
常见 clustering keys 选择包括:用于 time-series data 的 timestamp fields,用于经常按分类过滤的应用的 category 或 type fields,用于 spatial queries 的 geographic fields,以及用于 multi-tenant systems 的 user 或 tenant IDs。每种选择都反映一种不同 access pattern 和 optimization strategy。
对于 scalar-type clustering keys,Milvus 采用 range-based partitioning strategy。系统会分析 clustering key field 的 value distribution,然后创建覆盖特定不重叠 ranges 的 segments。例如,如果使用 timestamps 作为 clustering key,系统可能会将 1 月 1 日到 1 月 15 日的所有 records 组织到一个 segment,将 1 月 16 日到 1 月 31 日的 records 组织到另一个 segment。这样,当 queries 指定特定 date range 时,系统可以快速识别相关 segments。
对于 vector-type clustering keys,Milvus 采用不同方法。系统使用 k-means clustering algorithm 计算 centroids,代表 vector space 中不同区域。在 clustering compaction 期间,每个 vector 会被分配到最近 centroid,而分配到同一 centroid 的 vectors 会被组织到同一个 segment。这种方法特别适合 vector similarity search 场景,因为它确保相似 vectors 在物理上聚集在一起,从而显著提升 approximate nearest neighbor search efficiency。
Triggers
不同于 mix 和 L0 compaction 的自动触发,clustering compaction 需要手动触发,因为它可能计算开销很大,并且高度依赖 workload patterns。你可以通过 SDK 显式调用它:
c = MilvusClient()
c.compact("test", is_clustering=True)
Clustering compaction 在 multi-tenant SaaS applications 中特别有价值。在这类场景中,创建 collection 时应将 tenant ID field 设置为 clustering key。与 partition keys 结合后,可以获得最佳查询性能,因为 partition keys 通过 hashing 支持 partition-level pruning,而 clustering keys 支持每个 partition 内的 segment-level pruning。Clustering compaction 完成后,像 tenant_id = 'tenant_123' 这样的查询,就可以精确定位包含该 tenant 数据的少数 segments,大幅减少扫描范围。对于 time-series data 场景,使用 timestamp fields 作为 clustering key 可以实现高效 time-range queries。无论是哪种场景,管理员都需要根据数据增长和查询性能需求,定期手动触发 clustering compaction,以维持优化收益。
Compaction 为高效数据组织打好基础后,我们接下来转向通过 GC 管理过时数据这一关键角色。
使用 garbage collection 管理过时数据
Compaction 通过组织和优化数据布局提升 Milvus 性能,但它不可避免会留下副作用,也就是大量 obsolete data residue。然而,compaction 并不是 Milvus 系统中数字垃圾的唯一来源。在一个长期运行的 Milvus 部署中,多种操作都会留下需要清理的痕迹。
为解决这一问题,Milvus 引入数据生命周期管理中的最后关键环节,即 garbage collection(GC)。GC 是 DataCoord 组件中的核心功能,负责识别并安全删除系统不再使用的所有 files,从而释放 object storage 和 meta store 中的存储空间。它会按照可配置固定间隔周期性触发。具体来说,GC 的目标主要包括以下几类数据:
Dropped segments:这是最主要类别。无论是因为 compaction tasks 用新 segments 替换旧 segments,还是用户通过 drop collection 或 drop partition APIs 主动删除数据,这些操作都不会立即物理删除相关 segments 和 files。它们只会在 metadata 中被标记为 dropped。GC 的首要任务就是清理这些已标记 segments,包括它们的所有 files:entity data(binlogs)、statistics、index files 和 delta logs。
Abandoned index files:用户操作可以更细粒度。例如,当用户使用 drop_index API 删除某个特定 field 上的 index,同时保留数据本身时,被替换的旧 index files 就成为需要回收的 garbage。GC 的另一项重要职责,是周期性扫描并清理这些 orphaned garbage files,防止存储空间被无效数据悄悄消耗。
Orphaned garbage files:Milvus 中的一些后台任务,例如 data import 和 index building,可能因各种原因失败或重试。这些异常流程有时会在 object storage 中留下临时、无效或不完整的 orphaned files。
因此,GC 是一个系统化、全面的清理流程,确保 Milvus 存储环境长期健康和高效。接下来,我们讨论其工作原理。
GC 工作原理
这里会出现一个看似简单的问题:系统如何知道一个旧文件什么时候可以被安全删除?例如,当一个 segment 被 compaction task soft deleted 时,它什么时候可以被物理删除,而不影响正在进行的查询?在 Milvus 这样的分布式系统中,答案远比 “立即删除” 复杂得多。GC 机制必须在高效回收存储空间和维持查询性能的同时,确保数据安全。为实现这一点,Milvus 实现了多重检查的延迟删除机制,严格遵循 safety first 和 performance priority 原则。
Safety first:多层保护确保数据完整性
Milvus GC 机制把数据安全放在第一位。系统采用多个时间窗口和检查机制,确保任何数据删除操作都不会危及系统正常运行。
第一道防线是 orphaned file protection period。当数据写入 object storage 时,会出现一个微妙的时间差:文件在物理上已经存在,但 metadata system 尚未完成更新。在这个短暂窗口中,这些 files 看起来像 orphaned files。dataCoord.gc.missingTolerance 配置,默认 86400 秒,也就是 24 小时,会为这些暂时 orphaned files 提供保护伞。系统必须等待足够长时间,确认一个 file 真的是 orphaned,而不只是经历 metadata update delays,之后才会将其标记为删除。这个机制有效防止新写入数据在 metadata synchronization 完成前被误删。
第二道防线是 dropped segment buffer period。即使 segment 被明确标记为 dropped,dataCoord.gc.dropTolerance 参数,默认 10800 秒,也就是 3 小时,仍然要求系统保留这些 files 一段时间。这种 grace period 设计充满实践智慧:它为系统处理异常情况提供缓冲,也为管理员在发现问题时介入或恢复数据预留操作窗口。在生产环境中,这个缓冲期已多次证明其价值,可以防止因操作错误或系统异常造成永久数据丢失。
Performance priority:智能调度与查询保护
在安全基础之上,Milvus GC 机制也通过智能调度策略仔细考虑性能影响,将对 read path 的扰动降至最低。
最典型例子是 compaction 场景下的 delayed replacement strategy。当小 segments A 和 B 被合并成大 segment C 时,直觉上应立即删除 A 和 B 以释放空间。然而,Milvus 会选择更谨慎的做法。系统首先检查新 segment C 是否已经建立 indexes。只有当 C 的 indexes 完全 ready,并且可以提供与 A 和 B 相当或更好查询性能时,系统才会删除旧 segments。这一机制避免了严重性能陷阱:如果删除了带 index 的旧 segments,并用一个尚未建立 index 的新 segment 替换,查询延迟会飙升。
进一步说,即使新 segment C 的 indexes 已经 ready,如果旧 segments A 和 B 仍在 QueryNodes 上服务 queries,GC 也会延迟删除操作。系统会等待 QueryNodes 完成 segment switchover,确保没有 query 依赖这些旧 segments 后,才会物理删除它们。这种细致检查机制确保 query service 的连续性和稳定性。
在执行层面,GC 采用分层调度策略来优化资源使用。扫描 orphaned files 需要遍历整个存储系统,并与 metadata 对比,这是一个相对耗时的操作。因此,系统使用 dataCoord.gc.scanInterval 参数,默认 168 小时,也就是 7 天,单独控制 orphaned file checks 频率。对于已经标记为删除的 segments 及其关联 files,dataCoord.gc.interval 参数,默认 3600 秒,也就是 1 小时,控制检查频率。这种分离设计使系统能够根据不同垃圾类型进行针对性优化。
微调 GC 行为
理解 GC 的运行原理后,使用 Milvus 的 DevOps 团队可以根据具体业务特征和资源条件调整配置参数,在 storage efficiency 和 system stability 之间找到最佳平衡。
对于检查频率调整,应考虑系统的数据变化模式。在高频写入和删除的 online transaction processing(OLTP)systems 中,可以将 garbage collection interval,也就是 dataCoord.gc.interval,缩短到 15–30 分钟。这可以加速 GC,防止存储空间快速增长。相反,在以查询为主、数据相对稳定的 online analytical processing(OLAP)systems 中,可以将 interval 延长到 2–4 小时。这种策略可以减少 GC 开销,让更多系统资源分配给 query services。
对于保留周期配置,需要在 safety 和 cost 之间平衡。dataCoord.gc.dropTolerance 参数直接影响数据恢复能力。如果你的业务对数据安全要求极高,或者已经建立完整灾难恢复流程,可以将保留周期延长到 48–72 小时,为问题诊断和数据恢复提供足够时间。反过来,如果存储成本是重要关注点,并且系统频繁执行 compaction operations,产生大量 pending-deletion segments,就可以适当将保留周期缩短到 1 小时,以加快空间回收。
通过这套精心设计的 GC 机制,Milvus 实现了自动化、智能化存储管理。它不仅能高效回收未使用存储空间,更重要的是,整个过程对用户完全透明,并且不会影响系统正常操作。
小结
本章介绍了 compaction 和 garbage collection 如何协同管理 Milvus 中的数据。我们看到 compaction 如何减少 segment fragmentation、提升查询性能并处理 deletions,而 garbage collection 如何清理未使用数据,以释放存储并维持效率。
这些流程构成 Milvus 数据管理的 backbone,确保系统组织良好且资源高效。这些知识也为我们在下一章探索更高级的 Milvus 功能做好准备。