资讯动态

Prometheus TSDB Memory Snapshot 格式解析:Head 内存快照的 WAL 记录布局与回放机制

发布时间:2026/9/7 20:00:18 来源:尧图企业网站定制
Prometheus TSDB Memory Snapshot 格式解析Head 内存快照的 WAL 记录布局与回放机制【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus本文深入讲解 Prometheus TSDB 的内存快照Memory Snapshot格式它由哪些 WAL 记录构成、每条记录的二进制布局、快照目录的命名规则以及在进程重启时 Head 如何基于快照完成状态恢复。读完本文你将理解 tsdb/docs/format/memory_snapshot.md 中定义的三种记录Series、Tombstone、Exemplar的具体结构并能对照 tsdb/head_wal.go 与 tsdb/head.go 的源码验证其写入与回放路径。1. 什么是 Memory SnapshotMemory Snapshot代码中常称 chunk snapshot是 Prometheus TSDB Head 在内存中数据的磁盘化备份。它复用 WAL 包wlog的 segment 文件格式将 Head 中的每个 series 写为一条 WAL 记录。其目的是在进程正常退出后重启时不必从头回放整条 WAL而是直接从快照恢复 Head 状态series 元数据、未压缩的活跃 chunk、tombstone、exemplar从而显著缩短启动时间。从源码可以确认几个关键事实快照功能由 Head 的选项EnableMemorySnapshotOnShutdown控制定义在 tsdb/head.go。快照写入发生在优雅退出shutdown路径上Head的关闭流程在 mmap 完除最后一个 chunk 之外的所有 chunk 后若选项启用则调用performChunkSnapshot()见 tsdb/head.go实际实现在 tsdb/head_wal.go。启动时若启用该选项Head 会先尝试从快照回放再决定是否需要回退到 WAL 回放见 tsdb/head.go。2. 快照目录的命名与查找规则快照不落在 WAL 目录里而是独立存放在 chunk 数据根目录data/chunks_head/下。从 tsdb/head_wal.go 可以看到目录名前缀常量为const chunkSnapshotPrefix chunk_snapshot.目录名由 WAL 的末尾位置index、offset格式化为chunk_snapshot.%06d.%010d见 tsdb/head_wal.gofunc chunkSnapshotDir(wlast, woffset int) string { return fmt.Sprintf(chunkSnapshotPrefix%06d.%010d, wlast, woffset) }因此一个典型目录形如chunk_snapshot.000012.0000000123其中的 12/123 对应快照创建时 WAL 的最后一段序号与段内偏移。这一命名直接支撑了后续两个行为定位最新快照LastChunkSnapshot 遍历目录按前缀过滤后解析出(idx, offset)取字典序最大的一个找不到时返回record.ErrNotFound。清理过期快照DeleteChunkSnapshots 删除所有严格小于给定(index, offset)的快照目录。源码注释说明遗留的旧快照不会造成问题只是占磁盘空间因为它们会被更高的 chunk snapshot 覆盖而忽略。WAL 落后于快照时的兜底启动回放时如果 WAL 最后位置比快照索引还要靠后即 WAL 文件缺失、落后于快照Head 会认为快照不可用删除全部快照并回退到 WAL 回放见 tsdb/head.go。此外快照加载结果还会体现在可观测指标上失败计数prometheus_tsdb_snapshot_replay_error_totaltsdb/head.go以及启动统计信息中的chunk_snapshot_load_durationtsdb/head.go。3. 快照内记录的总体顺序这是原文档的核心约定务必牢记。快照中记录的顺序是固定的先写 series 记录每个 series 一条记录彼此不排序所有 series 写完后写一条tombstone 记录包含 Head 块中的全部 tombstone最后写一条或多条exemplar 记录每条记录内将多个 exemplar 打包batchingexemplar 的顺序即其写入环形缓冲区circular buffer时的顺序。三种记录各自的记录类型字节record type byte在源码中有明确定义见 tsdb/head_wal.gochunkSnapshotRecordTypeSeries uint8 1 chunkSnapshotRecordTypeTombstones uint8 2 chunkSnapshotRecordTypeExemplars uint8 3注意这与普通 WAL 的记录类型是不同的编号体系——快照定义了自己的一套类型字节这也呼应了原文档对 exemplar 记录“与 WAL 相同编码、但记录类型不同”的描述。4. Series 记录单序列的完整快照Series 记录是 Head 中单个序列的快照每条记录只包含一个 series内容包括该序列的元数据labels以及若存在则包含的内存 chunk 数据。其中sampleBuf是内存 chunk 中最后 4 个样本用于回放时快速重建尾部状态。原文档给出的布局图如下完整保留┌──────────────────────────┬────────────────────────────┐ │ Record Type byte │ Series Ref uint64 │ ├──────────────────────────┴────────────────────────────┤ │ Number of Labels uvarint │ ├──────────────────────────────┬────────────────────────┤ │ len(name_1) uvarint │ name_1 bytes │ ├──────────────────────────────┼────────────────────────┤ │ len(val_1) uvarint │ val_1 bytes │ ├──────────────────────────────┴────────────────────────┤ │ . . . │ ├──────────────────────────────┬────────────────────────┤ │ len(name_N) uvarint │ name_N bytes │ ├──────────────────────────────┼────────────────────────┤ │ len(val_N) uvarint │ val_N bytes │ ├──────────────────────────────┴────────────────────────┤ │ Chunk Range int64 │ ├───────────────────────────────────────────────────────┤ │ Chunk Exists uvarint │ │ # 1 if head chunk exists, 0 otherwise to detect a nil | │ # chunk. Below fields exists only when its 1 here. | ├───────────────────────────┬───────────────────────────┤ │ Chunk Mint int64 │ Chunk Maxt int64 │ ├───────────────────────────┴───────────────────────────┤ │ Chunk Encoding byte │ ├──────────────────────────────┬────────────────────────┤ │ len(Chunk) uvarint │ Chunk bytes │ ├──────────────────────────┬───┴────────────────────────┤ │ sampleBuf[0].t int64 | sampleBuf[0].v float64 | ├──────────────────────────┼────────────────────────────┤ │ sampleBuf[1].t int64 | sampleBuf[1].v float64 | ├──────────────────────────┼────────────────────────────┤ │ sampleBuf[2].t int64 | sampleBuf[2].v float64 | ├──────────────────────────┼────────────────────────────┤ │ sampleBuf[3].t int64 | sampleBuf[3].v float64 | └──────────────────────────┴────────────────────────────┘逐字段解读字段类型说明Record Typebyte固定为1chunkSnapshotRecordTypeSeriesSeries Refuint64序列在 Head 中的引用编号chunks.HeadSeriesRef回放时用于重建 ref→series 映射Number of Labelsuvarint标签个数 Nlen(name_i) / name_iuvarint bytes第 i 个标签名len(val_i) / val_iuvarint bytes第 i 个标签值Chunk Rangeint64内存 chunk 覆盖的采样间隔chunk range决定该 chunk 的容量语义Chunk Existsuvarint标记位1表示存在 head chunk0表示 chunk 为 nil其后的字段仅在取值为 1 时存在Chunk Mint / Maxtint64chunk 内样本的最小/最大时间戳Chunk Encodingbytechunk 的编码方式对应chunkenc的编码常量len(Chunk) / Chunkuvarint byteschunk 原始字节直接内嵌在记录中sampleBuf[0..3]4×(int64, float64)内存 chunk 的最后 4 个样本时间戳 值关于sampleBuf值得注意它只保存“最后 4 个样本”而不是整个 chunk 的样本流——完整样本数据已经由上面的Chunk bytes携带sampleBuf的作用是在回放时提供序列尾部状态的快速锚点。从源码结构看Series 记录的编码入口在 tsdb/head_wal.go写入chunkSnapshotRecordTypeSeries后逐字段编码解码校验在 tsdb/head_wal.go类型字节不匹配即报错。5. Tombstone 记录单条记录容纳全部删除区间Tombstone 记录包含 Head 块中的全部tombstone——整个快照只写入这样一条记录。其编码方式与磁盘块中 tombstone 文件tombstones文件的编码完全一致可直接参考 tsdb/docs/format/tombstones.md 了解块内 tombstone 的二进制布局。┌─────────────────────────────────────────────────────────────────┐ │ Record Type byte │ ├───────────────────────────┬─────────────────────────────────────┤ │ len(Encoded Tombstones) uvarint │ Encoded Tombstones bytes │ └───────────────────────────┴─────────────────────────────────────┘Record Type 固定为2chunkSnapshotRecordTypeTombstonesEncoded Tombstones是一整块编码后的字节长度为前导 uvarint回放时按 tombstone 文件同样的解码逻辑还原为Head的 tombstone 集合。“单条记录承载全部 tombstone”的设计意味着无论 Head 中有多少删除区间快照里 tombstone 部分恒为一条记录简化了“先 series 后 tombstone”的固定顺序约定。6. Exemplar 记录批量打包顺序即环形缓冲区顺序一条 Exemplar 记录可以包含多个exemplar。其内部编码方式与 WAL 中的 exemplar 记录相同首条 exemplar 完整写 series ref 与 timestamp后续条目用ref_delta与timestamp_delta增量编码只是记录类型字节被替换为快照自己的类型3chunkSnapshotRecordTypeExemplars。┌───────────────────────────────────────────────────────────────────┐ │ Record Type byte │ ├───────────────────────────────────────────────────────────────────┤ │ ┌────────────────────┬───────────────────────────┐ │ │ │ series ref 8b │ timestamp 8b │ │ │ └────────────────────┴───────────────────────────┘ │ │ ┌─────────────────────┬───────────────────────────┬─────────────┐ │ │ │ ref_delta uvarint │ timestamp_delta uvarint │ value 8b │ │ │ ├─────────────────────┴───────────────────────────┴─────────────┤ │ │ │ n len(labels) uvarint │ │ │ ├───────────────────────────────┬───────────────────────────────┤ │ │ │ len(str_1) uvarint │ str_1 bytes │ │ │ ├───────────────────────────────┴───────────────────────────────┤ │ │ │ ... │ │ │ ├───────────────────────────────┬───────────────────────────────┤ │ │ │ len(str_2n) uvarint │ str_2n bytes │ │ │ ├───────────────────────────────┴───────────────────────────────┤ │ │ . . . │ └───────────────────────────────────────────────────────────────────┘批量batching行为在源码中有明确实现见 tsdb/head_wal.go写入循环中每攒够maxExemplarsPerRecord 10000个 exemplar 就 flush 一条记录遍历结束后再 flush 剩余部分maxExemplarsPerRecord : 10000 batch : make([]record.RefExemplar, 0, maxExemplarsPerRecord) ... if len(batch) maxExemplarsPerRecord { if err : flushExemplars(); err ! nil { return err } }这解释了原文档“一条或多条 exemplar 记录”的由来exemplar 数量少时只有 1 条记录多时则被切成多条。而“exemplar 顺序即其写入环形缓冲区的顺序”则对应h.exemplars.IterateExemplars(...)的迭代顺序。7. 回放路径loadChunkSnapshot 如何恢复 Head启动时的回放入口是 loadChunkSnapshot其核心流程与格式设计严格对应定位快照调用LastChunkSnapshot(h.opts.ChunkDirRoot)找到最新的chunk_snapshot.*目录用wlog.NewSegmentsReader以 WAL segment 读取器打开快照目录——这正是“快照复用 WAL 包格式”的直接体现。并发解码按h.opts.WALReplayConcurrency起多个 worker每条记录经chunkSnapshotRecord通道分发解码使用record.NewDecoder并为其创建全新的labels.SymbolTablesyms整个快照共享一张符号表见 tsdb/head_wal.go。按类型分派回放循环依据类型字节分派到三个分支chunkSnapshotRecordTypeSeries/Tombstones/Exemplars见 tsdb/head_wal.goseries 记录通过getOrCreateWithOptionalID(csr.ref, ...)按快照中保存的 ref 重建 series并将ref→*memSeries映射分片缓存shardedRefSeriestombstone 记录解码后写回 Head 的 tombstone 集合exemplar 记录源码注释明确“Exemplars 位于快照末尾此时所有 series 均已加载完成”且只有当h.opts.EnableExemplarStorage启用且MaxExemplars 0时才会真正存储 exemplar否则直接跳过——这为消费方提供了一个重要的兼容性开关。与 WAL 的衔接loadChunkSnapshot返回(snapIdx, snapOffset, refSeries, err)Head 继续从快照对应的 WAL 位置之后回放增量记录若 m-map chunk 需要修复repair则会丢弃快照数据而改用 WAL 回放见 tsdb/head.go。失败处理也是显式的loadChunkSnapshot的注释说明“若返回错误清空 Head 内容是调用方的责任”而head.go中的回放逻辑在失败时递增prometheus_tsdb_snapshot_replay_error_total并回退到 WAL 回放。8. 与相邻格式的关联与适用前提Memory Snapshot 不是孤立格式它处于 TSDB 磁盘格式族的一个明确位置可结合以下文档交叉阅读tsdb/docs/format/wal.mdWAL segment 与记录编码基础快照直接复用其 segment 文件结构tsdb/docs/format/tombstones.md块内 tombstone 文件编码快照的 tombstone 记录与其同构tsdb/docs/format/head_chunks.mdchunks_head/目录下 m-map chunk 文件magic0x0130BC91的格式——快照写入前除最后一个 head chunk 外的 chunk 已先被 mmap 落盘tsdb/head.go快照只负责这些 m-map chunk之后仍未落盘的数据。适用前提与限制快照只有在启用EnableMemorySnapshotOnShutdown且进程正常退出时才会生成异常崩溃不会产生新快照但旧快照仍可用于回退场景只要 WAL 未落后于它。快照与 WAL 存在严格的位置一致性要求WAL 最后位置必须 ≥ 快照对应的(index, offset)否则快照被整体删除并回退 WAL 回放。快照是 WAL 回放的优化而非替代快照加载后仍会回放其后的 WAL 增量且 m-map chunk 的修复场景会强制放弃快照。小结Memory Snapshot 用“series 记录每个序列一条含内存 chunk 与尾部 4 样本 单条 tombstone 记录 批量 exemplar 记录”的固定顺序把 Head 的内存状态完整编码进一个 WAL 风格的 segment 目录。类型字节1/2/3tsdb/head_wal.go、chunk_snapshot.%06d.%010d目录命名、每 10000 条 exemplar 一批等细节均可在当前仓库源码中逐一定位使其成为 TSDB 快速启动机制中值得深入理解的一环。【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价