第 23 关 · ★★★★

工作负载与资源治理

Workload Group、内存、Spill、熔断与多租户隔离。

已点亮 · 最佳 分
工作负载与资源治理 第 1 页

Day 23|工作负载治理:Workload Group、资源隔离、并发、队列、Spill 与熔断

Day 23 工作负载治理总览
Day 23 工作负载治理总览

Day 22 解决了 FE、BE、副本、扩缩容、升级、备份和恢复等生命周期问题。生产集群具备高可用拓扑以后,还会面临另一类长期风险:系统整体没有节点故障,业务体验却持续恶化。经营大屏突然变慢,临时分析占满 CPU,历史回算挤压磁盘带宽,某条大 Join 消耗大量内存,几十个相同报表同时到达后把查询线程和连接池堆满。此时 FE、BE 都可能保持 Alive,真正失控的是共享资源的分配秩序。

Doris 的工作负载治理围绕三件事展开:先把不同业务划入清晰的资源合同,再用并发与队列吸收流量峰值,最后用 Spill、SQL Block Rule 和 Workload Policy 处理超出合同的异常任务。今天的目标,是让在线报表、Ad-hoc、ETL、导入和物化视图刷新在同一平台中保持可控共存。

完成本篇后,你应当能够:

  • 区分 Resource Group、Workload Group 与 Compute Group 的隔离边界;
  • 创建 Workload Group,并完成权限授予、用户默认绑定和会话临时路由;
  • 使用 CPU、内存、IO、并发和队列参数表达业务优先级;
  • 解释 BE Process、Workload Group、Query 三级内存约束;
  • 判断一次查询应继续使用内存、进入 Spill、排队、熔断或取消;
  • 使用 SQL Block Rule 阻断无分区条件、超大扫描和危险 SQL 模式;
  • 使用 Workload Policy 根据真实执行时间、扫描量和内存消耗止损;
  • 通过系统表、Audit Log、Query Profile 和监控指标形成资源诊断证据链;
  • 输出一份能够进入生产评审的《多租户资源隔离策略》。

零、先校准 Doris 4.x 的术语和能力边界

工作负载治理领域存在一些历史名称。当前 4.x 官方文档将资源控制分成物理隔离与逻辑隔离两层:存算一体模式可以使用 Resource Group 按 BE 节点划分资源池;存算分离模式使用 Compute Group 划分独立计算池;Workload Group 在单个 BE 进程内部继续细分 CPU、内存和 IO,并附带并发与队列能力。[1]

0.1 Workload Group 是进程内隔离

一个 BE 进程中可以同时存在多个 Workload Group。查询线程、内存 Tracker 和扫描吞吐会按组进行计量与约束。多个组仍然共享 BE 进程、部分 Cache、RPC 线程池和后台组件。某个 BE 进程发生崩溃或被操作系统 OOM Killer 终止时,该 BE 上的全部组都会中断。[2]

因此,Workload Group 适合细粒度资源分配、突发流量控制和低成本多租户。核心业务需要进程级故障隔离时,应使用 Resource Group 或 Compute Group,把关键负载放到独立 BE 或独立计算组。

0.2 4.0 统一了软限制和硬限制的表达

当前推荐的 CPU 参数是 min_cpu_percentmax_cpu_percent,内存参数是 min_memory_percentmax_memory_percent。前者表达竞争时的最低保障,后者表达任何时候都不能突破的最高上限。系统表和部分兼容接口中仍可能出现 cpu_sharecpu_hard_limitmemory_limit 等历史字段,生产配置应以目标 Patch 版本的 SHOW WORKLOAD GROUPS 和官方 SQL 参考为准。[2]

0.3 Workload Group 无法完整约束所有后台资源

当前 Workload Group 可以管理查询的 CPU、内存、内部表和外部表读取吞吐。高吞吐导入可能触发 Compaction,Compaction 当前没有完整纳入 Workload Group CPU 限制;共享 Cache、部分 RPC 和系统线程同样位于组外。在线与离线混合压测需要包含真实导入和 Compaction,单纯运行 SELECT 难以覆盖生产争用形态。[2]

0.4 Spill 默认关闭

当前支持 Spill 的算子包括 Hash Join、Aggregation、Sort 和 CTE。Spill 会将中间状态写入本地磁盘,帮助内存受限的查询继续完成,同时显著增加磁盘 IO 和执行时间。enable_spill 默认值为 false,启用时应配置独立 Spill 路径、容量限制和更合理的 query_timeout。[3]

0.5 分区强制规则存在 Patch 版本边界

SQL Block Rule 的 require_partition_filter 在 4.0 系列从 4.0.7 起支持,在 4.1 系列从 4.1.2 起支持。当前 Stable 4.0.8 与 Latest 4.1.3 均覆盖该能力。[4]

截至 2026 年 8 月 17 日,Apache Doris 下载页将 4.1.3 标记为 Latest、4.0.8 标记为 Stable。本文的命令和限制以当前 4.x 文档为基线,上线前仍需在计划部署的 Patch 版本重新执行语法与行为验证。[5]


一、混合工作负载为何容易失控

混合工作负载冲突模型
混合工作负载冲突模型

数据库资源争用经常从一条看起来合理的 SQL 开始。某个分析师查询过去一年的订单明细,SQL 使用了有效 Join 和聚合,单独运行也能在几十秒内完成。问题出现在它与其他任务同时执行时:经营大屏每十秒刷新,ETL 正在重算昨日分区,Routine Load 持续写入,后台 Compaction 合并小版本,另一个团队又发起了宽表导出。

这几类负载的资源曲线不同:

负载 典型目标 主要资源 常见风险
核心在线报表 低延迟、稳定 p95/p99 CPU、线程、缓存 被长查询挤压后尾延迟上升
Ad-hoc 探索 允许较长时间,结果灵活 内存、Shuffle、Spill SQL 不可预测,容易产生大中间结果
ETL / 回算 吞吐和完成时间 CPU、IO、网络、磁盘 长时间占用资源,污染缓存
实时导入 持续稳定写入 MemTable、磁盘、Compaction 小批过多导致版本与合并压力
MV 刷新 按时完成,数据新鲜 Scan、Join、Aggregate 与报表争抢相同数据和计算资源

一条大扫描首先增加本地或远程读取;扫描数据进入 Join 和聚合后继续消耗 CPU 与内存;内存不足会触发 Spill;Spill 又占用本地磁盘和线程;同一时间 Compaction 也在使用磁盘;RPC 和 Pipeline 调度得到的 CPU 变少,短查询开始排队。最终表现可能是查询超时、导入延迟、FE 入口堆积和 BE 尾延迟共同升高。

1.1 query_timeout 只能约束时间

单条 query_timeout 可以终止运行时间过长的 SQL,却无法保证它在超时前使用多少 CPU、扫描多少数据、占用多少内存,也无法控制几十条查询同时进入系统。一个设置为 300 秒超时的查询,可能在第 20 秒已经让 BE 内存紧张;五十条 5 秒查询也可能在短时间内把并发和线程池推到极限。

稳定治理需要五个维度共同工作:

  1. 资源分组:明确谁与谁共享资源;
  2. 资源上限:限制 CPU、内存和 IO;
  3. 入口节流:限制并发并建立等待队列;
  4. 执行降级:允许大查询 Spill,降低 OOM 风险;
  5. 异常熔断:在规划或执行阶段阻断超出边界的任务。

二、三种隔离方式怎样选择

Resource Group、Workload Group 与 Compute Group
Resource Group、Workload Group 与 Compute Group

Doris 当前的三种隔离方式可以理解成两道边界:先决定是否拆分 BE 进程或计算池,再决定是否在进程内进一步分配资源。

2.1 Resource Group:存算一体下的节点级物理隔离

Resource Group 通过 BE Tag 划分节点,并让表副本按照 Tag 放置。绑定到某个组的用户只在对应 BE 节点上查询。在线组与离线组使用不同 BE 进程,离线查询造成 BE OOM 时,在线组的 BE 不会一起退出。

这种方式的代价主要在副本和容量。每个需要读取某张表的 Resource Group 都要拥有可用副本,组越多,存储开销和副本调度复杂度越高。跨组查询受到副本放置约束,资源借用能力也较弱。

2.2 Workload Group:BE 进程内的逻辑隔离

Workload Group 不增加节点和副本,可以为不同用户设置 CPU 最低保障、CPU 最高上限、内存上下限、IO 吞吐、扫描线程、并发、队列与 Spill 策略。系统空闲时,软限制允许业务借用更多资源;竞争出现后,最低保障和最高上限开始发挥作用。

这类隔离成本较低,也保留了共享集群的资源利用率。它无法隔离 BE 进程级故障,系统 Cache 和后台任务也可能形成残余影响。

2.3 Compute Group:存算分离下的计算池硬隔离

存算分离模式下,Compute Group 使用独立计算节点承担查询。在线、ETL、AI 检索可以分别进入不同 Compute Group,共享远端存储和元数据。工作负载需要更细粒度控制时,可以在每个 Compute Group 内再创建 Workload Group。[6]

2.4 一条实用选择路径

可以按照以下顺序判断:

  • 业务能否接受同一 BE 进程故障时共同中断;
  • 是否愿意承担额外节点、副本或计算池成本;
  • 是否需要共享数据、共享缓存与弹性借用资源;
  • 主要诉求是稳住资源比例,还是隔离进程级故障;
  • 部署形态是存算一体还是存算分离。

核心支付报表、监管查询和全量回算之间需要强隔离时,节点级资源池通常更稳妥。多个普通业务共享同一集群,希望控制突发和公平性时,Workload Group 提供更高的资源利用率。


三、Workload Group 的对象、权限与路由

Workload Group 对象与绑定流程
Workload Group 对象与绑定流程

Workload Group 的落地包含四个对象:组定义、使用权限、用户默认绑定和 Session 临时绑定。只创建一个组并不会自动把业务流量送进去。

3.1 创建在线报表组

CREATE WORKLOAD GROUP IF NOT EXISTS wg_bi
PROPERTIES (
    "min_cpu_percent" = "30%",
    "max_cpu_percent" = "60%",
    "min_memory_percent" = "25%",
    "max_memory_percent" = "45%",
    "max_concurrency" = "12",
    "max_queue_size" = "30",
    "queue_timeout" = "3000",
    "slot_memory_policy" = "dynamic"
);

参数值只是实验起点。生产比例需要结合 BE 核数、mem_limit、实际并发、Query Profile 和目标 SLA 校准。

3.2 授权和绑定

GRANT USAGE_PRIV ON WORKLOAD GROUP 'wg_bi' TO 'bi_user'@'%';

用户能够看到并使用目标组后,可以设置持久默认组:

SET PROPERTY 'default_workload_group' = 'wg_bi';

当前 Session 需要临时切换时:

SET workload_group = 'wg_bi';

Session 变量优先级高于用户默认属性。连接池中执行临时切换时,需要在借出连接后设置,在归还连接前恢复,避免下一个请求继承错误的组。

3.3 normal 组的作用

Doris 2.1 起会自动创建不可删除的 normal 组。没有匹配到其他组的查询通常进入该组。生产治理中应避免把所有默认流量长期留在 normal:未知应用、临时账号和遗漏绑定会继续共享一个大池,最终绕过业务优先级设计。

3.4 存算分离需要绑定 Compute Group

存算分离模式创建 Workload Group 时需要显式使用 FOR <compute_group>。Workload Policy 绑定组时也需要使用 <compute_group>.<workload_group> 全限定名。组一旦绑定到某个 Compute Group,当前实现不支持直接迁移到其他 Compute Group,需要重新创建并切换流量。[6]


四、CPU 治理:软保障、硬上限和系统余量

CPU 软限制与硬限制
CPU 软限制与硬限制

4.1 min_cpu_percent 表达最低保障

当多个组同时争抢 CPU 时,最低保障部分不能被其他组抢走;系统空闲时,该组可以使用更多 CPU。在线报表具有明显高峰和低谷时,最低保障可以提高高峰期的稳定性,同时保留低峰期的整体利用率。

所有组的 min_cpu_percent 之和不能超过 100%,单个组的最小值也不能大于最大值。实际规划通常还会保留一部分 CPU 给 RPC、Compaction、Cache 回收和系统线程。将所有组的保障值加到 100%,会让内部组件在高负载时缺少余量。

4.2 max_cpu_percent 表达最高上限

最高上限属于硬限制。Ad-hoc 和 ETL 即使在系统空闲时也不能突破该比例。硬限制能够防止某个组把整个 BE CPU 打满,对核心在线业务更有保护价值。代价是离线任务在空闲时也无法充分使用全部核心,完成时间可能延长。

一个 64 核 BE 的起始模板可以是:

最低保障 最高上限 说明
核心报表 35% 60% 高峰期优先,允许突发
Ad-hoc 5% 20% 低保障、严格封顶
ETL 5% 20% 稳定运行,限制峰值
系统余量 约 15% 留给内部线程与后台任务

该表只表达优先级,最终结果需要以目标集群压测为准。

4.3 CGroup 环境

Workload Group 的 CPU 管理依赖 CGroup。物理机需要确认 CGroup V1/V2、创建 Doris 目录、设置权限,并配置 doris_cgroup_cpu_path;机器重启后目录可能被清空,应通过 systemd 固化创建与授权步骤。容器中的 CPU 限制基于容器已经获得的 CPU 继续细分:容器只有 8 核、组上限 50% 时,实际最多使用约 4 核。[2]

容器需要具备操作宿主机 CGroup 的权限。Kubernetes 场景优先使用 Doris Operator,并在安全评审中明确 privileged 或等效权限边界。

4.4 IO 与扫描线程同样属于资源合同

CPU 被限制以后,离线任务仍可能通过大量并行 Scanner 把本地盘或对象存储带宽打满。Workload Group 提供 read_bytes_per_secondremote_read_bytes_per_second,分别限制读取 Doris 内部表与外部表的吞吐;scan_thread_nummax_remote_scan_thread_nummin_remote_scan_thread_num 用于控制扫描线程池规模。

这些参数需要和磁盘、对象存储、网络及查询并行度一起验证。把线程数压得过低,会让 CPU 空闲但扫描排队;只限制吞吐、保留过多线程,又可能增加上下文切换与小 IO。内部表读取上限按目录生效,Spill 文件位于同一受控目录时也会受影响。在线报表依赖热缓存时,不应通过清空 OS Cache 直接评估 IO 上限,生产 POC 应分别记录冷缓存和热缓存结果。


五、三级内存模型

Process、Workload Group 与 Query 三级内存
Process、Workload Group 与 Query 三级内存

Doris 的内存治理有三层:BE Process、Workload Group、Query。一次算子申请内存时,需要同时满足这三层约束。[3]

5.1 BE Process 层

be.conf 中的 mem_limit 控制整个 BE 进程可使用的内存上限。这里覆盖查询、导入、Cache、Compaction 和其他内部对象。BE 与 Kafka、HDFS、代理程序混部时,操作系统实际可用内存可能远小于配置值,Doris 内部释放机制还没有完成,系统 OOM Killer 已经开始杀进程。

生产环境应尽量让一台机器只运行一个 BE,容器资源限额也要与 mem_limit 的自动检测结果一致。监控需要同时观察 BE Tracker、进程 RSS、系统 Available Memory 和容器限制。

5.2 Workload Group 层

  • min_memory_percent:内存紧张时为组保留的最低份额;
  • max_memory_percent:组的内存最高边界,超过后触发 Spill 或 Kill;
  • memory_low_watermark:低水位,用于区分相对宽松的内存区间;
  • memory_high_watermark:高水位,超过后 Reserve Memory 更容易失败并触发 Spill。

当前 Workload Group 指南列出的默认值为 50% 与 80%,而部分当前 SHOW WORKLOAD GROUPS 示例显示 75% 与 85%。这类默认值可能随版本和配置演进,目标集群应以 SHOW WORKLOAD GROUPS 的实际结果为准。

所有组的最低内存保障之和不能超过 100%。最低保障配置过高会让系统缺少弹性,配置过低又可能让在线业务在资源紧张时得不到稳定内存。

5.3 Query 层

exec_mem_limit 在查询开始前确定,执行过程中不能动态修改。官方文档说明,3.1 之前默认值为 2 GB,3.1 及以后调整为 100 GB。升级和迁移环境应执行:

SHOW VARIABLES LIKE 'exec_mem_limit';

单查询上限过高时,一条大查询可以快速占满组内存;上限过低时,正常查询会频繁 Spill 或失败。适合的值来自真实 SQL 的 Peak Memory 分布,而非按机器总内存直接取一个比例。


六、并发与队列:先控制进入执行层的任务数量

并发与队列状态机
并发与队列状态机

6.1 三个核心参数

参数 默认值 作用
max_concurrency 最大整数 同一组正在运行的查询上限
max_queue_size 0 等待队列长度,0 表示不排队
queue_timeout 0 最长等待毫秒数,0 表示立即失败

当运行数达到 max_concurrency 时,新查询进入队列;队列已满时直接拒绝;等待超过 queue_timeout 后返回失败。排队把突发流量转化成有界等待,帮助 BE 避免在几秒内启动过多 Fragment 和 PipelineTask。

6.2 多 FE 集群的并发口径

当前队列参数按照单 FE 粒度生效。3 个 FE、每个 FE 的 max_concurrency=10 时,均匀分流下的集群并发可能接近 30。业务期望集群同时运行 12 条核心报表,前面有 3 个 FE 时,可以从单 FE 设置 4 开始验证,并考虑入口流量是否均匀。[7]

这项边界也要求负载均衡器具备健康检查和相对均匀的连接分配。某个长连接池长期只连接一台 FE,会让该 FE 的队列提前饱和,其他 FE 仍然空闲。

6.3 队列参数要与调用方一起设计

数据库队列超时为 3 秒,应用请求超时为 2 秒时,客户端会先断开;应用随后自动重试,又在队列中制造一个新请求。合理的时间关系通常是:数据库队列超时短于应用总超时,并给真正执行、网络返回和少量重试保留预算。

运维账号在紧急处置时可能也被队列阻塞,可以在当前会话临时设置:

SET bypass_workload_group = true;

操作完成后应关闭或重新建立连接,避免日常查询绕过治理。


七、Slot 内存:把并发与单查询预算联动

Slot 内存策略
Slot 内存策略

静态 exec_mem_limit 很难同时适应小查询和大查询。Workload Group 同时配置 max_memory_percentmax_concurrency 后,可以把组内存逻辑切成多个 Slot:

单查询内存预算
= Workload Group 可用内存
× query_slot_count
÷ max_concurrency

默认每条查询使用 1 个 Slot。需要更多内存的查询可以设置:

SET query_slot_count = 2;

查询占用更多 Slot 后,同组可运行的查询数量会下降,新增任务进入队列。这让“给大查询更多内存”和“降低并发”形成同一个资源合同。

slot_memory_policy 有三种模式:

模式 行为 适用特点
none 默认模式,查询尽可能使用内存,到组上限后 Spill 配置简单,单查询预算不稳定
fixed 按 Slot 计算固定硬上限 可预测,空闲资源利用较保守
dynamic 空闲 Slot 可以动态分配给运行中的大查询 利用率较高,需要严格设置并发

fixeddynamic 会覆盖静态 exec_mem_limit。如果 max_concurrency 设置过高,每个 Slot 的内存很小,查询更容易触发 Spill。设计 Slot 时要把并发、内存和完成率一起验证。


登录后可阅读本文完整内容。