1、前言
上一篇 Impala SQL on Kudu优化谈到了如何从执行计划的角度来优化查询SQL,感兴趣的同学可以去回看关注下(Impala SQL on Kudu优化(一)_一个数据小开发的博客-CSDN博客),本篇优化将主要从建表层面来谈谈优化。
2、概念&理念
官网上对分区表也有一些表述,大家可以看看。Apache Kudu - Using Apache Kudu with Apache Impala
根据主键列上的分区schema,表被划分为tablets。每个tablets至少有一个tablet服务器。理想情况下,一个表应该被划分为分布在多个tablets服务器上的tablet,以最大化并行操作。您使用的分区schema的细节完全取决于您存储的数据类型以及如何访问它。有关Kudu中schema设计的完整讨论,请参阅Schema设计。
Kudu目前没有在创建表后拆分或合并tablets的机制。在创建表时,必须为它提前提供分区模式。在设计表时,考虑使用主键,这将允许您将表数据平均划分到不同的的tables中并保持数据匀速增长。
您可以使用Impala的partition BY关键字对表进行分区,该关键字支持RANGE或HASH分布。分区方案可以包含零个或多个HASH定义,后面跟着一个可选的RANGE定义。RANGE定义可以引用一个或多个主键列。基本分区和高级分区的示例如下所示。
2.1 分区类型描述
2.1.1 RANGE分区(PARTITION BY RANGE)
可以为一个或多个主键列指定范围分区。Kudu中的范围分区允许基于所选分区键的特定值或值的范围来拆分表。这允许您平衡读和写的并行度和效率。
假设您有一个包含state、name和purchase_count列的表。下面的示例创建50个tablets,每个美国州一个。
CREATE TABLE customers (
state STRING,
name STRING,
purchase_count int,
PRIMARY KEY (state, name)
)
PARTITION BY RANGE (state)
(
PARTITION VALUE = 'al',
PARTITION VALUE = 'ak',
PARTITION VALUE = 'ar',
-- ... etc ...
PARTITION VALUE = 'wv',
PARTITION VALUE = 'wy'
)
STORED AS KUDU;
注意事项:
如果在一个值单调递增的列上按范围划分,则最后一个tablet将比其他tablet增长得大得多。此外,所有插入的数据都将一次写入单个tablet,这限制了数据摄取的可伸缩性。在这种情况下,可以考虑使用HASH来代替RANGE。
2.1.2 HASH分区(PARTITION BY HASH)
不是通过显式的范围分布,或者结合范围分布,你可以通过哈希分布到特定数量的“桶”中。您可以指定要分区的主键列,以及要使用的存储桶的数量。行是通过散列指定的键来分布的。假设被散列的值本身没有明显的倾斜,这将有助于将数据均匀地分布到各个桶。
您可以指定多个定义,也可以指定使用复合主键的定义。但是,一个列不能在多个散列定义中提到。考虑两列,a和b: * √ HASH(a), HASH(b) * √ HASH(a,b) * X HASH(a), HASH(a,b)
如果主键值均匀地分布在它们的域中,并且没有明显的数据倾斜(如时间戳或串行id),那么哈希分区是一种合理的方法。
下面的示例通过哈希id和sku列来创建16个tablets。这将在所有16个tablets上进行书写。在本例中,对一系列sku值的查询可能需要读取全部16个tablets,因此这可能不是这个表的最佳模式。有关扩展示例,请参阅高级分区。
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH PARTITIONS 16
STORED AS KUDU;
注意事项:
不指定列的PARTITION BY HASH是通过对所有主键列进行散列来创建所需数量的bucket的快捷方式。
2.1.3 高级分区(Advanced Partitioning)
您可以组合HASH和RANGE分区来创建更复杂的分区模式。您可以指定零个或多个HASH定义,然后是零个或一个RANGE定义。每个定义可以包含一个或多个列。虽然枚举每个可能的分布模式超出了本文的范围,但有几个示例说明了其中的一些可能性。
a)Hash和Range一起用的分区
考虑上面的简单哈希示例,如果您经常查询一系列sku值,您可以通过结合哈希分区和范围分区来优化示例。
下面的示例仍然创建了16个tablet,首先将id列散列为4个bucket,然后根据sku字符串的值应用范围分区将每个bucket分割为4个tablet。写操作至少分布在4个平板电脑上(可能多达16个)。当查询连续的sku值范围时,很有可能只需要从四分之一的平板读取即可完成查询。
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH (id) PARTITIONS 4,
RANGE (sku)
(
PARTITION VALUES < 'g',
PARTITION 'g' <= VALUES < 'o',
PARTITION 'o' <= VALUES < 'u',
PARTITION 'u' <= VALUES
)
STORED AS KUDU;
b)两个不同字段列的Hash分区
再次展开上面的示例,假设查询模式是不可预测的,但您希望确保写分布在大量的tablets 上。您可以通过对两个主键列进行散列来实现整个主键的最大分布。
CREATE TABLE cust_behavior (
id BIGINT,
sku STRING,
salary STRING,
edu_level INT,
usergender STRING,
`group` STRING,
city STRING,
postcode STRING,
last_purchase_price FLOAT,
last_purchase_date BIGINT,
category STRING,
rating INT,
fulfilled_date BIGINT,
PRIMARY KEY (id, sku)
)
PARTITION BY HASH (id) PARTITIONS 4,
HASH (sku) PARTITIONS 4
STORED AS KUDU;
总结:
言外之意,也就是说,如果是那种分布比较均匀的数据,譬如说按照全国省份统计人口数,就可以使用range分区来存储。反之,如果是存储身份证号码这种明细数据,用hash分区来存储比较合适。
对于大型表,比如事实表,目标是在集群中有多少核心就有多少tablets 。
对于小型表,例如维度表,确保每个tablets 至少有1gb大小。
通常,在当前的实现中,要注意tablets 的数量限制了读取的并行性。如果tablets 的数量远远超过核心数量,那么效率和性能可能会递减。
写在最后,如果需要对Kudu表Range分区增加值,可以执行如下Impala语法
ALTER TABLE table_name ADD RANGE PARTITION col = col_vaule;
3、实战演练&验证
我们在日常生产中有很多系统会产生日志数据,日志数据到底应该怎么存,才会最大化的提升Kudu的性能?
首先分析下,系统日志,量一定很大,如果建不好表,那肯定是会带来灾难性的性能,日志是一条条的,都是事件数据,而每一个事件都必然会存在一个事件产生的时间,所以这里就很容易想到使用高级分区模式,Range和Hash两种模式一起使用来建表,然后通过 tablet-servers 这个查询页面,查询下一共有多少个tablet,建表的时候就采用多少个tablet个数
CREATE TABLE system_log(
log_id BIGINT,
log_time timestamp,
log_content STRING,
PRIMARY KEY (log_id , log_time )
)
PARTITION BY HASH (log_id) PARTITIONS 4, --这里的4取值的时候小于等于tablet servers 个数
RANGE (log_time )
(
PARTITION VALUES < '2021-09-20',
PARTITION '2021-09-20' <= VALUES < '2021-09-21',
PARTITION '2021-09-22' <= VALUES < '2021-09-23',
PARTITION '2021-09-24' <= VALUES
)
STORED AS KUDU;
经过测试发现,平常查看数据的时候,速度提升了百倍以上