在上一期的文章(《五分钟掌握TDengine时序数据的保留策略》)中,我们知道了TDengine Database是如何按照时间段对数据进行分区来管理数据的。
接下来,我们和大家一起从产品使用者的视角继续向前探索——TDengine中的时序数据到底是如何存储的?
在那篇文章里,在我们第一次写入数据并重启数据库服务后,在vnode xx路径下看到了这三个文件。
这哥仨,其实就是TDengine中广义上的数据文件,也可以说它们是一个数据文件组。
从后缀上看,仿佛.data这个文件就是数据文件组中的数据文件。而其他二位都是为它保驾护航的“辅助”。但是这个感觉是不够准确的,因为.last文件同样也会存储数据。而.head文件则是存储数据块的索引的文件。
在了解它们之前,首先我们要知道两个参数:
1.minRows:数据块中记录的最小条数,单位为条,默认值为100。
2.maxRows:数据块中记录的最大条数,单位为条,默认值为4096。
数据块,是每个.data数据文件里存储数据的单位,每一个数据块都只能存储一个表的数据。所以上面的参数描述的意思就是——形成一个数据块默认的最小行数是100行,最多就是4096行。
那么,对于一张表来说,小于100行的话数据会去哪儿呢?大于4096行的话数据又去哪儿了呢?
答案是,.last文件正是表行数不足100时数据存放的位置;而大于4096行的表会生成一个新的数据块。最终在.data文件中,数据块的分布方式如下:
当然,这样讲会比较抽象,我们继续用实例说明:
我们假设创建库时使用的参数为,maxrows=1000,minrows=100。某库中有两张表A和B,我们向其中分别插入1000行和99行数据。然后,我们重启taosd服务,以上数据就会从内存中落盘到存储上。这个时候.data文件中会生成1个数据块,它就是表A的数据块1,里面拥有1000条数据。而表B的99条数据因为不足minrows所以就进入了.last文件。
接下来,继续向它们分别插入1000行和99行,然后重启taosd服务落盘。这个时候表A总共拥有2000条数据,新写入的1000行数据会被写入进表A的数据块2。而表B的数据量现在已经有了198行,大于了100行。于是它们也会被写入.data文件里面,成为表B的数据块1。
值得注意的是,当.last文件小于32k的时候,所有数据都只会追加进来。但是当.last文件大于32k的时候,每次落盘.last文件都是重写生成的了——这个的32k限制是为了防止数据的移动过于频繁。
所以,我们在做测试的时侯会发现:为什么当该表行数从99到100行以上时,.data文件的大小已经增加可.last文件却没有变小。只有当.last文件大于32k的时候,才能看到符合我们心理预期的效果——.last文件把数据移到.data文件,.last文件变小,.data文件变大。
以上场景只针对两个表,但其实放大到100个表,1000个表都是一样的逻辑。尽管每个vnode内存里存储的大量数据分属于不同的表,但是每次落盘只要这些表的行数保证大于minrows,它们都会落入到.data文件的数据块中。不满足上述条件的表数据被写入.last文件后,继续等待新数据的写入,直到该表满足了行数minrows的大小后,.last文件中该表的数据会被读入到内存,之后一起写入到.data文件中。
最后总结一下:
.data类文件存储的是真正的时序数据,为多个数据块构成。一个数据块只属于一张表,且数据块的顺序只与落盘的先后顺序有关。
.last文件与.data文件一样,也是存储时序数据的,只不过.last文件存储的块中的数据条数小于minRows。
本篇文章重点讲述的是.last与.data文件的关系。
关于.head文件,我们会在后面的文章中讲解。在此之前,我们只要知道它是用来方便查询数据的索引文件就可以了。.head文件的用途是什么?它会影响到哪些重要的功能?minrows和maxrows的变化会给性能带来多大的影响呢?以上这类问题都会涉及到很广泛的性能问题,我们将会在今后的日子慢慢讨论。
如果不了解TDengine Database的体系架构,对于用户来说很可能是事倍功半的。现在,涛思数据的每一期内容推送都是在为大家未来可以事半功倍而做的内容沉淀。为了强化关于TDengine存储模型的理解,建议大家可以回头看看这两篇文章(《这几个神秘参数,教你TDengine集群的正确使用方式》,《存储成本仅为OpenTSDB的1/10,TDengine的最大杀手锏是什么?》),其中的知识点都不是割裂的。
我们担心的不是产品不好用,而是产品没用好。