Grafana Pyroscope Compactor 架构与配置完全指南:块压缩、Split-and-Merge 算法与磁盘估算
2026/9/15 10:44:29 网站建设 项目流程

Grafana Pyroscope Compactor 架构与配置完全指南:块压缩、Split-and-Merge 算法与磁盘估算

【免费下载链接】pyroscopeContinuous Profiling Platform. Debug performance issues down to a single line of code项目地址: https://gitcode.com/GitHub_Trending/py/pyroscope

Grafana Pyroscope 是一个持续性能分析(Continuous Profiling)平台,其存储层采用块(block)为单位组织 profiling 数据。本文聚焦 Pyroscope v1 架构中的compactor(压缩器)组件,系统讲解它如何通过垂直/水平压缩合并块、去重复制样本、降低长期存储成本并提升查询性能,深入剖析 split-and-merge 分片合并算法、compactor 分片(sharding)、块删除机制,并给出完整的配置参数、磁盘空间估算与调优建议。读完本文,你将掌握 compactor 的完整工作原理,能够针对大租户场景正确配置压缩并发、分片数、分组数、压缩作业排序与磁盘容量。

提示:compactor 是 Pyroscopev1 架构组件。v2 架构中的对等组件是 compaction worker,本文不展开介绍。

Compactor 的职责:查询加速与存储瘦身

compactor 是一个**无状态(stateless)**组件,在 Pyroscope 集群中承担两项核心职责:

  • 合并块并去重:将某个租户(tenant)的多个块压缩为单个、经过优化的更大块。合并过程会去重 chunk(数据块)并缩小索引体积,从而降低存储成本;同时,查询时扫描的块数量更少,查询速度也因此提升。
  • 维护每租户的 bucket index:bucket index 是 querier 和 store-gateway 发现存储中新增块与被删除块的依据,compactor 负责及时更新它,保证查询路径看到一致的块视图。

从源码结构看,compactor 的核心实现位于 pkg/compactor 目录:MultitenantCompactor(compactor.go)是顶层服务,负责周期性地扫描存储桶、发现租户并驱动压缩;BucketCompactor负责单个租户的块压缩执行;BlocksCleaner(blocks_cleaner.go)负责块的软删除与硬删除。整个目录还包含split_merge_*系列文件,对应下文将详细介绍的 split-and-merge 算法。

压缩的工作原理:垂直压缩与水平压缩

压缩按租户独立进行,compactor 以可配置的固定间隔周期运行。压缩分为两个阶段:

垂直压缩(Vertical compaction)

垂直压缩将 ingester 上传的、属于同一时间范围(默认 1 小时)的某个租户的所有块合并为一个块,同时去重因复制(replication)而写入 N 个块的重复样本。其效果是把单一时间范围内的块数量从"ingester 数量"降为"每个租户一个块"。这也解释了为何配置中存在-compactor.first-level-compaction-wait-period(默认 25 分钟)——它让 compactor 在压缩一级块之前等待一段时间,减少"部分 ingester 尚未上传块"就启动压缩的情形(见 compactor.go 中的 flag 说明)。

水平压缩(Horizontal compaction)

水平压缩在垂直压缩之后触发,将时间范围相邻的几个块合并为更大的块。水平压缩不会改变块 chunk 的总大小,但它能显著缩小索引(index)以及 store-gateway 在内存中维护的 index-header 体积——因为块数量变少了,每个块需要维护的索引条目总量下降。

下图直观展示了两种压缩的差异:垂直压缩合并并去重同一租户时间上重叠的块,水平压缩合并时间上相邻的块。

压缩运行周期与重试(源码佐证)

MultitenantCompactor.running()(compactor.go)显示:compactor 启动后会先立即执行一次初始压缩,随后按-compactor.compaction-interval(默认 30 分钟,带 5% 抖动)周期性触发compactUsers。每次运行中,compactor 会:

  1. 从存储桶发现租户列表(bucket.ListUsers),并随机打乱顺序以降低多副本同时压缩同一租户的概率(compactor.go);
  2. 通过shardingStrategy.compactorOwnUser判断该租户是否属于本实例的 shard(compactor.go);
  3. 对归属本实例的租户调用compactUserWithRetries,按-compactor.compaction-retries(默认 3 次)重试失败的压缩。

扩容策略:垂直扩容与水平扩容

压缩按租户进行,因此可以对"拥有大租户的集群"进行针对性调优,配置同时涵盖垂直与水平两个维度的扩展:

  • 垂直扩容(Vertical scaling)-compactor.compaction-concurrency配置单个 compactor 实例内并行的压缩任务数上限,每个压缩任务占用一个 CPU 核(默认值为 1,见 compactor.go)。增大该值可让单个实例利用更多 CPU 并发压缩不同租户/作业。
  • 水平扩容(Horizontal scaling):默认情况下,任意 compactor 都可以压缩任意租户的块。当启用 compactor shuffle sharding 后——即把-compactor.compactor-tenant-shard-size(或其 YAML 配置compactor_tenant_shard_size)设置为大于 0 且小于可用 compactor 数量的值——只有指定数量的 compactor 才有资格为给定租户压缩块。该限值是按租户生效的(见 limits.go 中 flag 说明:"0 to disable the limit and use all compactors"),适合将大租户隔离到独立 compactor 组、避免大租户压缩任务拖累小租户。

此外,-compactor.compaction-retries(默认 3)控制单次压缩运行内失败压缩的重试次数,-compactor.max-compaction-time(默认 1 小时)限制单个租户在单次压缩周期内启动新压缩的最大时间,避免单个租户耗尽整个压缩周期(多租户场景尤其有用,见 compactor.go)。

压缩算法:Split-and-Merge

Pyroscope 采用名为split-and-merge的分层压缩算法。从设计上看,该算法克服了时序数据库(TSDB)索引的天然限制,避免了大租户在任何压缩阶段出现"压缩块无限增长"的问题。

split-and-merge 是分阶段(split)与合并(merge)的两阶段过程默认配置下 split 阶段被禁用-compactor.split-and-merge-shards默认值为 0,见 limits.go)。

Split 阶段

在第一个压缩级别(例如2h范围)执行 split 时,compactor 把所有源块划分为N个组(由-compactor.split-groups控制)。对每个组,compactor 压缩这些块,但不是输出单个结果块,而是输出M个块(由-compactor.split-and-merge-shards控制),即所谓的split blocks(拆分块)。每个 split block 只包含属于M个 shard 中某一 shard 的 series 子集。split 阶段结束时,compactor 产生N × M个块,每个块在自身的meta.json中记录其所属 shard。

Merge 阶段

compactor 对每个 shard 的 split blocks 执行合并:把给定 shard 的全部N个 split block 压缩到一起,块数量从N × M降为M。对于给定压缩时间范围,最终每个 shard 对应一个压缩后的块。merge 随后继续在其余配置的压缩时间范围(例如1h4h)上运行,合并属于同一 shard 的块。

下图展示了-compactor.split-and-merge-shards=2时的完整流程:源块被两个 compactor 分别拆成 2 个 shard,再由另外两个 compactor 合并为压缩块。

参数与权衡

该策略面向大租户集群。M(shard 数)通过-compactor.split-and-merge-shards按租户配置,可根据各租户的 series 数量调整:租户的 series 越多,配置的 shard 数可以越大,从而提升压缩并行度,并让每个 shard 的压缩块大小保持可控。

N(split 组数)通过-compactor.split-groups按租户调整(默认值为 1,见 limits.go)。增大N会在 split 阶段产生更多、块数更少的压缩作业,让多个 compactor 能并行处理这些作业、更快完成 split;但代价是 split 阶段产生更多中间块,这些中间块只能在后续 merge 阶段被消减。

需要特别注意:如果压缩进行中修改了-compactor.split-and-merge-shards,该变更只影响尚未 split 的块;已 split 的块在 merge 时仍使用原始配置——原始配置记录在每个 split block 的meta.json中。因此修改 shard 数需要等待现有 split 块全部合并完成才完全生效。

split 与 merge 均可水平扩展:不冲突、不重叠的作业会被并行执行。从源码看,splitAndMergeShardingStrategy.ownJob(compactor.go)先确认租户归属,再对作业的ShardingKey()哈希后判断本实例是否拥有该作业,从而保证每个作业只被一个 compactor 执行;SplitAndMergePlanner.Plan(split_merge_planner.go)则校验待压缩块都落在最大时间范围内(作为分组逻辑正确性的双重检查)。此外split_and_merge_compactor.gosplit_merge_grouper.go与对应的_test.go文件共同构成了这一算法的实现与验证。

Compactor 分片(Sharding)与哈希环

compactor 会对压缩作业分片——无论是来自单个租户还是多个租户的作业。单个租户的压缩也可以被拆分成多个作业、由多个 compactor 实例并行处理

当 compactor 实例池扩容或缩容时,租户与作业会在可用实例之间自动重新分片(resharding),无需人工干预。Compactor 分片基于哈希环(hash ring)实现:

  • 启动时,compactor 生成随机 token 并注册到 compactor hash ring(每个实例在 ring 中拥有 512 个 token,见 compactor_ring.go);
  • 运行期间,它以-compactor.compaction-interval为周期扫描存储桶,发现租户列表,并只为 hash 落在本实例 token 范围内的租户压缩块compactorOwnUser通过ring.ShuffleShard(userID, shardSize)判断实例是否属于该租户的 shard,见 compactor.go)。

若要配置 compactors 的哈希环,请参考 configuring memberlist。ring 相关配置项集中在compactor.ring.*前缀下,例如-compactor.ring.wait-active-instance-timeout(默认 10 分钟,等待 compactor 在 ring 中变为 ACTIVE)等(compactor_ring.go)。

启动时等待哈希环稳定

集群冷启动或同时扩容 2 个及以上 compactor 实例时,各实例启动时刻可能略有差异,导致每个 compactor 基于不同状态的哈希环执行首次压缩。这不是错误状态,但可能低效:多个实例可能几乎同时开始压缩同一个租户。

为缓解该问题,可以配置 compactor 在启动时等待 ring 稳定。ring 稳定的判定标准是:-compactor.ring.wait-stability-min-duration时间内没有实例加入或离开哈希环。等待总时长上限由-compactor.ring.wait-stability-max-duration控制(默认 5 分钟)。当 compactor 结束等待(ring 已稳定或达到最大等待时间)后,即正常启动。若-compactor.ring.wait-stability-min-duration为默认值0,则禁用等待 ring 稳定的功能。

对应的源码实现在 compactor.go:当WaitStabilityMinDuration > 0时调用ring.WaitRingStability,若超过最大等待时间仍未稳定,则以 WARN 日志"proceeding anyway"继续启动。

压缩作业的排序策略

compactor 通过-compactor.compaction-jobs-orderflag(或对应 YAML 配置compaction_jobs_order)配置压缩作业的执行顺序。该排序决定了哪些压缩作业优先执行。支持以下取值(定义见 job_sorting.go):

  • smallest-range-oldest-blocks-first(默认)

    优先处理最小时间范围最旧块

    例如压缩范围为1h, 4h, 8h时,compactor 先压缩1h范围的块,并优先处理其中最旧的块;全部1h范围压缩完成后,再进入2h范围,最后是8h范围。源码注释(job_sorting.go)给出的设计理由是:更小的范围能更早完成样本去重,而更旧的块更可能"完整"(不存在尚未上传的缺失块)。

    所有split 作业会被移到工作队列最前面,因为完成某时间范围内的全部 split 作业可以解除对应 merge 作业的阻塞(merge 作业只有在同一时间范围没有 split 作业时才会生成),从而让更多 compactor 有机会并行工作。

  • newest-blocks-first

    优先处理最新的时间范围,无论其压缩级别如何。

    例如压缩范围为1h, 4h, 8h时,compactor 先压缩最新的块(一直到8h范围),再处理更旧的块。该策略假定最新块被查询得最频繁,适合 compactor 落后(lagging behind)时优先补齐近期数据。

两种排序的实现分别位于sortJobsBySmallestRangeOldestBlocksFirstsortJobsByNewestBlocksFirst(job_sorting.go),并通过GetJobsOrderFunction在启动时注入到MultitenantCompactor.jobsOrder;若配置了不支持的取值,compactor 会启动失败并报unsupported compaction order错误(compactor.go)。

块的删除机制:软删除与硬删除

压缩成功后,原始块会从存储中删除。但删除不是立即执行的,而是两阶段过程:

  1. 标记删除(软删除):原始块先被标记为待删除;
  2. 硬删除:块被标记为待删除的时间超过-compactor.deletion-delay(默认 12 小时,见 compactor.go)后,才从存储中真正删除。

compactor 同时负责"标记块"与"硬删除"两项工作。软删除基于存放在桶内块位置的一个小型deletion-mark.json文件实现。BlocksCleaner在清理周期中读取 bucket index 中的BlockDeletionMarks,筛选出标记时间超过删除延迟的块并执行硬删除(blocks_cleaner.go);当-compactor.deletion-delay设为 0 时,块会被立即删除,但 flag 帮助信息明确警告:立即删除块可能导致查询失败

软删除机制的意义在于:它给 querier 和 store-gateway 留出发现新压缩块的时间窗口,再删除原始块。如果原始块被立即硬删除,涉及压缩块的某些查询可能会临时失败或返回部分结果。与之配套,-compactor.cleanup-interval(默认 15 分钟)控制块清理与维护、以及 bucket index 更新的频率,-compactor.cleanup-concurrency(默认 20)控制并行执行清理的租户数。

Compactor 磁盘空间估算

compactor 需要从桶下载块到本地磁盘,也需要把压缩后的块暂存到本地磁盘后再上传回桶。最大的租户可能需要大量磁盘空间。

假设max_compaction_range_blocks_size表示最长-compactor.block-ranges周期内最大租户的总块大小,那么估算最小所需磁盘空间的表达式为:

compactor.compaction-concurrency * max_compaction_range_blocks_size * 2

即:compaction-concurrency个并发压缩任务,每个任务同时需要"下载中的原始块"与"待上传的压缩块"两份空间,因此乘 2。-compactor.block-ranges是压缩时间范围列表,默认值为1h, 2h, 8h(compactor.go),且要求每个范围都能被前一个范围整除、第一个范围能被最大块时长整除(校验逻辑见 compactor.go)。在规划节点磁盘时,应按最大并发数 × 最大租户最大范围块大小 × 2 预留容量,并为临时目录(-compactor.data-dir,默认./data-compactor)提供足够空间——该目录不需要在重启之间持久化(compactor.go)。

Compactor 配置速查

compactor 配置分为两部分:compactor 自身配置块limits 配置块,完整参数清单可参考 reference configuration parameters 文档中的compactorlimits两个区块。以下是本文涉及的关键参数汇总(默认值均来自当前仓库源码):

参数(flag / YAML)默认值作用
-compactor.compaction-interval/compaction_interval30m压缩运行周期(启动后先立即执行一次)
-compactor.compaction-concurrency/compaction_concurrency1单实例最大并发压缩任务数,每个任务占用一个 CPU 核
-compactor.compaction-retries/compaction_retries3单次压缩运行内失败压缩的重试次数
-compactor.first-level-compaction-wait-period25m压缩一级块前的等待时间,降低 ingester 未上传完就开始压缩的概率
-compactor.max-compaction-time/max_compaction_time1h单租户在单次压缩周期内启动新压缩的最大时间,0 表示禁用
-compactor.block-ranges/block_ranges1h, 2h, 8h压缩时间范围列表,需可逐级整除
-compactor.deletion-delay/deletion_delay12h块被标记删除后到硬删除的延迟,0 表示立即删除(有查询失败风险)
-compactor.cleanup-interval/cleanup_interval15m块清理、维护与 bucket index 更新频率
-compactor.cleanup-concurrency/cleanup_concurrency20并行执行清理的租户数
-compactor.compaction-jobs-order/compaction_jobs_ordersmallest-range-oldest-blocks-first压缩作业执行顺序(另一取值:newest-blocks-first
-compactor.compactor-tenant-shard-size/compactor_tenant_shard_size0单个租户可用的 compactor 数量上限,0 表示使用全部 compactor
-compactor.split-and-merge-shards/compactor_split_and_merge_shards0split-and-merge 的 shard 数,0 表示禁用 split 阶段
-compactor.split-groups/compactor_split_groups1split 阶段源块分组数
-compactor.ring.wait-stability-min-duration0启动时等待 ring 稳定的最短时间,0 表示禁用等待
-compactor.ring.wait-stability-max-duration5m启动时等待 ring 稳定的最大时间,超时后照常启动
-compactor.data-dir/data_dir./data-compactor压缩临时目录,无需持久化
-compactor.block-sync-concurrency8下载块与上传压缩块的并发数
-compactor.meta-sync-concurrency20同步块 meta 文件的并发数
-compactor.max-opening-blocks-concurrency16压缩前打开块的 goroutine 数
-compactor.enabled-tenants/disabled-tenants允许/禁止压缩的租户白名单/黑名单(受分片约束)

典型大租户调优示例

以下 YAML 片段展示了为大型租户集群配置 compactor 的典型方式(compactoroverrides分属不同配置区块,具体挂载位置以 reference configuration parameters 为准):

compactor: # 单实例最多并行 4 个压缩任务(占用 4 核) compaction_concurrency: 4 # 每 15 分钟扫描一次存储桶并执行压缩 compaction_interval: 15m # 压缩范围 1h → 2h → 8h block_ranges: - 1h - 2h - 8h # 块标记删除后等待 24 小时再硬删除 deletion_delay: 24h # 压缩作业排序:优先小范围、最旧块 compaction_jobs_order: smallest-range-oldest-blocks-first sharding_ring: # 启动时等待 ring 稳定 1 分钟,最多 5 分钟 wait_stability_min_duration: 1m wait_stability_max_duration: 5m overrides: "big-tenant": # 该租户允许 3 个 compactor 参与压缩(shuffle sharding) compactor_tenant_shard_size: 3 # 按 series 规模配置 8 个 shard,提升并行度 compactor_split_and_merge_shards: 8 # 4 组 split,产生更多可并行的小作业 compactor_split_groups: 4

总结

compactor 是 Pyroscope 长期存储链路上"让数据更少、查询更快"的关键组件:垂直压缩去重复制样本、水平压缩缩减索引体积,split-and-merge 算法则让超大租户的压缩可以横向扩展到多实例并行。在生产环境中,建议按照租户规模动态调整-compactor.split-and-merge-shards-compactor.split-groups,结合 shuffle sharding 隔离大租户、利用-compactor.compaction-jobs-order控制作业优先级,并按"并发数 × 最大租户最大范围块大小 × 2"预留磁盘。所有关键参数与默认值均可在 pkg/compactor/compactor.go 与 pkg/validation/limits.go 中核对,相关算法实现与测试可进一步阅读 pkg/compactor 目录下的split_merge_*系列文件。

【免费下载链接】pyroscopeContinuous Profiling Platform. Debug performance issues down to a single line of code项目地址: https://gitcode.com/GitHub_Trending/py/pyroscope

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询