订阅更新

OLAP引擎里也有倾斜——Doris、ClickHouse的倾斜和Spark有什么不同?

数据倾斜手册 第 11 题 约 11 分钟读完 1 次阅读

本文目录
  1. 先说结论
  2. Doris的数据分布和倾斜
  3. Doris倾斜的处理:选对分桶key
  4. ClickHouse的分片倾斜
  5. MPP倾斜 vs Spark倾斜的核心区别
  6. 面试高频追问
  7. 实战踩坑
  8. 总结

先说结论

MPP倾斜的本质:MPP引擎(Doris/StarRocks/ClickHouse)把数据分片存在多个节点,查询时各节点并行计算。如果数据分片不均匀(某个分片数据远多于其他),承担大分片的节点成为瓶颈,拖慢整个查询,这就是MPP倾斜。

和Spark倾斜的区别:Spark倾斜发生在Shuffle阶段(运行时动态分配),MPP倾斜发生在数据存储阶段(建表时分片策略决定)。Spark倾斜是动态的,每次查询可能不同;MPP倾斜是静态的,数据写入后就固定了,每次查询都一样慢。

处理时机不同:Spark倾斜靠查询时优化(加盐、Broadcast、AQE);MPP倾斜靠建表时优化(选对分桶key和分桶数),一旦数据写入,很难在查询时纠正,只能重建表。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

很多人谈数据倾斜只想到Spark和Hive,但OLAP引擎(Doris、ClickHouse、StarRocks、Hologres)同样存在倾斜问题,而且因为它们是MPP架构,倾斜的表现和处理方式和Spark有明显不同。我在某元宇宙社交应用用Doris,在某短剧出海公司用Hologres,都遇到过OLAP层面的倾斜。这篇把OLAP引擎的倾斜原理和处理方案梳理清楚,这是很多面试者的知识盲区,能说清楚的候选人凤毛麟角。

Doris的数据分布和倾斜

Doris用哈希分桶(Hash Bucketing)把数据分布到各BE节点。建表时指定DISTRIBUTED BY HASH(key)和BUCKETS N,同一个key的所有数据进同一个桶。如果key分布不均匀,就产生数据倾斜。

-- Doris建表时的分桶设计(关键决策点)
CREATE TABLE orders (
  order_id    BIGINT NOT NULL,
  user_id     BIGINT,
  amount      DECIMAL(10,2),
  create_time DATETIME
)
DUPLICATE KEY(order_id)   -- 数据模型:明细模型
DISTRIBUTED BY HASH(user_id) BUCKETS 32;  -- 按user_id分32个桶
 
-- 如果user_id分布不均匀(如某些用户有海量订单),就会产生倾斜
-- 检查桶内数据分布是否均匀:
SHOW TABLET FROM orders;
-- 查看各Tablet的RowCount,如果差异超过5倍,说明有倾斜
 
-- 查看数据在BE节点的分布:
SELECT
  BackendId,
  COUNT(*) AS tablet_cnt,
  SUM(RowCount) AS total_rows
FROM information_schema.tablets
WHERE TABLE_NAME = 'orders'
GROUP BY BackendId;
-- 各BE的total_rows应该接近,差异大 = BE级倾斜

Doris的倾斜有两个层次:Tablet级倾斜(某些Tablet数据特别多)和BE级倾斜(某些BE节点存储的数据特别多)。Tablet级倾斜影响查询性能;BE级倾斜既影响查询也影响存储节点的负载均衡。

Doris倾斜的处理:选对分桶key

Doris倾斜的根本解法是建表时选对分桶key,让数据尽量均匀分布:

分桶key选择优点缺点适用场景
高基数列(如order_id)分布均匀,几乎不倾斜按user_id查询时需全桶扫描明细数据,随机访问多
业务JOIN/GROUP BY的key(如user_id)JOIN/GROUP BY时Colocation Join可能生效如有热点user,倾斜明显用户维度分析为主
复合key(如user_id+dt)平衡均匀性和查询效率桶数要足够多既要均匀又要按用户查询
随机分桶(RANDOM)绝对均匀,无倾斜无法利用Colocation Join数据量大且无明显查询pattern
-- Doris 2.0+支持随机分桶(Auto Bucket),彻底避免分桶key选择的困境
CREATE TABLE orders_random (
  order_id BIGINT NOT NULL,
  user_id  BIGINT,
  amount   DECIMAL(10,2)
)
DUPLICATE KEY(order_id)
DISTRIBUTED BY RANDOM BUCKETS AUTO;  -- 随机分桶,自动决定桶数
-- 优点:数据绝对均匀,无倾斜
-- 缺点:Colocation Join无法生效(因为相同user_id可能在不同桶)
 
-- 什么是Colocation Join?
-- 两张表按相同key分相同桶数,相同key的数据在同一个BE节点
-- Doris查询时可以本地Join,不需要网络传输,极快
-- 但前提是两张表的分桶key和桶数完全相同

ClickHouse的分片倾斜

ClickHouse是列式存储数据库,分布式场景下把数据分片(Shard)存在不同节点,查询时各Shard并行计算,最后汇总。分片倾斜和Doris的分桶倾斜原理相同,但配置方式不同。

-- ClickHouse分布式表建表时指定分片key
-- 本地表:
CREATE TABLE orders_local (
  order_id UInt64,
  user_id  UInt64,
  amount   Float64
) ENGINE = MergeTree()
ORDER BY (user_id, order_id);
 
-- 分布式表:用user_id做分片key(如果user_id有热点,会产生倾斜)
CREATE TABLE orders_distributed AS orders_local
ENGINE = Distributed(
  'my_cluster',   -- 集群名
  'default',      -- 数据库名
  'orders_local', -- 本地表名
  user_id         -- 分片key:按user_id的哈希值分片
);
 
-- 检查各Shard的数据量:
SELECT
  hostName() AS host,
  COUNT(*) AS row_count
FROM orders_distributed
GROUP BY host;
-- 各host的row_count差异大 = 倾斜
 
-- 更均匀的分片key选择:用高基数列或使用rand()随机分片
-- rand()分片:绝对均匀但无法做Shard本地聚合
ENGINE = Distributed('my_cluster', 'default', 'orders_local', rand())

MPP倾斜 vs Spark倾斜的核心区别

维度Spark倾斜MPP(Doris/CH)倾斜
发生时机查询运行时(Shuffle时)数据写入时(分桶/分片策略)
可见性Spark UI的Task分布SHOW TABLET或系统表查询
影响范围单次查询变慢所有查询都慢(固定的)
处理时机查询时加盐/Broadcast/AQE建表时选对key,或重建表
紧急程度可以先跑完再优化数据写入后改动代价大
动态调整AQE可运行时调整基本不能运行时调整

这个区别在面试里很容易被考到:「Doris的数据倾斜和Spark的倾斜有什么不同?」能说出「一个是存储层的静态倾斜,一个是计算层的动态倾斜」,就展示了对两类系统本质区别的理解。

面试高频追问

追问一:Doris的Colocation Join是什么?和倾斜有什么关系?

Colocation Join是Doris的一个重要优化:如果两张表的分桶key相同、桶数相同,且数据分布在同一组BE节点上(Colocation Group),那么这两张表的Join可以在BE本地完成,不需要网络数据传输,性能极好。但Colocation Join和倾斜是一对矛盾:为了Colocation,分桶key要选业务JOIN的key(如user_id);但user_id可能有热点,导致倾斜。实际中要根据业务特点权衡:如果user_id分布相对均匀,用user_id分桶同时享受Colocation;如果user_id极度不均匀,用order_id分桶避免倾斜但放弃Colocation。

追问二:Hologres(阿里云)的倾斜怎么处理?

Hologres是阿里云的实时数仓产品,底层也是MPP架构。我在某短剧出海公司大量使用Hologres。Hologres的分布列(Distribution Key)相当于Doris的分桶key,选择原则相同:高基数、查询常用的JOIN/GROUP BY key、避免NULL多的列。Hologres有一个特殊功能:PARTITION BY(分区)+DISTRIBUTE BY(分布)可以组合使用,先按时间分区减少扫描数据量,再按业务key分布保证均匀。Hologres还支持CLUSTERING KEY(聚簇索引),按聚簇key排序存储,范围查询效率极高。倾斜排查可以通过Hologres的系统视图hologres.hg_table_info查看各Shard的数据分布。

追问三:如果已经发现Doris建表时分桶key选错了,数据已经写进去了,怎么办?

这是最棘手的情况,有几个方案:1)创建新表(选对分桶key)→ INSERT INTO SELECT把数据从旧表迁到新表 → 切换查询入口。代价是写一遍全量数据,对大表可能要几小时甚至几天;2)使用Doris的ROLLUP(物化视图):在原表上创建一个按不同key聚合的Rollup,查询时Doris自动路由到Rollup,绕过原表的倾斜问题(但只适合聚合查询,不适合明细查询);3)短期应急:增加BE节点、调整查询并行度,让倾斜的节点有更多资源处理更多数据,但这只是缓解,不能根治。长期还是要重建表。

实战踩坑

我在某元宇宙社交应用使用Doris做实时数仓时,有一张「用户礼物流水表」,建表时用user_id做分桶key(当时考虑的是按用户查询方便)。上线3个月后,有一个按直播间统计礼物收益的查询越来越慢——从秒级到了30秒级。

-- 排查倾斜:查各Tablet的数据量
SELECT
  TabletId,
  RowCount,
  DataSize
FROM information_schema.tablets
WHERE TABLE_NAME = 'gift_flow'
ORDER BY RowCount DESC
LIMIT 20;
 
-- 发现:TOP 3的Tablet行数是其他Tablet的800倍!
-- 原因:某头部主播的粉丝给他送礼,user_id集中在这几个粉丝账号
-- 按user_id分桶,这些粉丝的所有礼物数据都在同一个桶
 
-- 解决方案:创建新表,改用gift_id(高基数)分桶
CREATE TABLE gift_flow_v2 (
  gift_id     BIGINT NOT NULL,   -- 礼物流水ID,高基数,均匀分布
  user_id     BIGINT,
  live_id     BIGINT,
  gift_amount DECIMAL(10,2),
  gift_time   DATETIME
)
DUPLICATE KEY(gift_id)
DISTRIBUTED BY HASH(gift_id) BUCKETS 64;  -- 改用gift_id分桶
 
-- 全量数据迁移(利用业务低峰期)
INSERT INTO gift_flow_v2
SELECT * FROM gift_flow;
 
-- 迁移后验证各Tablet数据均匀,查询从30秒降到1.2秒

教训:建表时分桶key的选择决定了数据的物理分布,一旦数据写入就很难改,代价极高。建表前要做充分的数据分析:目标key的基数是多少、分布是否均匀、有没有热点值。宁可在设计阶段多花1小时分析,也不要在运行3个月后重建表花3天。

总结

OLAP引擎的倾斜是存储层的静态倾斜,发生在数据写入阶段,影响所有查询且难以事后纠正。Doris靠分桶key、ClickHouse靠分片key控制数据分布,选对高基数且分布均匀的列是关键。和Spark的动态倾斜(可以AQE兜底)不同,MPP倾斜必须在建表设计阶段就想清楚。面试时能区分「存储层静态倾斜」和「计算层动态倾斜」,能讲出Doris分桶key的选择原则,就展示了OLAP引擎的工程经验。

下一篇聊一个非常实用的面试题:数据倾斜和小文件问题哪个更难搞?两者有没有关联?如何同时解决?

你们用过Doris或StarRocks吗?分桶key是怎么选的?有没有遇到过不得不重建表的情况?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器