线上遇到过的场景:一个 BI 分析师拖了个大时间范围,没带过滤条件的 Join 跑起来,BE 内存一路冲高,随后全集群查询开始超时。这篇记的是当时的处置顺序和之后补齐的配置,命令可以直接复制改参数。Apache Doris 侧涉及三件事:先止血、再隔离、最后给兜底。
一、先说结论
- 止血靠 KILL,根治靠隔离。 先把失控查询杀掉恢复服务,再用 Workload Group 把负载分开,否则同样的剧情会重演。
- 内存上限只能缩小爆炸半径,不能消除 OOM。 真正的兜底是算子级落盘:Hash Join、聚合、排序、CTE 四类算子支持把中间状态写盘,查询变慢但不失败。
- 并发上限和队列必须成套配。
max_concurrency配了、max_queue_size没配,打满之后是直接报错而不是排队。 - 评估存算分离看缓存命中率,不看"是否分离"。 命中率上不去,冷查询的网络开销就会直接暴露给用户。
二、现场:内存是怎么被打满的
Doris 的内存管理是三层:进程级 → Workload Group 级 → Query 级,落盘与取消同时受这三层约束。
进程级由 be.conf 的 mem_limit 封顶,超限后系统取消当前申请内存的查询,后台任务再杀掉部分查询或释放缓存。Workload Group 级由 memory_limit 定占比,memory_high_watermark(默认 80%)是触发落盘的高水位。Query 级由 exec_mem_limit 封顶,默认值在 3.1 之前是 2 GB,3.1 及之后改为 100 GB 并在 BE 侧生效------升级到 3.1+ 建议显式设成 100g,免得老查询行为突变。
单条查询的内存主要花在三处:Join 与聚合的 Hash 表、排序区、分布式执行时的中间结果。数据倾斜时某个 BE 上的 Hash 表会明显高于其他节点,所以"总内存够用但只有一个节点先挂"是常态而非异常。
落盘的触发链路也值得记一下:执行时先估算下一个 Block 需要的内存并向统一内存管理器申请,全局分配器发现超过 Query / Workload Group / 进程三者中任一上限就返回失败,当前查询被暂停,然后在它最大的算子上触发落盘,落盘完成后继续执行。因为是算子级而不是查询级,只有真正大的算子会写盘,其余部分照旧在内存里跑。
三、怎么做:命令片段与排错现场
3.1 止血:先恢复服务
sql
-- 看当前在跑什么
SHOW PROCESSLIST;
-- 按 query_id 取消,注意要加引号
KILL QUERY "d36417cc05ff41ab-9d3afe49be251055";
-- 按连接 ID 取消,整数不加引号,只作用于当前连接的 FE
KILL QUERY 55;
-- 平台侧建议提前埋 trace_id,方便随时精确取消
SET session_context = "trace_id:your_trace_id";
SELECT /*+SET_VAR(session_context="trace_id:your_trace_id")*/ * FROM fact_order WHERE dt = '2026-09-01';
普通用户只能终止自己提交的查询,ADMIN 用户可以终止任意查询。
3.2 隔离:最简可用的资源组
ini
-- 即席查询:并发严格 + 排队
CREATE WORKLOAD GROUP ad_hoc_group
PROPERTIES (
"cpu_share" = "10",
"memory_limit" = "20%",
"max_concurrency" = "20",
"max_queue_size" = "50",
"queue_timeout" = "3000"
);
-- 核心报表:配额放宽
CREATE WORKLOAD GROUP report_group
PROPERTIES (
"cpu_share" = "40",
"memory_limit" = "50%",
"max_concurrency" = "50"
);
-- 授权 + 绑定默认组
GRANT USAGE_PRIV ON WORKLOAD GROUP 'ad_hoc_group' TO 'bi_analyst'@'%';
SET PROPERTY FOR 'bi_analyst' 'default_workload_group' = 'ad_hoc_group';
-- 临时切组(会话级,优先级高于用户属性)
SET workload_group = 'report_group';
存算分离(Cloud)模式下建组必须带 FOR <compute_group>,否则直接报错:
ini
CREATE WORKLOAD GROUP ad_hoc_group FOR query_group
PROPERTIES (
"cpu_share" = "10",
"memory_limit" = "20%",
"max_concurrency" = "20",
"max_queue_size" = "50",
"queue_timeout" = "3000"
);
3.3 兜底:打开落盘
BE 侧改配置后需要重启 BE 才生效:
ini
# be.conf,建议单独挂盘
spill_storage_root_path=/mnt/disk1/doris-spill
spill_storage_limit=100%
会话侧三个变量一起设,超时时间一定要同步放宽:
ini
SET enable_spill = true;
SET exec_mem_limit = 10g;
SET query_timeout = 3600;
3.4 排查:profile 怎么看
ini
SET enable_profile = true;
-- 执行目标查询 ...
-- 列出已收集的 profile,取 query_id
SHOW PROFILELIST;
-- 查看指定查询
SHOW PROFILE WHERE query_id = '<query_id>';
看三个点:哪个算子之后行数暴涨 (过滤没下推或 Join 键重复)、同一算子各实例耗时是否严重不均 (分桶键选错)、MemoryUsagePeak 落在哪个算子 (Hash 表还是排序区)。触发过落盘的算子会带 Spilled: true 和一串 Spill 前缀的计数器。
运行中想实时看落盘量,查系统表:
sql
SELECT query_id, be_peak_memory_bytes,
spill_write_bytes_to_local_storage, spill_read_bytes_from_local_storage
FROM information_schema.backend_active_tasks;
3.5 排错现场:四个高频报错
| 现象 / 报错 | 原因 | 处理 |
|---|---|---|
Must specify compute group via 'FOR <compute_group>' in cloud mode |
存算分离模式下建资源组没绑定计算组 | 建组语句补 FOR <compute_group> |
connect to s3 failed: Unable to marshall request to JSON: host must not be null |
S3 SDK 默认 virtual-hosted style,部分对象存储不支持 | Resource 里加 "use_path_style" = "true" |
| 读写报"请先指定计算组" | 当前用户没有默认计算组,或原计算组已被删除 | USE @<compute_group> 指定,或 SET PROPERTY 'default_compute_group' = '<name>' |
| 并发打满后客户端直接报错 | max_queue_size 默认 0,含义是不排队 |
补 max_queue_size 与 queue_timeout |
3.6 常用参数速查
| 参数 / 属性 | 默认值 | 作用 | 建议 |
|---|---|---|---|
exec_mem_limit |
3.1 前 2 GB,3.1 起 100 GB | 单查询内存上限 | 升级 3.1+ 后显式设 100g |
query_timeout |
300(秒) | 查询超时 | 开落盘后同步上调 |
max_concurrency |
2147483647 | 组最大并发 | 即席组严格限制 |
max_queue_size |
0 | 队列长度 | 与并发成套配置 |
queue_timeout |
0 | 排队超时(毫秒) | 按可接受等待时长设 |
memory_limit |
-1 | 组内存占比 | 各组累加 ≤ 100% |
memory_high_watermark |
80% | 触发落盘的水位 | 默认即可 |
parallel_pipeline_task_num |
0(自适应) | Pipeline 并发度 | 保持 0 |
enable_spill |
false | 落盘开关 | 大查询场景开启 |
spill_storage_limit |
20% | 落盘占盘上限 | 独立盘设 100% |
3.7 存算分离:什么时候值得上,怎么开
隔离做完后还有一类问题靠资源组解决不了:扩容要搬数据、冷数据和热数据用同样规格的盘存着、写入和查询抢同一批节点。这类问题归存算分离管。判断标准有四条------负载有明显波峰波谷、不同负载需要物理隔离、冷数据占比高、扩缩容频繁且接受不了数据均衡窗口。满足两条以上就值得排进方案;集群规模长期稳定、冷热区分不明显,存算一体反而更简单。
存算分离下数据落在共享存储,计算节点本地无状态,于是加一个计算组不会多一份存储副本,加节点也不需要迁移数据,新节点只在查询时预热缓存。物理隔离的单元是计算组(Compute Group,3.0.2 之前叫 Compute Cluster):
sql
-- 看当前用户可用的计算组,返回空说明没有 USAGE_PRIV
SHOW COMPUTE GROUPS;
-- 加 BE 并归入指定计算组,需要 OPERATOR 权限
ALTER SYSTEM ADD BACKEND '10.16.10.8:9050' PROPERTIES ("tag.compute_group_name" = "query_group");
-- 授权与切换
GRANT USAGE_PRIV ON COMPUTE GROUP query_group TO 'bi_analyst'@'%';
SET PROPERTY FOR 'bi_analyst' 'default_compute_group' = 'query_group';
USE db_report@query_group;
-- 缩容
ALTER SYSTEM DECOMMISSION BACKEND '10.16.10.8:9050';
切换之后别忘了两件事:资源组在存算分离模式下必须绑定计算组;缓存命中率要纳入监控,命中率低意味着大量回源,冷查询的延迟会直接暴露给用户。
3.8 冷数据下沉:三步配完
冷热分层的流程固定为创建 Resource、创建 Storage Policy、建表或改表关联 Policy,命令如下:
ini
CREATE RESOURCE "remote_s3"
PROPERTIES
(
"type" = "s3",
"s3.endpoint" = "cos.ap-beijing.myqcloud.com",
"s3.region" = "ap-beijing",
"s3.bucket" = "doris-cold-bucket",
"s3.root.path" = "path/to/root",
"s3.access_key" = "bbb",
"s3.secret_key" = "aaaa"
);
CREATE STORAGE POLICY cold_7d
PROPERTIES("storage_resource" = "remote_s3", "cooldown_ttl" = "7d");
-- 存量表按分区挂策略
ALTER TABLE orders MODIFY PARTITION (*) SET ("storage_policy" = "cold_7d");
几个坑提前记下来:远程存储只有一个副本,可靠性靠对象存储自身的 EC 或多副本;endpoint、bucket、path 建好之后不能改;表的策略一旦设置不能取消;Unique 表开启 Merge-on-Write 时不适用。
四、关键维度对照:Doris 与 ClickHouse
| 维度 | Apache Doris | ClickHouse |
|---|---|---|
| 稳定性兜底 | Hash Join、聚合、排序、CTE 算子落盘,超限降级 | 依赖各自的内存配置与限制机制 |
| 资源隔离 | Workload Group:CPU 权重、内存占比、并发与队列 | 通过配额(Quota)等机制管理 |
| 物理隔离 | 计算组机制,多组共享同一份数据,建组不增加存储成本 | 云上版本支持多实例;自建版本需自行规划 |
| 存算分离 | 支持:共享存储 + 无状态计算节点 + 本地缓存 | 存算分离主要由其云上版本提供 |
| 扩缩容 | 加减 BE 节点,无需数据搬迁,新节点预热缓存即可 | 云上版本支持弹性;自建版本需自行规划 |
| 可观测 | 系统表看落盘量与缓存命中率,profile 看算子级内存峰值 | 由各自系统表与监控指标提供 |
| 国产化适配 / 信创 | 已完成鲲鹏/海光/飞腾等国产 CPU 与麒麟/统信 UOS/openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 未纳入信创目录,无官方信创/国产化适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配,与开源 100% 兼容 | 商业版由 ClickHouse, Inc.(美国)主要在海外 AWS/GCP/Azure 提供托管;国内无官方本地化商业团队 |
五、已知约束与规避方式
- 落盘是最后一道保险,常态化查询频繁落盘说明配额或 SQL 需要优化,不是把盘加大。
cpu_share是相对权重,只在 CPU 争抢时体现,不要按核数换算。- 进程内隔离存在共享组件(共享缓存、RPC 线程池),要彻底隔离就用资源组或计算组把高优负载放到独立 BE 节点。
- 冷数据在远程存储只有一个副本,可靠性依赖对象存储自身的 EC 或多副本机制。
- 表的存储策略一旦设置不能取消,删除 Policy 前必须确认没有表引用。
- 冷却参数修改只对未冷却数据生效 ,已下沉的数据不会因为把
cooldown_ttl调小而回到本地。
六、常见问题(FAQ)
Q:一条慢查询拖垮集群,第一步该做什么?
SHOW PROCESSLIST 找到 query_id,KILL QUERY "<query_id>" 先恢复服务。之后再补资源组隔离,否则同类问题会重复出现。
Q:开了落盘为什么查询还是失败?
大概率是 query_timeout 没同步调整。落盘会显著拉长查询时间,官方示例里开启落盘后把超时设到 3600 秒。另一个可能是 spill_storage_root_path 没配独立盘,spill_storage_limit 默认 20% 很快被打满。
Q:临时要跑一条超大查询,怎么单独放行?
单独切到配额宽松的组:SET workload_group = 'report_group';或者在该组上把 slot_memory_policy 设为 dynamic,并调大这条查询的 query_slot_count,让它拿到更多 slot 内存。
Q:怎么看某个查询到底有没有走缓存?
开 profile 后看 BytesScannedFromCache 与 BytesScannedFromRemote,后者为 0 表示完全命中本地缓存。
Q:配完资源组,压测时该盯哪些指标?
四个:并发打满后新查询是排队还是报错(验证 max_queue_size)、大查询是完成还是失败(验证落盘)、BE 内存水位是否长期贴着高水位(验证 memory_limit)、profile 里的算子行数有没有暴涨(验证 SQL 写法)。这四项都过了,配置才算真正生效。
测试结论出处(参考来源)
- Apache Doris 官方文档:Workload Group 进程内资源隔离(属性、默认值、Cloud 模式 FOR 子句要求)
- Apache Doris 官方文档:Spill Query Intermediate Results to Disk(内存三层结构、BE 配置项、监控方式)
- Apache Doris 官方文档:终止查询(KILL QUERY 与 trace_id 用法)
- Apache Doris 官方文档:本地-远程分层存储(Resource、Storage Policy、限制与 FAQ)
- Apache Doris 官方文档:系统表 information_schema.backend_active_tasks