跳到主要内容

时序数据库原理

问题

时序数据库的核心原理是什么?它如何实现高写入和高压缩?

答案

一、时序数据模型

时序数据由以下三个要素组成:

Metric(度量名)+ Tags(标签/维度) + Fields(值) + Timestamp(时间戳)

示例:
cpu_usage, host=server1, region=us-east | value=85.3 | 2024-01-15T10:30:00Z
cpu_usage, host=server2, region=us-west | value=72.1 | 2024-01-15T10:30:00Z
要素说明示例
Metric测量指标名称cpu_usagehttp_requests_total
Tags维度标签(用于过滤和分组)host=server1region=us-east
Fields实际测量值value=85.3count=1024
Timestamp数据采集时间2024-01-15T10:30:00Z
Series(时间线)

Metric + Tags 的唯一组合构成一个 Series(时间线)。例如 cpu_usage{host=server1, region=us-east} 就是一条时间线。

时序数据库的核心挑战之一是管理海量的时间线(Series Cardinality)。

二、存储引擎

时序数据库通常使用基于 LSM-Tree 的变体作为存储引擎,针对时序特征做了大量优化。

TSM(Time-Structured Merge Tree)

TSM 是 InfluxDB 使用的存储引擎,本质是 LSM-Tree 的时序优化版:

写入流程

  1. WAL:写入先追加到 WAL 文件(保证持久性)
  2. Cache:数据缓存在内存中(按 Series 分组排序)
  3. Flush:内存达到阈值后,刷写到磁盘生成 TSM 文件
  4. Compaction:后台合并小文件,去重,压缩

为什么写入快?

  • 追加写:时序数据天然按时间递增,所有写入都是 Append-Only
  • 批量写:通常 SDK 会攒批(batch)后一次写入
  • 无更新:时序数据极少修改,避免了 Read-Modify-Write

三、列式存储

传统行式存储按行存储数据,时序数据库按存储:

行式存储(MySQL):
Row1: [timestamp, host, cpu, memory]
Row2: [timestamp, host, cpu, memory]

列式存储(TSDB):
timestamp列: [t1, t2, t3, t4, ...]
cpu列: [85.3, 72.1, 90.5, ...]
memory列: [4096, 8192, 4096, ...]

列式存储的优势:

  1. 高压缩比:同一列的数据类型相同,压缩率极高
  2. 快速聚合SUM(cpu) 只需读取 cpu 列,不读其他列
  3. 按需读取:查询只读取需要的列

四、数据压缩

时序数据有很强的时间局部性(相邻时间点的值变化不大),可以实现极高的压缩比。

时间戳压缩:Delta-of-Delta

原始时间戳:   1000, 1010, 1020, 1030, 1040
Delta: -, 10, 10, 10, 10
Delta-of-Delta: -, -, 0, 0, 0

等间隔采集的时间戳,Delta-of-Delta 几乎全是 0,只需要 1 bit 编码。

浮点数压缩:XOR 编码(Gorilla 压缩)

相邻值:85.3, 85.5, 85.2, 85.4
XOR: 很多前导零/尾部零
→ 只存储有效位,通常每个值只需 1~2 字节

Facebook 的 Gorilla 论文提出了 XOR 编码,利用相邻浮点数的二进制表示高度相似的特性,压缩比可达 10:1 ~ 20:1

压缩效果对比

数据类型原始大小压缩后压缩比
时间戳(10s 间隔)8B~0.1B80:1
浮点数(缓慢变化)8B~1B8:1
整数(计数器)8B~2B4:1
字符串(标签)变长字典编码5~10:1

五、降采样与数据保留

时序数据量随时间快速增长。降采样(Downsampling)将高精度数据聚合为低精度数据,减少存储开销。

数据保留策略示例:
- 原始数据(1s 粒度):保留 7 天
- 1 分钟聚合:保留 30 天
- 1 小时聚合:保留 1 年
- 1 天聚合:保留 3 年

这种分层保留策略能将存储成本降低 90% 以上

六、时序查询特点

-- 典型时序查询:过去 1 小时的平均 CPU 使用率,按主机分组
SELECT
time_bucket('5 minutes', time) AS bucket,
host,
AVG(cpu_usage) AS avg_cpu,
MAX(cpu_usage) AS max_cpu
FROM metrics
WHERE time > NOW() - INTERVAL '1 hour'
AND region = 'us-east'
GROUP BY bucket, host
ORDER BY bucket DESC;

时序查询的共同特征:

  1. 时间范围过滤:几乎所有查询都带时间范围
  2. 聚合函数:AVG、MAX、MIN、SUM、COUNT、PERCENTILE
  3. 分组:按时间窗口和标签维度分组
  4. 最新数据优先:大部分查询关注最近的数据

常见面试问题

Q1: 时序数据库为什么比 MySQL 快?

答案

针对时序数据的四个核心优化:

  1. 追加写入:数据按时间递增,全部是 Append-Only,无随机写
  2. 列式存储:聚合查询只读需要的列
  3. 极致压缩:Delta-of-Delta + XOR 编码,压缩比 10~20 倍
  4. 时间分区:数据按时间分区,查询只扫描相关时间段

MySQL 是通用型 OLTP 数据库,为随机读写优化;TSDB 为顺序写入 + 范围聚合查询优化。

Q2: 什么是 Series Cardinality(时间线基数)问题?

答案

每个唯一的 Metric + Tags 组合对应一条时间线。如果标签值的组合过多,时间线数量会爆炸。

例如:http_requests{host, path, status_code, user_id}
如果 user_id 有 100 万个不同值 → 100 万条时间线

时间线过多会导致:

  • 内存索引膨胀(每条时间线需要索引)
  • 查询性能急剧下降
  • 这就是「高基数」问题

解决:避免将高基数值(如 user_id、trace_id)作为 Tag,改为 Field 或使用其他存储。

Q3: 时序数据库的数据保留策略如何设计?

答案

三层策略:

层级粒度保留时间用途
原始数据秒级7~14 天实时告警、精细排查
分钟级聚合1~5 分钟30~90 天趋势分析、日报
小时级聚合1 小时1~3 年长期趋势、容量规划

降采样时保留 AVG、MAX、MIN、COUNT 等聚合值,满足不同分析需求。

Q4: Prometheus 和 InfluxDB 的区别?

答案

对比PrometheusInfluxDB
数据采集Pull 模式(主动拉取)Push 模式(被动接收)
查询语言PromQLInfluxQL / Flux
适合场景基础设施监控通用时序数据
集群Thanos/Cortex(第三方)企业版原生支持
长期存储需要外部方案原生支持
生态Grafana + AlertManagerTelegraf + Grafana

Prometheus 更专注于监控告警,InfluxDB 更通用。

相关链接