当前位置: 首页 > 后端技术 > Java

列式数据仓库如何更高效

时间:2023-04-02 00:40:39 Java

很多数据仓库产品都使用列式存储。如果数据表的总列数较多,参与计算的列较少,则使用列存储只读取需要的列,可以减少硬盘访问量,提高性能。尤其是在数据量非常大的时候,硬盘扫描读取时间占的比重很大。这时候,列存的优势就会很明显。那么,是不是只要使用列存储就可以达到最佳性能呢?下面我们就来看看列式存储在哪些方面可以更高效。压缩结构化数据的编码方式一般都不是很紧凑,往往会有一定的压缩空间。数据仓库通常在列存的基础上对数据进行压缩,从物理上减少数据存储量,从而减少读取时间,提高性能。数据表中同一字段的数据类型一般是相同的,甚至有些情况下取值非常接近。这样一批数据通常有较好的压缩率。列存储将相同的字段值存储在一起,因此比行存储更有利于数据压缩。但是,通用的压缩算法不能假设数据具有某些特性,只能将数据编码为随机字节流,有时无法获得最佳压缩率。而且,经过压缩率高的算法压缩后的数据,往往会增加CPU的计算量,解压时会消耗更多的时间。这部分额外消耗的时间甚至会大于压缩节省下来的硬盘读取时间,得不偿失。如果我们先对数据做一些处理,人为的创造一些数据特征来使用,再配合压缩算法,就可以在保持低CPU消耗的同时,达到高压缩率。对数据进行排序和存储是一种有效的处理方法。数据表往往有很多维度字段,比如地区、日期等。这些维度的值基本都在一个很小的设定范围内,数据量大的时候会有很多重复的值。如果数据按这些列排序,则相邻记录通常具有相同的值。这时候使用非常轻量级的压缩算法也可以获得很好的压缩率。简单来说,可以直接存储列值及其重复次数,而不用多次存储同一个值,占用的空间相当可观。排序的顺序也很重要。尽量先对字段值较长的列进行排序。比如有地区和性别两列,地区的值(“北京”、“上海”等)的字符数多于性别(“男”、“女”),则效果先按地域再按性别排序,总比反过来这里的情况要好。我们还可以优化数据类型,例如将字符串、日期等转换为合适的数字编码。如果将地区和性别字段转换为小整数,则字段值的长度将相同。这时候可以选择重复次数多的字段排在第一位。比如性别只有两个枚举值,但是地区比较多。因此,在每条记录中,性别重复会比较多,先性别后地区排序占用的空间通常较小。开源数据计算引擎SPL提供的列存方案实现了这种压缩算法。在SPL组表中添加有序数据时,默认会自动执行上述方法,只记录一个值和重复次数。SPL创建有序存储组表并完成遍历计算的写法,大致是这样的:示例代码1:有序压缩列存储及遍历计算A1=file("T_ordinary.ctx").open().cursor(f1,f2,f3,f4,...).sortx(f1,f2,f3)2>file("T.ctx").create(#f1,#f2,#f3,f4,...).append@i(A1)3=file("T.ctx").open().cursor().groups(…;sum(amt1),avg(amt2),max(amt3+amt4),…)A1:创建游标原始数据,并按f1、f2、f3三个字段排序。A2:新建组表,依次指定f1、f2、f3三个字段。将排序后的数据写入组表。A3:打开新建的组表,进行组汇总。在下面的测试中,SPL采用数据类型优化和有序压缩列存储后,数据存储容量减少了31%,计算性能提升了9倍以上。测试结果如下图所示:关于本次测试更详细的信息可以参考:多维分析背景实践3:维度排序压缩并行多线程并行可以充分利用多个CPU的计算能力和是提速的重要手段。为了并行运行,首先需要对数据进行分段。行存储的分段比较简单。一般是根据数据量进行切分,然后找到记录的结束标志,确定切分点的位置。但是列式存储不能用同样的方法。由于列存的不同列是分开存储的,所以也必须分开分段。并且由于变长字段和压缩数据的存在,每列相同的切分点位置不一定落在同一条记录上,会导致读取错误。业界普遍采用块方案来解决列式存储分段的同步问题:块中的数据以列式存储,分段必须以块为单位,块不再并行分段。要实现此方法,必须首先确定每个块的数据大小。如果数据表中的数据总量是固定的,以后不会再增加额外的数据,那么很容易计算出一个合适的块大小。但是数据表一般都有新的数据不断增加,这会造成blocksize如何确定的矛盾。如果块很大,初始总数据量较小时,块数会比较少,无法实现灵活切分。均匀灵活的切分是决定并行计算性能的关键。如果block比较小,随着数据量的增加,block的个数也会增加,列数据会在物理上被分割成很多不连续的小block,block之间会读入少量无用的数据。考虑seek硬盘的时间,块越多,问题越严重。许多数据仓库或大数据平台无法解决块大小和块数之间的矛盾,因此难以充分利用并行计算来提高性能。SPL提供了乘法分割的方法,将固定的(物理的)块变为动态的(逻辑的)块,可以很好的解决这个矛盾。具体方法是:为每一列数据创建一个固定大小(例如1024个索引位)的索引区,每个索引位存储一条记录的起始位置,相当于一条记录为一个块。添加记录直到索引位满,重写索引区,丢弃偶数索引位,奇数位前移,清空索引区的后半部分。相当于把块数减少到512,两条记录为一个块。以此类推,重复追加数据、填满、改写索引区的过程。随着数据量的增加,块的大小(块中的记录数)不断加倍。所有列的索引区必须同步填充,填充后同步重写,使其始终一致。这种方法本质上是以记录数作为切分的依据,而不是字节数,所以即使单独切分也能保证每一列都是同步的,不会出现错位的情况。以动态块为单位切分时,块数保持在512~1024之间(小于512的记录数除外),可以满足灵活切分的要求。每列动态块对应的记录数完全相同,也能满足统一切分的要求。无论数据量大小,都可以获得很好的分割效果。关于乘法切分原理的详细介绍可以看这里:SPL的乘法切分。示例代码1中生成的组表T默认采用双倍切分方案。使用T进行并行计算,只需修改A3代码:=file("T.ctx").open().cursor@m().groups(...;sum(amt1),avg(amt2),max(amt3+amt4),...)游标函数加上@m选项,可以进行并行计算。后面添加数据时,不需要重新生成组表。打开组表,直接追加。代码大致是这样的:>file("T.ctx").open().append@i(cs)这里要保证游标cs中追加的数据,根据f1,f2,这三个f3的字段继续排序。在实际应用中,追加的数据不一定满足这个条件。针对这种情况,SPL也提供了高性能的解决方案。具体方法请参考:SPL的有序存储。查找列存储更适合遍历计算,比如分组和汇总。对于大多数查找任务,列存储会导致性能变差。当没有使用索引时,通常的列存储即使已经有序存储也无法使用二分查找。其原因和上面并行部分描述的一样,因为列存不能保证每一列的同步,可能会出现错位,导致读取错误。这时只能通过遍历的方式查找列存数据,性能会很差。也可以在列存数据表上建立索引来避免遍历,但是很麻烦。从理论上讲,如果要在索引中记录每个字段的物理位置,那么索引容量会比行存储索引大很多,甚至可能和原始数据表一样大(因为每个字段都有一个物理位置,索引中的数据量与原始数据相同,但数据类型简单)。而且读取的时候要到每个字段的数据区去读取,而硬盘有一个最小读取单元,这会导致每列的总读取量远远超过行内存,这说明搜索性能更差。SPL采用乘法切分机制后,可以根据记录号快速找到列存格式中各个字段的值,然后执行二分法。同时在索引中记录整条记录的序号就可以了,容量可以小很多,类似于行存储。但是在使用二分法或者索引查找的时候,还是需要分别读取每个字段的数据块,性能上还是不如行存储。因此,如果要追求极致的搜索性能,还是需要使用行存。在实际应用中,最好让程序员根据计算的需要来选择是否存储。但是有些数据仓库有透明的机制,不允许用户自由选择行存和列存,所以很难达到最好的效果。SPL把这个自由度留给了开发者,开发者可以根据实际需要决定是否使用列存,哪些数据使用列存,从而获得极致的性能。在前面的介绍中,组表默认使用列存储,同时也提供了行存储方式,创建时可以通过选项@r指定。示例代码1中的A2可以改为:=file("T_r.ctx").create@r(#f1,#f2,#f3,f4,…).append@i(A1)这样行存储生成的组表。拥有列存和行存两组表,程序员可以根据需要自由选择使用。对于遍历和搜索性能要求高的场景,只能用存储空间换取计算时间。即对数据进行两次冗余存储,列存用于遍历,行存用于查找。但是这种共存方案中的数据需要进行二次冗余,行存储需要重新建立索引,所以整体占用的硬盘空间会比较大。SPL还提供了一个有值的索引,索引时将其他字段值一起复制。原来组表继续使用列存进行遍历,而索引本身保存了字段值,使用行存。查找时一般不会访问原表,这样可以获得更好的性能。和行列共存方案一样,值索引可以兼顾遍历和查找的性能。而且,值索引相当于行存储加上索引,比行列共存方案占用空间更少。示例代码2:值为1的索引A=file("T.ctx").open()2=A1.index(IDS;f1;f4,amt1,amt2)3=A1.icursor(f1,f4;f1==123456).fetch()4=A1.icursor(f4,amt2;f1>=123456&&f2<=654321)A2建索引IDS时,复制参数中需要引用的字段f4、amt1、amt2,并且你可以在索引中复制这些字段值。以后取目标值的时候,只要涉及到的字段在这部分,就不需要再去读原表了。回顾总结使用列存储只能读取需要的列。当总列数较多,计算涉及的列较少时,可以减少硬盘访问量,提高性能。但这还不够。列存数据仓库还需要在数据压缩、多线程并行、搜索计算等方面进行优化,以达到列存的最佳效果。开源数据计算引擎SPL充分利用了有序数据存储的特??点。在保持低CPU消耗的前提下,实现了高压缩率的压缩算法,大大减少了物理存储量,进一步提升了性能。SPL还提供了双倍切分机制,解决了列存切分的问题,让列存数据充分利用并行计算,提高效率。而且,SPL可以自由创建行存和列存的数据表,让开发者可以自主选择使用,并提供了值索引机制,可以同时实现高性能的遍历和搜索计算。##SPL资料SPL下载SPL源码