...
大端存储
比如:
int
0x8
将会被存储为00 00 00 08
, 而不是08 00 00 00
可变长的字符串类型
存储的方式是以一个
int
类型的Size
+ 字符串组成。Size
的值可以为 0。Size
指的是字符串所占的字节数,它并不一定等于字符串的长度。举例来说,"sensor_1" 这个字符串将被存储为
00 00 00 08
+ "sensor_1" (ASCII编码)。另外需要注意的一点是文件签名 "TsFile000001" (
Magic String
+Version
), 因为他的Size(12)
和 ASCII 编码值是固定的,所以没有必要在这个字符串前的写入Size
值。
数据类型
0: BOOLEAN
1: INT32 (
int
)2: INT64 (
long
)3: FLOAT
4: DOUBLE
5: TEXT (
String
)
编码类型
0: PLAIN
1: DICTIONARY
2: RLE
3: DIFF
4: TS_2DIFF
5: BITMAP
6: GORILLA_V1
7: REGULAR
8: GORILLA
压缩类型
0: UNCOMPRESSED
1: SNAPPY
2: GZIP
3: LZO
4: SDT
5: PAA
6: PLA
7: LZ4
1.2 TsFile 概述
<!-- TODO
下图是关于TsFile的结构图。
此文件包括两个设备 d1、d2,每个设备包含两个测点 s1、s2,共 4 个时间序列。每个时间序列包含两个 Chunk。
-->
TsFile 整体分为两大部分:数据区和索引区。
数据区所包含的概念由小到大有如下三个:
...
一个 Chunk
存储了一个物理量(Measurement) 一段时间的数据,Chunk 内数据是按时间递增序存储的。Chunk
是由一个字节的分隔符 0x01
, 一个 ChunkHeader
和若干个 Page
构成。
ChunkHeader 数据块头
成员 | 类型 | 解释 |
---|---|---|
measurementID | String | 传感器名称 |
dataSize | int | chunk 大小 |
dataType | TSDataType | chunk的数据类型 |
compressionType | CompressionType | 压缩类型 |
encodingType | TSEncoding | 编码类型 |
numOfPages | int | 包含的page数量 |
Page 数据页
一个 Page
页存储了一段时间序列,是数据块被反序列化的最小单元。 它包含一个 PageHeader
和实际的数据(time-value 编码的键值对)。
PageHeader 结构:
成员 | 类型 | 解释 |
---|---|---|
uncompressedSize | int | 压缩前数据大小 |
compressedSize | int | SNAPPY压缩后数据大小 |
statistics | Statistics | 统计量 |
这里是statistics
的详细信息:
成员 | 描述 | DoubleStatistics | FloatStatistics | IntegerStatistics | LongStatistics | BinaryStatistics | BooleanStatistics |
---|---|---|---|---|---|---|---|
count | 数据点个数 | long | long | long | long | long | long |
startTime | 开始时间 | long | long | long | long | long | long |
endTime | 结束时间 | long | long | long | long | long | long |
minValue | 最小值 | double | float | int | long | - | - |
maxValue | 最大值 | double | float | int | long | - | - |
firstValue | 第一个值 | double | float | int | long | Binary | boolean |
lastValue | 最后一个值 | double | float | int | long | Binary | boolean |
sumValue | 和 | double | double | double | double | - | - |
extreme | 极值 | double | float | int | long | - | - |
ChunkGroupFooter 数据块组结尾
成员 | 类型 | 解释 |
---|---|---|
entityID | String | 实体名称 |
dataSize | long | ChunkGroup 大小 |
numberOfChunks | int | 包含的 chunks 的数量 |
1.2.3 索引区
1.2.3.1 ChunkIndex 数据块索引
第一部分的索引是 ChunkIndex
:
成员 | 类型 | 解释 |
---|---|---|
measurementUid | String | 传感器名称 |
offsetOfChunkHeader | long | 文件中 ChunkHeader 开始的偏移量 |
tsDataType | TSDataType | 数据类型 |
statistics | Statistics | 统计量 |
1.2.3.2 TimeseriesIndex 时间序列索引
第二部分的索引是 TimeseriesIndex
:
成员 | 类型 | 解释 |
---|---|---|
measurementUid | String | 物理量名称 |
tsDataType | TSDataType | 数据类型 |
startOffsetOfChunkIndexList | long | 文件中 ChunkIndex 列表开始的偏移量 |
ChunkIndexListDataSize | int | ChunkIndex 列表的大小 |
statistics | Statistics | 统计量 |
1.2.3.3 IndexOfTimeseriesIndex 时间序列索引的索引(二级索引)
第三部分的索引是 IndexOfTimeseriesIndex
:
成员 | 类型 | 解释 |
---|---|---|
IndexTree | IndexNode | 索引节点 |
offsetOfIndexArea | long | 索引区的偏移量 |
bloomFilter | BloomFilter | 布隆过滤器 |
索引节点 (IndexNode) 的成员和类型具体如下:
成员 | 类型 | 解释 |
---|---|---|
children | List<IndexEntry> | 节点索引项列表 |
endOffset | long | 此索引节点的结束偏移量 |
nodeType | IndexNodeType | 节点类型 |
索引项 (MetadataIndexEntry) 的成员和类型具体如下:
成员 | 类型 | 解释 |
---|---|---|
name | String | 对应实体或物理量的名字 |
offset | long | 偏移量 |
所有的索引节点构成一棵类B+树结构的索引树(二级索引),这棵树由两部分组成:实体索引部分和物理量索引部分。索引节点类型有四种,分别是INTERNAL_ENTITY
、LEAF_ENTITY
、INTERNAL_MEASUREMENT
、LEAF_MEASUREMENT
,分别对应实体索引部分的中间节点和叶子节点,和物理量索引部分的中间节点和叶子节点。 只有物理量索引部分的叶子节点(LEAF_MEASUREMENT
) 指向 TimeseriesIndex
。
...
例6:1个实体,30个物理量,2个多元时间序列组,每个多元时间序列组分别有15个物理量
d0 | d1 |
---|---|
【一元时间序列】s0,s1...,s4 | 【一元时间序列】s0,s1,...s14 |
【多元时间序列】v0.(s5,s6,...,s14) | 【多元时间序列】v0.(s15,s16,..s18) |
【一元时间序列】z15,z16,..,z18 |
索引采用树形结构进行设计的目的是在实体数或者物理量数量过大时,可以不用一次读取所有的 TimeseriesIndex
,只需要根据所读取的物理量定位对应的节点,从而减少 I/O,加快查询速度。有关 TsFile 的读流程将在本章最后一节加以详细说明。
...