apache / apache/doris

BE remote scanner thread pool (rs_normal) never shrinks — thread count grows to 27k+ and BE freezes

Open
#65,416 24 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
15.9k
Forks
3.9k
Avg merge
2d 23h
Merged PRs (30d)
520

Description

# BE remote scanner thread pool (`rs_normal`) never shrinks — thread count grows to 27k+ and BE freezes

## Environment

- **Doris version**: 4.1.1-rc01
- **Cluster size**: 4 BE + 3 FE, deployed via DorisOperator on Kubernetes
- **BE image**: official `apache/doris:doris-4.1.1-rc01-be` (x86_64)
- **Workload**: BE queries external tables backed by Paimon on S3 (Doris × Paimon catalog), mixed with normal internal OLAP queries
- **BE resources**: cpu 8 core, memory limit 40G (cgroup), `mem_limit=32G`

## Symptom

BE pod RSS climbs monotonically from ~3G to 6–7G over ~4h, then either:
- keeps growing until the pod is OOM-ish and gets killed by cgroup, **or**
- at ~27k threads the BE process freezes: it stops responding to FE heartbeats, `SHOW BACKENDS` shows `Alive=false` with `SocketTimeoutException: Read timed out`, CPU drops to ~1m, readiness probe times out. **Not** OOMKilled — internal lock/resource exhaustion.

The only recovery is `kubectl delete pod` (restart the BE process).

## Misleading signals

All conventional memory metrics stay green, which made this look like a "memory leak" for a while:

- `JVM heap used` — normal GC sawtooth, returns to baseline
- `jemalloc resident` — stable at ~2G
- `jvm non-heap` — stable at ~130M
- `mem_tracker` — stable

## Root cause

`/proc/$BE_PID/status` shows `Threads=16000–27000`, of which ~90% are `rs_normal [worker]` threads in `S` (sleeping) state. Each thread has a 512K stack → **7–12GB just for thread stacks**, which is what pushes RSS up.

Thread census command:
```bash
BE_PID=$(pgrep -f doris_be)
ls /proc/$BE_PID/task/ | while read t; do cat /proc/$BE_PID/task/$t/comm; done \
| sort | uniq -c | sort -rn | head
```

Typical output on a frozen BE:
```
27012 rs_normal
48 olap_scan
24 brpc
...
```

The `rs_normal` pool (remote scanner, used for querying external catalogs like Paimon/Hive/Iceberg) **only grows, never shrinks**. Idle keep-alive eviction either isn't implemented or isn't effective in 4.1.1-rc01.

## Configs I tried to cap the pool (all ineffective on rc01)

| # | Where | Setting | Result |
|---|-------|---------|--------|
| 1 | `be.conf` | `doris_scanner_thread_pool_thread_num=256` | Only caps `ls_normal` (local scanner). `rs_normal` still linear-grows. |
| 2 | `be.conf` | `doris_max_remote_scanner_thread_pool_thread_num=256` | **No effect.** `rs_normal` keeps growing past 256. |
| 3 | FE | `ALTER WORKLOAD GROUP normal PROPERTIES ('max_remote_scan_thread_num'='256', 'min_remote_scan_thread_num'='48');` | **No effect.** |

Related defaults on this BE:
- `doris_remote_scanner_thread_pool_thread_num=48` (initial)
- `doris_max_remote_scanner_thread_pool_thread_num=-1` (unlimited)

## Current workaround

CronJob that rotates one BE pod every 4h (`kubectl delete pod` on the oldest ready BE, with guards: `ready BE >= 3` and `pod age > 6h`). This keeps the leak from ever reaching the freeze threshold, but it's purely symptomatic.

YAML for the workaround is available if useful.

## Expected behavior

At least one of these should actually work on rc01:

1. `doris_max_remote_scanner_thread_pool_thread_num=N` should hard-cap `rs_normal` size, excess idle threads should be reaped when the cap is hit and load drops.
2. Idle `rs_normal` workers should be evicted after a keep-alive timeout (like `ls_normal` does), so the pool shrinks back to `doris_remote_scanner_thread_pool_thread_num` when external-scan load stops.
3. `ALTER WORKLOAD GROUP … max_remote_scan_thread_num=N` should bound concurrent remote scans per workload group — currently appears to be ignored or not enforced against the `rs_normal` pool.

## Reproduction

1. Fresh BE 4.1.1-rc01 with a Paimon catalog on S3.
2. Run a sustained mixed workload that issues external scans against Paimon (a few QPS is enough; the pool grows even under modest load).
3. Within 4–8h, `Threads=` in `/proc/$BE_PID/status` crosses 20k and RSS climbs correspondingly.
4. Eventually (usually 24k–28k threads) the BE stops answering FE heartbeats and has to be killed externally.

Happy to provide thread dumps, `be.conf`, `SHOW BACKENDS`/`SHOW WORKLOAD GROUPS` output, or a heap profile if it helps. Just let me know what's most useful.

## Question

Is this a known issue in 4.1.1-rc01? Is there a fix in a later 4.1.x or 4.2.x release? If a fix exists, I'd like to upgrade instead of running the rotator CronJob forever.

Contributor guide

Open the contributing guide

Research direction

Start with the rs_normal thread census using /proc/$BE_PID/status and the provided worker-count command, then compare behavior with the be.conf remote scanner settings and ALTER WORKLOAD GROUP limits. Reproduce the sustained Paimon-on-S3 workload and verify that rs_normal is capped or reaps idle workers, RSS stops growing, and the BE continues answering FE heartbeats.

Written by the indexing model from the issue text.

Assessment

Tech stack
kubernetes
Domain
databases, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.