在学习关系数据库的时候,很多教材都会提供一些经典的例子,比如如何为学生建表,管理他们学习的科目、成绩、综合表现等,先从逻辑上分解定义库、表,同时通过主键外键构建起表与表之间的关系,然后转换成具体的 SQL 语句,最后通过各类关系运算实现数据的增删改查。
但是在使用时序数据库(Time Series Database,TSDB)的时候,我们首先要面对的一个问题就是如何建立数据模型(Data Model),也就是如何将业务逻辑中的实体数据具体映射到所选的时序库。
而且出于对读写性能、压缩效率等目标的权衡,不同的时序数据库产品有可能会选择不同的模型,这时候就要仔细分析了。本文将以 InfluxDB 和 TDengine 为例,对比学习一下它们的数据模型。
概述
InfluxDB 针对现实世界的测量值选择了 bucket、measurement 这两个核心概念,没有关系数据库里的 database、table 等概念;而 TDengine 选择了兼容 SQL、降低门槛的路线,也就继承了 database、table 等概念,同时创新性地提出了“一个数据采集点一张表”的数据模型。
InfluxDB 的数据模型
我们先来看看 InfluxDB 中的核心概念。根据其官方文档:
InfluxDB 数据模型将时序数据组织为 bucket 和 measurement。一个 bucket 可以包含多个 measurement。measurement 可以包含多个标签和字段。
可以将 bucket 理解为存储时序数据的位置,我们可以为其指定时序数据的保存策略。measurement 则用于时序数据的逻辑分组。从逻辑意义上对比,我们可以把 bucket 看作关系数据库中的 database,把 measurement 看作 table。
measurement 又包括几个部分:
- 标签(tag):其值不经常改变的键值对。用来保存一些相对静态的信息,比如采集设备的主机、位置、站点等。
- 字段(field):其值随着时间经常改变的键值对,比如温度、压强、股价等。
- 时间戳(timestamp):与当前数据关联的时间戳。
在此基础上,InfluxDB 又有两个新的概念,point(数据点)和时间线(series),当然这在很多时序数据库中都是类似的。
整体来看,InfluxDB 的数据模型比较直观,而且不需要提前定义好模式,容易上手,后期如果有需要,动态增加新的字段也比较容易。当然,考虑后续的查询性能,在数据量非常大的情况下,也需要仔细设计 measurement 中的 tag 和 field,在此就不做赘述。新用户可以直接使用 InfluxDB 的图形界面或命令行客户端来创建 bucket,然后就可以利用行协议写入数据了。就像这样:
influx write \
--bucket get-started \
--precision s "
home,room=Living\ Room temp=21.1,hum=35.9,co=0i 1641024000
home,room=Kitchen temp=21.0,hum=35.9,co=0i 1641024000
home,room=Living\ Room temp=21.4,hum=35.9,co=0i 1641027600
home,room=Kitchen temp=23.0,hum=36.2,co=0i 1641027600
home,room=Living\ Room temp=21.8,hum=36.0,co=0i 1641031200
home,room=Kitchen temp=22.7,hum=36.1,co=0i 1641031200
home,room=Living\ Room temp=22.2,hum=36.0,co=0i 1641034800
"
在查询方面,InfluxDB 提供了类 SQL 的 InfluxQL 语言,后来又推出了函数式的脚本语言 Flux。需要一定的学习成本。
TDengine 的数据模型
TDengine 充分利用了时序数据的特点,提出了“一个数据采集点一张表”与“超级表”的概念,设计了创新的存储引擎,让数据的写入、查询和存储效率都得到极大的提升。
首先,TDengine 为了让有数据库经验的从业者快速上手,直接选择了以 SQL 为接口语言的设计策略。所谓“一个数据采集点一张表”,就是在设计数据模型时,要对每个数据采集点单独建一张表,用来存储这个数据采集点所采集的时序数据。这种设计有几大优点:
- 由于不同数据采集点产生数据的过程完全独立,每个数据采集点的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写,写入速度就能大幅提升。
- 对于一个数据采集点而言,其产生的数据是按照时间排序的,因此写的操作可用追加的方式实现,进一步大幅提高数据写入速度。
- 一个数据采集点的数据是以块为单位连续存储的。如果读取一个时间段的数据,它能大幅减少随机读取操作,成数量级的提升读取和查询速度。
- 一个数据块内部,采用列式存储,对于不同数据类型,采用不同压缩算法,而且由于一个数据采集点的采集量的变化是缓慢的,压缩率更高。
由于一个数据采集点一张表,导致表的数量巨增,难以管理,而且应用经常需要做采集点之间的聚合操作,聚合的操作也变得复杂起来。为解决这个问题,TDengine 引入了超级表(Super Table,简称为 STable)的概念。在 TDengine 的设计里,表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合。
所以在使用 TDengine 时,我们要先创建库(database),再创建超级表,然后为具体的数据采集点创建子表。从创建到管理数据,都可用自己熟悉的 SQL 语法。TDengine 3.0 新增的流式计算语法,也是直接扩充了 SQL,很容易上手。
当然,硬币总有两面。TDengine 为了性能选择了这样的设计,但是像 InfluxDB 的无模式操作,实现起来就要复杂一些。不过 TDengine 也通过内部功能提供了无模式写入功能。
本文简单对比了 InfluxDB 和 TDengine 的数据模型。总结而言,如果没有数据库相关背景,InfluxDB 上手比较直观;如果有 SQL 经验,甚至在业务中已经用 MySQL 等关系数据库处理过时序数据,则 TDengine 更为友好。