3. TSDB 存储引擎详解
Prometheus 的时序数据库(TSDB)是其核心组件,负责高效地存储和查询海量时间序列数据。理解 TSDB 的写入路径、内存映射、WAL 以及 Block 结构,对于进行资源配额和性能优化至关重要。本章将深入剖析这些机制及其对内存和磁盘 I/O 的影响。
3.1 写入路径:从样本到持久化
一个样本数据从被接收至最终落盘,经历了一条精心设计的管道。这条路径在保证写入吞吐量的同时,也兼顾了数据的一致性和恢复能力。
核心目标:在内存中快速聚合写入,通过 WAL 保证数据不丢失,最终异步刷盘形成不可变的 Block。
- 接收样本:Appender 接口接收来自 scrape 或 remote write 的样本。
- 写入 Head (内存):样本首先被写入内存中的 Head 模块。Head 维护了最新的数据,并包含一个内存中的倒排索引,用于快速标签匹配。
- 记录 WAL:在写入 Head 的同时,样本会被序列化并追加到预写日志(WAL)中。这是崩溃恢复的关键。
- 内存压缩 (MemSeries):Head 内部,每个时间序列(Series)的数据被组织成一系列内存中的 Chunk。当 Chunk 填满(默认约 120 个样本)后,会被标记为“已满”,并准备刷盘。
- 刷盘为 Block:后台的压缩器定期将 Head 中已满的 Chunk 以及对应的索引数据,写入磁盘,形成一个不可变的 Block。
- WAL 截断:一旦数据被安全地写入 Block,对应的 WAL 段可以被安全地删除或截断,以回收磁盘空间。
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 的不可变性是其高性能查询的关键。
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 写入速率 | 评估写入吞吐量,规划磁盘性能 |