首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Doris 也太懒了吧...

Doris 也太懒了吧...

作者头像
一臻数据
发布2025-11-17 16:23:45
发布2025-11-17 16:23:45
140
举报
文章被收录于专栏:一臻数据一臻数据

见字如面,我是一臻

“哥,报表又超时了,领导要看新增用户 Top100 城市。”电话那头的 BI 同事压着嗓子,像在怕吵醒谁。 老王盯着屏幕上那条缓慢的进度条,脑子里先闪过成本,再闪过网络延迟,最后落在了“读多了”三个字上。 把查询切到 Doris 集群,监控一亮,老王能感到自己心跳变稳了。几分钟后,同事再来一句:“这次这么快?” 老王回复:“Doris懒得读那些不该看的。

Doris懒得读的原因

Doris对“少读”的执念,写在了存储布局里。

表会被切成分区;分区再切成很多 Tablet;每个 Tablet 里是一堆 Segment;Segment 里,列分开单独存。听起来像拆乐高,但每一层都在为“能不读就不读”服务。

最有价值的一条,是 Segment 内的 Key 列有序。Key 好比这批数据的骨架,Doris把它按序摆好,后面很多看似复杂的优化,本质都在利用这个“有序”:范围查询可以二分定位起止位置,TopK如果按Key排序,甚至只用摸一摸每个Segment的前 K 行,很多时候根本不必把全量数据翻一遍。

列式存储带来的另一件法宝,是“元数据比数据先说话”。

哪怕你还没真正去解压一页数据,Doris也已经在列文件外侧贴好了小纸条:每页、每段的最小值和最大值、页位置信息、短键索引、甚至可选的 Bloom/Bitmap/倒排等索引。

你一句“我要 b 在 100 到 200 之间的行”,这些元数据就能替你把整页整段划掉。真读数据前,Doris就完成了第一轮“懒读”。

静态裁剪,让无关数据连门都进不来

很多查询的胜负,在你点回车的那一刻就已经分出来了。优化器拿到 SQL 之后,能在不运行的前提下,先做三重“懒读”。

以如下表作为后续举例:

代码语言:javascript
复制
CREATE TABLEIFNOTEXISTS`tbl` (    
  a int,    
  b int,    
  c int) 
ENGINE=OLAP
DUPLICATEKEY(a,b)
PARTITIONBYRANGE(a) (    PARTITION partition1 VALUESLESSTHAN (1),    PARTITION partition2 VALUESLESSTHAN (2),    PARTITION partition3 VALUESLESSTHAN (3))DISTRIBUTEDBYHASH(b) BUCKETS 8PROPERTIES (    "replication_allocation" = "tag.location.default: 1");

示例数据:

图片
图片

第一道是分区裁剪

FE 只看分区元信息就能判断:哪些分区根本不可能命中,把它们直接从候选集合里剔除。时间分区是最常见的例子。你的报表要看近七天,七天之外的分区,一个字——读。

很多团队吃的亏,是把常用过滤维度藏在明细里,结果系统不得不把分区都打开,慢的根源在这里。

建模时,尽可能把“高选择性、常出现”的谓词放到分区列,收益总是最直接的。

好比如下sql的流转:

代码语言:javascript
复制
# 分区裁剪在 Frontend 层完成,通过与元信息交互就可以查询到所有需要的分区
SELECT * FROM `tbl` WHERE `a` > 0;
图片
图片

第二道是 Key 范围裁剪

因为 Segment 内 Key 有序,优化器会把谓词投影到 Key 上,生成上下界。

BE 收到范围之后,不是笨笨地从头扫,而是用二分去找“从哪一行开始读、在哪一行可以停”,这个定位的开销很小,但能让你绕开一大片不相关的数据区间。

即便谓词不是直接落在 Key 上,只要能改写成和 Key 同构的范围,也值得引导优化器这么做。

好比如下sql的流转:

代码语言:javascript
复制
# `b`列为 key 列
# 存储层使用谓词中 Key 列的下界 0(非包含)对 segment 进行二分查找,最终返回符合条件的数据的行号 1(第二行),根据行号再进行其他列的数据读取。
SELECT * FROM `tbl` WHERE `b` > 0;
图片
图片

第三道是列块元数据过滤

FE的元数据存着每页/每段的最值,只要和你的谓词区间不相交,整页整段就地跳过,不必解压;再加上可选的 Bloom/Bitmap/倒排索引,在等值、集合、包含等场景里还能再提一档过滤率。

静态过滤的顺序,通常遵循“成本最低先上”的原则:先用最粗粒度、最便宜的判定挡掉大头,再让更精细的过滤器出手。

代码语言:javascript
复制
# `c`列为数据列
# 存储层在所有的 Segment 中使用数据谓词中`c`列的数据文件进行计算,在计算之前首先根据数据文件中维护的最大最小值决定是否跳过当前文件的读取,在本例中,Column File 0 的最大值小于我们需要读取的数据的下界,所以可以直接跳过对应数据的读取,而在计算 Column File 1 后我们得到了匹配数据的行号,根据行号我们就可以再进一步读取其它列对应行的数据。
SELECT * FROM `tbl` WHERE `c` > 2;
图片
图片

动态裁剪的三把快刀

静态能挡住一批,剩下的得在执行期“见招拆招”。

Doris的三把刀,分别对准了 LIMIT、TopK 和 JOIN

先说 LIMIT

图片
图片

多数系统习惯“多并发去扫,扫多了丢掉”,Doris在这里有点“反常识”——如果 Scan 节点上就带着 LIMIT,它会把扫描并发调成 1,不让多余的并发把“过量数据”推上来。

读够行数就地停手,还没打开的 Segment/页一个都不动。更妙的是,下游一旦“够了”,会把“!”的信号向上游一路反向传递,整个流水线跟着刹车。

预览页、抽样探查、调试查询,在这种策略下往往直接“秒回”。

再看 TopK

图片
图片

全量排序是最昂贵的操作之一,Doris把它拆成两段。

第一段只读“参与排序的列”,每个扫描线程维护一个大小为 K 的最小堆(降序场景),当堆还没满时,看到好东西就往里放;一旦满了,堆顶的值就成了“滚动门槛”,后续扫描只要比这个门槛还差,就直接扔掉。 这个门槛越滚越苛刻,局部就完成了强力降噪。

各线程把“各自最有希望的 K 行”交给协调者,第二段再做一次全局堆排序,得出最终 K 行对应的“行号集合”。

最后一步才“回表”把其他列补齐。

最后是 JOIN 的裁剪。

多表连接通常是查询里最耗时的环节,是因为它要在两张表里大量“配对”,一不小心就会把整张大表扫一遍,网络和CPU都被拖住。高效的 Hash Join 基本做法是:先用较小的一侧建哈希表(Build),再让另一侧去探测(Probe)。Doris做的优化点很简单但有效:在探测真正开始前,先给它一个“门槛”,只让可能匹配的行通过。

这个“门槛”来自 Build 侧的键值域。Doris会基于这些键值,构造一个过滤器下推到 Probe 侧,再开始扫描大表。这样一来,大表在入口就能做快速拦截:不可能命中的行直接绕开,既少读,也少算,还少传

图片
图片

门槛怎么做更合适,取决于值域大小和分布。如果值不多,直接把它们做成一个 In 谓词,下推到扫描和算子里,判断成本低、命中精准。

图片
图片

如果值很多但又分散,用 Bloom Filter 更划算,占用小、判断快,能在大表入口层面先挡掉一大批无关数据,真正需要精配对的再交给哈希表处理。

为了让门槛尽快生效,Doris会短暂等待过滤器构建完成(通常只等一小会儿)。如果一时还没好,也不会干等——探测先启动;一旦过滤器就绪,会立刻下发,后续批次马上受益。这样既不拖慢整体启动,又尽可能把“先筛后算”的收益吃满。

最终的效果很直接:探测侧读得更少,网络传得更少,算得更少。对大表 JOIN、宽表 JOIN、热点Key JOIN 都更稳、更快,把“少读即快”的原则落在了 JOIN 的关键路径上。

建模与查询的协奏

很多团队学会了“Doris的原因”,却在业务上感受不到“量变到质变”的速度,那通常是建模和查询没有为裁剪让路。

时间分区几乎是共识,但别止步于此。把业务高选择性维度(比如地区、端、渠道)也纳入二级分区或路由策略,热分区和冷分区分开对待,让热数据更紧致,冷热查询都爽快。

Key 的选择别只看写入,要和常用的过滤/排序贴紧,这能让范围裁剪和 TopK天然占便宜。文本检索、标签场景多,就补齐倒排/Bitmap/Bloom 的索引组合,别指望 Zone Map 单挑一切。

查询侧的改写同样关键。

把“函数包裹”的谓词拆回原生区间,下推到存储侧:

代码语言:javascript
复制
date_trunc(order_time, 'day') = '2025-01-01'
改为:
order_time >= '2025-01-01 00:00:00' AND order_time < '2025-01-02 00:00:00'

ORDER BY + LIMIT 尽可能只用少数列,给 TopK一个轻装上阵的机会;遇到复杂 JOIN,把“容易炼出高质量过滤器的小表”放到 Build 侧,越早生成 Runtime Filter,对大表扫描越是“当头一棒”。链路里有倾斜风险的地方,先用分区/Key 范围把读面收紧,再用 Bloom 补刀,尾延迟会明显收敛。

很多人把性能优化当成“加速器”的堆叠,Doris的路径有点反直觉:不是把引擎转速拉满,而是一路踩刹车,先问一句——这块数据,我能不能不读?能不读就不读,必须读就读得更少、读得更集中。

把这种“懒美学”写进数据布局、写进优化器、写进执行期协调,剩下的速度,自然顺着水流下来。

结语

那晚老王挂断电话,进度条安静地停在“完成”。

他心里有数,这是Doris在每一层都替他省了一把:分区在门口拦住陌生人,Key的有序替他指路,列块元数据替他说“不必解压”,LIMIT替他“够了就停”,TopK替他“只摸关键列”,JOIN替他“让小表先打光”。Doris的“聪明”,不是多读多算,而是读该读的,别读不该读的

当系统开始学会“偷懒”,工程师就能安心睡觉;而这份“”,恰好是你在业务里真正要的“”。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一臻数据 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Doris懒得读的原因
  • 静态裁剪,让无关数据连门都进不来
  • 动态裁剪的三把快刀
  • 建模与查询的协奏
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档