数据分片(Sharding)是分布式数据库分而治之 (Divide And Conquer) 这一规划思维的体现。曩昔的单机数据库在大数据量下往往面临存储和 IO 的约束,而分布式数据库则通过数据区别的规矩,将数据打散分布至不同的机器或节点上,形成分布式存储,因而突破了单机存储空间和 IO 的瓶颈、使库表数据量能够无限拓宽。

数据分片主要有规模分片或哈希分片这两种方法,而在实践数据库的实现中,往往呈现为分区和分桶两种方法。分区一般是按照时刻或其他连续值对数据进行区别,在履行查询操作时能够通过分区裁剪过滤不必要的规模扫描,进步履行功率,一起也使得对分区数据的增删改等管理操作更为快捷。而分桶则是按照某个关键字履行哈希运算,将相同哈希值的数据放到一起,这样能够有用定位数据、防止数据倾斜。

在 Apache Doris 中,相同也遵照必定的数据分布规矩。数据以联系表(Table)的方法进行呈现,会顺次按照先分区(Partition)、再分桶(Bucket)的方法区别,终究在同一个分桶中的数据会形成数据分片(Tablet)。Tablet 是 Apache Doris 中多副本高可用、集群间数据调度与均衡的最小物理存储单位。

一文教你玩转 Apache Doris 分区分桶新功能

图1:Table-Partition-Tablet 之间的联系

# 现状与问题

在 Doris 中,分区与分桶是怎么创立的?咱们以一个网站站点的建表实例阐明分区与分桶的创立方法,该网站的站点建表句子如下:

--该表记录了某个时刻点,在某个站点上各个用户的pv数据
CREATETABLEdemo.test_tbl(
sdateDATETIME,--日期
siteINT,--站点id
cityVARCHAR(64),--城市
userVARCHAR(32)DEFAULT'',--用户名
pvBIGINT--pv量
)ENGINE=olapDUPLICATEKEY(sdate,site,city)
[PARTITION_DESC]
[BUCKET_DESC]
PROPERTIES("replication_num"="1");

其中 [PARTITION_DESC] 表明创立分区的详细句子,[BUCKET_DESC] 表明创立分桶的句子。

创立分区

Apache Doris 支撑两种分区方法,List Partition 与 Range Partition。

List Partition

List Partition 相当于对分区的列值进行枚举,因而挑选的分区列最好是有区别度的可枚举值,例如本例中的 city。依据 city 列的枚举值创立多个 List Partition,则PARTITION_DESC能够写为:

--以city作为分区列,创立华北、东北、华中、西南等分区
PARTITIONBYLIST(city)
(
PARTITION`p_huabei`VALUESIN("beijing","tianjin","shijiazhuang"),
PARTITION`p_dongbei`VALUESIN("shenyang","dalian"),
PARTITION`p_huazhong`VALUESIN("wuhan","changsha")
PARTITION`p_xinan`VALUESIN("chengdu","chongqing")
)

Range Partition

创立 Range partition 一般运用时刻列,Range Partition 又能够分为静态和动态两种方法:

– 静态 Range Partition

此类 Partition 的创立会生成一个左闭右开的区间,定义一个分区只需求指定右鸿沟,该分区的左鸿沟由上一个分区的右鸿沟确认,PARTITION_DESC能够写为:

--以sdate这个时刻列作为分区列,
--日期处于[min, 2023-01-01)的数据,都放到名为p2022的分区下;
--日期处于[2023-01-01, 2023-01-02)的数据,都放到名为p20230101的分区下;
--日期处于[2023-01-02, 9999-12-31)的数据,都放到名为pmax的分区下;
PARTITIONBYRANGE(sdate)
(
PARTITION`p2022`VALUESLESSTHAN("2023-01-01"),
PARTITION`p20230101`VALUESLESSTHAN("2023-01-02"),
PARTITION`pmax`VALUESLESSTHAN("9999-12-31")
)

能够看出,p20230101 这个分区的左鸿沟由 p2022 分区的右鸿沟确认,而 pmax 的左鸿沟由 p20230101 的右鸿沟确认。需留意的是,此处为了举例阐明动态分区,运用了一个很大的边”9999-12-31″,实践事务中很少会直接创立从 2023-01-02 到 9999-12-31 的分区。

– 动态 Range Partition

上述静态的分区需求手动指定鸿沟,分区个数太多运用起来也不便利。动态 Range Partition 协助咱们处理了这个问题,只需指定一些分区的参数即可动态创立,PARTITION_DESC相对更简单,只需指定哪个列作为分区列即可:

PARTITIONBYRANGE(sdate)()

剩余参数需求在PARTITION进行装备:

PROPERTIES(
"dynamic_partition.enable"="true",
"dynamic_partition.time_unit"="DAY",
"dynamic_partition.start"="-30",
"dynamic_partition.end"="3",
"dynamic_partition.prefix"="p",
"dynamic_partition.create_history_partition"="true",
"replication_num"="1"
);

动态分区参数阐明如下:

一文教你玩转 Apache Doris 分区分桶新功能

创立分桶

分桶在物理层面即数据分片(Tablet)。在数据表完结分区后,指定部分列作为分桶列,将这些列数据中相同哈希值的数据合到一起,形成了 Tablet。一个表中Tablet 总数量 = 分区数(Partition num)x 分桶数(Bucket num)x 数据副本数(Replication_num)

[BUCKET_DESC] 句子十分简单,只需求一句:

DISTRIBUTEDBYHASH(site)BUCKETS20

此刻指定以 site 列的哈希值作为分桶,而且分桶个数设置为 20 个,需求留意的是这里的 20 仅作为示例,合适的分桶个数需求依据分区巨细来确认。实践上单个分桶即 Tablet 的数据量理论上没有上下界,但建议在1GB – 10GB的规模内,即假定分区巨细为 20GB,那么分桶个数设置为 10-20 个是合适的。

缺乏与考虑

从以上对分区别桶的介绍,信任有不少用户和读者仍能发现其中一些缺乏之处:

  • 分区数量过多的状况下,运用 List Partition 或许静态 Range Partition 会使得SQL 较为繁琐,编写起来费时费力
  • 若是运用动态 Range Partition,则需求把握多个参数,运用方法不友好且学习本钱较高;而当存在很多前史冷数据来说,动态 Range Partition 只能指定单一粒度,无法灵敏组合不同的分区粒度
  • 分桶个数的设置十分依靠用户对 Apache Doris 数据分布机制和事务数据自身的了解,运用门槛较高。不合理的分桶设置将对系统功用和稳定性形成必定程度冲击:分桶数太多将导致单个 Tablet 的数据量过小,数据聚合作用不佳、查询功用不能得到有用发挥,而且元数据管理压力大;个数太少则单个 Tablet 包括的数据量过大,不利于副本的搬迁、补齐,且会增加 Schema Change 或许 Rollup 操作失利重试的价值。

# 批量分区与Auto Bucket的规划与实现

克服数据库的复杂性是 Apache Doris 一直追求的方针之一,针对以上分区别桶存在的易用性问题,在 Apache Doris 最新的版别中现已得到处理。在Apache Doris 1.2.1 版别中,咱们新增了批量创立分区功用,简练的语法和灵敏的运用方法让批量创立前史分区愈加得心应手;而针对分桶设置带来的学习本钱,Apache Doris 在即将发布的 1.2.2 版别中新增了 Auto Bucket 主动分桶核算功用,分桶个数不再依靠于人工设置,通过规矩的智能核算即可确保合理的数据区别,降低用户学习本钱的一起还能够最大化进步用户开发功率。

批量创立分区

批量创立分区功用在前期充沛调研了用户的需求,本着简练、强壮、易用的规划方针,将规划中心锁定在几个要素中:

  • 时刻区间规模(会考虑开闭问题)
  • 时刻跨度(即每个分区的时刻维度的巨细)
  • 时刻单位(年、月、日、时、周等)

结合前面提到的网站站点模型,假定其数据包括从几年前直到现在的全量信息,想要将十年内的数据按每一天一个分区进行创立。在批量分区功用中,PARTITION_DESC只需求一句,而且不用在PARTITION中设置分区相关参数:

--当然,分区创立个数遭到max_multi_partition_num参数控制,该值默认为4096,有需求能够修正
PARTITIONBYRANGE(sdate)
(
FROM("2013-01-01")TO("2023-01-01")INTERVAL1DAY
)从这个 case 来看,批量分区功用的语法更为简练,但该功用的易用性和灵敏性远不止于此。

从这个 case 来看,批量分区功用的语法更为简练,但该功用的易用性和灵敏性远不止于此。

假定有另一批数据:公司前几年的数据量较大且为冷数据,故能够将一年的数据合到一个分区里边;而后来因为事务迅速发展,需求将每一月的数据作为一个分区;随着公司事务进一步发展,按月分区现已不能满意快速增长的数据需求,需求按周进行分区;……;时至今日,公司每天发生海量数据,或许需求按小时分区才干符合需求。依据这个场景,不难写出批量分区创立的PARTITION_DESC:

--此处需求留意,假如要运用小时等级的分区,则分区列有必要是datetime类型
--相同的,分区创立个数也遭到max_multi_partition_num参数控制
PARTITIONBYRANGE(sdate)
(
FROM("2000-01-01")TO("2021-01-01")INTERVAL1YEAR,
FROM("2021-01-01")TO("2022-01-01")INTERVAL1MONTH,
FROM("2022-01-01")TO("2023-01-01")INTERVAL1WEEK,
FROM("2023-01-01")TO("2023-02-01")INTERVAL1DAY,
FROM("2023-02-0100")TO("2099-12-3123")INTERVAL1HOUR
)

除了上述不一起刻粒度的分区能够灵敏组合外,还能够将静态 Range Partition 和批量分区功用结合起来。例如需求将该公司 2022-01-01 到 2023-01-01 的数据按天创立分区,2022-01-01 之前的数据归到一个名为”pold”分区中,咱们能够将静态分区和批量分区组合起来,PARTITION_DESC如下:

PARTITIONBYRANGE(sdate)
(
PARTITIONpoldVALUESLESSTHAN("2022-01-01"),
FROM("2022-01-01")TO("2023-01-01")INTERVAL1DAY
)

批量分区创立功用支撑不一起刻粒度,其语法简练有力,且各种类型分区能够灵敏组合,在面对很多前史分区和部分特别分区的需求时,该功用显得挥洒自如,能够极大进步开发功率。 批量分区功用 PR:github.com/apache/dori…****

Auto Bucket 主动分桶核算

以往创立分桶时需求手动设定分桶数,而主动分桶核算功用是 Apache Doris 能够动态地核算分桶个数,使得分桶数始终保持在一个合适规模内,让用户不再操心桶数的细枝末节。首要阐明一点,为了便利论述该功用,该部分会将桶拆分为两个时期的桶,即初始分桶以及后续分桶。 (这里的初始和后续只是本文为了描绘清楚该功用而采用的术语,Apache Doris 分桶自身没有初始和后续之分) 从上文中创立分桶一节咱们知道,BUCKET_DESC十分简单,可是需求指定分桶个数;而在主动分桶核算功用上,BUCKET_DESC的语法直接将分桶数改成”Auto“,并新增一个 Properties 装备即可:

--旧版别指定分桶个数的创立语法
DISTRIBUTEDBYHASH(site)BUCKETS20
--新版别运用主动分桶核算的创立语法
DISTRIBUTEDBYHASH(site)BUCKETSAUTO
properties("estimate_partition_size"="100G")

新增的装备参数estimate_partition_size表明一个单分区的数据量。该参数是可选的,假如没有给出则 Doris 会将estimate_partition_size的默认值取为 10GB。从上文中现已得知,一个分桶在物理层面就是一个Tablet,为了取得最好的功用,建议 Tablet 的巨细在 1GB – 10GB 的规模内。那么主动分桶核算是怎么确保 Tablet 巨细处于这个规模内的呢?总结起来不外乎几个原则:

  • 若是全体数据量较小,则分桶数不要设置过多
  • 若是全体数据量较大,则应使桶数跟总的磁盘块数相关,充沛利用每台 BE 机器和每块磁盘的能力

初始分桶核算

从原则动身,了解主动分桶核算功用的详细逻辑就变得简单了:首要来看初始分桶:

  1. 先依据数据量得出一个桶数 N。首要运用estimate_partition_size的值除以 5(按文本格式存入 Doris 中有 5 比 1 的数据压缩比核算),得到的成果为:
    • < 100MB,则取 N=1
    • < 1GB,则取 N=2
    • = 1GB,则每一个 GB 一个分桶

  1. 依据 BE 节点数以及每个 BE 节点的磁盘容量,核算出桶数 M。其中每个 BE 节点算 1,每 50G 的磁盘容量算 1,那么 M 的核算规矩为:*M = BE 节点数 ( 一块磁盘块巨细 / 50GB) * 磁盘块数, 例如有 3 台 BE,每台 BE 都有 4 块 500GB 的磁盘,那么 M = 3 * (500GB / 50GB) * 4 = 1203. 得到终究的分桶个数核算逻辑:
    • 先核算一个中心值 x = min(M, N, 128),
    • 假如 x < N而且x < BE节点个数,则终究分桶为 y 即 BE 节点个数;不然终究分桶数为 x

上述过程伪代码表现方法为:

intN=核算N值;
intM=核算M值;
inty=BE节点个数;
intx=min(M,N,128);
if(x<N&&x<y){
returny;
}
returnx;

有了上述算法,咱们再引进一些例子来更好地了解这部分逻辑:

case 1:

数据量 100 MB,10 台 BE 机器,2TB * 3 块盘

数据量 N = 1

BE 磁盘 M = 10 * (2TB/50GB) * 3 = 1230

x = min(M, N, 128) = 1

终究: 1

case 2:

数据量 1GB, 3 台 BE 机器,500GB * 2 块盘

数据量 N = 2

BE 磁盘 M = 3 * (500GB/50GB) * 2 = 60

x = min(M, N, 128) = 2

终究: 2

case 3:

数据量 100GB,3 台 BE 机器,500GB * 2 块盘

数据量 N = 20

BE 磁盘 M = 3 * (500GB/50GB) * 2 = 60

x = min(M, N, 128) = 20 *

终究: 20*

case 4:

数据量 500GB,3 台 BE 机器,1TB * 1 块盘

数据量 N = 100

BE 磁盘 M = 3 * (1TB /50GB) * 1 = 60

x = min(M, N, 128) = 63

终究: 63

case 5:

数据量 500GB,10 台 BE 机器,2TB * 3 块盘

数据量 N = 100

BE 磁盘 M = 10 * (2TB / 50GB) * 3 = 1230

x = min(M, N, 128) = 100

终究: 100

case 6:

数据量 1TB,10 台 BE 机器,2TB * 3 块盘

数据量 N = 205

BE 磁盘 M = 10 * (2TB / 50GB) * 3 = 1230

x = min(M, N, 128) = 128

终究: 128

case 7:

数据量 500GB,1 台 BE 机器,100TB * 1 块盘

数据量 N = 100

BE 磁盘 M = 1 * (100TB / 50GB) * 1 = 2048

x = min(M, N, 128) = 100

终究: 100

case 8:

数据量 1TB, 200 台 BE 机器,4TB * 7 块盘

数据量 N = 205

BE 磁盘 M = 200 * (4TB / 50GB) * 7 = 114800

x = min(M, N, 128) = 128

终究: 200

能够看到,详细逻辑与原则是匹配的。

后续分桶核算

上述是关于初始分桶的核算逻辑,后续分桶数因为现已有了必定的分区数据,能够依据已有的分区数据量来进行评价。后续分桶数会依据最多前 7 个分区数据量的 EMA[1](短期指数移动平均线)值,作为estimate_partition_size进行评价。此刻核算分桶有两种核算方法,假定以天来分区,往前数第一天分区巨细为 S7,往前数第二天分区巨细为 S6,顺次类推到 S1;

  1. 假如 7 天内的分区数据每日严格递增,则此刻会取趋势值

有6个delta值,分别是S7 – S6 = delta1,S6 – S5 = delta2,…S2 – S1 = delta6由此得到平均的delta值:avg_delta = (delta1 + delta2 + … + delta6) / 6 = (S7 – S1) / 6那么,今天的estimate_partition_size = S7 + avg_delta

  1. 非第一种的状况,此刻直接取前几天的 EMA 平均值

今天的 estimate_partition_size = EMA(S1, …, S7)

依据上述算法,初始分桶个数以及后续分桶个数都能被核算出来。跟之前只能指定固定分桶数不同,由于事务数据的改变,有或许前面分区的分桶数和后面分区的分桶数不一样,这对用户是透明的,用户无需关怀每一分区详细的分桶数是多少,而这一主动核算的功用会让分桶数愈加合理。

主动分桶核算功用 PR:github.com/apache/dori…

作用

当咱们有了合适的分区别桶时,导入数据导到 Doris 后,数据会按照建表句子中的分区别桶列进行存储。上述网站站点数据的存储示例如图示:

一文教你玩转 Apache Doris 分区分桶新功能

图2:Doris 分区别桶后的数据存储

此刻假如履行 SQL 查询:

select*fromtest_tblwheresdate="2020-03-23"andsite=1

依据谓词 sdate = “2020-03-23” 能够定位到分区 p20200323,谓词 site = 1 能定位到该分区下的 bucket_1。假定有 30 天数据,主动分桶核算得到的分桶个数为 20 个。则通过明确的分区别桶谓词下推,则能够将数据全表扫描量变为本来的 1/600(30 天*20 个桶 = 600),极大减少了数据的扫描规模、进步了查询的功率

# 总结

全体来看,批量创立分区功用语法简练有力,处理了用户针对很多前史数据分区创立的难题,既防止了手动创立很多分区的低效语法,又防止了动态分区很多参数的学习运用本钱,且方法灵敏多变、随意调配组合各种类型的分区,大大进步了 Doris 在建表过程中的易用性主动分桶揣度功用智能高效,用户不需再关怀分桶的细枝末节,系统主动协助用户扩缩不同分区的分桶数,真正做到桶随事务变,降低学习本钱的一起更是进步了查询功率。在与社区用户持续交流中,咱们也不断收成着许多新的需求,例如分区列为非时刻列等,因而后续咱们仍将持续完善对其他分区列的支撑,例如数字分区列的批量创立等。最后,咱们等待倾听更多用户的声音,在不断回馈用户以极简易用的运用体验的一起,也等待有更多人参与到 Apache Doris 的建设中来,欢迎你的加入!

本文引证

[1]zhuanlan.zhihu.com/p/587187198

作者介绍:

许瑞亮,SelectDB 存储研制工程师

胡得潮,SelectDB 生态研制工程师

李仕杨,SelectDB 生态研制工程师