系列:《Cloud GIDO 三产品特性深讲》加餐 · 场记第 1 篇 标签:Doris、存算分离、线程泄漏、K8s、pids.max、排障
开篇:组件齐了,BE 照样「周期性消失」
Cloud GIDO 跑在 Kafka / Flink / Doris / K8s 之上。运行时再强,底座也会出「看起来像内存、其实不是」的故障。
很常见的现场:
Compute(CG)节点隔一两天重启一次
第一反应:加内存(8G → 16G)
第二反应:怪 Routine Load / 大查询
FE 开始报 UnknownHostException、offset 拉失败——其实是 BE 已经挂了
真正要命的日志往往只有一行:
Could not create thread. (error 11) Resource temporarily unavailable
Thread pool routine_load failed to create thread
这篇按真实排查顺序写:痛点 → 证据 → 纠偏 → 根因 → 分层治理。 环境参考:Doris BE 3.1.4(Cloud / Storage Vault / S3)、K8s、pids.max ≈ 37776。 配图已脱敏:数据源 / 集群 / namespace / pod 标签均已打码或裁掉,勿使用未打码原图发文。

现象:重启曲线是「锯齿」,不是偶发尖峰
时间线通常是:
线程数线性爬升(监控呈锯齿)
顶到容器 pids.max
某次再 pthread_create 失败(日志常落在 Routine Load)
BE abort 重启 → 线程归零 → 再爬
图上要点:
-
斜率近似直线 → 更像「创建不回收」,不像随负载抖动
-
每条线顶在 ~38k,与 pids.max≈37776 吻合
-
多条线错开爬升:重启会换 container id,图例会变多
结论先行:不是 OOM,加内存挡不住。
error 11:先查 PID,不是先加内存
pthread_create 返回 EAGAIN(error 11),常见上限:
|
容器 PID 配额 |
K8s podPidsLimit / cgroup pids.max |
cat /sys/fs/cgroup/pids.max |
|
用户线程上限 |
ulimit -u / nproc |
ulimit -u |
|
节点内核 |
kernel.pid_max / threads-max |
sysctl |
重启后进容器,常见是「掉下来之后」的稳态:
cat /sys/fs/cgroup/pids.max # ≈ 37776
cat /sys/fs/cgroup/pids.current # 几千~一万多
ulimit -u # unlimited
sysctl kernel.pid_max # 很大,不是瓶颈
崩溃前是否贴顶,必须看监控历史(夜莺 / Grafana / Prometheus),不能只看重启后的 pids.current。
用监控钉死「直接死因」
PromQL(脱敏示例):
container_threads{
namespace="<ns>",
pod=~"doris-be-.*",
container="compute"
}
只认 container="compute"、镜像为 doris-be 的那条。
|
峰值 |
≈ 37774~37776 |
贴满 pids.max≈37776 |
|
重启后 |
约 1.1万~1.7万 |
abort 后线程掉下来 |
因果链:
线程打满 pids.max → 再建线程 EAGAIN → abort 重启
24 小时窗口同样是锯齿:

坑:日志写 Routine Load ≠ RL 是主因
崩溃栈落在 RL,容易误判:「增量也不大,怎么打挂?」
拆两层:
|
触发者 |
撞顶时正好 RL 再开线程 → 日志写 RL |
|
水位贡献者 |
真正堆到 3.7 万的,未必是 RL |
要回答「谁占线程」,必须对还活着、正在上涨的 BE 做线程名采样,不能只看 abort 栈。
关键一招:按线程名聚合
PID=$(pidof doris_be)
echo "PID=$PID total=$(ls /proc/$PID/task | wc -l) time=$(date -u +%H:%M:%S)"
for t in /proc/$PID/task/*/comm; do cat "$t"; done 2>/dev/null \\
| sort | uniq -c | sort -rn | head -40
早期采样示例:
|
RScan_normal |
5545 |
远端/云存储扫描,已是大头 |
|
brpc_* |
数百 |
RPC |
|
routine_load |
24 |
很少 |
|
total |
~7300 |
水位更高时(total≈25377):
|
总线程 |
25377 |
|
RScan_normal |
23620(约 93%) |
|
routine_load |
24,且 RL task_count≈0 |
再对照 BE metrics:
curl -s http://127.0.0.1:8040/metrics \\
| grep -E 'doris_be_thread_pool_active_threads|doris_be_routine_load_task_count' \\
| grep -E 'RScan|routine_load'
对比很扎心:
-
指标里 RScan_normal:max=512,active=0
-
系统里却有 两万多个 同名线程
不是扫描真的需要两万线程,而是泄漏:创建了不回收。 RL 只是贴顶后再 pthread_create 失败的那一棒。
根因
|
直接原因 |
线程打满 pids.max≈37776,EAGAIN → abort |
|
主要贡献 |
RScan_normal 远端扫描线程异常堆积 |
|
业务相关 |
扫云表、INSERT … SELECT、Storage Vault/S3 读路径 |
|
易误解 |
abort 栈常在 RL;RL ≠ 水位主因 |
|
无效手段 |
只加内存(如提到 16G) |
一句话:
RScan_normal 线程泄漏 → 贴满 pids.max → EAGAIN → BE abort。
处理分层:止血 / 告警 / 治本
止血
-
PAUSE 重复 / 非必要 Routine Load
-
降低大宽表 INSERT…SELECT 频率(现场曾把 ADS 宽表从 1 分钟改为 10 分钟,爬升斜率明显变缓)
-
必要时滚动重启泄漏严重的 BE(治标)
-
评估提高 podPidsLimit / pids.max(如 65536)

降频能「减速」,说明宽表/扫云会加速泄漏;曲线仍可能爬到 ~38k → 治本还得解决 RScan 不回收。
告警
pids.max≈37776 时,日常可能就在 1.7万~2.5万。建议:
|
Warning |
30000(约 80%) |
≥10 分钟 |
|
Critical |
35000(约 93%) |
≥1~2 分钟 |
max by (pod) (
container_threads{
namespace="<ns>",
pod=~"doris-be-.*",
container="compute"
}
)
以后若提高 pids.max,按 66% / 85% / 93% 同比调整。
治本
-
收敛并发扫云表 / 宽表加工
-
核对 remote scan / scanner 相关上限
-
对照 Doris 版本是否有 Remote Scan 线程泄漏补丁
-
RScan_normal 线程采样与 container_threads 叠图
社区公开 Issue(本来就在公网,可直接引用):
-
apache/doris#66997 [Bug][Cloud] RScan_normal ThreadPool idle workers never shrink; OS threads grow far beyond max_threads=512

排障清单
容器内
cat /sys/fs/cgroup/pids.max
cat /sys/fs/cgroup/pids.current
ulimit -u
PID=$(pidof doris_be)
ls /proc/$PID/task | wc -l
for t in /proc/$PID/task/*/comm; do cat "$t"; done 2>/dev/null \\
| sort | uniq -c | sort -rn | head -40
curl -s http://127.0.0.1:8040/metrics \\
| grep 'doris_be_thread_pool_active_threads' \\
| grep -v '} 0$' | sort -t' ' -k2 -nr | head -20
监控
container_threads{namespace="<ns>",pod=~"doris-be-.*",container="compute"}
container_memory_working_set_bytes{namespace="<ns>",pod=~"doris-be-.*",container="compute"}
FE
SHOW BACKENDS\\G
SHOW ROUTINE LOAD\\G
SHOW PROCESSLIST;
经验教训
abort 栈 ≠ 根因水位——日志写谁,只说明谁最后踩空。
error 11 优先查 PID / 线程,不要先扩内存。
重启后现场会「变干净」——依赖监控历史 + 上涨中采样。
池配置要对齐 OS 线程数——max=512、active=0 却有两万同名线程 → 高度可疑泄漏。
告警阈值贴日常水位——太低只会吵到麻木。
排查叙事可记成:
表象(重启)→ 误判(内存 / RL)→ 证据(pids.max + 夜莺锯齿)→ 纠偏(线程名采样)→ 根因(RScan 泄漏)→ 分层治理
今日小结
底座组件「齐了」不等于不会踩坑。Doris Cloud / 存算分离场景下,RScan_normal 这类远端扫描路径一旦泄漏,会先打满容器 PID,再以「很像 OOM / 很像 RL」的样子把 BE 打挂。
对跑 GIDO 批加工、宽表 INSERT…SELECT、扫云表的团队:监控里请把 container_threads 和线程名采样当成一等公民,别只盯内存曲线。
讨论:
你们 Doris Compute 的 pids.max / 线程告警阈值是怎么定的?
撞过 error 11 时,最后是内存、RL,还是某个内部线程池泄漏?
延伸
-
系列总览:Cloud GIDO | Open Infrastructure for Data, Governance & Risk
-
Apache Doris 文档 / Issue(Remote Scan / Cloud)
网硕互联帮助中心




评论前必须登录!
注册