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万条记录。表的结构如下:
create table orders(
order_id long,
store_id long,
order_total float,
order_time timestamp,
order_comment varchar,
...
)
partition by hash key(order_id)
partition number(256);
那如何将这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在订单系统中不可能每条记录都是唯一的,也就是说
count(distct store_id) <> count(*)
但是我们还是计划使用store_id来作为分区列,那如何在数据导入ADS前就知道是否存在数据倾斜呢?如果原始数据在odps或者oracle等其他数据库里的话,可以使用如下sql:
select count(distinct store_id),count(*) as total_cnt from orders ;
select store_id,count(*) as top_cnt from orders group by store_id order by cnt desc limit 1;
根据上述两个SQL的查询结果,有下面几种情况:
二级分区列的选择相对一级分区列较为简单,我们推荐尽量选择和时间有关的列来作为二级分区列,因为二级分区列有一个最大的特点是会按照数量滚动删除。和上面的例子相同,我们修改为增加二级分区列,增加ds这个列,按照每天的交易数据把数据归并到某个二级分区里。
create table orders(
order_id long,
store_id long,
order_total float,
order_time timestamp,
order_comment varchar,
...
)
partition by hash key(store_id)
partition number(256)
SUBPARTITION BY LIST KEY (ds bigint)
SUBPARTITION OPTIONS (available_partition_num = 90);
这里的二级分区列我们使用交易日期ds,按照上图所示available_partition_num = 90,也就是说我们会保存90天的历史数据,当第91天的记录进入到ADS后,ADS会自动把91天前的记录删除,这样的设计可以极大的简化DBA的日常运维工作。
在建表语句确定以后,我们在查询语句中如何使用分区列来加速我们的查询呢?下面是几个最佳实践
假设我们的order_id和store_id是有对应关系的,例如order_id前5位就是store_id的,如果从开发角度,我们可以直接写
select * from orders where order_id=99999111;
select * from orders where order_id=88888222;
这样的查询,但如果想提高sql的查询性能,最好使用
select * from orders where order_id=99999111 and store_id=99999;
select * from orders where order_id=88888222 and store_id=88888;
如果我们要查询所有商店的某一天时间段的交易数据,我们可以直接写
select store_id,sum(order_total) from orders where order_time <'2017-11-10 18:00:00' and order_time > '2017-11-10 08:00:00' group by store_id;
这样的查询,但这样的查询会导致ADS在所有的二级分区都进行一遍检索。所以如果想提高sql的查询性能,可以使用下面这样的SQL:
select store_id,sum(order_total) from orders where ds=20171110 and order_time <'2017-11-10 18:00:00' and order_time > '2017-11-10 08:00:00' group by store_id;
这样ADS可以只在20171110这个分区去做检索,提高查询效率。
为了充分发挥并行计算的特点,ADS设计了一级分区列的概念,一级分区列的首要选择是避免数据倾斜。其次要尽量选择会参与谓词运算的列。同时为了减少DBA的负担,ADS又设计了二级分区的概念,二级分区列尽量按照时间去选择,二级分区列尽量也参与到谓词运算同样可以提高查询效率。
根据上文的介绍,相信用户一定能对ADS的分区概念有更深刻的理解,只要用户能设计较好的分区列,并对SQL进行简单改造,就可以体验百亿级记录表在秒级或者毫秒级返回查询结果的震撼效果,更好的帮助分析人员对业务进行快速查询,从数据中更快更多的发掘真正的业务价值。