Table of Contents |
---|
TsFile 文件格式
1. TsFile 设计
本章是关于 TsFile 的设计细节。
1.1 变量的存储
...
- 比如:
可变长的字符串类型
存储的方式是以一个
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
)
编码类型
为了提高数据的存储效率,需要在数据写入的过程中对数据进行编码,从而减少磁盘空间的使用量。在写数据以及读数据的过程中都能够减少I/O操作的数据量从而提高性能。IoTDB支持多种针对不同类型的数据的编码方法:0: PLAIN
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
int
0x8
将会被存储为 00 00 00 08
, 而不是 08 00 00 00
1.2 TsFile 概述
<!-- TODO
下图是关于TsFile的结构图。
此文件包括两个设备 d1、d2,每个设备包含两个测点 s1、s2,共 4 个时间序列。每个时间序列包含两个 Chunk。
-->
游程编码,比较适合存储某些整数值连续出现的序列,不适合编码大部分情况下前后值不一样的序列数据。
游程编码也可用于对浮点数进行编码,但在创建时间序列的时候需指定保留小数位数(MAX_POINT_NUMBER,具体指定方式参见本文SQL 参考文档)。比较适合存储某些浮点数值连续出现的序列数据,不适合存储对小数点后精度要求较高以及前后波动较大的序列数据。
游程编码(RLE)和二阶差分编码(TS_2DIFF)对 float 和 double 的编码是有精度限制的,默认保留2位小数。推荐使用 GORILLA。
3: DIFF
4: TS_2DIFF
- 二阶差分编码,比较适合编码单调递增或者递减的序列数据,不适合编码波动较大的数据。
5: BITMAP
6: GORILLA_V1
GORILLA编码是一种无损编码,它比较适合编码前后值比较接近的数值序列,不适合编码前后波动较大的数据。
当前系统中存在两个版本的GORILLA编码实现,推荐使用
GORILLA
,不推荐使用GORILLA_V1
(已过时)。使用限制:使用Gorilla编码INT32数据时,需要保证序列中不存在值为
Integer.MIN_VALUE
的数据点;使用Gorilla编码INT64数据时,需要保证序列中不存在值为Long.MIN_VALUE
的数据点。
7: REGULAR
8: GORILLA
9: ZigZag
- ZigZag编码将有符号整型映射到无符号整型,适合比较小的整数。
数据类型与支持编码的对应关系
数据类型 支持的编码 BOOLEAN PLAIN, RLE INT32 PLAIN, RLE, TS_2DIFF, GORILLA, ZigZag INT64 PLAIN, RLE, TS_2DIFF, GORILLA, ZigZag FLOAT PLAIN, RLE, TS_2DIFF, GORILLA DOUBLE PLAIN, RLE, TS_2DIFF, GORILLA TEXT PLAIN, DICTIONARY
1.2 TsFile 概述
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
。
...
索引树节点的度(即每个节点的最大子节点个数)可以由用户进行配置,配置项为max_degree_of_index_node
,其默认值为256。在以下例子中,我们假定 max_degree_of_index_node = 10
。
需要注意的是,在索引树的每类节点(ENTITY、MEASUREMENT)中,键按照字典序排列。在下面的例子中,若i<j,假设字典序di<dj。(否则,实际上[d1,d2,...d10]的字典序排列应该为[d1,d10,d2,...d9])
其中,例1~4为一元时间序列的例子,例5~6为多元时间序列的例子,例7为综合例子。
...
在150个实体,每个实体中有150个物理量的情况下,物理量和实体个数均超过了 max_degree_of_index_node
,形成索引树的物理量层级和实体索引层级。在这两个层级里,每个 IndexNode 均最多由10个 IndexEntry 组成。如前所述,从根节点到实体索引层级的叶子节点,类型分别为INTERNAL_ENTITY
和 LEAF_ENTITY
,而每个实体索引层级的叶子节点都是物理量索引层级的根节点,从这里到物理量索引层级的叶子节点,类型分别为INTERNAL_MEASUREMENT
和 LEAF_MEASUREMENT
。
例5:1个实体,20个物理量,2个多元时间序列组,每个多元时间序列组分别有9个物理量例5:1个实体,18个物理量,2个多元时间序列组,每个多元时间序列组分别有9个物理量
例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 的读流程将在本章最后一节加以详细说明。
...
注意: 如果没有设置输出文件的存储路径, 将使用 "TsFile_sketch_view.txt" 做为默认值。
在mac系统中的示例:
/iotdb/server/target/iotdb-server-{version}/tools/tsfileToolSet$ ./print-tsfile-sketch.sh test.tsfile
|````````````````````````
Starting Printing the TsFile Sketch
|````````````````````````
TsFile path:test.tsfile
Sketch save path:TsFile_sketch_view.txt
-------------------------------- TsFile Sketch --------------------------------
file path: test.tsfile
file length:
...
15462
14:40:55.619 [main] INFO org.apache.iotdb.tsfile.read.TsFileSequenceReader - Start reading file test.tsfile metadata from 15356, length 96
POSITION| CONTENT
-------- -------
...
0|
...
[magic head] TsFile
...
6|
...
[version number]
...
3
||||||||||||||||||||| [Chunk Group] of root.
...
sg_
...
1.
...
d1, num of Chunks:
...
4
7| [Chunk Group Header]
...
| [marker] 0
| [deviceID] root.sg_1.d1
...
...
...
...
21|
...
...
[
...
Chunk] of s6, numOfPoints:1000, time range:[0,999], tsDataType:INT64,
startTime: 0 endTime: 999 count: 1000 [minValue:6,maxValue:9996,firstValue:6,lastValue:9996,sumValue:5001000.0]
| [chunk
...
header] marker=5, measurementId=s6, dataSize=1826, serializedSize=9
| [chunk] java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
|
...
...
[page] CompressedSize:1822, UncompressedSize:1951
1856|
...
[Chunk] of
...
s4, numOfPoints:
...
1000, time range:[
...
0,
...
999], tsDataType:INT64,
...
...
startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:
...
4,maxValue:
...
9994,firstValue:
...
4,lastValue:
...
9994,sumValue:
...
4999000.0]
| [chunk
...
header] marker=5, measurementId=s4, dataSize=1826, serializedSize=9
| [chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page]
...
CompressedSize:1822, UncompressedSize:1951
3691|
...
[Chunk] of
...
s2, numOfPoints:
...
1000, time range:[
...
0,
...
999], tsDataType:INT64,
...
startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:
...
3,maxValue:
...
9993,firstValue:
...
3,lastValue:
...
9993,sumValue:
...
4998000.0]
| [chunk header]
...
marker=5, measurementId=s2, dataSize=1826, serializedSize=9
|
...
[chunk] java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page]
...
CompressedSize:1822, UncompressedSize:1951
5526|
...
[Chunk
...
]
...
of s5, numOfPoints:1000,
...
time range:[0,999], tsDataType:INT64,
startTime: 0 endTime: 999 count: 1000 [minValue:5,maxValue:9995,firstValue:5,lastValue:9995,sumValue:5000000.0]
| [chunk header] marker=5, measurementId=s5, dataSize=1826, serializedSize=9
|
...
[chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page] CompressedSize:1822, UncompressedSize:1951
||||||||||||||||||||| [
...
Chunk Group] of
...
root.sg_1.d1 ends
||||||||||||||||||||| [Chunk Group] of root.
...
sg_
...
1.d2,
...
num of Chunks:4
7361| [Chunk
...
Group Header]
|
...
[marker]
...
0
|
...
[deviceID] root.sg_1.d2
7375| [Chunk] of
...
s2, numOfPoints:
...
1000, time range:[
...
0,
...
999], tsDataType:
...
INT64,
...
startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:
...
3,maxValue:
...
9993,firstValue:
...
3,lastValue:
...
9993,sumValue:
...
4998000.0]
| [chunk
...
header] marker=5, measurementId=s2, dataSize=1826, serializedSize=9
| [chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page]
...
CompressedSize:1822, UncompressedSize:1951
...
9210|
...
[Chunk] of
...
s4, numOfPoints:
...
1000, time range:[
...
0,
...
999], tsDataType:
...
INT64,
...
...
startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:
...
4,maxValue:
...
9994,firstValue:
...
4,lastValue:
...
9994,sumValue:
...
4999000.0]
| [chunk
...
header] marker=5, measurementId=s4, dataSize=1826, serializedSize=9
| [chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page]
...
CompressedSize:1822, UncompressedSize:1951
11045| [Chunk] of
...
s6, numOfPoints:
...
1000, time range:[
...
0,
...
999], tsDataType:
...
INT64,
...
...
startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:
...
6,maxValue:
...
9996,firstValue:
...
6,lastValue:
...
9996,sumValue:
...
5001000.0]
| [chunk
...
header] marker=5, measurementId=s6, dataSize=1826, serializedSize=9
| [chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
| [page]
...
CompressedSize:1822, UncompressedSize:1951
12880| [Chunk] of s5, numOfPoints:1000, time
...
range:[0,999], tsDataType:INT64,
startTime: 0 endTime: 999
...
count: 1000 [minValue:5,maxValue:9995,firstValue:5,lastValue:9995,sumValue:5000000.0]
| [chunk
...
header] marker=5, measurementId=s5, dataSize=1826, serializedSize=9
| [chunk]
...
java.nio.HeapByteBuffer[pos=0 lim=1826 cap=1826]
|
...
[page] CompressedSize:1822, UncompressedSize:1951
||||||||||||||||||||| [Chunk Group] of root.
...
sg_
...
1.
...
d2 ends
14715| [marker] 2
...
...
14716| [TimeseriesIndex] of root.sg_1.d1.s2, tsDataType:INT64
|
...
[
...
ChunkIndex]
...
s2, offset=3691
| [startTime: 0
...
endTime: 999 count:
...
1000 [minValue:3,maxValue:9993,firstValue:3,lastValue:9993,sumValue:4998000.0]]
14788| [TimeseriesIndex] of root.
...
sg_
...
1.
...
d1.s4,
...
tsDataType:INT64
|
...
...
...
[ChunkIndex] s4, offset=1856
|
...
[startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:4,maxValue:9994,firstValue:
...
4,lastValue:
...
9994,sumValue:4999000.0]]
14860| [TimeseriesIndex] of root.sg_1.d1.s5, tsDataType:INT64
...
...
| [ChunkIndex] s5, offset=5526
|
...
[startTime: 0 endTime: 999 count: 1000 [minValue:5,maxValue:9995,firstValue:5,lastValue:9995,sumValue:5000000.0]]
...
14932|
...
[TimeseriesIndex] of root.sg_1.d1.s6, tsDataType:INT64
| [ChunkIndex] s6, offset=21
|
...
[startTime:
...
0 endTime:
...
999 count:
...
1000 [minValue:6,maxValue:9996,firstValue:
...
6,lastValue
...
:9996,sumValue:5001000.0]]
15004| [TimeseriesIndex] of root.sg_1.d2.s2, tsDataType:INT64
...
...
...
| [ChunkIndex] s2, offset=7375
|
...
[startTime: 0 endTime: 999 count: 1000 [minValue:3,maxValue:9993,firstValue:3,lastValue:9993,sumValue:4998000.0]]
15076|
...
[TimeseriesIndex] of root.sg_1
...
.d2.s4, tsDataType:INT64
|
...
[
...
ChunkIndex]
...
s4, offset=9210
| [startTime: 0
...
endTime: 999 count: 1000 [minValue:4,maxValue:9994,firstValue:4,lastValue:9994,sumValue:4999000.0]]
15148| [TimeseriesIndex] of root.sg_1.d2.s5, tsDataType:INT64
| [ChunkIndex] s5, offset=12880
|
...
...
[startTime: 0 endTime: 999 count: 1000 [minValue:5,maxValue:9995,firstValue:5,lastValue:9995,sumValue:5000000.0]]
...
15220| [
...
TimeseriesIndex] of root.
...
sg_
...
1.d2.s6, tsDataType:INT64
...
|
...
...
[ChunkIndex] s6, offset=11045
...
|
...
...
[startTime: 0 endTime: 999 count:
...
1000 [minValue:6,maxValue:9996,firstValue:6,lastValue:9996,sumValue:5001000.0]]
|||||||||||||||||||||
...
15292| [IndexOfTimerseriesIndex Node] type=LEAF_MEASUREMENT
| <s2, 14716>
| <s6, 14932>
...
...
...
...
...
...
| <endOffset, 15004>
...
15324| [
...
IndexOfTimerseriesIndex Node]
...
type=LEAF_MEASUREMENT
| <s2, 15004>
...
...
| <s6, 15220>
...
...
|
...
<endOffset, 15292>
...
15356|
...
...
...
[TsFileMetadata]
|
...
[meta offset]
...
14715
| [num
...
of devices] 2
|
...
2 key&TsMetadataIndex
| [bloom filter
...
bit vector byte
...
array length]
...
32
| [bloom filter bit
...
vector
...
byte
...
array]
| [bloom filter
...
number of
...
bits]
...
256
| [bloom filter
...
number of hash
...
functions]
...
5
15452| [TsFileMetadataSize] 96
15456| [magic tail] TsFile
15462| END of TsFile
---------------------------- IndexOfTimerseriesIndex Tree -----------------------------
[MetadataIndex:LEAF_DEVICE]
└───[root.sg_1.d1,15292]
[MetadataIndex:LEAF_MEASUREMENT]
...
└───[
...
s2,14716]
...
└───[s6,14932]
...
└───[root.sg_1.d2,15324]
[MetadataIndex:LEAF_MEASUREMENT]
...
...
└───[s2,15004]
...
└───[s6,15220]]
---------------------------------- TsFile Sketch End ----------------------------------
...
3.4 TsFileSequenceRead
您可以使用示例中的类 example/tsfile/org/apache/iotdb/tsfile/TsFileSequenceRead
顺序打印 TsFile 中的内容.
...
clear all;close all;
% 1. load visdata generated by TsFileExtractVisdata
filePath = 'D:\visdata1.csv';
[timeMap,countMap] = loadVisData(filePath,'ms'); % mind the timestamp unit
% 2. plot figures given the loaded data and two plot parameters:
% `showSpecific` and `isFileOrder`
draw(timeMap,countMap,{},false)
title("draw(timeMap,countMap,\{\},false)")
draw(timeMap,countMap,{},true)
title("draw(timeMap,countMap,\{\},true)")
draw(timeMap,countMap,{'root.vehicle.d0.s0'},false)
title("draw(timeMap,countMap,{'root.vehicle.d0.s0'},false)")
draw(timeMap,countMap,{'root.vehicle.d0.s0','root.vehicle.d0.s1'},false)
title("draw(timeMap,countMap,{'root.vehicle.d0.s0','root.vehicle.d0.s1'},false)")
draw(timeMap,countMap,{'root.vehicle.d0.s0','root.vehicle.d0.s1'},true)
title("draw(timeMap,countMap,{'root.vehicle.d0.s0','root.vehicle.d0.s1'},true)")
绘图结果:
...
.vehicle.d0.s1'},true)
title("draw(timeMap,countMap,{'root.vehicle.d0.s0','root.vehicle.d0.s1'},true)")
绘图结果:
附录
大端存储
比如:
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: UNCOMPRESSED
1: SNAPPY
2: GZIP
3: LZO
4: SDT
5: PAA
6: PLA
7: LZ4