AnalyticDB(简称ADS,以下ADS均代表AnalyticDB) 是阿里巴巴自研的一款面向互联网,高并发的在线数据库。目前已经广泛应用于阿里巴巴集团内部,并在阿里公有云上进行售卖,同时也可以通过阿里专有云的形式向大客户提供高效的计算分析能力。
由于ADS产品设计的目的就是要解决PB级,千万级到万亿记录的复杂查询性能。对一些传统数据库的用户来说有些概念有很大不同,需要理解其背后含义才能充分发挥ADS的最强性能。在所有的名词中分区可能是较难理解,同时又对ADS性能影响最大的一个关键点。本文会对分区的概念进行解释,并帮助用户选择合适的分区,并尽可能修改查询来更加合理的使用分区从而提高查询性能。
如下图所示,LocalNode是ADS处理数据的计算节点。
上图中假设这个Database有m个LocalNode,表有l个分区,这个表有n列。并且这个表设计了2级分区,一共保存了90天的二级分区,每个二级分区保存一天的数据。可以看出每个LocalNode会处理 [l/m] 或者 [l/m]+1 个一级分区,每个一级分区都会保存90个二级分区的数据。每个二级分区内都保存这个表在这一天的所有列(col1,col2......coln)。
ADS的一级分区最大的目的是将数据均匀分散,这样可以充分发挥ADS分布式计算的能力,一级分区是HASH分区。而二级分区更像传统数据库的分区,目的是将数据分散到不同的存储文件上。
如上图所示每个Database在创建的时候都会根据用户选择自动创建多个LocalNode来并发的处理数据。举个例子,如果我们创建了一个database,这个database创建的时候选择有8个ECU,对应会有8个LocalNode在同时提供服务。如果我们现在有一张订单表(orders)有8000万条记录。表的结构如下:
那如何将这8000万条记录分到这8个LocalNode去处理呢? ADS使用分区列来决定怎么将8000万条记录分配到8个LocalNode,也就是上图ddl中的partition by hash key(order_id)中选择的order_id作为分区列。ADS会对order_id 进行函数计算来决定分配到哪个分区,ADS内部同时又会对256个分区进行计算来决定是哪个LocalNode去处理哪个分区。
这里简单起见我们假设所有的算法都是用取模来完成,也就是说每个LocalNode会处理(256/8)=32个分区。
如果我们假设order_id没有重复的,也就是说count(distinct order_id)=8000万,这样每个分区的记录数大约为 80,000,000/256= 31,250条记录。 根据上图所示可知,每个LocalNode处理 31,250*32 =10,000,000条记录。这样看来似乎非常简单,但这里有个最关键的概念就是数据平均分配,而且我们假设order_id是没有重复值的,但这样的情况在现实中不一定完全满足。
如果我们的查询有90%的是按照store_id进行统计分析,那这时候用order_id作为分区列就不一定是最佳选择。如果我们打算用store_id来作为分区列,我们根据经验也知道store_id在订单系统中不可能每条记录都是唯一的,也就是说
但是我们还是计划使用store_id来作为分区列,那如何在数据导入ADS前就知道是否存在数据倾斜呢?如果原始数据在odps或者oracle等其他数据库里的话,可以使用如下sql:
根据上述两个SQL的查询结果,有下面几种情况:
如果上面第一个查询里store_id大于分区数目,并且第二个查询里top_cnt小于 total_cnt/分区数(80,000,000/256) 31250条记录的话,可以大概率认为这个数据是没有倾斜的,store_id可以作为分区列。
如果上面第一个查询里store_id 数目小于分区列的数目,那store_id不适合作为分区列,必须使用其它列。
如果第一个查询store_id大于分区数目,但是第二个查询store_id对应的记录数大于 31250,数据会有倾斜,这时候必须先把数据导入到ADS,并尝试执行查询,如果效率还可以接受,并且数据倾斜也在可以接受范围内的话才可以继续使用,但从长远来看尽量不建议这样选择分区列。
二级分区列的选择相对一级分区列较为简单,我们推荐尽量选择和时间有关的列来作为二级分区列,因为二级分区列有一个最大的特点是会按照数量滚动删除。和上面的例子相同,我们修改为增加二级分区列,增加ds这个列,按照每天的交易数据把数据归并到某个二级分区里。
这里的二级分区列我们使用交易日期ds,按照上图所示available_partition_num = 90,也就是说我们会保存90天的历史数据,当第91天的记录进入到ADS后,ADS会自动把91天前的记录删除,这样的设计可以极大的简化DBA的日常运维工作。
分区数目过少,小于LocalNode的数目,如上图所示,如果分区数目过少,可能会出现有的LocalNode没有分区需要处理。假设分区数目为8个,而LocalNode的个数为16个,那必然会出现有8个LocalNode没有任何数据需要处理,这时候资源会出现浪费现象。
分区数目不是LocalNode的倍数,如上图所示,如果某个表的分区数目是100个,而LocalNode的个数是64个,那么必然会出现有36个LocalNode处理2个分区,而另外28个分区需要处理1个分区。这时候的查询有很可能会出现处理36个分区的LocalNode成为慢节点。
二级分区数目过多,例如有的表每天只有30w的数据,但是需要存储1年的数据,这时候如果每天的记录都作为二级分区会导致查询效率降低。最佳选择是每个月做一个二级分区,这样每个二级分区大概有900w数据,一共有12个二级分区。
避免数据倾斜是分区的最主要目的,在数据均匀分布的情况下尽可能将查询中最常使用的谓词列或者多表Join的时候JOIN的列作为分区列可以极大提高查询效率。
在建表语句确定以后,我们在查询语句中如何使用分区列来加速我们的查询呢?下面是几个最佳实践
假设我们的order_id和store_id是有对应关系的,例如order_id前5位就是store_id的,如果从开发角度,我们可以直接写
这样的查询,但如果想提高sql的查询性能,最好使用
如果我们要查询所有商店的某一天时间段的交易数据,我们可以直接写
这样的查询,但这样的查询会导致ADS在所有的二级分区都进行一遍检索。所以如果想提高sql的查询性能,可以使用下面这样的SQL:
这样ADS可以只在20171110这个分区去做检索,提高查询效率。
为了充分发挥并行计算的特点,ADS设计了一级分区列的概念,一级分区列的首要选择是避免数据倾斜。其次要尽量选择会参与谓词运算的列。同时为了减少DBA的负担,ADS又设计了二级分区的概念,二级分区列尽量按照时间去选择,二级分区列尽量也参与到谓词运算同样可以提高查询效率。
根据上文的介绍,相信用户一定能对ADS的分区概念有更深刻的理解,只要用户能设计较好的分区列,并对SQL进行简单改造,就可以体验百亿级记录表在秒级或者毫秒级返回查询结果的震撼效果,更好的帮助分析人员对业务进行快速查询,从数据中更快更多的发掘真正的业务价值。