FE / 协调节点内存满问题故障排查
防止"FE内存已满"问题,快速恢复,并尽可能找出原因以防止再次发生。
- This information is applicable to FE nodes and Coordinator nodes (shared-data deployments). For readability, we will use "FE" to refer to both types of frontend nodes.
- This article mainly analyzes the situation and solutions of in-heap memory, without covering off-heap memory. If the OOM is caused by off-heap memory issues, consider reducing the JVM XMX configuration or expanding the machine's memory.
FE 内存结构与分配建议
FE 内存组成
前端服务(FE)运行在 JVM 上,其内存消耗来自 Java 堆内存和堆外内存开销。
JVM 内存模块概述
在FE进程中,通过JVM分配的所有内存大致可以分为以下几个模块:
| 模块 | 类型 | 描述 |
|---|---|---|
| Java 堆 | 堆 | Java 对象实例的内存,例如 Plan Cache、元数据、临时对象等。 |
| 类 | 堆外 | 加载 Class 时生成的元数据等结构 |
| 线程 | 堆外 | 线程占用的内存,主要是每个线程的栈空间(通常每个线程约 1MB)。 |
| 代码 | 堆外 | JVM 将字节码编译为本机机器码时占用的内存 |
| GC | 堆外 | 垃圾收集器的内部工作数据结构 |
| 编译器 | 堆外 | HotSpot 编译器使用的内存可能与 Code 的内存类似。 |
| 其他 | 堆外 | 包括直接内存,如 Direct ByteBuffer、Netty buffer 等。 |
| 符号 | 堆外 | SymbolTable 结构,如字符串常量池、整数常量池等。 |
| 本机内存跟踪 (NMT) | 堆外 | NMT 自身记录的内存使用所占用的内存 |
| Arena 块 | 堆外 | JVM 内部使用 malloc 分配的临时内存块,用于临时存储和重用 |
| 日志记录 | 堆外 | 日志系统(如 log4j)占用的内存 |
| 参数 | 堆外 | JVM 启动参数传递所占用的内存 |
| 模块 | 堆外 | Java 模块系统中使用的结构 |
考虑到JVM和StarRocks的运行特性,FE的内存主要由以下几部分组成:
-
JVM堆内存(Heap)
- 存储 Java 对象实例,例如 Plan Cache、元数据缓存等。
- 最大大小通过参数
-Xmx设置,并受垃圾回收机制的显著影响。
-
非堆内存(Metaspace)
- 存储类定义、常量池等元数据。
- Java 8 及更高版本替代了 PermGen。
-
直接内存
- Netty、ByteBuffer、日志缓冲区、RPC 框架等 使用的堆外内存。
- 由
sun.misc.Unsafe分配,通常不受 GC 控制。
-
线程栈内存
- 每个 Java 线程在创建时会申请一个线程栈,默认大小约为 1MB(可通过
-Xss配置)。 - 当线程数量较多时(例如高并发场景),容易导致内存积压。
- 每个 Java 线程在创建时会申请一个线程栈,默认大小约为 1MB(可通过
-
JNI / 本地内存调用
- 数据库在通过日志组件(如 RocksDB、Netty)、Cache 等调用本地层时可能会消耗内存。
JVM 参数配置建议
在 JVM 中,堆内存通过 Xmx 进行配置。通常,进程使用的内存小于 Xmx 的 130% 是合理的。也就是说,除了 JVM Xmx 配置的内存之外,还会使用一部分堆外内存。例如,如果 JVM 配置了 21g,则在 top 中看到的进程 RSS 内存使用量约为 27g。
根据经验,tablet 数量与 FE 内存的推荐关系如下:
| Tablet 数量 | 推荐 FE 内存配置 |
|---|---|
| 少于 1M | 16 GB |
| 1M - 2M | 32 GB |
| 2M - 5M | 64 GB |
| 5M - 10M | 128 GB |
对于独立部署环境,建议为系统预留足够的内存。除了为系统预留的内存外,还需要考虑堆外内存。
对于混合部署环境,需 要根据其他服务的使用情况尽可能预留足够的内存,以防止 OOM。
在 FE 独立部署的情况下(不建议混合部署):
- 当机器内存小于 32G 时,
-Xmx(最大堆大小)应配置为机器内存的最多 70%。 - 当机器内存大于 32G 时,
-Xmx(最大堆大小)应配置为机器内存的最多 80%。 - 由于 FE leader 节点需要执行 checkpoint,通常情况下,leader 节点的内存约为 follower 节点的两倍。
在 3.4+ 版本中,leader 会选择一个 follower 节点执行 checkpoint,然后下载 follower 的结果并分发给其他 follower。leader 的内存使用量将接近 follower 的内存使用量。请参阅PR #52103。
FE JVM 监控与内存 Profile 部署
对于 FE 状态,堆监控和 GC 监控能够有效监控服务状态,也是事后排查问题的关键线索。建议及时配置监控,并在必要时配置相应的告警。
具体监控指标如下:
| 指标 | 描述 |
|---|---|
jvm_heap_size_bytes | 堆内存使用量。 |
jvm_non_heap_size_bytes | 主要包括 metaspace、代码缓存等,不包含所有堆外内存。 |
jvm_old_gc{type="count"} | Full GC 次 数。 |
jvm_old_gc{type="time"} | Full GC 时间。 |
这些指标及其他指标的监控信息请参阅监控与告警
内存 profile 可以分析堆内存的突然增长。需要在服务安装完成后确认其是否正常工作。
在 3.3.6+ 版本中,数据库会定期打印内存 profile 并输出到 log/proc_profile,格式为 HTML,通过 tgz 压缩。
控制配置:
proc_profile_collect_interval_s = 600
proc_profile_collect_time_s = 300
proc_profile_cpu_enable = true
proc_profile_mem_enable = true
对于 3.3.6 之前的版本,可以通过脚本定期打印 profile。在每个 FE 的安装目录下新建一个名为 mem_profiler.sh 的脚本文件,并将以下内容复制到该文件中。
此脚本对 FE 进程无影响,不影响服务的正常使用,并会自动清理已过期 48 小时的文件。
#!/bin/bash
mkdir -p mem_alloc_log
cleanup_old_files() {
find mem_alloc_log -name "alloc-profile-*.html" -mmin +2880 -exec rm -f {} \;
}
while true
do
cleanup_old_files
current_time=$(date +'%Y-%m-%d-%H-%M-%S')
file_name="mem_alloc_log/alloc-profile-${current_time}.html"
./bin/profiler.sh -e alloc --alloc 2m -d 300 -f "$file_name" `cat bin/fe.pid`
done
# 后台启动
chmod +x mem_profiler.sh
nohup ./mem_profiler.sh > mem_profiler.out 2>&1 &
# 检查进程是否存在
ps aux | grep mem_profiler.sh
# 停止进程
pkill -f mem_profiler.sh
FE 崩溃
FE 崩溃的原因大致分为 4 种类型。
1. FE 进程内存占用过高,被操作系统杀死
您可以通过查询系统日志来检查是否为此原因:
# 查看 dmesg
dmesg | grep -iE 'out of memory|oom|kill|killed process'
# 查看消息日志
sudo grep -Ei 'killed process|oom.kill|sending SIG' -C5 /var/log/messages /var/log/syslog 2>/dev/null | tail -n 100
被 OOM 杀死后,日志可能会打印系统进程的内存使用情况。OOM 显示的 total_vm 和 rss 等字段以页为单位,默认 1 页 = 4KB:
total_vm = 8793525→ 表示已申请约 8793525 × 4KB ≈ 33.5 GB 的虚拟内存rss = 7218120→ 表示实际已使用约 27.5 GB 的物理内存,即 7218120 × 4KB
OOM 日志示例:
Jun 10 11:20:24 tp-prod-bigdata-sr-ss-fe-2-b kernel: [ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
Jun 10 11:21:58 tp-prod-bigdata-sr-ss-fe-2-b kernel: [22654] 1005 22654 8793525 7218120 14943 0 0 java
检查实际 RSS 使用量。如果非常接近机器内存,通常是由于 JVM 分配不合理。请参考JVM 参数配置建议章节。
2. FE leader 堆内存过高,触发 Full GC,导致 leader 切换
当旧 leader 因 leader 切换而退出时,fe.out 中会打印以下信息:
transfer FE type from LEADER to UNKNOWN. exit
或
transfer FE type from LEADER to FOLLOWER. exit
大多数 leader 切换是由 Full GC 引起的;少数情况下,是由 leader CPU 使用率过高或执行 jstack 触发的。
分析 GC 日志:
将 GC 日志上传至https://gceasy.io/并点击暂停 GC 持续时间。如果单次暂停 GC 的持续时间超过 30 秒,或频繁出现超过 10 秒的暂停 GC,将会触发 leader 切换。
点击GC 前堆内存以找到堆内存上升的时间点。利用该时间范围定位该时段的内存 profile 文件,从而确定当时是哪个模块在申请内存。
3. FE 程序内部 bug 导致崩溃
此情况不在本文讨论范围内。请参考恢复元数据文档。
4. JVM bug 导致 FE 崩溃
此情况极为罕见。遇到后,可通过查看崩溃日志来排查原因,日志位于 log/hs_err_pid%p.log,其中 %p 为进程 ID。
FE 进程正常但内存占用过高
在 FE 长期运行过程中,堆内内存使用量可能异常增加,频繁触发 Full GC,导致查询或导入变慢。问题可能表现为堆内存突然增加或持续缓慢增长。
现场排查流程
打开您的监控工具:监控与告警
第一步:检查以下监控
- FE JVM 面板:JVM 堆内存使用量指标
第二步:识别内存增长类型
| 类型 | 特征 | 后续排查路径 |
|---|---|---|
| 突然增长 | 堆内存在短时间内(秒到分钟级)急剧飙升 | 优先查看 QPS、连接数和 SQL 请求 |
| 缓慢上升 | 内存持续增长,GC 后回收效果越来越差 | 优先排查内存泄漏或缓存积累 |
内存飙升排查
短时间内请求压力是否增大——高 QPS 或高并发的元数据操作可能导致内存骤增。
1. 检查以下监控:
- QPS 面板:FE 接收的请求数是否突然增加?
- 连接数面板:FE 的并发连接数是否瞬间飙升?
2. 通过审计表或日志确认异常 SQL:
查询最近 5-10 分钟内的高频 SQL 语句:
SELECT * FROM starrocks_audit_db__.starrocks_audit_tbl__
WHERE feip = 'IP of memory abnormal node'
AND `timestamp` >= now() - INTERVAL 1000 MINUTE
ORDER BY `timestamp` DESC;
重点关注:
- 含有深层子查询、多重 JOIN 及复杂聚合的语句
- 非常规业务 SQL(例如 BI 平台生成的数据集并发 SQL)
- 在 3.3.13+ 版本中,可以查看 FE 审计日志中的
queryFeMemory字段,该字段表示某个查询在 FE 中总共申请了多少内存。 - 当 FE 进程因 FE OOM 退出时,会自动打印正在执行的查询及对应的
QueryFEAllocatedMemory。
在 FE 日志中搜索关键词:
QueryFEAllocatedMemory
3. 通过 FE 日志检查是否存在大量元数据操作(建表和删表):
# 创建表
grep "Begin to unprotect create table" fe.log
# 删除表
grep "Finished drop table" fe.log
内存缓慢增长
4. JVM 配置的内存过小
监控 FE 进程的 GC 状态,判断是否频繁发生 Full GC。通过 jstat 命令查看 JVM 内存使用情况:
jstat -gcutil $pid 1000 1000
示例输出:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 27.78 95.45 97.77 94.45 24 0.226 1 0.065 0.291
0.00 100.00 44.44 95.45 97.77 94.45 24 0.226 1 0.065 0.291
0.00 100.00 55.56 95.45 97.77 94.45 24 0.226 1 0.065 0.291
如果 O(老年代)占比一直较高,说明 JVM 内存配置存在问题,需要增大 JVM 内存。
5. 通过 FE 的内存追踪器(3.3.7+)观察是否存在泄漏
可以通过查看 MemoryUsageTracker 的日志来定位哪个模块存在内存泄漏,该日志每小时打印一次各模块的内存消耗:
2025-06-06 16:37:50.633+08:00 INFO (MemoryUsageTracker|74)
[MemoryUsageTracker.trackMemory():161] (6ms) Module Dict -
CacheDictManager estimated 0B of memory. Contains ColumnDict with 0 object(s).
2025-06-06 16:37:50.657+08:00 INFO (MemoryUsageTracker|74)
[MemoryUsageTracker.trackMemory():161] (21ms) Module LocalMetastore -
LocalMetastore estimated 3.8MB of memory. Contains Partition with 17473 object(s).
2025-06-06 16:37:50.667+08:00 INFO (MemoryUsageTracker|74)
[MemoryUsageTracker.trackMemory():161] (0ms) Module TabletInvertedIndex -
TabletInvertedIndex estimated 37.7MB of memory. Contains TabletMeta with 353459 object(s).
2025-06-06 16:37:50.741+08:00 INFO (MemoryUsageTracker|74)
[MemoryUsageTracker.trackMemory():108] total tracked memory: 46.8MB,
jvm: Process used: 18.6GB, heap used: 4.4GB, non heap used: 289.1MB, direct buffer used: 395.5MB
对比最近几条日志中各模块的内存增长情况,找出持续增长的模块。
现场信息采集
如果进程尚未重启,请立即采集 jmap 信息。
jmap 内存排查流程
第一步:确认 FE 的进程 PID
ps aux | grep FE
第二步:使用 jmap
jmap -dump会短暂暂停 FE 进程。在对线上稳定性要求较高时请谨慎使用,建议提前告知相关人员。jmap -histo通常对进程无影响;触发 GC 可能需要数十毫秒。- 不建议频繁使用
-dump导出堆文件,因为这会带来较大的开销。 histo足以对大对象进行初步判断,不一定需要进行 dump。
jmap -histo:live(在生产环境中请谨慎使用)
通过强制执行 GC 来捕获当前堆内对象分布。使用 live 参数可能有助于解决内存占用过高的问题:
jmap -histo:live <pid> > jmap_histo_$(date +%s).log
建议采集两次样本并对比差异:
jmap -histo:live <pid> > histo_1.log
sleep 60
jmap -histo:live <pid> > histo_2.log
jmap -histo pid(轻量级,不触发 Full GC)
# 获取当前 JVM 堆中的所有对象,可能包含
# 已被清理的对象——会影响结果分析。
jmap -histo <pid> > histo.txt
第三步:分析 Top N 对象占用
查看文件的头部条目:
head -n 30 histo_2.log
字段说明:
| 列名 | 含义 |
|---|---|
| num | 类编号 |
| #instances | 实例数量 |
| #bytes | 总字节数 |
| class name | 对象名称 |
对比 histo_1 和 histo_2,以识别哪些类的实例数量或内存占用显著增加。
示例:以 java 开头的类是被业务类引用的工具类。从上到下查看,排在前面的相关类占比更大。例如,com.starrocks.lake.LakeTablet 占用大量内存表明 tablet 使用过多。
第四步:导出完整堆转储(若 histo 不足以分析)
# 将触发 Full GC,请谨慎使用。
jmap -dump:live,format=b,file=heap_$(date +%s).hprof <pid>
该文件可能非常大(数 GB),需要使用 MAT 或 VisualVM 打开并进行分析。
第五步:配置 OOM 时自动 dump
在 fe.conf 的 JVM 配置中添加以下内容,以便在 FE 内存耗尽时自动生成 dump 文件:
JAVA_OPTS="-Dlog4j2.formatMsgNoLookups=true \
-Xmx8192m \
-XX:+UseG1GC \
-Xlog:gc*:${LOG_DIR}/fe.gc.log.$DATE:time \
-XX:ErrorFile=${LOG_DIR}/hs_err_pid%p.log \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=${LOG_DIR}/heap_dump_oom.hprof \
-Djava.security.policy=${STARROCKS_HOME}/conf/udf_security.policy \
-Djava.security.krb5.conf=/etc/krb5.conf \
-Dsun.security.krb5.debug=true \
-Dsun.security.spnego.debug=true \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8113"
注意:HeapDumpPath 指定的是目录路径,而非文件名。