Doris FE OOM Heap Dump 分析报告
一、宕机概况
| 项目 | 详情 |
|---|---|
| FE版本 | Doris 3.1.4 |
| JVM配置 | -Xmx32768m -Xms32768m(32GB堆) |
| Java版本 | OpenJDK 17.0.12 |
| 宕机时间 | 2026-08-17 ~10:54 |
| FE启动时间 | 2026-08-03 16:05(运行约14天) |
| 宕机原因 | java.lang.OutOfMemoryError: Java heap space |
| Heap Dump | 30.46GB,545,178,838个对象 |
| 重启后堆配置 | 48GB(-Xmx49152m) |
fe.out 关键输出
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /data01/doris/fe/log/java_pid72774.hprof ...
Heap dump file created [30463119875 bytes in 98.764 secs]
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="kill -9 %p"
# Executing /bin/sh -c "kill -9 72774"...
二、MAT分析结果
使用 Eclipse Memory Analyzer (MAT) 对 29GB Heap Dump 进行自动化分析,识别出两个泄漏嫌疑点,均指向同一线程 all-fe-session-mgr-pool-0。
2.1 Problem Suspect 1:String 对象堆积(17.3GB,57.64%)
- 180,268,901 个
java.lang.String实例,占用 17,305,670,752 字节(17.3GB,57.64%) - 被一个
Object[180,221,098]数组引用(1.44GB) - 该数组由线程
all-fe-session-mgr-pool-0持有
2.2 Problem Suspect 2:ConcurrentHashMap 膨胀(10.8GB,35.97%)
- 一个
ConcurrentHashMap$Node[268,435,456](2^28 个桶)占用 10,800,711,176 字节(10.8GB,35.97%) - 包含 180,239,068 个 Node 条目
- 同样由线程
all-fe-session-mgr-pool-0持有
2.3 OOM 触发时的线程栈
at java.lang.OutOfMemoryError.<init>() (OutOfMemoryError.java:48)
at java.util.Arrays.copyOf() (Arrays.java:3481)
at java.util.concurrent.ConcurrentHashMap$CollectionView.toArray() (ConcurrentHashMap.java:4471)
at java.util.ArrayList.<init>() (ArrayList.java:181)
at org.apache.doris.catalog.Env.getAllAliveSessionIds() (Env.java:7031)
at org.apache.doris.catalog.FESessionMgr$FEAliveSessionHandler.run() (FESessionMgr.java:94)
at java.util.concurrent.Executors$RunnableAdapter.call() (Executors.java:539)
at java.util.concurrent.FutureTask.run() (FutureTask.java:264)
at java.util.concurrent.ThreadPoolExecutor.runWorker() (ThreadPoolExecutor.java:1136)
at java.util.concurrent.ThreadPoolExecutor$Worker.run() (ThreadPoolExecutor.java:635)
at java.lang.Thread.run() (Thread.java:842)
2.4 MAT Top Consumers(支配树最大对象)
| 对象类型 | 实例数 | Shallow Heap | Retained Heap | 占比 |
|---|---|---|---|---|
ConcurrentHashMap$Node[] |
5 | 2,147,483,672 | 10,806,532,600 | 35.99% |
java.lang.Thread (all-fe-session-mgr-pool-0) |
1 | 160 | 1,441,778,504 | 4.80% |
java.lang.String |
180,268,901 | 5,768,604,832 | 17,305,670,752 | 57.64% |
2.5 MAT Class Histogram(Top 10)
| 类名 | 对象数 | Shallow Heap | Retained Heap |
|---|---|---|---|
java.lang.String |
180,549,648 | 5,777,588,736 | >=17,339,850,056 |
byte[] |
180,690,113 | 11,773,285,544 | >=11,773,285,544 |
ConcurrentHashMap$Node[] |
25,884 | 2,151,264,016 | >=10,849,355,368 |
ConcurrentHashMap$Node |
180,353,465 | 8,656,966,320 | >=8,678,776,232 |
java.lang.Object[] |
96,712 | 1,471,594,280 | >=1,568,369,392 |
java.lang.Thread |
448 | 71,680 | >=1,447,339,640 |
com.sleepycat.je.tree.BIN |
1,054 | 278,256 | >=106,519,560 |
com.sleepycat.je.tree.IN |
1,062 | 25,488 | >=103,781,928 |
com.sleepycat.je.tree.Node[] |
1,075 | 1,113,704 | >=103,757,984 |
com.sleepycat.je.tree.LN |
132,262 | 4,232,384 | >=102,644,392 |
2.6 MAT 系统概览
| 项目 | 值 |
|---|---|
| Heap Dump 大小 | 28 GB |
| 对象总数 | 545,178,838 |
| 类总数 | 27,765 |
| 类加载器总数 | 309 |
| GC Roots | 4,316 |
| 不可达对象 | 170,284(仅21MB) |
关键发现:545M 对象中仅有 170,284 个不可达对象(21MB),几乎所有对象都是存活且无法回收的泄漏对象。
三、根因分析
3.1 根本原因:FE Session ID 泄漏
FESessionMgr 中的 aliveSessionIds ConcurrentHashMap 持续累积了约 1.8 亿个 Session ID,从未被有效清理。
3.2 泄漏机制详解
客户端高频创建连接
|
v
每次连接产生一个 Session ID,存入 aliveSessionIds (ConcurrentHashMap)
|
v
连接关闭后 Session ID 仍留在 map 中,等待定期清理
|
v
清理线程 all-fe-session-mgr-pool 队列满,清理任务被拒绝 (RejectedExecutionException)
|
v
FE 间同步也失败 (Socket is closed by peer / Connection refused)
|
v
Session ID 只增不减,14天累积 1.8 亿条目
|
v
FEAliveSessionHandler 定期调用 Env.getAllAliveSessionIds()
-> new ArrayList(sessionIds.keySet()) <-- 1.8亿条目全量拷贝
-> OOM
3.3 内存分布
| 对象类型 | 实例数 | 内存占用 | 占比 |
|---|---|---|---|
| String(Session ID) | 180,549,648 | 17.3GB | 57.64% |
| ConcurrentHashMap$Node[] | 5 | 10.8GB | 35.99% |
| ConcurrentHashMap$Node | 180,353,465 | 8.7GB | (含在上面) |
| 合计 | ~545M | ~28GB | 93.6% |
3.4 清理机制失效时间线
| 日期 | all-fe-session-mgr-pool 队列满次数 | 说明 |
|---|---|---|
| 8/12 | 0 | 正常 |
| 8/13 | 0 | 正常 |
| 8/14 | 8 | 首次出现清理任务被拒绝 |
| 8/15 | 6 | 持续恶化 |
| 8/16 | 4 | 持续恶化 |
| 8/17 | 27 | 密集爆发,最终OOM |
3.5 完整事件时间线
| 时间 | 事件 |
|---|---|
| 8/3 16:05 | FE启动,32GB堆 |
| 8/14 | 首次出现 RejectedExecutionException: queue size is full: all-fe-session-mgr-pool(8次) |
| 8/15-16 | 队列满错误持续(6次、4次) |
| 8/17 09:09 | 队列满错误密集爆发 |
| 8/17 09:18 | Full GC后残留内存已达24GB,无法回收 |
| 8/17 10:42-10:55 | 连续 Full GC,每次耗时20+秒,GC后残留25-27GB |
| 8/17 10:54:17 | PUBLISH_VERSION_EXEC 写BDB耗时19305ms,锁持有18.4秒 |
| 8/17 ~10:54 | Env.getAllAliveSessionIds() 触发 OOM |
| 8/17 10:55:29 | 最后一次 Full GC:27GB存活对象,GC耗时21秒 |
| 8/17 10:57 | Heap Dump 生成完毕(30.46GB) |
| 8/17 10:57 | kill -9 终止进程 |
3.6 GC 日志分析
8/17 10:42:45 Full GC: 32402M -> 24621M (2572ms) <- Full GC后仍残留24GB
8/17 10:43:37 Full GC: 31488M -> 25845M (3236ms)
8/17 10:44:27 Full GC: 27244M -> 25830M (3097ms)
...
8/17 10:54:48 Full GC前: 27134388088 bytes (~27GB)
8/17 10:54:56 Full GC后: 27145836592 bytes (~27GB) <- 几乎无法回收
8/17 10:55:07 Full GC后: 27134575736 bytes (~27GB)
8/17 10:55:21 Full GC后: 27134388088 bytes (~27GB) <- 最后一次,21秒
Full GC 后仅能回收几百MB,27GB为存活无法回收的泄漏对象。
四、业务负载分析
4.1 审计日志统计(8/17当天)
| 语句类型 | 数量 |
|---|---|
| TRANSACTION | 99,706 |
| SELECT | 80,998 |
| SET | 36,985 |
| INSERT | 8,290 |
| SHOW | 7,584 |
| CREATE | 1,817 |
| DELETE | 483 |
4.2 用户请求分布
| 用户 | INSERT/TRANSACTION 数 | 占比 |
|---|---|---|
| prod_qd_ab_wxh_w | 101,911 | 95.5% |
| prod_cd_option_w | 3,353 | 3.1% |
| test_cd_option_w | 2,603 | 2.4% |
| admin | 121 | 0.1% |
| prod_cd_raven_w | 8 | <0.1% |
4.3 prod_qd_ab_wxh_w 语句分布
| 语句类型 | 数量 |
|---|---|
| TRANSACTION | 99,655 |
| SELECT | 51,982 |
| SET | 2,340 |
| INSERT | 2,256 |
| CREATE | 1,817 |
| DELETE | 441 |
4.4 典型业务SQL
崩溃前的主要负载为每分钟执行复杂 CTE 的 INSERT 语句:
INSERT INTO position_by_minute
WITH current_minute_changes AS (
SELECT instrument_id, MAX(produce_time) as max_time
FROM position
WHERE produce_time > 1786934880000000000
AND produce_time <= 1786934940000000000
AND expiry_date >= date '20260817'
GROUP BY instrument_id
),
current_records AS (
SELECT base.*
FROM position base
JOIN current_minute_changes cmc
ON base.instrument_id = cmc.instrument_id
AND base.produce_time = cmc.max_time
),
previous_snapshot_max_time AS (
SELECT prev.instrument_id, MAX(prev.produce_time) as prev_max_time
FROM position_by_minute prev
WHERE prev.snapshot_minute = '2026-08-17 10:48:00'
AND prev.zs_trading_date = 20260817
AND NOT EXISTS (
SELECT 1 FROM current_minute_changes cmc
WHERE prev.instrument_id = cmc.instrument_id
)
GROUP BY prev.instrument_id
),
...
SELECT '2026-08-17 10:49:00' as snapshot_minute, curr.*, 20260817 as zs_trading_date
FROM current_records curr
UNION ALL
SELECT '2026-08-17 10:49:00' as snapshot_minute, prev.*, 20260817 as zs_trading_date
FROM previous_base_records prev
查询特征:扫描 57M 行 / 62GB,峰值内存 752MB,CpuTimeMS 高达 329,538。
4.5 连接模式
- 客户端:MySQL Connector/J 8.0.33
- 来源:全部来自 172.24.16.108
- 模式:高频创建/断开连接(每分钟INSERT + 连接验证 + SET操作)
4.6 group_commit 问题
日志中还发现 group_commit 相关的事务冲突:
AnalysisException: begin transaction failed.
Label [group_commit_0a474c8412a331d3_cedb28f79ff3758e] has already been used,
relate to txn [3116755], status [PREPARE]
五、客户端正常close能否避免OOM
结论:不能完全避免
5.1 原因一:aliveSessionIds 的清理与连接关闭是解耦的
MAT 堆栈显示泄漏发生在 FESessionMgr$FEAliveSessionHandler.run() -> Env.getAllAliveSessionIds()。这是一个定期守护线程,通过跨 FE 同步来清理过期 session ID,不是在连接 close 时触发的。
即使客户端正常 close 连接,session ID 仍然留在 aliveSessionIds map 中,等待下次定期清理。而清理机制本身已经失效了。
5.2 原因二:清理机制从8月14日就已失效
8/14 RejectedExecutionException: queue size is full: all-fe-session-mgr-pool (8次)
8/15 同上 (6次)
8/16 同上 (4次)
8/17 同上 (27次) <- 密集爆发
线程池队列满了,清理任务被拒绝执行。同时 FE 间同步也失败:
Error occurred when get alive session from 172.24.16.108, msg = Socket is closed by peer
Error occurred when get alive session from 172.24.16.109, msg = Socket is closed by peer
清理线程跑不起来 -> session ID 只增不减 -> 14天累积1.8亿条目 -> OOM
5.3 原因三:prod_qd_ab_wxh_w 是最大贡献者
该用户单日执行了近10万次 TRANSACTION + 2256次 INSERT,全部来自 172.24.16.108。按14天外推,产生了大量 session。
5.4 客户端正常close的效果
| 措施 | 效果 |
|---|---|
| 客户端正常close连接 | 减少session创建速率,延缓OOM发生 |
| 但无法阻止 | 清理机制失效后,即使close了,session ID仍在map中 |
| OOM触发点 | 与连接关闭无关,是定期清理任务自身在拷贝时OOM |
5.5 根本结论
这是 Doris 3.1.4 的服务端Bug,不是单纯的客户端问题:
FESessionMgr的aliveSessionIds缺少有效的过期清理机制 — 只依赖跨 FE 定期同步,一旦同步失败就只增不减all-fe-session-mgr-pool线程池队列太小 — 高并发下队列满,清理任务被拒绝Env.getAllAliveSessionIds()实现有缺陷 — 直接new ArrayList(keySet())全量拷贝,session 数量大时必然 OOM
即使3个用户全部正常close,只要清理机制失效,session ID仍会累积到OOM。 正常close只能减缓累积速度,不能根本解决问题。
我的问题是,目前Doris3.1.4 fe节点是否发现类似内存泄露反馈?