【AB技术篇】FTHistorian历史数据库的存储机制解密

FThistorian是如何存储历史数据的。例如,在FThistorian存储一条32位浮点型的变量的记录平均只需5个字节,客户就很难想明白FThistorian是怎么做到的。因为一条记录至少需要包含三个字段VQT(V:Value,指变量值;Q:Quality,指数据质量;T:Timestamp,指变量值对应的时间戳),对于32位浮点型变量而言,变量值V就需要4个字节存储,这就意味着Q和T平均只占用一个字节,这几乎不可能,但是FThistorian做到了,接下来我们就来具体看看FThistorian是如何做到这点的。

首先简单介绍一下FThistorian的历史数据文件的基本格式,FThistorian的历史数据都存储在形如piarch.001这样的数据文件中,与之相对应的还有一个形如piarch.001.ann这样的文件,后者是用来存储针对变量记录的注释用的,一般很少使用,我也未做分析。对于形如piarch.001这样的数据文件内部,PI采用的是分块(分页)的管理方式,每块(页)的大小为1024字节,这实质上隐含限制了一条变量记录的长度是不可能超过1000字节的。页从文件开头开始编号,第一个页号为0,用于存储跟文件相关的信息。之后的页就用于存储变量历史数据,每个变量初始被分配一个页用于存储数据,随着记录的追加,更多的页被分配给变量,当页数多余3时(根据我的观察)时,FThistorian采用如下的数据结构来组织变量数据(图1)。

从图1可以看到,FThistorian采用的是一种两层的简单存储结构,而不是大家通常想象的多层树型结构(例如B+树)。每一层相邻的两个页之间是互相链接在一起的,同时叶子节点(记录节点)也维护到父节点(索引节点)的反向引用指针。在索引节点上存储的记录是每个记录节点的起始时间和对应的页号。这样的存储结构有什么优点、有什么缺点,相信大家都是一目了然,我就不多说了。
接下来说说每个页(块)内的数据组织,前面已经说过每个页的大小为1024字节,除去30字节左右的固定页头(不同数据类型的页头会有细微差别,但都包含以下信息:变量ID、当前页号、上一页号、下一页号、父页号、是否为索引节点标志、记录条数、起始时间戳等),每个页用于存储历史数据的有效空间为990个字节左右。
对于历史记录的存储,FThistorian最核心的理念就是“不顾一切的尽可能缩减存储记录所需要的磁盘空间并满足运行稳定性”。