修复WAL重放中已删除系列重复记录的处理
概述
在 Prometheus 的时间序列数据库(TSDB)中,预写日志(WAL)的重放机制是保证数据持久性和系统可靠性的关键环节。本文描述了一个特定的竞态条件 bug 及其修复方案:当 WAL 重放时,一条针对已删除(stale)系列的删除记录与一条同一标签集的重复系列记录紧密相连时,会导致数据一致性和指标准确性问题。修复此问题对于确保监控系统的数据完整性、避免指标误报以及保障在故障恢复后数据的准确性至关重要。
核心概念
WAL 重放与并行处理
WAL 记录了所有写入操作,在 Prometheus 启动或从崩溃中恢复时,需要按顺序重放这些日志以重建内存中的数据结构(head)。为了提升性能,重放过程采用了并行化设计:系列记录的解析在分发 goroutine(dispatch goroutine)中完成,而更耗时的系列删除记录则被分发到多个分片化的重放处理器(sharded replay processors)中异步执行。
竞态条件:删除与重复系列
问题的根源在于这两类操作的异步特性。考虑以下事件序列:
- WAL 记录了一个系列
S的删除操作。 - 紧接着,WAL 又记录了一个具有相同标签的系列
S的创建(重复记录)。
在重放时,分发 goroutine 可能先解析到重复的系列记录,并将其关联到现有的 memSeries 对象 S。但此时,删除记录可能尚未被分片处理器应用。这导致对一个即将(或已经逻辑上)删除的对象执行了“重置”操作,引发了不一致状态。
本次修复解决的具体问题
本次修复解决了此前修复(#18401)遗留的两个问题:
- 内存映射(mmap)就绪计数偏差:每条内存条带(stripe)的
mmapReady计数器被不必要地递减了两次,导致该计数可能变为负值。这会错误地阻止后续的头块(head chunks)内存映射操作,使得头块在堆内存中滞留更久,浪费内存。 - 指标与时间范围漂移:重置操作将本应属于新系列的已内存映射的块(m-mapped chunks)错误地附加到已删除的对象上,导致
prometheus_tsdb_head_chunks等指标被虚增,并扩大了已删除系列的数据时间范围,且之后没有纠正机制。
实践
修复方案详解
修复通过两个独立的 commit 实现,分别针对上述两个问题,并确保了修复的安全性:
-
Commit 1:修正计数器逻辑
- 问题:在
deleteSeriesByID函数中,虽然减少了headChunkCount,但并未将其清零。因此,后续针对重复系列记录的resetSeriesWithMMappedChunks操作会再次基于旧的headChunkCount值(>=2)尝试递减mmapReady计数器,导致双重递减。 - 修复:在删除系列时,将
headChunkCount显式清零。这确保了后续的重置操作看到headChunkCount为 0,从而跳过对mmapReady计数器的错误递减。这是一个最小化的修复,便于可能的向后移植。
- 问题:在
-
Commit 2:阻止无效的重置操作
- 问题:
resetSeriesWithMMappedChunks函数在未检查系列是否仍处于活跃状态时,就处理了重复记录的块,造成了上述的指标和时间范围漂移。 - 修复:修改
resetSeriesWithMMappedChunks函数,使其对于不在 head 中的系列(即已被标记删除的系列)直接成为无操作(no-op)。这从根源上阻止了对已删除对象的后续无效修改。
- 问题:
安全性与测试保障
此修复被设计为安全的,因为对于给定的系列对象,其删除记录和重复记录的重置操作会在同一个重放处理器中排队执行,因此新的检查-操作路径是串行化的。此外,在重放期间不运行 mmapHeadChunks。
- 回归测试:每个修复都附带了专门的回归测试。第一个修复的测试在没有它的情况下会失败,证明了计数器修复的有效性。第二个修复扩展了测试用例,覆盖了块附加、指标变化和时间范围,确保了完整场景的正确性。
SRE 要点
与可靠性、可观测性、事件响应的关联
- 数据可靠性:此 bug 会导致 TSDB 内部状态不一致,影响从 WAL 恢复后数据的完整性。修复确保了重放逻辑的正确性,是保障 Prometheus 作为监控系统核心可靠性的基础。
- 可观测性:受影响的指标如
prometheus_tsdb_head_chunks、prometheus_tsdb_head_chunks_created_total以及系列的时间范围,都是评估 TSDB 性能和健康状况的关键可观测性数据。错误的指标会导致误判。 - 事件响应:在处理 Prometheus 实例崩溃或重启后的数据恢复事件时,理解此类边界条件有助于更准确地诊断潜在的异常数据,避免将系统自身 bug 引起的指标异常误判为业务层面的问题。
最佳实践与常见反模式
- 最佳实践:
- 理解内部架构:SRE 工程师应了解核心组件(如 TSDB WAL 重放)的关键路径和并发模型,以便在升级或故障排查时做出更明智的决策。
- 关注关键指标漂移:在监控 Prometheus 自身健康时,应关注其 TSDB 相关指标(如上述
head_chunks指标)的趋势和突变,它们能揭示潜在的内部问题。 - 利用回归测试:Prometheus 社区对边界条件有严格的测试覆盖,升级时应关注相关修复说明,验证自身是否可能遇到类似场景。
- 常见反模式:
- 忽视异步操作的时序:在设计或分析系统时,低估异步操作间复杂的交互和时序依赖,是导致此类竞态条件 bug 的根源。
- 指标“只增不减”的假设:错误地认为某些计数器指标在没有外部事件时不会异常减少,而忽略了系统内部维护逻辑可能出错的情况。
相关指标(SLI/SLO/SLA)
此修复直接关联以下 Prometheus TSDB 内部指标,这些指标可作为系统可靠性的 SLI(服务级别指标):
prometheus_tsdb_head_chunks:当前 head 中活跃的块数量。修复避免了其被虚增。prometheus_tsdb_head_chunks_created_total:已创建的 head 块总数。修复阻止了其被错误累加。- 与内存映射相关的指标:虽然未直接列出,但修复影响了
mmapReady计数器,该计数器用于控制内存映射调度,与系统内存使用效率相关。 在 SLA 层面,确保 Prometheus 数据在重启后能够正确恢复,是提供连续、准确监控服务的基础承诺之一。此修复强化了这一能力。