wangcool opened a new issue, #67198:
URL: https://github.com/apache/doris/issues/67198

   ### Search before asking
   
   - [x] I had searched in the 
[issues](https://github.com/apache/doris/issues?q=is%3Aissue) and found no 
similar issues.
   
   
   ### Version
   
   # Doris3.1.4 FE OOM Heap Dump 分析报告
   
   | 项目 | 内容 |
   |---|---|
   | 集群节点 | FE 172.4.8.10(MASTER) |
   | Doris 版本 | doris-3.1.4-rc02 |
   | JDK | 17.0.12(崩溃时)/ 17.0.17(现网) |
   | 事故时间 | 2026-08-17 10:55:29 (+08:00) OOM,进程被 `OnOutOfMemoryError=kill -9` 
杀死 |
   | 堆配置 | 崩溃时 `-Xmx32768m`,重启后调整为 `-Xmx49152m` |
   | Dump 文件 | `/data01/doris/fe/log/java_pid72774.hprof`(30,463,119,875 
字节,545,178,838 个对象,写入耗时 98.7s) |
   | 分析工具 | Eclipse MAT 1.16.1(服务器 /tmp/mat116,解析堆 80GB) |
   
   ---
   
   ## 一、结论速览
   
   **OOM 根因是 `Env.aliveSessionSet`(存活会话 ID 集合)泄漏**:该集合堆积了 **~1.8 亿条会话 UUID 
字符串**,占满 32GB 堆的 **~98.5%**。
   
   - **触发线程**:`all-fe-session-mgr-pool-0`
   - **触发点**:`Env.getAllAliveSessionIds()` → `new ArrayList<>(aliveSessionSet)` 
→ `Arrays.copyOf` 申请 `Object[180,221,098]`(1.44 GB 连续数组)失败 → 
`java.lang.OutOfMemoryError: Java heap space`
   - **泄漏主体**:**FE 内部高频创建 ConnectContext 的组件**(统计任务、Routine Load、Group 
Commit、HTTP 接口、PLSQL、MV 等 40+ 处),以及 `FrontendServiceImpl.forward()` 三参构造导致的设计性泄漏
   - **与业务客户端基本无关**:三个高流量用户(prod_cd_option_w / test_cd_option_w / 
prod_cd_zquity_track_w)即使所有连接全部泄漏,14 天也仅 ~11.6 万条,占泄漏总量 **0.06%**
   - **增大堆至 48GB 只能延缓,不能根治**;清理逻辑自身存在"整体拷贝"缺陷,集合增大后清理必然自爆,须代码级修复或升级
   
   ---
   
   ## 二、崩溃时间线(fe.out / fe.log / fe.gc.log 佐证)
   
   | 时间 | 事件 |
   |---|---|
   | 2026-08-03 16:05:50 | FE 以 `-Xmx32768m` 启动(PID 72774) |
   | 2026-08-17 02:10 | 日志尚正常(report-thread 常规 WARN) |
   | 2026-08-17 ~09:18 | 开始频繁 Full GC;**Full GC 后堆底 ~25GB**(活对象不可回收,泄漏已达 ~25GB) 
|
   | 2026-08-17 09:18–10:54 | Full GC 每 ~2 分钟一次,堆反复顶到 32GB;业务侧开始报错 |
   | 2026-08-17 10:50–10:53 | thrift/mysql 大量异常:`TThreadPoolServer Thrift 
Error`、`Null packet received from network`、`Socket is closed by peer`;`edit log 
insert` 写 bdb 耗 19.3s、锁持有 18.4s(GC 停顿所致) |
   | 2026-08-17 10:55:29 | `java.lang.OutOfMemoryError: Java heap space`,写 dump 
98.7s 后 `kill -9` |
   | 2026-08-17 11:04:35 | FE 重启,`-Xmx49152m` |
   
   辅助信号:`fe.audit.log` 中 `Null packet received` 由 8/16 全天 84 次激增至 8/17 的 1153 
次(客户端 172.24.16.108 异常断连增多,系内存吃紧的并发症而非根因)。
   
   ---
   
   ## 三、MAT 分析结果
   
   总使用堆 **28GB / 5.45 亿对象**。两个 Problem Suspect 
指向**同一个对象(`Env.aliveSessionSet`)**:
   
   ### Problem Suspect 1 — 占堆 57.64%
   - **180,268,901 个 `java.lang.String`** = **17,305,670,752 字节(17.3 GB)**
   - 内容清一色为 UUID 格式会话 ID(如 `4041343b-f038-427d-a967-76570abb5b2e`)
   - 其下挂 180,268,875 个 `byte[]`(11.5 GB)——字符串底层数组
   - 被一个 `java.lang.Object[180,221,098] @ 0x14e4f8000000`(1.44 GB)引用 —— **正是 
OOM 时正在分配的那个数组**
   
   ### Problem Suspect 2 — 占堆 35.97%
   - 一个 `java.util.concurrent.ConcurrentHashMap$Node[268,435,456] @ 
0x14e428000000` = **10,800,711,176 字节(10.8 GB)**
   - 即 `aliveSessionSet` 的底层哈希表,内含 **180,239,068 个 
`ConcurrentHashMap$Node`**(8.65 GB)
   - 经 `all-fe-session-mgr-pool-0` 线程栈上的 
`ConcurrentHashMap$KeyIterator.toArray()` 关联
   
   ### OOM 调用栈(MAT 还原)
   ```
   java.lang.OutOfMemoryError: Java heap space
       at java.util.Arrays.copyOf(Object[], int)                       
(Arrays.java:3481)
       at java.util.concurrent.ConcurrentHashMap$CollectionView.toArray 
(ConcurrentHashMap.java:4471)
       at java.util.ArrayList.<init>(Collection)                       
(ArrayList.java:181)
       at org.apache.doris.catalog.Env.getAllAliveSessionIds()          
(Env.java:7031)
       at org.apache.doris.catalog.FESessionMgr$FEAliveSessionHandler.run() 
(FESessionMgr.java:94)
       ...
   ```
   
   ### 内存构成(泄漏占堆 ~98.5%)
   
   | 组件 | 大小 | 占比 |
   |---|---|---|
   | 会话 UUID 字符串(String + byte[]) | 17.3 GB | 57.6% |
   | aliveSessionSet 底层 CHM Node[] + Node | 10.8 GB | 36.0% |
   | OOM 时分配的 Object[180M] 拷贝数组 | 1.44 GB | 4.8% |
   | FE 正常工作对象 | ~0.4 GB | ~1.5% |
   
   ---
   
   ## 四、根因定位(反编译 doris-fe.jar 核实)
   
   ### 4.1 注册/注销机制
   ```java
   // Env.java:154 附近 – Guava 并发 Set(底层 ConcurrentHashMap)
   private Set<String> aliveSessionSet = Sets.newConcurrentHashSet();
   
   public void registerSessionInfo(String id)   { aliveSessionSet.add(id); }    
  // 唯一注册点
   public void unregisterSessionInfo(String id) { aliveSessionSet.remove(id); } 
  // 唯一注销点
   public List<String> getAllAliveSessionIds()  { return new 
ArrayList<>(aliveSessionSet); } // ★OOM 点
   ```
   
   ```java
   // ConnectContext.java
   public ConnectContext(StreamConnection, boolean) {
       ...
       init();                     // 324: invokevirtual init() —— 构造函数即注册!
   }
   public void init() {
       ...
       this.sessionId = UUID.randomUUID().toString();
       if (!runningUnitTest) 
Env.getCurrentEnv().registerSessionInfo(sessionId);  // 注册
   }
   public void cleanup()       { ... unregisterSessionInfo(sessionId); }
   protected void killConnection() { ... unregisterSessionInfo(sessionId); }
   ```
   
   **关键结论:`new ConnectContext(...)` 一出生就会在 `aliveSessionSet` 注册一条随机 UUID;只有走到 
`cleanup()/killConnection()` 才注销。**
   
   ### 4.2 注销路径核对(JDBC 连接的清理路径是完整的)
   
   | 路径 | 是否调用 cleanup/注销 | 依据 |
   |---|---|---|
   | MySQL 客户端正常退出(COM_QUIT) | ✅ | `ReadListener` 
正常分支:isKilled→stopAcceptQuery→cleanup→ConnectContext.remove |
   | 客户端异常断连(peer 关 socket / null packet) | ✅ | `ReadListener` 异常分支:记录 
`Exception happened in one session(...)` 后 setKilled→cleanup(8/17 的 1157 
次异断均被清理) |
   | 会话超时被 TimeoutChecker 杀 | ✅ | 
`ConnectScheduler$TimeoutChecker`→killConnection |
   | KILL connection / 查询取消 | ✅ | ConnectProcessor→killConnection |
   | **三参构造 `ConnectContext(stream, bool, String)`** | ❌ **构造即泄漏** | `init()` 
已注册随机 UUID,随后 sessionId **被覆盖**;cleanup 只移除"覆盖后的 id",随机 UUID 成为**永远清不掉的孤儿** |
   | FE 内部任务 `new ConnectContext()` 不做 cleanup | ❌ 视调用方而定 | 多个内部组件(见 4.3)未保证 
cleanup |
   
   ### 4.3 三参构造漏洞(设计性泄漏)
   
   唯一调用方:`org.apache.doris.service.FrontendServiceImpl.forward()`(FE→FE 
转发,follower 把 master-only 操作转发给 MASTER):
   
   ```
   2043: invokespecial 
ConnectContext."<init>":(Lorg/xnio/StreamConnection;ZLjava/lang/String;)V
           // init() 注册 UUID_A
           // 构造器尾部 putfield sessionId = 传入id
           // cleanup() 时移除的是“传入id”,UUID_A 永久泄漏
   ```
   
   ### 4.4 内部 ConnectContext 创建点(jar 全量扫描,40+ 处)
   
   调用频次较高/可疑的代表:
   
   | 类 | 处数 | 说明 |
   |---|---|---|
   | `org.apache.doris.statistics.util.StatisticsUtil` | 3 | 自动统计收集(ANALYZE 任务) 
|
   | `org.apache.doris.service.FrontendServiceImpl` | 3 | thrift 接口(含 forward 
漏洞路径) |
   | `org.apache.doris.load.routineload.RoutineLoadJob` | 3 | Routine Load 调度 |
   | `org.apache.doris.httpv2.rest.LoadAction` | 2 | **FE 侧 HTTP stream load 
入口,类内无 cleanup** |
   | `org.apache.doris.httpv2.controller.BaseController` | 2 | REST API 基类 |
   | `org.apache.doris.load.StreamLoadHandler` / `GroupCommitManager` / 
`GroupCommitPlanner` | 1/1/1 | Stream load / 分组提交 |
   | `org.apache.doris.plsql.executor.*` | 4 | PL/SQL 执行器 |
   | `org.apache.doris.mtmv.MTMVPlanUtil` | 1 | 物化视图 |
   | `org.apache.doris.nereids.minidump.MinidumpUtils` | 2 | Minidump |
   | 
`org.apache.doris.qe.ConnectContextUtil`、`org.apache.doris.job.extensions.insert.InsertTask`、`org.apache.doris.load.ExportTaskExecutor`
 等 | 1–3 | 内部任务 |
   
   ### 4.5 清理逻辑的致命缺陷(放大器)
   
   `FESessionMgr.FEAliveSessionHandler`(周期 
`alive_session_update_interval_second`)在清理本节点会话前,必须先执行 
`getAllAliveSessionIds()` —— **把整个 Set 整体拷贝成 `ArrayList`(O(n) 连续大数组)**。
   
   集合涨到上亿后:**拷贝动作本身先 OOM → 清理线程永远无法执行 → 集合只增不减 → 必然崩溃**。形成不可恢复的正反馈。
   
   相关配置(`fe.conf`):
   ```
   alive_session_update_interval_second   # 同步/清理周期
   fe_session_mgr_threads_num
   fe_session_mgr_blocking_queue_size
   ```
   
   ### 4.6 压缩指针关闭(次要放大因素)
   
   Dump 元信息 `Compressed object pointers = false`(32GB 堆边界触发 HotSpot 关闭压缩指针)→ 
每个对象引用按 8 字节计,`Object[180M]` 拷贝体积翻倍(1.44GB vs 720MB),OOM 提前到来。
   
   ---
   
   ## 五、泄漏主体排查(为什么是内部组件而不是业务客户端)
   
   ### 5.1 数量级对不上(决定性)
   
   8/3 16:05 重启 → 8/17 10:55 OOM(13.6 天),aliceSessionSet 累积 **~1.8 亿条 ≈ 153 
条/秒**:
   
   | 指标(14 天合计) | 数量 | 折算速率 | 与泄漏量比 |
   |---|---|---|---|
   | `aliveSessionSet` 累积 | **~180,000,000** | **~153/s** | 100% |
   | 审计 SQL(全部用户/全部语句) | ~3,400,000 | 2.8/s | <2% |
   | 新建 JDBC 连接(握手查询 `SELECT @@session...`) | ~116,000 | 0.096/s | 0.06% |
   | Stream load 事务(BE 端 begin/commit 指标) | ~60,000/BE | 0.05/s | 0.03% |
   
   **三个高流量用户(prod_cd_option_w / test_cd_option_w / prod_cd_zquity_track_w)占业务流量 
~95%,但即便其所有连接 100% 泄漏,也只占泄漏总量的 ~0.06%。**
   
   ### 5.2 JDBC 连接的注销路径完整(见 4.2)
   
   正常断开、异常断开、超时杀掉、KILL 均会 `unregisterSessionInfo`。8/17 激增的异常断连(`Null packet` 
↑84→1153)有日志佐证均走了 cleanup。
   
   ### 5.3 Stream load 不产生 FE MySQL 会话
   
   Stream load 走 HTTP 直达 BE(8040,FE 仅 307 重定向),不创建 9030 MySQL 协议会话,与本泄漏无关。BE 指标 
14 天仅 ~6 万次。
   
   ### 5.4 内部组件是唯一可达 153/s 量级的来源
   
   内部任务(统计、Routine Load、MV、PLSQL、REST/HTTP 等)创建 `ConnectContext` 
**不产生审计行**,且部分路径(构造器即注册的默认行为 + 缺 cleanup + forward 覆盖 bug)可长期静默累积。
   
   ---
   
   我的问题是,目前Doris3.1.4 fe节点是否发现类似内存泄露反馈?
   
   ### What's Wrong?
   
   doris 3.1.4 fe节点出现内存泄露,分析是否正确?
   
   ### What You Expected?
   
   确认doris 3.1.4 fe是否有内存泄露的bug
   
   ### How to Reproduce?
   
   _No response_
   
   ### Anything Else?
   
   doris 3.1.4 fe节点出现内存泄露,分析是否正确?
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to