【51CTO.com快译】联机分析处理(OLAP)需要有即时的响应,因此其性能是至关重要的。虽然其结构较为简单,但是在处理各种大的数据立方体(data cubes)时,会涉及到大量的计算。
常被称为OLAP(联机分析处理)的多维分析是一种交互式的数据分析过程,它包括:对于数据立方体(data cube)进行旋转(rotation)、切片与切块(slice and dice)、钻取(drill-down)等执行操作。其后端的计算结构较为简单,如下列SQL语句所示。
- SELECT D,..., SUM(M), ... FROM C WHERE D'=d' AND ... GROUP BY D,...
该语句通过多个维度(dimension)聚合了各种量度(measure)。其中C是一个数据立方体,D,…表示所选的维度,而M,…则代表用于聚合的各个量度。除了SUM,我们也可以使用其他的聚合函数。D'是一个切片维度。切块操作的范围标准,用语句D IN (d,...)来表示。我们还可以在量度中定义一个规则,用WHERE语句来选择一定范围内的值。
OLAP分析需要即时的响应,因此高性能是至关重要的。尽管该语句的结构较为简单,但是当我们处理各种大的数据立方体时,则可能会涉及到大量的计算。而在我们能够找到一种优化它们的方法之前,其分析过程一般比较缓慢的。以下我们列出了几种能够为多维分析提高后端性能的常见方法。
1. 预聚合(Pre-Aggregation)
早期的OLAP产品通常会采用预聚合作为一种有效地交换存储开销的方法。该方法通过一些或者所有的维度(在GROUP BY语句中被定义),预先计算聚合的值(在SELECT查询的各种量度中被定义),并且存储它们。这些中间结果可以直接被后期的计算所使用到,或是产生一些新的计算。通过这种方式,性能方面能够大幅地提升。
这些聚合的结果会占用大量的空间。通常情况下,可能会有几十个维度,而每个维度值的范围则从一位数到两位数不等。简单的数学运算表明:预聚合的结果将比原始数据立方体大几倍到几十倍(也就是说:如果考虑到各种类型的聚合函数的话,其比例为(k1+1)*(k2+1)*...到k1*k2*...)。尽管数据立方体不会因为太大而无法获得即时地响应,但是这个增大几十倍的数据量还是不可能实现的。
有一个折中的方法是仅计算其中一些维度的聚合值。因为只有少数的分组维度(在GROUP BY语句中被定义)会被显示在OLAP的接口中,所以我们能够以m的维度来执行聚合。如果m的值不大于5,则存储的开销将在一个合理的范围内,而大部分的用户操作也将能得到快速地响应。
当然,部分聚合是不能使用其他维度的切片标准来处理的。然而,钻取却正好是基于切片的。这就糟糕地导致了:即使是那些对于多维分析来说并非少见的且广泛使用的聚合,也无法使用同一个量度来处理某个切片标准(比如说,要获得超过¥1000的销售额)。因此,一个聚合函数很可能仅仅包含一个标准(比如说,只是低于¥100的总成本)。可见,预聚合的结果对于所有这些场景都是无用的。
预聚合只能处理一些最为常见的场景,而这些只在所有类型的多维分析场景中占有一小部分。而全量遍历(full traversal)则仍然在大多数的场景中被用到。
2. 基于段的并行处理(Segment-Based Parallel Processing)
从本质上讲,多维分析就是对数据的过滤和分组,这就很容易实现并行处理。它的步骤包括:将数据划分为多个段,分别处理它们,并收集那些相互独立的子任务(subtask)所处理的结果,从而进行聚合。无论是在单台机器上,还是在多节点集群的计算上,甚至是两者的结合,其多线程处理都不难以被实施。
虽然多维分析的结果是可视化的,但是我们用肉眼所能看到的数据还是远低于现代计算机内存里所能够保存的数据。对于一个足够小的数据集,它能够很容易地被加载到内存中,而不需要在内存和磁盘之间进行交换。其编程也相对比较简单,且性能优秀。不过,在一个计算过程中生成的大数据集则会被直接提交给接口,其计算随即被中止掉。
根据我们的测试,如果所有在同一个多线程处理的子任务,合并它们的结果到一个相同的结果集里,其性能则可能会由于多个线程使用单一的资源进行同步操作,而受到严重的影响。可见,通过使用共享的最终数据集,内存的占用会有所减少。
更多的线程并不一定总是更好的,当线程超过CPU内核数时它就变得无效了。对于存放在外部存储设备中的数据而言,为了获取多线程处理的实际结果,测试是必要的。因为硬盘的并发能力(通常会小于CPU内核的数量),需要被考虑到。
根据记录的数量和每一段结束处的标记,来划分静态数据是很容易的。但是如果要平均地划分动态数据就比较麻烦了。本文将在下面更详细地予以讨论。
对于一个单一的计算任务而言,并行处理能够带来性能上的成倍增加。而由于OLAP的操作基本就是一个并发的事务,其提高了的性能在用户的数量很小的情况下可能会被抵消掉。因此我们需要有一种更好的方法。
3. 排序索引(Sorted Index)
因为非切片式的聚合操作总会牵扯到整体的数据立方体,所以我们几乎无法通过执行预聚合来减少计算量。但是对于切片操作(钻取)来说,如果数据立方体已经被排序,则没有必要去做全量遍历了。
如果我们能为D维度创建一个索引,这就意味着将它的值与对应的序列号记录关联上了,并形成了一定的排列顺序。然后我们就能够快速地定位那些包含在D维度里符合切片标准的记录了。这是一个简单的二分查找。无需全量遍历所有的数据,其计算量将能够降低好几个数量级(这也取决于D的取值范围)。理论上说,我们可以为每个维度创建一个索引,因为其开销并不昂贵。而且在涉及到相应的切片时,其性能会有大幅提升。
不过,那种包含有D1和D2维度的多个字段索引实际上却鲜少被用到。因为它不能快速地定位到只包含D2维度的切片,它只是对同时包含D1和D2的切片非常有效。在定位到了记录一个包含着最大取值范围的维度切片之后,大量的计算将会被相应地大幅减少。当然,我们也可以通过他们的维度去遍历其他的切片。
不幸的是,这种原始的方法只适用于处理那些允许频繁、小额存取的内存中的数据。在大多数情况下,我们要处理的数据集还是相当大的,而且需要存储在磁盘之上。但是就算通过索引,检索那些大量无序记录的操作对于性能提升影响也不很大。数据只有在被真正排序,和切片里的记录在被连续存储时,其性能才会有明显的提升。
由于各种数据需要根据特定维度的排序目的来进行复制,因此其成本还是相当高的。
有一个用来创建两份数据拷贝的解决方案是:一处的数据按照D1,…,Dn维度进行排序,而另一处的数据则按照Dn,…,D1维度来排序。由此产生的数据量只是原来的两倍,这还是可以接受的。通过该二维序列,就有了一处切片维度总是从首部开始降序排列的,确保了此维度的切片数据在整体上是连续的,从而能够获得了更好的性能提高。
4. 压缩列式存储(Compressed Column Storage)
处理多维分析的一个强大工具是:列式存储。
通常情况下,我们在对数据立方体进行多维分析时,会有大量的字段(维度和测度),他们从数十到数百不等。但是其中真正有用的却并不多,如果不考虑切片维度的话,通常也就只有5个或更少。由于切片能够被索引来进行处理,那么只需要对某几个字段遍历便可。
基于这个考虑,列式存储正好可以发挥其优势。在外部存储的计算中,I/O的操作是非常耗时的。因此,与减少计算的数量相比,减少要检索的数据以提升性能就显得更有意义了。比如说一个具有100个字段的数据立方体,如果只检索五个字段的话,其I/O消耗将下降到原来的二十分之一,这会导致性能的数量级飙升。
列式存储的另一个优点是:它支持数据的压缩。在排序和存储数据的D1,…,Dn维度时,我们发现D1在一连串的记录中有着相同的值;而D2在一个较少的连续记录中也是如此;以此类推,在越来越少的连续记录中,都会有这样的相同值出现;直到Dn中几乎没有了这样的连续性。考虑我们没有必要去存储那些反复出现的连续相同值,我们完全可以一次性存储,并记录下它们的数字。通过这种减少数据占用空间的方法,我们就能减少对外部存储的I/O访问,并提高性能。
在使用列式存储时,我们还需考虑如下一些问题。
由于列式存储不会减少计算的数量,它对在内存中操作数据的帮助并不大。但其压缩存储的方案却能够有效地减少内存的消耗。
列式存储会复杂化基于段的并行处理和索引的创建。列的分割需要保持彼此的一致性,而索引则需要同时地且准确地参考所有的列。而当使用压缩的列式存储时,则会更加麻烦。虽然有这些繁琐的问题,但是一般来说,对于静态数据使用列式存储并不是太难(只是不要忘了对它们的处理)。
列式存储的使用会增加并发压力的风险。当字段的总数不多或我们需要检索太多的字段时,它会失去本身的优势。通过硬盘来额外地使用并行处理,将会进一步地增加并发的压力,也可能会导致性能的下降。因此,能够更好地支持并发性的SSD会更适用一些。
本文作者:陈峻译
来源:51CTO