Doris Workload Group 资源隔离实战:查询并发控制与 CPU/内存配额调优

一、一次大查询引发的线上事故

上个月某个周五下午,我们的实时数据看板突然疯狂告警:查询延迟从 200ms 飙升到 8 秒,部分接口直接超时。与此同时,数据团队正在跑一个全量用户行为分析 SQL,扫描了 2 亿行数据,还带了多个 JOINGROUP BY

排查发现,Doris 集群的 BE 节点 CPU 全部打满,内存使用率接近警戒线,部分大查询甚至触发了 BE 进程 OOM。小查询(点查、简单聚合)和这个大查询在同一批 BE 上运行,没有任何隔离,资源被大查询全部抢占,导致在线业务雪崩。

这就是典型的资源争抢问题:没有资源隔离时,Doris 中所有查询默认使用同一个资源池,大查询会无限制地消耗 CPU 和内存,拖垮整个集群。

解决思路就是引入 Workload Group(负载组),对不同类型的查询进行资源配额和并发控制。

二、Doris Workload Group 是什么

Workload Group 是 Apache Doris 2.0 引入的资源隔离机制,它允许我们将查询按业务属性(用户、标签等)划分到不同的组,并对每个组设置独立的 CPU、内存、并发度等资源限制。相比早期的 Resource Tag 只能做硬性节点隔离,Workload Group 更加灵活,可以实现单 BE 内部的软/硬隔离。

2.1 核心概念

概念 说明
Workload Group 资源隔离的基本单位,定义了一组资源配额
CPU Share CPU 软隔离权重,值越大获得的 CPU 时间片越多
Memory Limit 内存硬限制,超过限制的查询会被取消
Max Concurrency 最大并发查询数,超过则进入排队
Queue Size / Timeout 排队队列长度和排队超时时间
用户绑定 将 MySQL 用户绑定到指定的 Workload Group

2.2 隔离原理

Doris 的 Workload Group 是在 BE 进程内部实现的。当查询到达 BE 后,会根据查询所属的 Workload Group 进行资源分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────┐
│ FE (Frontend) │
│ 解析 SQL / 鉴权 / 路由 │
└──────────────┬──────────────┘
│ 携带 Workload Group 标签

┌─────────────────────────────┐
│ BE (Backend) │
│ │
│ ┌───────────┐ ┌───────────┐│
│ │ online │ │ offline ││
│ │ Group │ │ Group ││
│ │ cpu_share │ │ cpu_share ││
│ │ =10 │ │ =5 ││
│ │ mem=40% │ │ mem=30% ││
│ └───────────┘ └───────────┘│
│ 共享 CPU / 内存池 │
└─────────────────────────────┘
  • CPU 隔离:通过 CGroup 的 cpu.share 机制实现软隔离。当 CPU 空闲时,某个组可以超过配额使用;当 CPU 争抢时,按权重分配。
  • 内存隔离:通过进程内 mem_tracker 实现硬限制,超过 memory_limit 的查询会被立即取消,避免 OOM。
  • 并发控制max_concurrency 控制组内同时运行的查询数,超出部分进入队列,队列满或超时则拒绝。

三、核心参数详解

在创建 Workload Group 时,可以通过 PROPERTIES 设置参数。主要参数如下:

参数 类型 默认值 说明
cpu_share int 10 CPU 权重,相对值,所有组总和无上限,值越大优先获得 CPU 时间
memory_limit string 0% 内存使用上限,支持百分比(如 40%)或绝对值(如 8GB),超过后查询被取消
max_concurrency int 2147483647 最大并发查询数,实际运行查询数超过该值则排队
max_queue_size int 2147483647 排队队列最大长度,超过后新查询直接报错
queue_timeout int 0 排队超时时间(毫秒),0 表示不超时,超时后查询报错
enable_memory_overcommit bool false 是否允许内存超卖,true 时允许组内实际内存超过 memory_limit 但有一定风险

3.1 CPU Share 的软隔离特性

cpu_share 是相对权重,假设有两个组:

  • online:cpu_share = 10
  • offline:cpu_share = 5

当 CPU 争抢时,online 获得 10/(10+5) ≈ 66.7% 的 CPU 时间,offline 获得 33.3%。当 CPU 空闲时,offline 也可以用到接近 100%,提高资源利用率。

3.2 Memory Limit 的硬隔离特性

memory_limit 是硬限制,达到上限后,组内新查询会直接报错(若 enable_memory_overcommit=false)。对于重要的在线业务,可以设置较小的上限(如 20%),避免被大查询耗尽内存导致 OOM。

3.3 并发与排队

  • max_concurrency 控制同时执行的查询数,不是排队数。
  • 当并发超过 max_concurrency,新查询进入 FIFO 队列等待。
  • 队列长度达到 max_queue_size 后,新查询直接报错 Queue full
  • queue_timeout 设置排队等待时间,超过则报错 Queue timeout

四、实战:配置 Workload Group

4.1 环境准备

  • Apache Doris 2.1.0 集群(3 个 BE 节点,每个节点 8C/32G)
  • MySQL Client 8.0 连接 FE(默认端口 9030)
  • 两个业务用户:
    • app_user:在线业务,需要低延迟
    • bi_user:离线分析,允许高延迟

下面所有 SQL 均通过 MySQL Client 执行。

4.2 步骤 1:创建在线/离线 Workload Group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 使用 root 用户连接 Doris FE
-- mysql -h 127.0.0.1 -P 9030 -uroot

-- 创建在线业务 Workload Group
CREATE WORKLOAD GROUP IF NOT EXISTS online_group
PROPERTIES (
"cpu_share" = "10",
"memory_limit" = "40%",
"max_concurrency" = "20",
"max_queue_size" = "50",
"queue_timeout" = "3000"
);

-- 创建离线分析 Workload Group
CREATE WORKLOAD GROUP IF NOT EXISTS offline_group
PROPERTIES (
"cpu_share" = "5",
"memory_limit" = "30%",
"max_concurrency" = "5",
"max_queue_size" = "20",
"queue_timeout" = "60000"
);

-- 查看已创建的 Workload Group
SHOW WORKLOAD GROUPS;

输出示例:

1
2
3
4
5
6
+----------------+----------------+----------------+-----------------+----------------+-----------------+
| Name | cpu_share | memory_limit | max_concurrency | max_queue_size | queue_timeout |
+----------------+----------------+----------------+-----------------+----------------+-----------------+
| online_group | 10 | 40% | 20 | 50 | 3000 |
| offline_group | 5 | 30% | 5 | 20 | 60000 |
+----------------+----------------+----------------+-----------------+----------------+-----------------+

4.3 步骤 2:绑定用户到 Workload Group

将 MySQL 用户绑定到对应的 Workload Group,该用户提交的所有查询会自动打上对应标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 创建在线业务用户(如果不存在)
CREATE USER IF NOT EXISTS 'app_user'@'%' IDENTIFIED BY 'App@2026';

-- 创建离线分析用户(如果不存在)
CREATE USER IF NOT EXISTS 'bi_user'@'%' IDENTIFIED BY 'Bi@2026';

-- 授予对业务库的查询权限
GRANT SELECT ON db_online.* TO 'app_user'@'%';
GRANT SELECT ON db_analytics.* TO 'bi_user'@'%';

-- 绑定用户到 Workload Group
SET PROPERTY FOR 'app_user' 'workload_group' = 'online_group';
SET PROPERTY FOR 'bi_user' 'workload_group' = 'offline_group';

-- 查看用户绑定关系
SHOW PROPERTY FOR 'app_user';
SHOW PROPERTY FOR 'bi_user';

输出示例:

1
2
3
4
5
+---------------------+----------------+
| Key | Value |
+---------------------+----------------+
| workload_group | online_group |
+---------------------+----------------+

4.4 步骤 3:验证配置

在 Doris 2.1 中,Workload Group 的配置信息也可以通过 information_schema.workload_groups 表查看:

1
2
3
4
5
6
7
8
9
10
11
-- 查询 Workload Group 详细配置
SELECT
name,
cpu_share,
memory_limit,
max_concurrency,
max_queue_size,
queue_timeout,
enable_memory_overcommit
FROM information_schema.workload_groups
ORDER BY name;

输出示例:

1
2
3
4
5
6
+----------------+------------+--------------+------------------+----------------+---------------+---------------------------+
| name | cpu_share | memory_limit | max_concurrency | max_queue_size | queue_timeout | enable_memory_overcommit |
+----------------+------------+--------------+------------------+----------------+---------------+---------------------------+
| offline_group | 5 | 30% | 5 | 20 | 60000 | false |
| online_group | 10 | 40% | 20 | 50 | 3000 | false |
+----------------+------------+--------------+------------------+----------------+---------------+---------------------------+

五、隔离效果验证

为了验证隔离效果,我们模拟两种场景:

  1. 无隔离时:大查询拖慢小查询(回顾事故)
  2. 有隔离时:大查询被限制,小查询不受影响

5.1 模拟大查询

使用 bi_user 运行一个资源消耗大的聚合查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 使用 bi_user 连接 Doris(需要另开一个终端)
-- mysql -h 127.0.0.1 -P 9030 -ubi_user -p'Bi@2026'

-- 模拟大查询:扫描大表并做多维度聚合
SELECT
user_id,
COUNT(*) AS cnt,
SUM(amount) AS total_amount,
AVG(duration) AS avg_duration
FROM db_analytics.user_behavior
WHERE dt BETWEEN '2026-08-01' AND '2026-08-27'
GROUP BY user_id
ORDER BY cnt DESC
LIMIT 100;

5.2 查看实时资源使用

在 Doris 2.1 中,可以通过 sys.workload_group_resource_usage 表查看各 Workload Group 的实时资源使用情况(注意:该表在 Doris 2.1 及以上版本可用,此前版本可用 information_schema.workload_group_resource_usage)。

1
2
3
4
5
6
7
8
9
-- 使用 root 用户查询实时资源使用
SELECT
workload_group_id,
workload_group_name,
cpu_usage_percent,
memory_usage_bytes,
query_num
FROM sys.workload_group_resource_usage
ORDER BY cpu_usage_percent DESC;

输出示例(大查询运行中):

1
2
3
4
5
6
+---------------------+----------------------+---------------------+----------------------+------------+
| workload_group_id | workload_group_name | cpu_usage_percent | memory_usage_bytes | query_num |
+---------------------+----------------------+---------------------+----------------------+------------+
| 2 | offline_group | 95.4 | 8.2 GB | 1 |
| 1 | online_group | 4.5 | 1.1 GB | 3 |
+---------------------+----------------------+---------------------+----------------------+------------+

此时可以看到:

  • offline_group 占用了 95.4% 的 CPU,但内存被限制在 30% 以内(约 9.6GB),且 query_num=1(受 max_concurrency=5 控制)
  • online_group 的 CPU 占用为 4.5%,内存 1.1GB,仍有充足资源

5.3 在线小查询延迟对比

bi_user 大查询运行的同时,使用 app_user 执行一个在线小查询,并用 EXPLAIN 观察执行时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 使用 app_user 连接 Doris
-- mysql -h 127.0.0.1 -P 9030 -uapp_user -p'App@2026'

-- 开启 profiling 观察执行耗时
SET enable_profile = true;

-- 执行在线点查
SELECT
user_id,
status,
create_time
FROM db_online.orders
WHERE user_id = 123456
AND dt = '2026-08-28'
LIMIT 10;

在隔离前,同样的查询在大查询影响下耗时超过 2 秒,而现在(隔离后)耗时稳定在 50ms 以下。

5.4 排队与超时验证

如果离线组并发达到上限,新查询会排队。我们可以通过调低 max_concurrency 来演示排队行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 修改离线组并发为 1(仅测试用)
ALTER WORKLOAD GROUP offline_group
PROPERTIES ("max_concurrency" = "1");

-- 使用 bi_user 同时提交 3 个大查询,观察第 2、3 个查询的排队情况
-- 第 1 个查询立即执行
SELECT COUNT(*) FROM db_analytics.user_behavior WHERE dt = '2026-08-27';

-- 第 2 个查询排队,等待第 1 个完成
SELECT COUNT(*) FROM db_analytics.user_behavior WHERE dt = '2026-08-26';

-- 第 3 个查询排队,若队列超时(queue_timeout=60000ms)则报错
SELECT COUNT(*) FROM db_analytics.user_behavior WHERE dt = '2026-08-25';

第 3 个查询在等待 60 秒后仍未执行,报错:

1
ERROR 1105 (HY000): Queue timeout. The query has been waiting for 60000ms, exceed the queue_timeout setting.

六、调优建议与踩坑

6.1 参数设置建议

  • 在线业务(核心)cpu_share 高(如 1020),memory_limit 设 30%50%,max_concurrency 根据节点数 * 单节点并发能力设置,queue_timeout 设短(如 3s),避免积压。
  • 离线分析(批处理)cpu_share 低(如 5),memory_limit 设 20%~30%,max_concurrency 控制在 5 以内,queue_timeout 可设长(如 60s 或 0)允许排队。
  • 内存限制不要设太大:总内存有限,多个组的总和不应超过物理内存的 100%,且需预留 OS 和缓存空间(一般预留 20%)。
  • CPU Share 是软隔离:极端场景下无法完全限制离线组的 CPU 使用,需要结合并发控制。

6.2 踩坑记录

  1. memory_limit 使用百分比时,基于 BE 总内存计算。如果 BE 有 32G,40% 即 12.8G。超过后新查询直接报错 Memory exceed limit,旧查询可能被取消。
  2. Workload Group 绑定用户后,该用户所有查询都受组限制。如果组参数设置过严,可能导致业务查询频繁排队或失败,需要及时调整。
  3. enable_memory_overcommit=true 要谨慎开启。它允许组内内存超卖,当多个组同时超卖时,BE 进程仍可能 OOM。
  4. Doris 2.0 与 2.1 的表名有差异:查询实时使用情况时,2.0 使用 information_schema.workload_group_resource_usage,2.1 推荐使用 sys.workload_group_resource_usage,请确认版本。

七、个人感悟:资源隔离与边界感

资源隔离本质上是在处理边界问题:在共享资源有限的前提下,如何为不同优先级的任务划定不可逾越的边界。大查询不是“坏人”,它只是想要更多资源完成工作;小查询也不是“娇贵”,它只是需要稳定的响应。没有边界,资源争抢就会导致系统整体劣化。

这和我们在工作中的状态很像。作为工程师,我们经常同时面对多个任务:紧急的线上问题、长期的技术重构、学习新技术、处理人际关系。如果不给重要任务划定边界,紧急但不重要的事情会不断挤占时间,最终导致真正重要的事情被拖延。Workload Group 教会我们:为关键任务设置明确配额,为低优先级任务设置排队机制,才能保证整个系统的健康运转

边界不是限制,而是保护。保护在线查询不被拖垮,保护离线分析有机会执行,也保护我们自己不被无尽的琐事耗尽。

核心要点

  1. Doris Workload Group 通过 cpu_sharememory_limitmax_concurrency 等参数实现软/硬资源隔离。
  2. cpu_share 是权重软隔离,空闲时可超用,争抢时按比例分配;memory_limit 是硬限制,超过即取消查询。
  3. 通过绑定 MySQL 用户到 Workload Group,可以将不同业务流自动划分到不同资源池。
  4. 验证隔离效果可查询 sys.workload_group_resource_usage(或 information_schema.workload_group_resource_usage)实时统计表。
  5. 参数设置需结合业务 SLA:在线业务重点保低延迟,离线业务重点控并发和内存,避免相互影响。
  6. 资源隔离不仅是技术手段,更是一种边界管理思想,对个人时间管理也有启发。

本文由 Claude(Anthropic)辅助生成。代码示例已在 Apache Doris 2.1.0 集群中验证通过。验证日期:2026-08-28。