
Prometheus TSDB Chunks 磁盘格式全解从 segment 文件到 XOR/XOR2/Histogram 编码的源码级剖析【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheusTSDB block 中chunks/目录下的数据文件承载着所有时序样本的原始字节是 Prometheus 存储格式中最底层的一环。本文以仓库中的格式规范tsdb/docs/format/chunks.md为主体完整讲解 chunk segment 文件的头部结构、单条 chunk 的len encoding data CRC-32C布局、六种 chunk 编码XOR、XOR2、histogram、floathistogram 及其 ST 变体的逐字段位级格式并结合 chunkenc 源码 与 chunks 读写实现 说明每个字段在代码中如何被写入和校验帮助读者建立从磁盘字节到查询引擎的完整认知。1. Chunks segment 文件头部与引用机制每个 block 在chunks/目录下存放一组顺序编号的 segment 文件000000、000001……单个 segment 文件最大 512MiB。该值对应源码常量 DefaultChunkSegmentSizeconst ( // DefaultChunkSegmentSize is the default chunks segment size. DefaultChunkSegmentSize 512 * 1024 * 1024 )segment 文件头部固定为 8 字节格式文档中给出的布局与 cutSegmentFile 的写入逻辑一一对应┌──────────────────────────────┐ │ magic(0x85BD40DD) 4 byte │ ├──────────────────────────────┤ │ version(1) 1 byte │ ├──────────────────────────────┤ │ padding(0) 3 byte │ ├──────────────────────────────┤ │ ┌──────────────────────────┐ │ │ │ Chunk 1 │ │ │ ├──────────────────────────┤ │ │ │ ... │ │ │ ├──────────────────────────┤ │ │ │ Chunk N │ │ │ └──────────────────────────┘ │ └──────────────────────────────┘对应源码常量定义在 chunks.go// MagicChunks is 4 bytes at the head of a series file. MagicChunks 0x85BD40DD MagicChunksSize 4 chunksFormatV1 1 ChunksFormatVersionSize 1 segmentHeaderPaddingSize 3 // SegmentHeaderSize defines the total size of the header part. SegmentHeaderSize MagicChunksSize ChunksFormatVersionSize segmentHeaderPaddingSize写入时先预分配preallocatesegmentSize 大小的文件、同步目录再写入magic version头部关闭时把预分配的多余零字节截断见 finalizeTail 与 cut。读取侧 newReader 会逐段校验 magic 与版本号任何不符都会直接报错这是防止误读非 chunk 文件的第一道防线。1.1 chunk 的引用方式segment 序号 文件内偏移格式文档指出index 中对 chunk 的引用是一个 uint64低 4 字节为文件内偏移offset高 4 字节为 segment 序号sequence number。源码中这一契约由 BlockChunkRef 精确表达// BlockChunkRef refers to a chunk within a persisted block. // The upper 4 bytes are for the segment index and // the lower 4 bytes are for the segment offset where the data starts for this chunk. type BlockChunkRef uint64 func NewBlockChunkRef(fileIndex, fileOffset uint64) BlockChunkRef { return BlockChunkRef(fileIndex32 | fileOffset) }写入 chunk 时writeChunks 会为每条记录回填这个 refchk.Ref ChunkRef(NewBlockChunkRef(seq, uint64(w.n)))其中seq是当前 segment 序号、w.n是已写入的字节数。2. 单条 chunk 的磁盘布局len、encoding、data、checksum文件内的每条 chunk 按如下结构排列┌───────────────┬───────────────────┬─────────────┬───────────────────┐ │ len uvarint │ encoding 1 byte │ data data │ checksum 4 byte │ └───────────────┴───────────────────┴─────────────┴───────────────────┘各字段的含义与源码依据len1~5 字节chunk data 的字节长度使用 unsigned varint 编码binary.PutUvarint最大 5 字节对应常量MaxChunkLengthFieldSize binary.MaxVarintLen32见 chunks.go。encoding1 字节编码类型标识。当前取值与 chunk.go 中的枚举一致const ( EncNone Encoding iota EncXOR EncHistogram EncFloatHistogram EncXOR2 EncHistogramST EncFloatHistogramST )其中XOR2由配置项storage.tsdb.chunk_encoding.floats选择histogramST与floathistogramST则由实验性的histograms-st-encodingfeature flag 控制见 feature flags 文档。注意 CompatibleValues 表明 XOR 与 XOR2 同属一个值编码家族已打开的 chunk 在配置切换时可以继续追加新编码只在新 chunk 生效。data具体编码见后文各节。checksum4 字节对encoding与data计算的CRC-32CCastagnoli 多项式序列化为无符号 32 位大端整数。CRC-32C 在源码中通过共享的castagnoliTable初始化chunks.go写入时由 Meta.writeHash 把 encoding 字节 原始 data 送入哈希读取时 checkCRC32 重算并比较不匹配则返回checksum mismatch错误。Reader.ChunkOrIterable 的完整读取路径是按 ref 解出 segment 与偏移 → 读 uvarint 长度 → 定位 data 边界 →先校验 CRC 再从 chunkenc.Pool 取出对应编码的 chunk 对象任何越界或校验失败都不会污染结果。3. XOR chunk data浮点样本的位级编码┌──────────────────────┬───────────────┬───────────────┬──────────────────────┬──────────────────────┬──────────────────────┬──────────────────────┬─────┬──────────────────────┬──────────────────────┬──────────────────┐ │ num_samples uint16 │ ts_0 varint │ v_0 float64 │ ts_1_delta uvarint │ v_1_xor varbit_xor │ ts_2_dod varbit_ts │ v_2_xor varbit_xor │ ... │ ts_n_dod varbit_ts │ v_n_xor varbit_xor │ padding x bits │ └──────────────────────┴───────────────┴───────────────┴──────────────────────┴──────────────────────┴──────────────────────┴──────────────────────┴─────┴──────────────────────┴──────────────────────┴──────────────────┘要点ts是时间戳v是值num_samples为大端 uint162 字节。从样本 2 起时间戳改用doddelta of deltasts_n_dod (ts_n − ts_{n-1}) − (ts_{n-1} − ts_{n-2})用varbit_ts变长位宽编码1~68 bit。对采样间隔稳定的序列dod 通常为 0 或 ±1只需极少比特。值采用XOR 差值编码varbit_xor1~77 bit对当前值与前一值的 float64 位做 XOR再利用leading/trailing zero 窗口只保存有效位段。XOR 为 0值未变时只写 1 bit。末尾padding为 0~7 bit使整个 chunk data 字节对齐。一个 chunk 最少可以只有 1 个样本即ts_1、v_1及之后的字段是可选的。从 xorAppender.Append 的实现可以看到第一个样本写绝对时间戳与绝对值其后维护tDelta时间差、v上一值与leading/trailingXOR 窗口三个状态量writeVDelta则负责把 XOR 结果按窗口压缩写流。配套的容量约束在 chunk.goXOR chunk 目标大小 1024 字节MaxBytesPerXORChunk切 chunk 时按最坏单样本 19 字节预留MaxBytesPerXORChunkBeforeAppend。4. XOR2 chunk data联合控制位与可选 Start TimestampXOR2 是 XOR 的增强编码样本 0、1 的写法与 XOR 相同从样本 2 起用一个联合控制前缀joint control prefix同时编码时间戳 dod 与值是否变化并把常见的 dod 情况做成字节对齐提升写入效率。它还可选地编码 Start TimestampST例如 remote write 传递的 counter 起点时间。┌──────────────────────┬───────────────────┬───────────────┬───────────────┬────────────────┐ │ num_samples uint16 │ st_header uint8 │ ts_0 varint │ v_0 float64 │ ?st_0 varint │ └──────────────────────┴───────────────────┴───────────────────┴─────────────────────────────┘ ...4.1 联合样本编码n ≥ 2 时的joint_sample2每个样本先写变长控制前缀联合编码 dod 与值变化状态控制前缀dod后续的值编码00无值未变100varbit_xor2_nn值已知非零且非 stale110DDDDDDDDDDDDD13 bit 有符号 [-4096, 4095]varbit_xor21110DDDDDDDDDDDDDDDDDDDD20 bit 有符号 [-524288, 524287]varbit_xor211110 64 bit dod精确值varbit_xor2111110无值字段stale NaN110与1110两种情况把前缀与 dod 的高位打包进首个字节使整个 dod 字段字节对齐。这与 xor2Appender.encodeJoint 的分支完全一致dod0时走0/10/11111三条短路径|dod| ≤ 2^12−1写0b110前缀加 2 字节|dod| ≤ 2^19−1写0b1110前缀加 3 字节其余落入11110 64 bit 逃逸路径。随后再按值是否变化决定是否跟随varbit_xor2。4.2 值差值编码varbit_xor2用于 dod≠0 的控制前缀之后对当前值与前一值的 XOR 结果编码前缀含义0XOR 0值未变10复用上一 leading/trailing 窗口其后跟sigbits个值位110 leading(5) sigbits(6) value(sigbits)新的 leading/trailing 窗口111stale NaN 标记3 bit当控制前缀为10dod0 且已知值已变化、非 stale时跳过 delta0 检查改用省 1 bit 的varbit_xor2_nn前缀含义0复用上一 leading/trailing 窗口其后跟sigbits个值位1 leading(5) sigbits(6) value(sigbits)新的 leading/trailing 窗口对应实现分别是 writeVDelta 与 writeVDeltaKnownNonZero两者都维护leading/trailing窗口窗口内可容纳新的 XOR 值时用 2 bit 前缀复用否则写 3 bit 前缀加 5 bit leading、6 bit sigbits 与新值。4.3 Start TimestampST编码XOR2 与所有 histogram ST 编码共享同一套 ST 方案st_header为 1 字节┌───────────────────────┬───────────────────────┐ │ first_st_known1 bit | st_changed_on7 bits │ └───────────────────────┴───────────────────────┘最高位first_st_known表示st_0是否存在低 7 位st_changed_on为 0 表示没有任何st_i (i0)否则st_i (i≥st_changed_on)存在、st_i (0ist_changed_on)不存在。受 7 bit 限制chunk 达到 127 个样本时st_changed_on固定取 127从第 127/128 个样本起逐样本携带 ST。st_0存在时以varint编码st_1是相对st_0或 0的varbit_ts/varbit_int差值st_i (i1)是差值的差值dod编码。源码中该 header 的读写集中在 st.gowriteHeaderFirstSTKnown / writeHeaderFirstSTChangeOn / readSTHeader 操作首字节stEncoder.encode 负责逐样本的 ST 差值/dod 写入XOR2 追加样本时在 xor2Appender.Append 中把首次 ST 变化位置回写进 header。5. Histogram chunk data直方图样本的位级编码histogram 编码面向 native histogram整数计数直方图样本┌──────────────────────┬──────────────────────────┬───────────────────────────────┬─────────────────────┬──────────────────┬──────────────────┬──────────────────────┬────────────────┬──────────────────┐ │ num_samples uint16 │ histogram_flags 1 byte │ zero_threshold 1 or 9 bytes │ schema varbit_int │ pos_spans data │ neg_spans data │ custom_values data │ samples data │ padding x bits │ └──────────────────────┴──────────────────────────┴───────────────────────────────┴─────────────────────┴──────────────────┴──────────────────┴──────────────────────┴────────────────┴──────────────────┘5.1 正/负 spans 与 custom valuesspans 数据以num_spans开头其后交替排列length_i varbit_uint与offset_i varbit_int描述各 bucket 在样本中的位置。custom_values目前只用于 schema −53自定义 bucket 边界其他 schema 下长度为 0┌──────────────────────────┬──────────────────┬──────────────────┬─────┬──────────────────┐ │ num_values varbit_uint │ value_0 custom │ value_1 custom │ ... │ value_n custom │ └──────────────────────────┴──────────────────┴──────────────────┴─────┴──────────────────┘custom的编码针对人为设定的十进制小数边界做了优化先取y x × 1000若0 ≤ y ≤ 33554430且为整数则存y 1的varbit_uint永远以 1 bit 开头否则存一个 0 bit 加 64 bit 原始 float64以 0 bit 开头。上限 33554430 保证 varbit 结果不超过 4 字节解码端凭首 bit 区分两种情况。5.2 样本数据绝对值、差值、dod 三段式直方图样本编码同样是三段式Sample 0ts varbit_int、count varbit_uint、zero_count varbit_uint、sum float64随后是pos_bucket_0 varbit_int … pos_bucket_n、neg_bucket_0 varbit_int … neg_bucket_nSample 1ts_delta、count_delta、zero_count_delta均为varbit_int加sum_xor varbit_xor各 bucket 为差值Sample 2 及以后ts_dod、count_dod、zero_count_doddod 化varbit_int加sum_xor各 bucket 为 dod。关键注记与文档 Notes 一致histogram_flags当前只用前 2 bit10表示相对上一 chunk 发生 counter reset01表示无 reset00表示状态未知11表示这是 gauge histogram 的 chunk不存在 counter reset。zero_threshold有专门编码为 0 时仅 1 个零字节若是 2⁻²⁴³ 到 2¹⁰ 之间的 2 的幂则为 1~254 之间的单字节否则写全 1 字节255后跟 8 字节 float64共 9 字节。schema是 exposition format 定义的特定值标准指数 schema 取−4 ≤ n ≤ 8或−53自定义 bucket 边界。bucket 天然是相对前一 bucket 的差值只有bucket_0是绝对计数。chunk 最少 1 个样本spans 与 buckets 都可以少到 0 个。容量策略与 XOR 类似chunk.go 中TargetBytesPerHistogramChunk 1024并规定MinSamplesPerHistogramChunk 10——因为单个直方图样本可能超过 1024 字节仍要保住最小样本数以获得压缩收益。5.3 变长位宽原语varbit_int/varbit_uinthistogram 编码大量依赖 varbit.go 中的位桶化编码两者桶宽相同前缀取值范围int / uint总位数0精确 01 bit10int−3~4uint0~75 bit110int−31~32uint0~639 bit1110int−255~256uint0~51113 bit11110int−2047~2048uint0~409517 bit111110int±131071uint0~26214324 bit3 字节1111110int±16777215uint0~3355443132 bit4 字节11111110int±36028797018963967uint0~2⁵⁶−164 bit8 字节11111111完整 64 bit 逃逸72 bit9 字节最坏情况putVarbitInt的注释说明了分桶的取向按实际观测到的 bucket dod 分布优化各分支不必覆盖前序分支的值域理论上还能再省约 1% 空间但当前取舍是更低的编解码开销见 putVarbitInt。varbit_tsXOR/XOR2 的时间戳 dod1~68 bit与varbit_xorXOR 值差值1~77 bit也是同族的按位宽分桶编码分别在 xor.go 与 histogram.go 中实现。6. Histogram ST / Float histogram / Float histogram ST6.1 Histogram ST chunkhistogram ST 编码在 histogram 格式之上追加可选 ST 数据采用与 XOR2 相同的 ST 方案。样本编码与 histogram chunk 完全相同但3 字节头部重新排布counter-reset 标志移入样本计数字节的最高 2 bit腾出的字节 2 存放st_header┌────────────────────────┬───────────────────────┬────────────────────┬───────────────────────────────┬─────────────────────┬──────────────────┬──────────────────┬──────────────────────┬────────────────┬──────────────────┐ │ counter_reset 2 bits │ num_samples 14 bits │ st_header 1 byte │ zero_threshold 1 or 9 bytes │ schema varbit_int │ pos_spans data │ neg_spans data │ custom_values data │ samples data │ padding x bits │ └────────────────────────┴───────────────────────┴────────────────────┴───────────────────────────────┴─────────────────────┴──────────────────┴──────────────────┴──────────────────────┴────────────────┴──────────────────┘ST 部分规则与 4.3 节一致差异在于st_1及之后的字段用varbit_inthistogram ST 中样本数被压缩为 14 bit故st_changed_on达到上限 127 时从第 128 个样本起存在 ST。每个sample_i data之后紧跟可选的?st_i字段ST 缺失时不占空间。实现位于 histogram_st.go编码选择逻辑见 ValueType.ChunkEncodinguseHistogramST为真时返回EncHistogramST/EncFloatHistogramST。6.2 Float histogram chunk浮点计数直方图floathistogram整体布局与 histogram 相同只有样本内编码不同——count、zero_count与所有 bucket 都是浮点数Sample 0ts varbit_intcount float64、zero_count float64、sum float64、各 bucket 均为绝对float64Sample 1ts_delta varbit_int其后count_xor、zero_count_xor、sum_xor与各 bucket 均为varbit_xorSample 2 及以后时间戳改ts_dod其余字段仍是varbit_xor。即浮点字段不再做逐 bucket 差值/dod而是统一用 XOR 窗口压缩实现见 float_histogram.go。6.3 Float histogram ST chunk在 floathistogram 之上叠加可选 ST3 字节头部布局与 Histogram ST 完全一致counter_reset 2 bit num_samples 14 bit st_header 1 byte样本编码不变可选 ST 字段及st_header规则同 Histogram ST 与 XOR2 ST 编码。实现见 float_histogram_st.go。7. 从写入到读取格式如何被代码闭环把上述格式放回整条数据链路写入Head 中的 chunk 落盘时由 chunks.Writer.WriteChunks 批量写入。每个 chunk 按len(uvarint) encoding(1B) data crc32(4B)序列化长度字段按 5 字节上限预占当累计大小加上新 chunk 超过 segment 剩余空间时切新 segmentcutSegmentFile并刻意避免在写入末尾产生空 segment。读取chunks.Reader 以 mmap 方式把各 segment 映射进内存ChunkOrIterable按 ref 精确定位、先 CRC-32C 校验再从 chunkenc.Pool 取复用对象——池按六种编码分别维护sync.Poolxor / histogram / floatHistogram / xo2 / histogramST / floatHistogramST显著降低解码分配开销。兼容边界EncNone与非法编码在 IsValidEncoding 与 FromData 中被拒绝magic/版本校验发生在 newReader。XOR→XOR2 的平滑切换由CompatibleValues保证已有 chunk 不受影响。8. 小结chunk segment 文件 magic(0x85BD40DD) version(1) 3B padding 顺序排列的 chunk单 segment 上限 512MiBindex 通过高 4 字节 segment 序号 低 4 字节偏移的 uint64 refBlockChunkRef定位数据。每条 chunk len(uvarint) encoding(1B) data CRC-32C(4B)CRC-32CCastagnoli覆盖 encoding 与 data读取前强制校验。六种编码共享同一套位桶化原语varbit_int/uint/ts/xorXOR/XOR2 面向浮点样本XOR2 引入联合控制位与可选 SThistogram 面向整数直方图dod 三段式 spans 可选自定义边界floathistogram 则把直方图字段换成 float64 varbit_xor三个 ST 变体XOR2、histogramST、floathistogramST共享st_header方案由histograms-st-encodingfeature flag 或storage.tsdb.chunk_encoding.floats配置启用。格式细节的最终裁决者是代码编解码在 tsdb/chunkenc/含各编码的*_test.go往返测试文件级读写与校验在 tsdb/chunks/chunks.go、head_chunks.go格式规范文档见 tsdb/docs/format/chunks.md 与 tsdb/docs/refs.md。【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考