InfluxDB 到底在存什么?从 Measurement、Tag、Field 到 Series

Kaku Lv4

假设要做一套服务器监控,每隔 10 秒记录一次 CPU 使用率:

1
2
3
4
5
时间:2026-08-26 17:50:00
主机:web-01
机房:cn-north
用户态使用率:23.4
系统态使用率:8.1

放进 MySQL 没什么障碍:建表、插入、给时间和主机名加索引。但监控系统接下来会问:

  • web-01 最近一小时的 CPU 使用率怎么变化?
  • 每台主机每 5 分钟的平均值是多少?
  • 哪些主机刚刚出现异常尖峰?
  • 30 天前的秒级数据是否还要保留?

这些数据总在沿时间范围被读取、分组和聚合。InfluxDB 的数据模型也围绕这种访问方式展开。

InfluxDB 3 同样有表和列,也能用 SQL 查询。只看一行数据,它和普通关系表很像;把时间范围查询、持续写入、窗口聚合和数据保留放在一起,设计取向才显出来。关系型数据库需要照顾事务更新和复杂关联,时序数据库首先照顾一连串观测值。

可以把每次采集理解成一句话:某个对象,在某个时间点,产生了一组测量结果。后面的概念都在给这句话里的成分命名。

这一篇先回答一个基础问题:

一条监控数据写进 InfluxDB 后,到底变成了什么?

本文以 InfluxDB 3 Core 为主。InfluxDB 1.x、2.x 和 3.x 的存储容器、查询语言与部分实现不同,涉及版本差异时会单独说明。

一条观测数据,就是一个 Point

前面的 CPU 数据写成 Line Protocol,大致是这样:

1
cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1 <timestamp>

先不管语法,按内容拆开:

1
2
3
4
5
6
cpu
├── host=web-01
├── region=cn-north
├── usage_user=23.4
├── usage_system=8.1
└── timestamp

这是一个 Point:某个对象在某个时间点产生的一组测量结果。它有四个部分:

部分这个例子里的值回答的问题
Measurement / Tablecpu测量什么
Tag Sethost=web-01,region=cn-north数据来自哪里、属于谁
Field Setusage_user=23.4,usage_system=8.1测到了什么值
Timestamp<timestamp>什么时候发生

Point 会被写进某个 Database。Database 是外层逻辑容器,由写入请求指定,不出现在这行 Line Protocol 中。

1
2
3
4
5
6
Database
└── Table / Measurement
└── Point
├── Tag Set
├── Field Set
└── Timestamp

这个层次和“库、表、行、列”有相似之处,不能完全按关系表套用。Tag 会参与 Series 和主键的构成,Field 则保存观测值;两者在表里看着都是列,设计职责并不相同。

Measurement / Table:测量什么

Line Protocol 把 cpu 称为 Measurement。InfluxDB 3 的数据模型和 SQL 文档更常称它为 Table

1
SELECT * FROM cpu;

名称用来描述测量对象,cpumemorydisk_ioweatherhttp_request 都很自然。主机归属交给 Tag,不必按来源拆表:

1
2
3
cpu,host=web-01 ...
cpu,host=web-02 ...
cpu,host=web-03 ...

cpu_web_01cpu_web_02 会把同类数据拆散。主机一多,查询所有机器就得访问一批结构相同的表,新增机器还会继续制造表。

cpu_2026_08_26 这类名字也在重复 Timestamp 已经承担的工作。日期变化时,连续的时间线被人为切开,跨日期查询平白多出一层表名处理。

Timestamp:落在时间轴的哪里

Timestamp 是 Unix 时间戳。写入可以采用秒、毫秒、微秒或纳秒精度,客户端与服务端必须使用同一种解释。

例如 1724659200,按秒解释是正常日期,按纳秒解释会落到 1970 年附近。写入可能照样成功,只是查询最近一小时找不到它。

省略 Timestamp 时,InfluxDB 使用服务器接收数据时的当前 UTC 时间。手工测试可以这样做,采集系统则应优先提交事件实际发生的时间。传感器断网后补写时,接收时间和事件时间往往差得很远。

时间精度应在写入接口、客户端配置和实际数值之间统一。排查“写成功但查不到”时,先扩大时间范围并检查时间戳精度,通常比反复改 SQL 更有效。

Tag 和 Field 怎么分

这两个概念最容易把 Schema 带偏。

host=web-01region=cn-north 是 Tag。它们描述 Point 的上下文,常用于定位、筛选和分组。主机名、机房、服务名、环境名、传感器编号通常都从这里考虑。

例如“只看 web-01”“比较各区域”“按服务分组”都要依赖 Tag:

1
2
3
4
SELECT time, usage_user
FROM cpu
WHERE host = 'web-01'
AND time >= now() - INTERVAL '1 hour';

usage_user=23.4usage_system=8.1 是 Field。它们保存随时间变化的测量值,通常会参与平均值、最大值、分位数等计算。温度、请求耗时、队列长度、检查结果也属于这类候选值。

一个 Point 至少要有一个 Field,也可以同时写多个 Field。温度和湿度来自同一个传感器、发生在同一时间,放在一个 Point 里就很合适:

1
weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i <timestamp>

InfluxDB 3 会把 Field 表现为有类型的表列。Line Protocol 支持浮点数、有符号整数、无符号整数、字符串和布尔值;同一列的类型应保持一致。

Tag 的值也是字符串,但它在模型中承担另一种职责。在 InfluxDB 3 Core 里,Tag 列和时间列共同构成表的主键,Tag 的顺序还会成为数据的物理排序依据。把一列设为 Tag,会同时影响数据组织和查询。

判断时先问它会怎样被使用:

数据更可能是什么原因
host=web-01Tag经常按主机筛选或分组
region=cn-northTag经常比较不同区域
temperature=26.5Field随时间变化的测量值
response_time=83Field用来计算平均值、分位数或最大值
environment=productionTag常用作过滤条件
一大段错误堆栈Field,或者放进日志系统不适合进入主键,也未必适合时序数据库

status 没有固定答案。经常查“所有 offline 设备”,它适合做 Tag;只想保留每次检查结果,很少按状态筛选,做 Field 更省事。

数据是字符串还是数字,只能决定它能否以某种类型存储,不能替你决定 Tag 和 Field。

一个实用的检查方法是把候选列放进查询里试读:它经常出现在 WHEREGROUP BY 中,Tag 的理由就比较充分;主要出现在 AVGMAXSUM 等计算中,通常留作 Field。两边都用得到时,再结合主键宽度、基数和实际查询频率做取舍。

Series、重复点和 Cardinality

先省略 Field 与 Timestamp,看几条写入:

1
2
3
4
5
cpu,host=web-01,region=cn-north ...
cpu,host=web-01,region=cn-north ...
cpu,host=web-02,region=cn-north ...
cpu,host=web-01,region=cn-south ...
memory,host=web-01,region=cn-north ...

Measurement 与完整 Tag Set 相同的 Point 属于同一条 Series。所以上面有四条:

1
2
3
4
cpu,host=web-01,region=cn-north
cpu,host=web-02,region=cn-north
cpu,host=web-01,region=cn-south
memory,host=web-01,region=cn-north

开头两条 web-01 数据的 Measurement 和 Tag Set 完全相同,只是时间与 Field Value 不同,因此落在同一条 Series。web-02cn-southmemory 任意一项发生变化,都会换到另一条 Series。

Field 不参与 Series 身份计算。下面两条数据记录了不同的 Field,仍在同一条 Series 上:

1
2
cpu,host=web-01 usage_user=23.4 <timestamp-1>
cpu,host=web-01 usage_system=8.1 <timestamp-2>
1
2
3
4
5
cpu,host=web-01,region=cn-north

time ──────────────────────────────────────────────>
● ● ● ●
17:50 17:50:10 17:50:20 17:50:30

每个圆点是一个 Point;Measurement 和 Tag Set 确定它在哪条线上,Timestamp 确定它在线上的位置。

相同时间写两次会怎样

在 InfluxDB 3 Core 中,一个 Point 的逻辑身份可以写成:

1
Table + 完整 Tag Set + Timestamp

Field Set 携带数值,不负责唯一性。因此,增加一个 Field 不能制造“新版本”。

例如下面两次写入拥有相同的 Table、Tag Set 和 Timestamp,只是 usage_user 冲突:

1
2
cpu,host=web-01 usage_user=23.4 <same-timestamp>
cpu,host=web-01 usage_user=28.1 <same-timestamp>

重复写入中不重名的 Field 会按并集合并;麻烦出在同名 Field。InfluxDB 2.x 的 TSM 存储引擎对这类冲突有明确的覆盖规则,InfluxDB 3 Core 则不同:官方文档特别提醒,冲突值的覆盖结果不确定,查询可能看到任一版本,最终持久化哪一个也没有保证。

工程上应把 InfluxDB 3 Core 当作追加写入系统,别用重复点模拟可靠的 UPDATEUPSERT

  • 写入真实事件时间;
  • 两次事件都要保留时,使用不同的 Timestamp;
  • 重试前弄清重复写入会不会撞上相同主键。

如果业务确实要表达修订记录,应给修订事件独立的时间或重新设计可区分它们的主键。往 Field 里增加 revision=2 没用,因为 Field 不参与 Point 的身份计算。

Cardinality 数的是 Tag 组合

Series Cardinality 可以粗略理解为系统中不同 Measurement 与 Tag Set 组合的数量。

假设 cpu 有两个 Tag:

1
2
host   = 1000 种可能值
region = 5 种可能值

如果每台主机只属于一个区域,实际 Cardinality 大约是 1000,不是把两列取值数相乘得到 5000。它统计已经出现的组合。加入 environment 后,同一主机分别出现在生产和测试环境,才会继续产生新 Series。

所以要估算的是 Tag 将形成多少种组合,单看 Tag 列数没有用。

这个区别在评审 Schema 时很实用。region 有 5 个值,不代表它必然把 Series 数量放大 5 倍;每台主机固定属于一个区域时,hostregion 存在关联。反过来,如果每个请求都产生一个全新的 request_id,即使只有这一个 Tag,Series 也可能按请求数增长。

InfluxDB 2 和 3 对高基数的态度不同

InfluxDB 1.x、2.x 的 TSM/TSI 架构中,Series 索引会消耗大量资源。UUID、请求 ID、用户 ID 这类高变化值进入 Tag 后,可能快速推高 Cardinality,增加内存占用并拖慢读写。“高基数值放 Field”对 InfluxDB 2.x 仍是有现实意义的经验。

InfluxDB 3 使用了不同的存储和查询架构,官方明确把高 Series Cardinality 列为可处理场景。设备 ID、传感器 ID,甚至某些请求 ID 或跟踪 ID,如果确实需要用来查询,可以设计成 Tag。

“确实需要”指的是明确的查询路径,例如经常按 device_id 拉取单台设备的历史,或按 trace_id 定位一次调用。只是为了把原始数据完整搬进来,不足以支持这个决定。

但不能因此把每一列都改成 Tag。成本依然摆在那里:

  • Tag 越多,主键越宽;
  • 首次写入时的 Tag 顺序会影响 InfluxDB 3 的物理排序;
  • 长随机字符串仍需存储和比较;
  • 大量稀疏列会增加表的管理成本;
  • 从不用于筛选和分组的值,放进 Tag 没有查询收益。

InfluxDB 2 需要格外警惕 Series Cardinality;InfluxDB 3 放宽了这项约束,但 Tag 依然要服务查询。

先写查询问题,再写 Schema

照着现有 JSON 原样落库很省事:字符串做 Tag,数字做 Field,写入成功就算完成。麻烦会留给查询。

假设采集程序拿到:

1
2
3
4
5
6
7
{
"host": "web-01",
"region": "cn-north",
"usage_user": 23.4,
"usage_system": 8.1,
"collector_version": "1.7.0"
}

先列出准备回答的问题:

  • 是否需要按 host 查看趋势?
  • 是否需要比较不同 region
  • 是否会按采集器版本筛选故障主机?
  • usage_userusage_system 要做哪些聚合?

前两个查询经常出现,hostregion 就适合做 Tag。usage_userusage_system 是聚合对象,做 Field。

collector_version 取决于使用方式。只用于偶尔展示,可以做 String Field:

1
cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1,collector_version="1.7.0" <timestamp>

经常需要筛选“所有 1.7.0 版本的采集器”,则可以放进 Tag:

1
cpu,host=web-01,region=cn-north,collector_version=1.7.0 usage_user=23.4,usage_system=8.1 <timestamp>

同一份原始数据,由于查询意图不同,会得到不同的 Schema。

这也是 Schema 需要尽早讨论的原因。数据写进去以后再把 Field 改成 Tag,往往涉及新列、新写入格式和历史数据迁移,远不止给列换一个标签。

文章开头那句“过去一小时,每台主机每 5 分钟的平均 CPU 用户态使用率”,已经把四类信息都点出来了:

1
2
3
4
Table       → cpu
Timestamp → 过去一小时,每 5 分钟一个窗口
Tag → 按 host 分组
Field → 对 usage_user 求平均值

对应的 SQL 大致如下:

1
2
3
4
5
6
7
8
9
10
SELECT
DATE_BIN(INTERVAL '5 minutes', time) AS window_start,
host,
AVG(usage_user) AS avg_usage_user
FROM cpu
WHERE
time >= now() - INTERVAL '1 hour'
AND time <= now()
GROUP BY window_start, host
ORDER BY host, window_start;

这条 SQL 也能反过来审查设计:若 host 当初只是 String Field,按主机筛选和分组就没有利用 Tag 的数据组织方式;若 usage_user 被设计成 Tag,数值聚合的职责又放错了位置。

写入侧也随之明确:采集程序提交真实事件时间,高频 Point 批量发送,Field 类型尽早稳定;重试不能默认等同于一次幂等更新。

1
2
3
4
17:50:00  CPU 使用率 23.4%
17:50:10 CPU 使用率 28.1%
17:50:20 CPU 使用率 65.7%
17:50:30 CPU 使用率 31.2%

这条时间线记录的是变化过程。新 Point 通常接在后面,旧 Point 仍然保留;保留多久、是否降采样,再交给数据生命周期策略处理。

从查询反推还有一层好处:它会暴露选库问题。复杂事务、频繁多表关联适合留在关系型数据库,大段日志的全文检索适合日志系统。数据带时间,只说明它有时间属性,并不能自动说明它适合 InfluxDB。

把这条数据读完整

1
cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1 <timestamp>

cpu 是 Measurement,在 InfluxDB 3 中对应 Table;hostregion 是 Tag;两个使用率是 Field;Timestamp 给出观测发生时间。Measurement 与 Tag Set 相同的 Point 连成 Series,完整 Tag Set 与 Timestamp 又确定单个 Point 的逻辑身份。

1
2
3
4
Point = Measurement + Tag Set + Field Set + Timestamp
相同 Measurement 与 Tag Set 的 Point → Series
时间范围和 Tag → 找到数据
Field → 参与聚合

Schema 不该从 JSON 的数据类型照抄出来。先写出系统要回答的问题,再决定哪些上下文值得进入 Tag、哪些测量值留在 Field,这套模型才会在查询时顺手。

下一篇继续拆 Line Protocol,并把数据真正写进 InfluxDB 3 Core。

参考资料

  • 标题: InfluxDB 到底在存什么?从 Measurement、Tag、Field 到 Series
  • 作者: Kaku
  • 创建于 : 2026-08-26 17:50:00
  • 更新于 : 2026-08-26 18:33:46
  • 链接: https://www.kakunet.top/2026/08/26/InfluxDB-到底在存什么?从-Measurement、Tag、Field-到-Series/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论