TiDB是目前当红的NewSQL数据库,在提供高性能读写的同时又兼容传统的RDBMS,其底层的存储是TiKV。这里我们看一下如果用TiKV存储时序数据,其底层数据组织形式是怎么样的,与InfluxDB的数据存储模式相比有何优缺点。
时序数据库与传统数据库对比 首先从上层应用的角度对比一下时序数据库与通用型数据库所面对的需求。关于时序数据的细节,数据模型,以及InfluxDB的实现,可以参考本人前面有一篇文章:
1. 写入/读取操作比例:对于通用的场景,这个比例是比较低的,就算是在高并发的互联网应用中,平均比例可能不会超过3:7。但是对于时序数据来说,这个比例很可能会超过5:5。而且时序数据一旦丢失,就找不回来了,因为时序数据都是一次性生成的。所以时序数据库对写入性能的要求更高。
2. 数据删除:时序数据会面临着大量时间久远、价值低的数据需要清除,需要删除的数据量大致等于新生成的数据量。传统数据库是不会大规模删除数据的。
3. 数据更新:TiDB中存在大量的数据更新,比如商品的库存数据、账户余额数据等。但是时序数据都是一次性生成的,不需要更新。而且时序中单点数据是没有意义的,就算错误也没必要修正。对于这个特征,如果将数据根据时间进行range sharding,就会导致冷热数据相对分离,方便于数据管理。但是也造成在数据写入时会形成热点,容易造成单点压力。
4. 数据模型:就目前面对的场景来说,时序数据的模型是相对固定的,主要包含timestamp,tags和fields三个部分,而且序列值可以通过timestamp进行组织。相对于RDBMS,这带来一个优势就是对事务的支持需求更小。这里需要事务支持只有一点:数据写入和删除时同一timestamp对应的各个field要一致。
5. 数据运算:时序数据的应用更偏向于在时间轴上进行聚合,或者统计,很少对原始数据直接取出来使用。从这一个角度来说,时序数据库的需求有点偏向OLAP。关于TSDB和OLAP的对比,也有博文进行了详述[Time series databases vs OLAP](https://medium.com/@itz100ji/time-series-databases-vs-olap-57e8d70309c8)。 所以,时序数据库需要提供高吞吐量、高可用性,但是对事务支持的需求较小。所以是不是可以说时序数据库是OLTP和OLAP各自的部分结合,也算是HTAP?
用TiKV存储时序数据 如果直接用TiKV存储时序数据,底层存储结构是什么样呢?这里我们只讨论数据在单个TiKV节点的组织形式,暂时先不管分布式架构。 TiKV底层使用的是RocksDB来存储数据,RocksDB是基于LSM Tree,是一个Key-Value存储引擎。TiKV的数据存储原理细节建议参考以下博文,里面做了很详细的描述,包括底层的组件,以及如何从传统的RDBMS的数据存储映射到TiKV:
1. [TiKV 是如何存取数据的(上)]
2. [TiKV 是如何存储数据的(下)] 所以,这里需要确定的就是怎么设计RocksDB中数据条目的key和value。如前文所说,对于时序数据,需要存储的内容包括timestamp,tags和fields,还需要在tags上做索引,提高通过tags的检索的效率。首先是存储每一个point的数据,每一个point可以通过measurement,所涉及的所有tags,和timestamp来定位,这三个信息的组合等价于MySQL中的primary key,可以唯一确定一个point。存储的value就是point的fields。如果我们写入的数据是: ``` Labs,location=SH,host=server1 CUP=73,Mem=16.067 1574179200s Labs,location=SH,host=server2 CUP=31,Mem=32.087 1574179200s Labs,location=SZ,host=server1 CUP=11,Mem=8.021 1574179200s Labs,location=SZ,host=server2 CUP=43,Mem=16.779 1574179200s ``` 在RocksDB里存储的数据条目就是: ``` # Primary Key, 前缀t表示table, 数据条目 t_Labs_location=SH,host=server1_1574179200s ==> (73, 16.067) t_Labs_location=SH,host=server2_1574179200s ==> (31, 32.087) t_Labs_location=SZ,host=server1_1574179200s ==> (11, 8.021) t_Labs_location=SZ,host=server2_1574179200s ==> (43, 16.779) ``` 通过primary key定位数据非常简单,直接在RocksDB做查询。 接着,还需要存储基于tags的索引。TiKV中索引的组织结构跟InfluxDB的原理是一样的,也是反向索引。也就是说,给定一组`tag name`和`tag value`对,记录哪些point与它相关。所以,索引的key既要包含涉及到的tag,还要包含对应的point的primary key。而索引条目的value是无关紧要的,只需要判断key是否存在。 ``` # Tags Index, 前缀i表示index, 索引条目 i_location=SH___t_Labs_location=SH,host=server1_1574179200s ==> nil i_host=server1___t_Labs_location=SH,host=server1_1574179200s ==> nil i_location=SH___t_Labs_location=SH,host=server2_1574179200s ==> nil i_host=server2___t_Labs_location=SH,host=server2_1574179200s ==> nil i_location=SZ___t_Labs_location=SZ,host=server1_1574179200s ==> nil i_host=server1___t_Labs_location=SZ,host=server1_1574179200s ==> nil i_location=SZ___t_Labs_location=SZ,host=server2_1574179200s ==> nil i_host=server2___t_Labs_location=SZ,host=server2_1574179200s ==> nil ``` 当我们需要通过索引查找时,比如`location=SH`,首先构建key前缀`i_location_SH___`,找到以这个前缀开头的数据,这个过程在TiKV里面是很高效的,因为它使用了基于key的range sharding。这些查找出来的索引条目的key的后半部分,就是跟给定tag相关的所有数据条目primary key,然后通过这些primary key来检索对应的数据条目。
可以看到TiKV的索引和数据混合在一起进行存储,但它们的key-value条目是分开的,所以当把MySQL的innodb表转到TiKV后面应该会占用更大的空间。
与InfluxDB存储的对比
1. 索引的构建,InfluxDB是使用一个超大的map实现的,这一点我觉得TiKV的构建方式更加直观简单,也充分利用了kay-value存储引擎的优势,特别是适合巨量的tags。
2. 时序数据常规操作的性能: - 写入:二者差不多。首先会写入数据条目,然后会对每一个涉及的tag创建一个索引条目。 - 读取:时序数据最多的是基于tag和timestamp区间进行检索。在TiDB里面,第一步需要返回所有的primary key,第二步取出每一个primary key的数据条目。InfluxDB的组织方式是基于entry进行存储,第一步是找到相关的series,这一步返回数据量更少,第二步根据series找出给定timestamp范围内的数据条目,只需要基于少量series轮询在数据库中检索。 - 清除:使用TiKV,数据的过期删除策略效率很差,需要遍历所有数据和索引条目,判断所对应的timestamp是否到期。频繁删除会影响到数据写入性能。InfluxDB基于timestamp进行sharding,旧的数据会存储在相近的shard里面,很简单就可以删除。
3. 存储空间压缩效率,InfluxDB的同field的内容会按列存储,同一数据类型,有利于压缩。
4. 分布式解决方案。因为InfluxDB Cluster是闭源的,而TiDB又开源支持,所以TiDB胜。 根据上面的对比,对于时序数据的存储,InfluxDB更专业。但是如果需要其分布式解决方案,要么得买,要么得自己实现解决。