哪种列存格式适合并行计算?
大数据量时,硬盘扫描和读取的时间占比很大。采用列式存储,在总列数很多而计算涉及的列很少时,只要读取需要的列即可,能够减少硬盘访问量,提高性能。事实上,很多数据仓库产品都采用了列式存储。
但是,数据按列存储,做多线程并行计算时会出现分段同步性问题。分段是并行计算的前提,行存分段比较简单,按数据量大体平均分段,再找记录结束标记确定分段点位置就可以。而列存不能采用同样的办法。由于列存的不同列是分别存储的,也必须分别分段,又因为不定长字段和压缩数据的存在,各个列相同的分段点位置不一定会落在同一条记录上,会错位导致读取错误。
业界普遍采用分块列存方案解决分段同步性问题:块内数据用列式存储,分段必须以块为单位,在块内不再分段并行。这样会出现一个矛盾:如果分块数太少,就无法做到灵活分段,而均匀、灵活的分段是决定并行计算性能的关键;如果分块数很多,列数据在物理上会被拆成很多不连续的小块,会多读入分块之间的少量无用数据,考虑机械硬盘的寻道时间,分块数越多这些问题越严重。很多数据仓库或大数据平台都无法解决这个矛盾,很难充分利用并行计算的性能。
开源的集算器 SPL 研发了创新的倍增分段技术,并在其组表列存中实现,解决了上述矛盾,这样就可以充分利用并行计算来提高性能了。
倍增分段是将固定(物理)分块改为动态(逻辑)分块。具体做法是:为每列数据建立确定大小(例如 1024 个索引位)的索引区,每个索引位存储一条记录的起始位置,相当于一条记录为一块。追加记录到索引位填满后,重写索引区,丢弃偶数索引位,奇数位向前移动,空出索引区后一半位置。相当于将分块数缩减为 512 个,两条记录为一块。依次类推,重复追加数据、填满、重写索引区的过程。随着数据量的增加,块的大小(块内记录数)不断翻倍。所有列的索引区要同步填充,且填满后同步重写,始终保持一致。倍增分段实质上是以记录数作为分段依据的,而不是字节数,所以可以保证各个列即使分别分段也是同步的,不会出现错位的情况。
以动态块为单位分段时,块个数保持在 512 到 1024 之间(记录数小于 512 除外),可以满足分段灵活的要求。各列的动态块对应记录数完全相同,也可以满足分段均匀的要求。数据量无论大小,都可以获得良好的分段效果。
SPL 组表会缺省支持倍增分段方案,生成这种分段的列存数据非常简单:
file("T.ctx").create(…).append(cs)
基于列存的多线程并行代码也很简单:
=file("T.ctx").open().cursor@m(f1,amt1).groups(f1;sum(amt1)) 打开列存数据后,建立多线程游标,做并行分组汇总。
列存并行的应用案例:我们怎样把 W 银行预计算固定条件查询优化成实时灵活条件查询
英文版