云计算百科
云计算领域专业知识百科平台

Doris BE 周期性重启:不是内存不够,是 RScan 把 PID 打满了

系列:《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)

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Doris BE 周期性重启:不是内存不够,是 RScan 把 PID 打满了
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!