问题描述
我们知道,对于时序数据而言,每一个时间序列的数据,都有一个对应的时间戳,但是有没有必要为每一个时间序列都存一个时间戳,这个是值得商榷的,这得根据具体的时间序列的对齐程度去判断。
比如对于第一种写入情况而言,这四个时间序列的时间戳都是一样的,对齐程度很高,所以在磁盘上,只存一列时间戳,对应这四个时间序列的值是可取的。
但是对于第二种写入情况而言,四个时间序列的对齐程度低,每个时间序列只在部分时间戳上有值,对于这种场景,我们为每个时间序列单独存一个时间戳,那每一列只需要存一个时间戳和一个值,因为不存null值,列的压缩率也会更高。
对于四维这种时间戳完全对齐的场景,我们完全可以采用第一种存储方式。
收益与代价分析
Vector类型序列的Tsfile文件结构图
假设一个Vector包含m列
收益:
- flush时,会少(m - 1)列时间戳的encoding以及序列化时间
- 查询时,少从磁盘上反序列化(m - 1)列时间戳,在内存中按时间戳对齐时,按照这种方式,数据从磁盘上读取出来的时候,就已经按照时间戳对齐好了,所以少了这一步耗时。
- 一个Vector内的所有序列,在磁盘上是连续的,原来的存储方式,一个Vector内的不同sensor序列化到磁盘上时,有可能会被其他序列给分隔开,导致在磁盘上并不是连续存储的,而一个Vector内的序列通常会被一起查询,这种存储方式也会一定程度上减少磁盘的seek次数。理想情况下,如果Vector内的m列一起查询,这种存储方式可以减少(m - 1)次磁盘seek操作。
代价:
- 每一列需要维护多维护一个bitmap,假设每列有n行,则在磁盘上需要多占用m * (4 + [n/8])bytes
- 文件元数据方面,每一个Vector类型会多占用一个时间戳列的TimeSeriesMetadata以及一个ChunkMetadataList,假设一个tsfile文件中就存储一种Vector类型的序列,默认配置下,文件flush两次关一次,则元数据会多占用(40 + 2 * 40) bytes,一个TimeSeriesMetadata大小约为40B,两个ChunkMetadata大小约为80B。
- 单独查询Vector中的某些序列子集时,相较于原来的实现,可能会多反序列化一个ZSTree的叶子结点,读取数据时(Chunk),因为Time列和value列物理上不连续,所以会多一次磁盘seek。
实验对比
27个sensor,前15个为INT64类型,后12个为DOUBLE类型,时间戳完全对齐,master与vector分支对比
tsfile size | sort | encoding | io | flush total | |
master | 279MB | 2ms | 8,528ms | 1,599ms | 8,697ms |
vector | 212MB | 0ms | 4,478ms | 1,157ms | 6,205ms |