1.1 核心定义与 TSDB 特征


1.1 核心定义与 TSDB 特征

本节摘要时序数据库(TSDB) 面向按时间排序的数据点流,在存储布局、索引与查询语言上优化 append 写入与时间范围聚合。相对 OLTP,它把 PRIMARY KEY (metric, tags, time) 而非 (id) 当作组织主轴。本节用四大特征解释设计取舍,并对照四家主流产品。

学习目标

阅读完本节,你应当能够:

  1. 区分 series、sample、cardinality 三个术语
  2. 解释 TSDB 相对 MySQL 存 metrics 的两项架构差异
  3. 识别高基数 tag 反模式

一、问题与直觉

2010 年前后,工程师在 MySQL 单表日增千万级时间点时发现:INSERT 延迟飙升,按时间范围扫描索引碎片化,应用层不得不手写 GROUP BY FLOOR(UNIX_TIMESTAMP(time)/300) 做 5 分钟聚合——时间在关系模型里只是普通列,在业务里却是不可切分的主轴。TSDB 把「过程-状态(Process-State)」而非「实体-关系(E-R)」当作建模起点:一个 CPU 读数孤立无意义,嵌入毫秒时间戳与 hostregion 标签后才成为可聚合的信号。

把上面那句 SQL 展开看,会更清楚地意识到关系模型与时间轴的错位:

-- MySQL:用表达式拼 5 分钟桶,慢且难维护 SELECT CONCAT(DATE(ts),' ',HOUR(ts),':',FLOOR(MINUTE(ts)/5)*5) AS bucket, AVG(cpu_usage) FROM metrics WHERE host = 'web-01' AND ts >= NOW() - INTERVAL 1 DAY GROUP BY bucket;

这段查询的毛病不在写法本身,而在于:FLOOR(MINUTE(ts)/5) 这类表达式无法走常规索引,数据库只能先把一天的数据捞出来再在内存里分桶。当日写入量上到千万级时,这条查询会稳定吃掉几秒。TSDB 的替代做法是让存储层按时间组织数据,查询时直接定位「5 分钟块」再聚合,语义相同、执行路径完全不同。

二、核心原理

Series(时间序列) = 唯一 metric + 标签组合下的全部样本;Sample = (timestamp, value)

维度 OLTP(MySQL) TSDB
写入模式 随机 UPDATE/INSERT append 为主
典型查询 点查、JOIN 时间窗口聚合
主键逻辑 实体 id metric + tags + time
保留策略 长期全量 TTL、降采样

series 在存储中如何组织

理解 series 概念后,最好再想一层:一条 series 的样本在物理上如何存放?几乎所有现代 TSDB 的做法都类似——同一 series 的样本在内存与磁盘上都相邻排列,形成一个「按时间有序的样本数组」。

series = cpu_usage{host=web-01, region=cn} samples = [(t0,0.72),(t1,0.73),(t2,0.71),(t3,0.74),...]

这样组织带来两个好处:查询某 series 的时间段,只需顺序扫它的样本数组,天然适合顺序 IO 与向量化;压缩时同一 series 的相邻样本时间间隔稳定、数值平滑,delta-of-delta 与 Gorilla 才有发挥空间。反观 MySQL,同一 host 的数据分散在 B+ 树各处,既要回表又要随机读。

四大特征

高写入密度:Kubernetes 集群每 15s scrape 一次,单 Prometheus 实例可管理数百万 active series。

强时间局部性:最近 1 小时查询远多于 3 年前;TSM、hypertable chunk、QuestDB 分区都利用这一点。

低更新频率:点写入后极少改值;纠错常靠重写块而非行级 UPDATE。

高维标签化cpu_usage{host,region,service} 构成多维立方体;标签组合爆炸即 cardinality 问题——InfluxData 2023 报告指出,标签基数超 50 万时 P99 查询延迟平均升高 3.7 倍。

产品 序列标识方式 时间精度
Prometheus metric name + labels 毫秒
InfluxDB measurement + tag set 纳秒
TimescaleDB 表行 + 时间列 timestamptz
QuestDB 表 + designated timestamp 微秒/纳秒
# Prometheus exposition(文本格式) # metric{labels} value timestamp_ms cpu_usage{host="web-01",region="cn"} 0.73 1717023456789 # Influx line protocol # measurement,tag=v field=v timestamp_ns cpu,host=web-01,region=cn usage=0.73 1717023456789000000

cardinality 的放大机制

cardinality 为什么是「第一杀手」而不是磁盘?因为 series 数决定三样东西同时膨胀:内存里的 series 表、标签倒排索引的 posting list、查询时需要展开的样本组数。举个例子:

host(10) × pod(20) × status(5) = 1000 series / metric 再乘 200 个 metric = 20 万 series

如果误把 pod_uid 当 label,pod 维度从 20 变成 20 万,series 数直接放大一万倍。所以治理基数永远排在扩容前面。

三、工程实践要点

场景 推荐 避免
容器监控 Prometheus + Remote Write 把 pod_uid 作高基数 label
多字段 IoT 批次 Influx line protocol user_id 作 tag
已有 PG 生态 TimescaleDB hypertable 用 TSDB 做订单事务
低延迟分析 SQL QuestDB 在 TSDB 里跑复杂 JOIN

一个反模式排查示例

假设你发现一个集群的 series 数一周内翻了三倍,如何定位元凶?可以这样排查:先用 topk(10, count by (__name__)({__name__=~".+"})) 之类查询找出 series 最多的 metric,再对它按各 label 分组统计基数,定位到是哪个 label 在爆炸。实战中这类问题多半来自把 request_idtrace_iduser_id 这类随请求变化的唯一值放进了 label——它们在语义上是「事件属性」,不是「可聚合维度」。

⚠️ 常见坑:把 user_idrequest_id 放进 tag——series 数爆炸,倒排索引与内存撑满。

💡 关键直觉:tag 是「你用什么 GROUP BY」;field 是「你测什么数」。Prometheus 无 field 概念,多值需拆 metric 或用不同 label。

自测题

  1. cpu_usage{host="web-01",region="cn"} 是不是一条 series?再叠加 code="200" 呢?
  2. 为什么「按时间范围扫描」在 MySQL 里会慢,在 TSDB 里却快?
  3. user_id 放进 tag 会带来哪三类成本?至少说出两类。

一节小结

  • TSDB:为时间序列 append + 范围聚合特化
  • 四大特征:高写、时间局部、少更新、多标签
  • vs OLTP:时间是一等维度,非普通列
  • Cardinality:标签组合数是第一运维风险
  • 四家差异:Prometheus 拉取、Influx push+TSM、Timescale SQL、QuestDB 列式

下一节梳理从 OpenTSDB 到云原生四大产品的演进脉络。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U