3.4 管理Cube碎片
增量构建的Cube每天都可能会有新的增量。日积月累,这样的Cube中最终可能包含上百个Segment,这将会导致查询性能受到严重的影响,因为运行时的查询引擎需要聚合多个Segment的结果才能返回正确的查询结果。从存储引擎的角度来说,大量的Segment会带来大量的文件,这些文件会充斥所提供的命名空间,给存储空间的多个模块带来巨大的压力,例如Zookeeper、HDFS Namenode等。因此,有必要采取措施控制Cube中Segment的数量。
另外,有时候用户场景并不能完美地符合增量构建的要求,由于ETL过程存在延迟,数据可能一直在持续地更新,有时候用户不得不在增量更新已经完成后又回过头来刷新过去已经构建好了的增量Segment,对于这些问题,需要在设计Cube的时候提前进行考虑。
3.4.1 合并Segment
Kylin提供了一种简单的机制用于控制Cube中Segment的数量:合并Segments。在Web GUI中选中需要进行Segments合并的Cube,单击Action→Merge,然后在对话框中选中需要合并的Segment,可以同时合并多个Segment,但是这些Segment必须是连续的。单击提交后系统会提交一个类型为“MERGE”的构建任务,它以选中的Segment中的数据作为输入,将这些Segment的数据合并封装成为一个新的Segment(如图3-5所示)。这个新的Segment的起始时间为选中的最早的Segment的起始时间,它的结束时间为选中的最晚的Segment的结束时间。
图3-5 合并Segment
在MERGE类型的构建完成之前,系统将不允许提交这个Cube上任何类型的其他构建任务。但是在MERGE构建结束之前,所有选中用来合并的Segment仍然处于可用的状态。当MERGE构建结束的时候,系统将选中合并的Segment替换为新的Segment,而被替换下的Segment等待将被垃圾回收和清理,以节省系统资源。
用户也可以使用Rest接口触发合并Segments,该API在之前的触发增量构建中也已经提到过:
PUT http://hostname:port/kylin/api/Cubes/{CubeName}/rebuild
Path Variable
CubeName – 必须的,Cube名字
Request Body
startTime – 必须的,长整数类型的起始时间,例如使用1388563200000代表起始时间为2014-01-01
endTime – 必须的,长整数类型的结束时间
buildType – 必须的,构建类型,可能的值为‘BUILD’‘MERGE’和‘REFRESH’,分别对应于新建Segment、合并多个Segment,以及刷新某个Segment
我们需要将buildType设置为MERGE,并且将startTime设置为选中的需要合并的最早的Segment的起始时间,将endTime设置为选中的需要合并的最晚的Segment的结束时间。
合并Segment非常简单,但是需要Cube管理员不定期地手动触发合并,尤其是当生产环境中存在大量的Cube时,对每一个Cube单独触发合并操作会变得非常繁琐,因此,Kylin也提供了其他的方式来管理Segment碎片。
3.4.2 自动合并
在3.2.2节中曾提到过,在Cube Designer的“Refresh Settings”的页面中有“Auto Merge Thresholds”和“Retention Threshold”两个设置项可以用来帮助管理Segment碎片。虽然这两项设置还不能完美地解决所有业务场景的需求,但是灵活地搭配使用这两项设置可以大大减少对Segment进行管理的麻烦。
“Auto Merge Thresholds”允许用户设置几个层级的时间阈值,层级越靠后,时间阈值就越大。举例来说,用户可以为一个Cube指定(7天、28天)这样的层级。每当Cube中有新的Segment状态变为READY的时候,就会触发一次系统试图自动合并的尝试。系统首先会尝试最大一级的时间阈值,结合上面的(7天、28天)层级的例子,首先查看是否能将连续的若干个Segment合并成为一个超过28天的大Segment,在挑选连续Segment的过程中,如果遇到已经有个别Segment的时间长度本身已经超过了28天,那么系统会跳过该Segment,从它之后的所有Segment中挑选连续的累积超过28天的Segment。如果满足条件的连续Segment还不能够累积超过28天,那么系统会使用下一个层级的时间阈值重复寻找的过程。每当找到了能够满足条件的连续Segment,系统就会触发一次自动合并Segment的构建任务,在构建任务完成之后,新的Segment被设置为READY状态,自动合并的整套尝试又需要重新再来一遍。
举例来说,如果现在有A~H 8个连续的Segment,它们的时间长度分别为28天(A)、7天(B)、1天(C)、1天(D)、1天(E)、1天(F)、1天(G)、1天(H)。此时第9个Segment I加入,它的时间长度为1天,那么现在Cube中总共存在9个Segment。系统首先尝试能否将连续的Segment合并到28天这个阈值上,由于Segment A已经超过28天,它会被排除。接下来的B到H加起来也不足28天,因此第一级的时间阈值无法满足,退一步系统尝试第二级的时间阈值,也就是7天。系统重新扫描所有的Segment,发现A和B已经超过7天,因此跳过它们,接下来发现将Segment C到I合并起来可以达到7天的阈值,因此系统会提交一个合并Segment的构建请求,将Segment C到I合并为一个新的Segment X。X的构建完成之后,Cube中只剩下三个Segment,分别是原来的A(28天),B(7天)和新的X(7天)。由于X的加入,触发了系统重新开始整个合并尝试,但是发现已经没有满足自动合并的条件,既没有连续的、满足条件的、累积超过28天的Segment,也没有连续的、满足条件的、累积超过7天的Segment,尝试终止。
再举一个例子,如果现在有A~J 10个连续的Segment,它们的时间长度分别为28天(A)、7天(B)、7天(C)、7天(D)、1天(E)、1天(F)、1天(G)、1天(H)、1天(I)、1天(J)。此时第11个Segment K加入,它的时间长度为1天,那么现在Cube中总共存在11个Segment。系统首先尝试能否将连续的Segment合并到28天这个阈值上,由于Segment A已经超过28天,它会被排除。系统接着从Segment B开始观察,发现若把Segment B至K这10个连续的Segment合并在一起正好可以达到第一级的阈值28天,因此系统提交一个合并构建任务把B至K合并为一个新的Segment X,最终Cube中存在两个长度均为28天的Segment,依次对应原来的A和新的X。由于X的加入,触发了系统重新开始整个合并尝试,但是发现已经没有满足自动合并的条件,尝试终止。
“Auto Merge Thresholds”的设置非常简单,在Cube Designer的“Refresh Setting”中,单击“Auto Merge Thresholds”右侧的“New Thresholds”按钮,即可在层级的时间阈值中添加一个新的层级,层级一般按照升序进行排列(如图3-6所示)。从前面的介绍中不难得出结论,除非人为地增量构建一个非常大的Segment,自动合并的Cube中,最大的Segment的时间长度等于层级时间阈值中最大的层级。也就是说,如果层级被设置为(7天、28天),那么Cube中最长的Segment也不过是28天,不会出现横跨半年甚至一年的大Segment。
在一些场景中,用户可能更希望系统能以自然日的星期、月、年作为单位进行自动合并,这样在只需要查询个别月份的数据时,就能够只访问该月的Segment,而非两个毗邻的28天长度的Segment。对此,https://issues.apache.org/jira/browse/KYLIN-1865 记录了这个问题。
图3-6 设置自动合并阈值
3.4.3 保留Segment
从碎片管理的角度来说,自动合并是将多个Segment合并为一个Segment,以达到清理碎片的目的。保留Segment则是从另外一个角度帮助实现碎片管理,那就是及时清理不再使用的Segment。在很多业务场景中,只会对过去一段时间内的数据进行查询,例如对于某个只显示过去1年数据的报表,支撑它的Cube事实上只需要保留过去一年内的Segment即可。由于数据在Hive中往往已经存在备份,因此无需再在Kylin中备份超过一年的历史数据。
在这种情况下,我们可以将“Retention Threshold”设置为365。每当有新的Segment状态变为READY的时候,系统会检查每一个Segment:如果它的结束时间距离最晚的一个Segment的结束时间已经大于“Retention Threshold”,那么这个Segment将被视为无需保留。系统会自动地从Cube中删除这个Segment。
如果启用了“Auto Merge Thresholds”,那么在使用“Retention Threshold”的时候需要注意,不能将“Auto Merge Thresholds”的最大层级设置得太高。假设我们将“Auto Merge Thresholds”的最大一级设置为1000天,而将“Retention Threshold”设置为365天,那么受到自动合并的影响,新加入的Segment会不断地被自动合并到一个越来越大的Segment之中,糟糕的是,这会不断地更新这个大Segment的结束时间,从而导致这个大Segment永远不会得到释放。因此,推荐自动合并的最大一级的时间不要超过1年。
3.4.4 数据持续更新
在实际应用场景中,我们常常会遇到这样的问题:由于ETL过程的延迟,业务每天都需要刷新过去N天的Cube数据。举例来说,客户有一个报表每天都需要更新,但是每天的源数据更新不仅包含了当天的新数据,还包括了过去7天内数据的补充。一种比较简单的方法是,每天在Cube中增量构建一个长度为一天的Segment,这样过去7天的数据就会以7个Segment的形式存在于Cube之中。Cube的管理员除了每天要创建一个新的Segment代表当天的新数据(BUILD操作)以外,还需要对代表过去7天的7个Segment进行刷新(REFRESH操作,Web GUI上的操作及Rest API参数与BUILD类似,这里不再详细展开)。这样的方法固然可以奏效,但是每天为每个Cube触发的构建数量太多,容易造成Kylin的任务队列堆积大量未能完成的任务。
上述简单方案的另外一个弊端是,每天一个Segment也会让Cube中迅速地累积大量的Segment,需要Cube管理员手动地对历史已经超过7天的Segment进行合并,期间还必须小心翼翼地,不能错将7天内的Segment一起合并了。举例来说,假设现在有100个Segment,每个Segment代表过去的一天的数据,Segment按照起始时间排序。在合并时,我们只能挑选前面93个Segment进行合并,如果不小心把第94个Segment也一起合并了,那么当我们试图刷新过去7天(94~100)的Segment的时候,会发现为了刷新第94天的数据,不得不将1~93的数据一并重新计算,因为此时第94天的数据已经和1~93这93天的数据糅合在一个Segment之中了。这对于刷新来说是一种极大的浪费。糟糕的是,即使使用之前所介绍的自动合并的功能,类似的问题也仍然存在,目前为止,还没有一种机制能够有效阻止自动合并试图合并近期N天的Segment,因此使用自动合并仍然有可能将最近N天内的某些Segment与更早的其他Segment合并成一个大的Segment,这个问题将在https://issues.apache.org/jira/browse/KYLIN-1864中获得解决。
目前来说,比较折中的一种方案是不以日为单位创建新的Segment,而是以N天为单位创建新的Segment。举例来说,假设用户每天更新Cube的时候,前面7天的数据都需要更新一下,也就是说,如果今天是01-08,那么用户不仅要添加01-08的新数据,还要同时更新01-01到01-07的数据。在这种情况下,可设置N=7作为最小Segment的长度。在第一天01-01,创建一个新的Segment A,它的时间是从01-01到01-08,我们知道Segment是起始时间闭,结束时间开,因此Segment A的真实长度为7天,也就是01-01到01-07。即使在01-01当天,还没有后面几天的数据,Segment A也能正常地构建,只不过构建出来的Segment其实只有01-01一天的数据而已。从01-02到01-07的每一天,我们都要刷新Segment A,以保证1日到7日的数据保持更新。由于01-01已经是最早的日期,所以不需要对更早的数据进行更新。
到01-08的时候,创建一个新的Segment B,它的时间是从01-08到01-15。此时我们不仅需要构建Segment B,还需要去刷新Segment A。因为01-01到01-07中的数据在01-08当天仍然可能处于更新状态。在接下来的01-09到01-14,每天刷新A、B两个Segment。等到了01-15这天的时候,首先创建一个新的Segment C,它的时间是从01-15到01-22。在01-15当天,Segment A的数据应当已经被视作最终状态,因为Segment A中的最后一天(01-07)已经不再过去N天的范围之内了。因此此时接下来只需要照顾Segment B和Segment C即可。
由此可以看到,在任意一天内,我们只需要同时照顾两个Segment,第一个Segment主要以刷新近期数据为主,第二个Segment则兼顾了加入新数据与刷新近期数据。这个过程中可能存在少量的多余计算,但是每天多余计算的数据量不会超过N天的数据量。这对于Kylin整体的计算量来说是可以接受的。根据业务场景的不同,N可能是7天,也有可能是30天,我们可以适度地把最小的Segment设置成比N稍微大一点的数字,例如N为7的时候,我们可以设置为10天,这样即使ETL有时候没有能够遵守N=7的约定,也仍然能够刷新足够的数据。值得一提的是,在https://issues.apache.org/jira/browse/KYLIN-1864得到解决之前,我们不要重叠使用自动合并和本节中所描述的处理数据陆续更新的策略。