文件格式
文件格式
TsFile 按列保存时序数据。写入时,数据按设备分组依次追加,索引则放在文件尾部。读取时通常反过来:先从文件尾找到索引,定位目标时间序列,再跳转到保存数据的 Chunk。
本文介绍版本字节为 0x04 的 TsFile v4 布局,说明各结构的存储方式及相互关系。编码章节列出了编码编号,并展开 PLAIN 的字节布局;其他值编码算法的完整位布局不在本文范围内。
范围与术语
文件中的数据分为三个层级:
- Page 保存一组编码后的值,是独立压缩的单位。
- Chunk 保存某个物理列的所有 Page,头部记录数据类型、编码和压缩方式。
- Chunk Group 保存同一设备的一组 Chunk。随着数据分批刷出,一个设备可以在文件中出现多次,对应多个 Chunk Group。
非对齐数据的每个测点列各自保存时间戳和值。对齐数据把共享的时间戳放在 Time Chunk 中,各测点列分别放在 Value Chunk 中,再用位图记录哪些位置有值。两种布局的细节见数据页。
物理区域
文件以六字节魔数 TsFile 和一字节版本号开头。Page AEAD 文件随后还有一个可选的文件加密头。数据区由唯一的 0x02 分隔符结束,之后是索引和文件元数据。最后十个字节保存元数据长度和尾部魔数。
下表使用以下记号:
F:文件总长度。L:TsFileMetadata的序列化长度,由文件尾的四字节长度字段保存。E:可选文件加密头的总序列化长度;不存在时为0。data_start = 7 + E:数据区的起点。meta_offset:分隔符的偏移量,保存在TsFileMetadata中。
| 区域 | 包含首尾的字节区间 | 定位方式 |
|---|---|---|
| 头部魔数 | 0 … 5 | 固定位置 |
| 版本 | 6 | 固定位置 |
| 可选文件加密头 | 7 … 6 + E | 仅 Page AEAD 存在,marker 为 0x06 |
| 数据区 | data_start … meta_offset - 1 | 在分隔符前结束 |
| 分隔符 | meta_offset | 值为 0x02,由 TsFileMetadata 引用 |
| 索引元数据 | meta_offset + 1 … F - 11 - L | 由元数据偏移划分 |
TsFileMetadata | F - 10 - L … F - 11 | 根据文件尾长度反向定位 |
| 元数据长度 | F - 10 … F - 7 | 相对文件尾的固定位置 |
| 尾部魔数 | F - 6 … F - 1 | 相对文件尾的固定位置 |
偏移量从文件的第一个字节开始计算。上表的区间包含首尾;本文其余位置使用的 [start, end) 表示左闭右开区间。除非另有说明,长度不包含长度字段自身。E = 0 时,文件加密头对应的区间不存在。
索引元数据区中的 Timeseries Metadata 和索引节点可以交错存放,读取时应跟随偏移量,不依赖它们的物理排列顺序。TsFileMetadata 自身没有标记,需要通过文件尾保存的长度反向定位。
写入模型
写入器先追加各个 Chunk Group,结束文件时再写索引和文件尾。Page 大小、刷出阈值和索引扇出会影响结构边界,但这些边界都能从头部和偏移量中读出。时间单位和非对齐时间编码仍需读写双方约定,详见配置。
头部和尾部魔数使用相同的字节:
54 73 46 69 6c 65 # "TsFile"元数据优先读取算法
读取器无需扫描整个数据区,就能定位目标序列:
- 校验头部魔数和版本,检查偏移
7处是否有文件加密头,据此确定 Chunk Header 和 Page 载荷的解析方式。 - 校验尾部魔数,在
F - 10处读取L,计算file_metadata_pos = F - 10 - L。 - 读取
TsFileMetadata,检查meta_offset是否指向分隔符。 - 可以先用完整路径查询布隆过滤器。只有确认字符集、路径构造规则和哈希行为与写入端一致时,才能据此判断路径不存在。
- 依次查找表/设备索引、测量索引,定位目标序列的元数据。
- 根据序列和 Chunk 的统计信息选择 Chunk,跳转后再用可用的 Page 统计信息缩小范围。
- 读取选中 Page 的载荷,按需解密,然后解压和解码。
这样可以避免读取无关 Page 的数据。单 Page Chunk 不保存 Page Statistics,筛选时要使用 Chunk 或序列级的统计信息。
结构不变量
读取时需要检查的主要关系包括:
- 头尾魔数和版本有效。
- 可选文件加密头只在偏移
7处出现一次,且长度和版本有效。 0 <= L <= F - 10,且file_metadata_pos位于分隔符之后。- 一个 Chunk 的全部 Page 恰好占据完整 Chunk Header 之后的
data_size字节。 - 每个非空 Page 的载荷恰好占据
compressed_size字节,且没有越过 Chunk 边界。 - 同一索引节点内的偏移量单调不减,且不超过
end_offset。 - 对齐 Value Page 和相同序号的 Time Page 覆盖同一组位置,且
position_count等于该 Time Page 的位置数。
不满足这些关系即为格式错误。
通用的长度和偏移量检查见二进制表示,Page AEAD 额外要求的认证检查见加密。标记字节只在结构边界处有含义,不能把 Page 载荷中碰巧相同的字节当成标记。
阅读顺序与相关章节
初次阅读可以先看二进制表示、Chunk 与 Chunk Group 和数据页,再通过元数据了解索引查找过程。