Skip to content
Charles Shao
Go back

时序数据库 InfluxDB 深挖 · 数据模型、TSM 存储引擎与降采样保留

views

广告系统上线后,除了把曝光/点击/转化这些业务事件灌进 Kafka 再进 OLAP 出报表,还有另一条被忽视但同样致命的数据流:系统自己的运行指标——每个 bidder 的 QPS、P99 延迟、竞价成功率、超时率、每秒烧掉多少钱。这些指标有一个共同特征:按时间戳持续写入、几乎不改、查询总带时间范围。用 MySQL 存会被写垮,用 ClickHouse 又偏重——这正是**时序数据库(TSDB)**的主场,而 InfluxDB 是这条赛道上最有代表性的一块积木。

这篇把 InfluxDB 从数据模型讲到存储引擎再到版本演进:为什么它敢在单机上扛住百万级 metric/s、series cardinality 为什么是新手第一个踩的坑、TSM 引擎怎么用 LSM 思路对付「写多」、以及 1.x→2.x→3.x 这一路从自研列存到 Apache Arrow 生态的大转身。

一句话定位:InfluxDB 把「按时间追加、按时间范围聚合」这条访问模式吃到极致——Line Protocol 极简写入、TSM 引擎顺序落盘、保留策略自动过期、降采样把原始高频数据压成看板能秒开的粗粒度序列。它擅长的不是「存事件明细做 join」,而是「存指标序列做趋势」。

TL;DR

Table of contents

Open Table of contents

1. 先分清:什么数据才配叫「时序」

不是「带了个时间字段」就是时序数据。真正的时序负载有三个共同特征,正是这三条决定了该不该上 TSDB:

  1. 写远多于读,且写是持续洪流:一个中等规模广告集群,几百台机器 × 每台几十个指标 × 每 10 秒一个点,轻松几十万 point/s。写入必须是主路径优化的对象。
  2. 几乎只 append,不 update2026-08-05 14:30:00 的 bidder-07 QPS = 8123 这个事实写下就永不修改。没有「改历史某个点」的需求——这让存储可以放弃就地更新,全用不可变文件 + 合并。
  3. 查询总带时间范围 + 聚合:没人问「所有时间里 QPS 等于 8123 的点」,只会问「过去 1 小时各 bidder 的 P99 延迟趋势」。时间是天然的第一分区维度。

对照一下就知道通用库为什么不合适:

负载特征OLTP(MySQL/InnoDB)缓存(Redis)TSDB(InfluxDB)
访问模式按主键点查 + 事务改行KV 点查按时间范围扫 + 聚合
写入形态增删改混合覆盖写纯 append
数据生命周期长期保留、手动清理TTL 淘汰自动过期 + 降采样
索引重点B+ 树主键/二级索引哈希series + 时间

心智切换:把「一行对象」换成「一条随时间前进的序列(series)」。你不是在存记录,你是在给每一条 series 的时间轴上不断钉点。理解了 series,就理解了 InfluxDB 的一切。

用 InnoDB 存指标的经典翻车:时间戳做主键 → 海量顺序插入把 B+ 树右端写成热点,二级索引膨胀,几周后磁盘和查询双双崩溃。这正是 InnoDB B+ 树结构 面对「纯追加超高写入」的不适配。

2. 数据模型:measurement / tag / field / timestamp

InfluxDB 的一条数据点由四部分组成,理解它们的索引差异是用好(不用炸)InfluxDB 的关键。

2.1 Line Protocol:极简到不需要建表

写入用的是纯文本的 Line Protocol,一行一个点:

bidder_metrics,host=bidder-07,region=sg qps=8123i,p99=18.5,win_rate=0.42 1722866400000000000
│─────┬─────│ │────────┬────────│ │──────────┬──────────│ │────────┬────────│
 measurement       tag set              field set            timestamp(ns)

规则要点:

2.2 series:tag 组合唯一确定的那条时间轴

这是全篇最重要的概念。一条 seriesmeasurement + 完整 tag set + field key 唯一确定,它的「身份证」叫 series key

bidder_metrics,host=bidder-07,region=sg#qps
bidder_metrics,host=bidder-07,region=sg#p99
bidder_metrics,host=bidder-08,region=sg#qps

上面第一行和第三行只差一个 host,就是两条独立 series。InfluxDB 的存储、索引、内存占用全部以 series 为单位。 series 数量(cardinality)就是它的规模标尺。

InfluxDB 数据模型示意图。上半部分拆解一行 Line Protocol:measurement=bidder_metrics 用琥珀色标出、tag set(host=bidder-07,region=sg)用蓝色标出并注明「被索引、字符串」、field set(qps/p99/win_rate)用绿色标出并注明「不索引、实际测量值」、timestamp 用灰色标出注明「纳秒精度」。下半部分展示 tag 组合如何映射成多条 series:由 host×region 的不同取值组合 + field key 组合,派生出 bidder-07#qps、bidder-07#p99、bidder-08#qps 等多条独立 series,每条 series 是一条独立的时间轴。底部红色注解强调:series 数量 = cardinality,把高基数字段放进 tag 会导致 series 爆炸。

measurement 是表、tag 是被索引的维度、field 是不索引的测量值;tag 组合的笛卡尔积决定了 series 数量。

2.3 cardinality:新手第一个(也是最痛的)坑

series 数量 ≈ 各 tag 取值数量的笛卡尔积。看一个反面教材:

# 灾难写法:把 user_id / request_id 放进 tag
requests,host=web-01,user_id=8f3a...,request_id=abc123 latency=12.3

user_id 上千万、request_id 每次都不同——series 瞬间爆到千万甚至无上限。1.x/2.x 的 series 索引严重依赖内存(TSI 缓解但不根治),cardinality 一高:内存打爆、写入变慢、查询超时、OOM

黄金法则:

一句话记牢:「你会拿它 GROUP BY / WHERE 的,才配当 tag;其余全是 field。」 这条纪律违反了,再好的存储引擎也救不了你。

3. TSM 存储引擎:用 LSM 思路对付「写多」

InfluxDB 1.x/2.x 的核心是自研的 TSM(Time-Structured Merge Tree) 引擎,名字直接致敬 LSM-Tree(和 RocksDB/Cassandra 一个家族 的思路)。它的目标很纯粹:把随机写变成顺序写,把小文件合并成大文件。

写入路径分四个组件:

  1. WAL(Write-Ahead Log):写入先追加到 WAL 落盘,保证 crash 后不丢——和 MySQL redo log 同一套 WAL 哲学。
  2. Cache(内存):同一份数据同时进内存 Cache,供即时查询。
  3. TSM 文件(不可变):Cache 达到阈值就 snapshot 成一个只读的 TSM 文件,顺序写盘、按 series 组织成列式 block
  4. Compaction(后台合并):小 TSM 文件分级合并成更大文件,去重、重压缩、丢弃过期数据。

TSM 存储引擎写入路径示意图。左侧为持续涌入的写入(Line Protocol points)。中间上方分两路:一路写入 WAL(预写日志,落盘保证持久化,用绿色标注 fsync durability),一路进内存 Cache(用蓝色标注可即时查询)。Cache 达阈值后向下箭头 snapshot 成不可变的 TSM 文件(用紫色多个文件块表示,标注列式 block + 时间戳 delta/zigzag + 压缩)。底部 Compaction 层用琥珀色表示后台把多个小 TSM 文件分级合并成大文件,标注去重/重压缩/丢弃过期。右侧 TSI 面板表示落盘的倒排索引(series → 文件位置),标注突破内存 cardinality 限制。整体自左向右、自上而下的数据流,配红色注解:查询同时读 Cache + TSM 文件再合并。

写先进 WAL + Cache,Cache 满了固化成不可变 TSM 文件,后台 Compaction 分级合并——典型 LSM 套路,把随机写摊平成顺序写。

3.1 TSM 文件内部:列存 + 时间戳专属压缩

一个 TSM 文件里,数据按 series 分块,每块内部时间戳和值分开、各自列式存储——这点和 OLAP 列存 同源。压缩因此可以针对性极强:

这套「按数据类型选专属编码」的思路,和 ClickHouse 的 Delta/DoubleDelta/Gorilla codec 是同一份工程直觉:同列同类型相邻 → 压缩比拉满。指标数据压到原始体积的几十分之一很常见。

3.2 shard 与保留:时间就是天然分区

InfluxDB 按时间把数据切成 shard(默认按 shard group duration,如 7 天一个)。好处:

3.3 TSI:把 series 索引搬下盘

早期 1.x 的 series 索引全在内存(in-memory index),cardinality 一高就 OOM。TSI(Time Series Index) 把倒排索引(tag → seriesseries → 文件位置)落盘 + mmap,靠操作系统页缓存按需加载,把可支撑的 cardinality 抬高了一个量级。但要强调:TSI 是缓解,不是免死金牌——把 request_id 塞 tag 该炸还是炸。根治要等 3.x。

4. 查询、保留策略与降采样

4.1 InfluxQL vs Flux:两代查询语言

SELECT mean("p99") FROM "bidder_metrics"
WHERE "region" = 'sg' AND time > now() - 1h
GROUP BY time(1m), "host"
from(bucket: "metrics")
  |> range(start: -1h)
  |> filter(fn: (r) => r._measurement == "bidder_metrics" and r.region == "sg")
  |> aggregateWindow(every: 1m, fn: mean)
  |> group(columns: ["host"])

Flux 是 2.x 的旗舰卖点,但社区反响两极——很多人只想写 SQL。这也埋下了 3.x 回归 SQL 的伏笔。

4.2 保留策略(RP)+ 降采样:TSDB 的产品化红利

这是 TSDB 相对通用库最香的能力。你不会想把 10 秒精度的原始点存 5 年——存储成本爆炸、看板也画不动。标准玩法是分级降采样 + 分级保留

精度保留时长用途
原始 10s7 天排障、看瞬时毛刺
降采样 5min90 天周/月趋势
降采样 1h2 年容量规划、年度对比

实现方式随版本:

保留策略与降采样分级示意图。左侧一条高频原始序列(10s 精度,密集的点),标注保留 7 天。中间一个降采样任务(Continuous Query / Task)把原始点按 5 分钟窗口聚合(mean/max),产生较稀疏的中频序列,标注保留 90 天。右侧再降采样成 1 小时精度的稀疏序列,标注保留 2 年。三级之间用箭头连接并标注 aggregateWindow / GROUP BY time()。底部注解强调:过期直接按 shard 删除文件(O(1)),原始高频只留短期、聚合粗粒度留长期,兼顾排障精度与长期成本。

原始高频只留短期供排障,聚合成粗粒度长期保存供趋势分析;到期按 shard 整块删除,成本可控。

落地经验:降采样要在写入侧就规划好三级精度,别等原始数据存满了才想起来。 一旦 cardinality 和数据量起来,补建降采样任务的回填(backfill)会非常痛苦。

5. 版本大转身:1.x → 2.x → 3.x(IOx)

InfluxDB 的版本演进不是小修小补,而是两次架构级重写,理解这条线才能做对选型。

5.1 1.x:经典 TSM 栈

TSM + TSI 引擎、InfluxQL、RP + CQ。稳定、文档多、Grafana 支持最成熟。至今仍是大量存量系统的主力。TICK 技术栈(Telegraf 采集 / InfluxDB 存储 / Chronograf 可视化 / Kapacitor 告警)就是这一代。

5.2 2.x:Flux + 一体化平台

5.3 3.x(InfluxDB 3 / IOx):Rust 重写的列存新引擎

这是最关键的一跳。InfluxDB 3 用 Rust 重写了引擎(代号 IOx),彻底换血:

维度1.x2.x3.x(IOx)
引擎TSM + TSITSM + TSIIOx(Rust)
存储本地 TSM 文件本地 TSM 文件Parquet + 对象存储
查询语言InfluxQLFlux(主推)SQL + InfluxQL
高基数易爆缓解基本解决
降采样RP + CQbucket + Task表 + SQL/任务

选型提醒:新项目直接评估 InfluxDB 3;存量 1.x 稳定跑着的别盲目迁移(Flux/查询语言、RP→bucket 的迁移成本不低)。3.x 的架构和它想解决的高基数问题,本质上是把 InfluxDB 从「自研 TSDB」拉进了「Arrow 生态的列存分析库」,和 OLAP 列存 的边界越来越模糊。

6. AdTech 落地:广告系统指标监控

回到开头的场景。广告链路里 InfluxDB 的典型位置是系统可观测性的指标存储

tag / field 的划分直接照搬第 2 节纪律:

bidder_metrics,host=bidder-07,region=sg,dc=ap-southeast-1 \
  qps=8123i,p99=18.5,win_rate=0.42,timeout_rate=0.003,spend=1240.5 1722866400000000000

host/region/dc 做 tag(低基数、要按它切看板),qps/p99/win_rate/spend 做 field。绝不request_idimp_iduser_id 放 tag。

6.1 InfluxDB vs Prometheus:一对常被拿来比的组合

广告监控里另一主流是 Prometheus。核心差异:

维度InfluxDBPrometheus
采集模型Push(客户端主动写)Pull(server 定时抓 /metrics
长期存储强(RP + 降采样,可存年)弱(默认本地短期,靠 Thanos/Cortex 远程存)
数据模型measurement/tag/fieldmetric name + labels(≈ tag)
查询InfluxQL/Flux/SQLPromQL
定位通用时序 + 长期存储云原生监控 + 告警一等公民

常见组合:Prometheus 抓实时监控告警,InfluxDB(或远程存储)扛长期趋势;或统一走 InfluxDB push 模型。两者都不是拿来做「按 imp_id 查单条明细」的——那是 OLAP 或 KV 的活。

6.2 什么时候别用 InfluxDB

7. 生产踩坑与经验清单

参考


views
Share this post on:

Next Post
IDFA 之后的归因:MMP、SKAdNetwork 与 Postback 结算