1. 常用引擎
☕️ Doris 表数据模型
- duplicate key
🎬 场景:适用于数据无需提前聚合的分析业务。
⚠️ 注意点:只指定排序列,相同的行并不会合并。
- unique key
🎬 场景:适用于有更新需求的业务。
⚠️ 注意点:key相同时,新记录覆盖旧记录。
- aggregate key
🎬 场景:可以提前聚合数据,适合报表和多维度业务。
⚠️ 注意点:
将会进行聚合操作,目前支持sum,min,max,replace 等
(1)sum:求和,多行的 value 进行累加。
(2)replace:替代,下一批数据中的 value 会替换之前导入过的行中的 value。
(3)max:保留最大值。
(4)min:保留最小值。
欢迎关注,一起学习
☕️ Clickhouse 表引擎
- *MergeTree
🎬 场景:
(1)支持索引和分区。
(2)支持生命周期TTL(列级 和表级)。
⚠️ 注意点:
(1)主键并不唯一,会建索引。
(2)order by 是必须的,主键、分区非必须。
- ReplacingMergeTree
🎬 场景:去重功能。
⚠️ 注意点:
(1)数据的去重只会在合并的过程中出现。合并会在未知的时间在后台进行,所以你无法预先作出计划。有一些数据可能仍未被处理。
(2)如果表经过了分区,去重只会在分区内部进行去重,不能执行跨分区的去重。
(3)认定重复的数据保留,版本字段值最大的如果版本字段相同则按插入顺序保留最后一次。
- SummingMergeTree
🎬 场景:对于不查询明细,只关心以维度进行汇总聚合结果的场景。
⚠️ 注意点:
(1)以 order by 的列为准,作为维度列,其他的列按插入顺序保留第一行。
(2)不在一个分区的数据不会被聚合。
2. Join 方式
- 查询速度和并发能力,单表性能ClickHouse更好。
- 多表Join Doris优势更明显,特别是复杂Join和大表Join大表的场景。
首先了解下向量化引擎:核心思想就是一次处理一批数据,从而大大提高数据计算的速度,例如对于一列数据,我们通过向量化技术可以一次处理1000行数据,一次将这1000行数据做比较或者做加减运算,这种处理方式在列存数据库上尤其有效,因为列存数据库通常一列一列的将数据读取处理,在内存中都是以Array的形式存储,这种方式更容易使用向量化方式做计算。
☕️ Doris Join方式
- Broadcast Join
🎬 说明:默认Join,将小表加载到内存中,形成一张Hash内存表,然后将Hash表广播到大表所在的各个节点。
⚠️ 注意点:
如果小表数据过大,Doris将自动转换为Shuffle join。
- Shuffle Join
🎬 说明:小表数据无法放入内存则进行shuffle join。
⚠️ 注意点:
每个数据扫描节点将数据扫出来之后进行Partition 分区,然后根据 Partition 分区的结果分别把左右表的数据发送到对应的 Join 计算节点上。
- Bucket Shuffle Join
🎬 说明:
利用建表时候分桶的特性,当join的时候,join的条件和左表的分桶字段一样的时候,将右表按照左表分桶的规则进行shuffle操作,使右表中需要join的数据落在左表中需要join数据的BE节点上的join。
⚠️ 注意点:
(1)Join 条件为等值的场景才有效。
(2)需要左表的分桶列的类型与右表等值 join 列的类型需要保持一致。
(3)只作用于 Doris 原生的表,其他表比如,ES,MYSQL无效。
- Colocation Join
🎬 说明:
是将一组拥有相同 CGS 的 Table 组成一个 CG。保证这些 Table 对应的数据分片会落在同一个 BE 节点上。使得当 CG 内的表进行分桶列上的 Join 操作时,可以通过直接进行本地数据 Join。
两个概念:
(1)Colocation Group(CG):位置协同组。
(2)Colocation Group Schema (CGS): CG 中的 Table的元数据信息,比如:分桶列类型,分桶数以及分区的副本数等等信息。
⚠️ 注意点:
(1)建表时两张表的分桶列的类型和数量需要一致,保证多张表的数据分片能够一一对应分布控制。
(2)同一个 CG 内所有表的所有分区的副本数必须一致。如果不一致,可能出现某一个tablet 的某一个副本,在同一个 BE 上没有其他对应的表分片的副本。
(3)同一个 CG 内的表,分区的个数、范围以及分区列的类型不要求一致。
☕️ Clickhouse Join方式
- 普通 Join
🎬 步骤:假设集群有4个节点,2个分区,2个副本,其中data1、data2 节点为一个分片 shard1,data3、data4 为一个分片 shard2。执行的SQL语句如下:
SELECT l_.a, r_.aFROM left_all as l JOIN right_all as r
on l_.a = r_.a
(1)Client发送data1节点上面的sql,准备执行
(2)data1节点会把自己本机表分片shard1数据left_local准备好,也即下面sql
当然,同理,data3也会把自己的left_local数据准备好
SELECT l_.a, r_.a FROM left_local as l JOIN right_all as r
on l_.a = r_.a
(3)当data1与data3 要开始执行第二步中 join的右表,也是一个分布式表。这个时候,data1需要shard2中的右边表数据到本地,同理data3需要shard1中的右边数据到本地。这样这两个节点都有一份全量的right_all_local。
(4)节点data1,data3 left_local数据与right_all_local做计算。
SELECT l_.a, r_.a FROM left_local as l JOIN right_all_local as r
on l_.a = r_.a
(5)data3执行完成以后,把结果发送到data1。
(6)data1收到数据,然后和自己计算数据做汇总,最后把结果给Client。
- Global Join
🎬 步骤:
还与普通Join不同的是,比如:
(1)data3节点将右表数据查询出来在data1上汇总为right_all_local。
(2)然后data1把right_all_local发送到data3上执行。
(3)data3的本地left_local数据与right_all_local计算好,发送到data1。
(4)data1收到数据,然后和自己计算数据做汇总,最后把结果给Client。
⚠️ 注意点:
(1)如果分片数为n,Global Join 右表本地表查询次数为n,而普通 Join 右表查询次数为n*n。这样可以减少读数据次数。
(2)数据在节点之间传播,占用部分网络流量。如果数据量较大,同样会带来性能损失。
3. 数据划分
☕️ Doris 数据划分
🎬 基本组成:
(1)Row & Column:一张表包括行(Row)和列(Column)。
(2)Tablet:Doris 的存储引擎中,用户数据被水平划分为若干个数据分片(Tablet,也称作数据分桶Bucket)。每个 Tablet 包含若干数据行。各个 Tablet 之间的数据没有交集,并且在物理上是独立存储的。
(3)Partition:多个 Tablet 在逻辑上归属于不同的分区(Partition)。一个 Tablet 只属于一个 Partition。而一个 Partition 包含若干个 Tablet。
🎬 数据划分:
Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。第二层是 Bucket(Tablet),支持 Hash 和 Random 的划分方式。
⚠️ 官方给出的注意点:
(1)一个表的 Tablet 总数量等于 (Partition num * Bucket num),在不考虑扩容的情况下,推荐略多于整个集群的磁盘数量。
(2)单个 Tablet 的数据量建议在 1G - 10G 的范围内。
(3)一个 Partition 的 Bucket 数量一旦指定,不可更改。所以在确定 Bucket 数量时,需要预先考虑集群扩容的情况。比如当前只有 3 台 host,每台 host 有 1 块盘。如果 Bucket 的数量只设置为 3 或更小,那么后期即使再增加机器,也不能提高并发度。
(4)官方举一些例子:假设在有10台BE,每台BE一块磁盘的情况下。如果一个表总大小为 500MB,则可以考虑4-8个分片。5GB:8-16个分片。50GB:32个分片。500GB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片。5TB:建议分区,每个分区大小在 50GB 左右,每个分区16-32个分片。
☕️ Clickhouse 数据划分
🎬 数据分片
分片策略:
(1)random随机分片:写入数据会被随机分发到分布式集群中的某个节点上。
(2) constant固定分片:写入数据会被分发到固定一个节点上。
(3) column value分片:按照某一列的值进行hash分片。
(4) 自定义表达式分片:根据表达式的值进行hash分片。
🎬 数据分区
分区设计:
(1)不指定分区键,则数据默认不分区,所有数据写到一个默认分区a里面。
(2)如果分区键取值属于整型,则直接按照该整型的字符形式输出作为分区ID的取值。
(3)如果分区键取值属于日期类型,或者是能够转换为YYYYMMDD日期格式的整型,则按照分区表达式逻辑格式化后作为分区ID的取值。
(4)如果分区键取值既不属于整型或日期类型,则通过128位Hash算法取其Hash值作为分区ID的取值。
(5)分区键也可以是表达式的tuple元组。