3. TSDB 存储引擎详解

Prometheus 的时序数据库(TSDB)是其核心组件,负责高效地存储和查询海量时间序列数据。理解 TSDB 的写入路径、内存映射、WAL 以及 Block 结构,对于进行资源配额和性能优化至关重要。本章将深入剖析这些机制及其对内存和磁盘 I/O 的影响。

3.1 写入路径:从样本到持久化

一个样本数据从被接收至最终落盘,经历了一条精心设计的管道。这条路径在保证写入吞吐量的同时,也兼顾了数据的一致性和恢复能力。

核心目标:在内存中快速聚合写入,通过 WAL 保证数据不丢失,最终异步刷盘形成不可变的 Block。
  1. 接收样本:Appender 接口接收来自 scrape 或 remote write 的样本。
  2. 写入 Head (内存):样本首先被写入内存中的 Head 模块。Head 维护了最新的数据,并包含一个内存中的倒排索引,用于快速标签匹配。
  3. 记录 WAL:在写入 Head 的同时,样本会被序列化并追加到预写日志(WAL)中。这是崩溃恢复的关键。
  4. 内存压缩 (MemSeries):Head 内部,每个时间序列(Series)的数据被组织成一系列内存中的 Chunk。当 Chunk 填满(默认约 120 个样本)后,会被标记为“已满”,并准备刷盘。
  5. 刷盘为 Block:后台的压缩器定期将 Head 中已满的 Chunk 以及对应的索引数据,写入磁盘,形成一个不可变的 Block。
  6. WAL 截断:一旦数据被安全地写入 Block,对应的 WAL 段可以被安全地删除或截断,以回收磁盘空间。
TSDB 写入路径流程图 1. 接收样本 2. 写入 Head 3. 记录 WAL 4. 内存压缩 5. 刷盘为 Block 6. WAL 截断 💡 关键点:WAL 与 Head 内存写入是同步的,确保数据不丢失。Block 的写入是异步批量操作,最大化 I/O 效率。

3.2 WAL (预写日志):崩溃恢复的基石

WAL 是 TSDB 保证数据持久性的核心机制。在数据被写入内存 Head 的同时,会立即追加到 WAL 文件中。如果进程崩溃,重启时可以通过回放 WAL 来重建内存中的 Head 数据。

特性 描述 对内存/磁盘 I/O 的影响
写入模式 顺序追加写入,避免随机 I/O。 磁盘 I/O: 高吞吐、低延迟,对 HDD 友好,SSD 更佳。
内存缓冲 WAL 写入通常由操作系统 Page Cache 缓冲,批量刷盘。 内存: 占用 Page Cache,但可被回收。刷盘频率影响 I/O 峰值。
文件结构 由多个固定大小的 Segment 文件(默认 128MB)组成。 磁盘 I/O: 写满后创建新文件,旧文件在数据落盘后可被删除,减少碎片。
压缩与截断 定期将已落盘的 WAL Segment 标记为可删除。 磁盘 I/O: 删除操作为元数据操作,开销小。回收磁盘空间。
⚠️ 性能陷阱: 如果 WAL 的刷盘策略过于保守(例如每次写入都 fsync),会显著增加写入延迟,降低吞吐量。Prometheus 默认利用操作系统 Page Cache 进行批量写入,在性能和持久性之间取得了平衡。监控 prometheus_tsdb_wal_fsync_duration_seconds 指标可以评估 WAL 刷盘延迟。

3.3 Block 结构:不可变的数据单元

Block 是 TSDB 在磁盘上的核心存储单元,它是一个自包含的目录,包含了该时间范围内所有序列的样本数据和索引。Block 的不可变性是其高性能查询的关键。

单个 Block 的磁盘结构 Block (时间范围: 2024-01-01 12:00 ~ 14:00) meta.json 元数据 (时间、统计) index 倒排索引、Postings chunks/ 样本数据文件 tombstones 逻辑删除标记 📁 每个 Block 都是一个独立目录,包含上述文件。这种结构使得查询可以快速定位到相关时间范围的 Block。

3.3.1 Block 内部文件详解

  • meta.json: 包含 Block 的元数据,如最小/最大时间戳、样本数量、序列数量、压缩级别等。用于快速过滤和统计。
  • index: 内存映射文件,包含从标签名/值到序列 ID 的倒排索引(Postings List),以及从序列 ID 到 Chunk 位置的映射。这是实现高效查询的核心。
  • chunks/: 目录下包含多个 xxxxx 文件,每个文件包含多个 Chunk。Chunk 是样本数据的实际存储单元,使用 XOR 压缩算法(Facebook Gorilla )进行高效压缩。
  • tombstones: 用于标记被删除的数据。由于 Block 不可变,删除操作不会立即擦除数据,而是写入 tombstone 文件。在压缩(Compaction)时,被标记的数据才会被真正清理。

3.4 内存映射 (mmap):加速查询的关键技术

Prometheus TSDB 大量使用 mmap 系统调用来加载 Block 中的 index 和 chunks 文件。mmap 将磁盘文件直接映射到进程的虚拟地址空间,使得访问文件如同访问内存一样。

方面 mmap 的优势 对内存/磁盘 I/O 的影响
内存占用 只有被访问到的页面才会被加载到物理内存(按需分页)。 内存: 有效利用内存,不浪费空间加载不常用的数据。进程的 RSS 可能远小于映射的文件大小。
I/O 效率 避免了用户态和内核态之间的数据拷贝(read/write 系统调用需要)。 磁盘 I/O: 减少 CPU 开销和内存带宽占用。对于大文件索引的随机访问,性能提升显著。
共享内存 多个进程可以共享同一个 mmap 映射,节省内存。 内存: 在 Prometheus 多副本或 sidecar 模式下,理论上可以共享 Block 数据。
Page Cache mmap 依赖操作系统 Page Cache 进行缓存。 内存: 与 WAL 共享 Page Cache。当系统内存不足时,内核会回收 Page Cache,可能导致后续访问产生缺页中断和磁盘 I/O。
💡 优化建议: 监控 prometheus_tsdb_mmap_files 和 process_resident_memory_bytes。如果发现 RSS 持续增长且接近物理内存上限,说明活跃的 Block 过多或查询模式导致大量 mmap 页面常驻。此时应考虑增加内存、调整数据保留时间(Retention)或优化查询以减少数据扫描范围。

3.5 对内存和磁盘 I/O 的综合影响

TSDB 的各个组件协同工作,对系统资源产生了特定的影响模式。理解这些模式是进行性能瓶颈分析和资源规划的基础。

  • 内存:
    • Head 内存: 主要消耗者,存储最近的数据和索引。大小与活跃序列数和样本采集频率成正比。
    • mmap 页面: 用于访问磁盘上的 Block 索引和数据。常驻内存的页面数取决于查询频率和数据访问局部性。
    • Page Cache: 用于缓冲 WAL 写入和 Block 读取。是操作系统动态管理的资源。
  • 磁盘 I/O:
    • 写入: 主要是 WAL 的顺序追加写入和 Block 刷盘时的批量写入。写入吞吐量相对稳定,与采集量线性相关。
    • 读取: 主要是查询时对 Block 中 index 和 chunks 文件的随机读取。读取模式是突发性的,取决于查询频率和复杂度。
    • 压缩 (Compaction): 后台进程读取多个较小的 Block,合并、去重、删除 tombstone 数据,然后写入一个新的更大的 Block。这是一个典型的读-改-写操作,会产生大量的临时 I/O。
关键指标速查表:
指标 含义 优化方向
prometheus_tsdb_head_series Head 中的活跃序列数 减少序列基数(Cardinality)
prometheus_tsdb_head_chunks Head 中的 Chunk 数量 调整 scrape_interval 或 chunk_range
prometheus_tsdb_compactions_total 压缩操作总数 监控压缩延迟,避免 I/O 瓶颈
rate(prometheus_tsdb_wal_writes_total[5m]) WAL 写入速率 评估写入吞吐量,规划磁盘性能