StarRocks BE Jemalloc 堆剖析实战:ADMIN EXECUTE ON 开启 Heap Profile 并可视化内存分配热点
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
本文基于 StarRocks 官方文档,讲解如何在不重启 BE 的前提下,通过ADMIN EXECUTE ON下发 Python 脚本动态开启/关闭 Jemalloc 堆剖析、采集堆快照并将其转换为 Graphviz DOT 格式,最终定位 BE 节点的内存分配热点。读完本文,你能独立完成一次完整的内存诊断流程:开启剖析 → 验证状态 → 导出调用图 → 可视化分析,并理解jemalloc_conf中prof相关选项与 BE 侧 heap_prof.cpp 实现的对应关系。
:::注意
- 开启 Jemalloc Heap Profiling 可能对 StarRocks 的性能产生一定影响,建议在排查窗口内开启、排查完成后关闭。
- 该能力仅在 StarRocks v3.1.6 及之后版本可用(文档明确说明)。 :::
一、前提条件:BE 必须以prof:true启动
开启剖析的前提是 BE 进程启动时jemalloc_conf配置中包含prof:true,否则enable_prof语句会直接失败。这一约束来自 jemalloc 本身:profiling 必须在进程初始化阶段(JEMALLOC_CONF被解析时)声明开启,运行期只能切换全局的prof.active开关。
StarRocks 在 config.h 中给出的默认值如下:
jemalloc_conf = "percpu_arena:percpu,oversize_threshold:134217728,muzzy_decay_ms:5000,dirty_decay_ms:5000," "metadata_thp:auto,background_thread:true,prof:true,prof_active:false"关键点:
prof:true:允许运行期通过prof.active打开/关闭堆采样;prof_active:false:进程启动时默认不采样,即默认状态下零额外开销,需要诊断时再动态打开。
从源码结构看,heap_prof.cpp 中切换剖析状态的函数正是只操作prof.active这一个 mallctl 节点:
static int set_jemalloc_profiling(bool enable) { // Only prof.active, which is also what `prof_active` in JEMALLOC_CONF maps to. return je_mallctl("prof.active", nullptr, nullptr, &enable, sizeof(enable)); }值得注意的是,该实现刻意不触碰prof.thread_active_init。源码注释解释了原因:它不是第二个开关,而是在线程创建时复制进thread.prof.active的初始值;一旦在进程启动时由运维设为false以只对特定线程采样,运行期覆盖它可能永久改变采样范围且无法恢复。这一点也被单元测试 heap_prof_test.cpp 的toggling_the_profile_keeps_thread_active_init用例固化为回归约束:切换enable_prof/disable_prof前后,prof.thread_active_init的值必须保持不变。
查看当前配置
当前生效的jemalloc_conf也可以在information_schema.be_configs中确认:查询NAME为jemalloc_conf的那一行,读取其VALUE中的prof_active选项即可判断剖析当前是否处于激活状态。
二、开启 / 验证 / 关闭 Heap Profiling
2.1 开启
语法:
ADMIN EXECUTE ON <be_id> 'System.print(HeapProf.getInstance().enable_prof())'其中be_id为 BE/CN 节点 ID,可执行SHOW BACKENDS或SHOW COMPUTE NODES获取。
示例:
mysql> admin execute on 10001 'System.print(HeapProf.getInstance().enable_prof())'; +--------+ | result | +--------+ | OK | +--------+ 1 row in set (0.00 sec)2.2 验证是否已开启
ADMIN EXECUTE ON <be_id> 'System.print(HeapProf.getInstance().has_enable())'示例:
mysql> admin execute on 10001 'System.print(HeapProf.getInstance().has_enable())'; +--------+ | result | +--------+ | true | +--------+ 1 row in set (0.01 sec)实现侧 heap_prof.cpp 的has_enable通过je_mallctl("prof.active", ...)读取该布尔节点。源码注释里还有一段值得学习的细节:prof.active是 bool 类型节点,je_mallctl对长度不匹配(例如读入int)的读取会返回EINVAL并只按实际能拷贝的长度部分填充缓冲区——因此实现中先检查返回值再采信值,否则一个零初始化的int会“碰巧工作”,而一旦补上返回值检查又会导致永远误报false。测试 has_enable_is_false_without_startup_prof 覆盖的正是“进程未以prof:true启动时必须报告 false”这一场景。
2.3 关闭
ADMIN EXECUTE ON <be_id> 'System.print(HeapProf.getInstance().disable_prof())'示例:
mysql> admin execute on 10001 'System.print(HeapProf.getInstance().disable_prof())'; +--------+ | result | +--------+ | OK | +--------+ 1 row in set (0.00 sec)2.4 底层调用链:ADMIN EXECUTE ON 如何落到 C++
ADMIN EXECUTE ON会把单引号内的脚本发送到指定 BE 执行,BE 侧通过 ScriptEngine 注册了一组 Python 可访问的 C++ 绑定。从 script.cpp 可以看到HeapProf的完整注册面:
auto& cls = m.klass<HeapProf>("HeapProf"); REG_STATIC_METHOD(HeapProf, getInstance); cls.funcExt<&heap_prof_enable_prof>("enable_prof"); cls.funcExt<&heap_prof_disable_prof>("disable_prof"); REG_METHOD(HeapProf, has_enable); REG_METHOD(HeapProf, snapshot); REG_METHOD(HeapProf, to_dot_format); REG_METHOD(HeapProf, dump_dot_snapshot);有两处工程细节值得一提:
enable_prof/disable_prof走的是配置通道而非直接 mallctl。script.cpp 中它们被绑定为heap_prof_enable_prof/heap_prof_disable_prof,内部调用set_prof_active_via_config,即把jemalloc_conf字符串里的prof_active改写为对应值后统一更新;has_enable、snapshot、to_dot_format、dump_dot_snapshot则直接透传HeapProf方法。- 避免死锁的单向依赖。源码注释说明:
jemalloc_conf的配置更新回调会调用HeapProf(经由 config_update_hooks.cpp 注册的回调触发JemallocConfUpdater::update),因此脚本侧的开关函数被刻意放在HeapProf之外——若反过来从HeapProf回调配置更新路径,就会在HeapProf的互斥锁上发生死锁。同时 JemallocConfUpdater 的注释表明:jemalloc 的opt.*节点是只读的,因此只有prof_active这类运行期可切换的选项才能通过该通道热更新。
另外需要说明:该能力仅支持 Linux 平台。heap_prof.cpp 中所有实现均被#ifndef __APPLE__包裹,macOS 上enable_prof/disable_prof为空操作、has_enable恒返回false、snapshot返回空串(to_dot_format返回"not support on MacOS")。
三、采集堆快照(dump_dot_snapshot)
开启剖析并让业务/测试负载运行一段时间后,执行:
ADMIN EXECUTE ON <be_id> 'System.print(HeapProf.getInstance().dump_dot_snapshot())'该调用等价于to_dot_format(snapshot())(见 heap_prof.h),两步含义分别是:
snapshot():调用je_mallctl("prof.dump", ...)触发 jemalloc 写出原始堆 dump 文件。文件名为{pprof_profile_dir}/heap_profile.{pid}.{rand},其中pprof_profile_dir默认值为${STARROCKS_HOME}/log(config.h);to_dot_format():调用 BE 安装目录下的jeprof --dot工具解析该 dump,生成 Graphviz DOT 调用图:
std::string jeprof = fmt::format("{}/bin/jeprof", base_home); std::string binary = fmt::format("{}/lib/starrocks_be", base_home); return lite_exec({jeprof, "--dot", binary, heapdump_filename});示例输出(官方文档中的实际返回,节选):
mysql> admin execute on 10001 'System.print(HeapProf.getInstance().dump_dot_snapshot())'; +------------------------------------------------------------------+ | result | +------------------------------------------------------------------+ | digraph "/home/disk/opt/env/default/be/lib/starrocks_be; 1.0 MB" { | node [width=0.375,height=0.25]; | Legend [shape=box,fontsize=24,shape=plaintext,label="...starrocks_be\lTotal MB: 1.0\lFocusing on: 1.0\lDropped nodes with <= 0.0 abs(MB)\lDropped edges with <= 0.0 MB\l"]; | N1 [label="brpc\nInputMessenger\nOnNewMessages\n0.0 (0.0%)\rof 1.0 (100.0%)\r",shape=box,fontsize=8.0]; | N2 [label="brpc\nSocket\nProcessEvent\n0.0 (0.0%)\rof 1.0 (100.0%)\r",shape=box,fontsize=8.0]; | N3 [label="bthread\nTaskGroup\ntask_runner\n0.0 (0.0%)\rof 1.0 (100.0%)\r",shape=box,fontsize=8.0]; | N4 [label="bthread_make_fcontext\n0.0 (0.0%)\rof 1.0 (100.0%)\r",shape=box,fontsize=8.0]; | N6 [label="brpc\npolicy\nParseRpcMessage\n0.5 (50.1%)\r",shape=box,fontsize=43.4]; | N13 [label="std\nmake_unique\n0.5 (49.9%)\r",shape=box,fontsize=43.3]; | N2 -> N1 [label=1.0, weight=16398, style="setlinewidth(2.000000)"]; | N3 -> N2 [label=1.0, weight=16398, style="setlinewidth(2.000000)"]; | N4 -> N3 [label=1.0, weight=16398, style="setlinewidth(2.000000)"]; | N1 -> N5 [label=0.5, weight=10102, style="setlinewidth(2.000000)"]; | N5 -> N6 [label=0.5, weight=10102, style="setlinewidth(2.000000)"]; | N9 -> N11 [label=0.5, weight=10086, style="setlinewidth(2.000000)"]; | N12 -> N10 [label=0.5, weight=10086, style="setlinewidth(2.000000)"]; | N11 -> N12 [label=0.5, weight=10086, style="setlinewidth(2.000000)"]; | N8 -> N9 [label=0.5, weight=10086, style="setlinewidth(2.000000)"]; | N10 -> N13 [label=0.5, weight=10086, style="setlinewidth(2.000000)"]; | } +------------------------------------------------------------------+ 29 rows in set (30.22 sec)如何读懂这张图
- 节点(N1、N6…):一行
label由若干层组成,形如命名空间\n函数\n局部占比 (百分比)\rof 累计占比 (百分比)。例如N6表示brpc::policy::ParseRpcMessage,0.5 (50.1%)是该栈帧自持(self)内存,of 1.0 (100.0%)是它子树的累计内存。fontsize会随占比放大(N6为 43.4,其余为 8.0),即字号越大的框越是内存热点。 - 边(N2 -> N1 …):表示调用关系,
label是沿该路径的 MB 数,weight/setlinewidth用于加粗渲染,便于人眼追踪主调用链。 - 图标题:
digraph "/home/disk/opt/env/default/be/lib/starrocks_be; 1.0 MB"中的路径即 jeprof 解析用的二进制,1.0 MB是本次快照聚焦的内存总量;Legend 中Total MB/Focusing on/Dropped nodes with <= x abs(MB)说明采样总盘与过滤阈值。 - 示例中的调用链语义为:brpc 收到新消息 → 解析 RPC → 走
PInternalServiceImplBase::execute_command(即ADMIN EXECUTE ON的服务端路径)→ 最终在std::make_unique上自持了约 0.5 MB——恰好印证了“剖析动作本身也会出现在快照里”,实际排障时应以负载期间的快照为准。
注意dump_dot_snapshot是同步执行且依赖jeprof解析完整二进制(示例耗时 30.22 秒),高峰期使用需评估阻塞影响;若只想要原始 dump 文件路径,也可单独调用System.print(HeapProf.getInstance().snapshot()),再在 BE 机器上手动执行jeprof --dot $STARROCKS_HOME/lib/starrocks_be <dump文件>得到同样结果。
四、可视化 Heap Profile
将上一步返回的 DOT 文本完整复制,粘贴到 docs/en/developers/jemalloc_heap_profile.md 中提到的在线 Graphviz 渲染工具(GraphvizOnline,dreampuf.github.io/GraphvizOnline)即可得到可视化调用图,并可下载渲染后的图片用于归档或分享。
文档给出的可视化效果示例:
五、操作小结与排错要点
| 步骤 | SQL | 成功输出 | 说明 |
|---|---|---|---|
| 开启 | admin execute on <be_id> 'System.print(HeapProf.getInstance().enable_prof())' | OK | 要求 BE 以prof:true启动 |
| 验证 | ... has_enable() | true/false | 运行期只读prof.active |
| 采集 | ... dump_dot_snapshot() | DOT 文本 | 同步执行,耗时取决于 dump 大小 |
| 关闭 | ... disable_prof() | OK | 排查完成后及时关闭以消除性能开销 |
排错要点:
enable_prof报错/无效:先查information_schema.be_configs中NAME='jemalloc_conf'的VALUE是否包含prof:true;由于prof是启动期选项,若缺失需修改 be.conf 中的jemalloc_conf并重启 BE。has_enable()恒为false:按上文 2.2 节说明,读取失败(EINVAL)同样会被实现保守地报告为 false,应结合 BE 日志(try to enable the heap profiling等 LOG 输出)确认切换是否真正发生。- macOS 开发机:功能不可用(见 2.4 节平台限制),请在 Linux BE 节点上操作。
- 性能影响:剖析期间每次分配都会记录栈信息,建议遵循“开启 → 复现/观察 → 采集 → 立即关闭”的最小窗口原则。
相关源码与文档索引
- be/src/runtime/prof/heap_prof.h / heap_prof.cpp:
HeapProf单例的 mallctl 实现与jeprof --dot转换 - be/src/script/script.cpp:
ADMIN EXECUTE ON脚本侧HeapProf绑定 - be/src/common/config.h:
jemalloc_conf默认值;L846:pprof_profile_dir默认值 - be/src/runtime/memory/jemalloc_conf_updater.h:
prof_active热更新逻辑 - be/src/service/service_be/config_update_hooks.cpp:
jemalloc_conf配置回调注册 - be/test/runtime/prof/heap_prof_test.cpp:开关行为与读值正确性的单元测试
- docs/en/developers/jemalloc_heap_profile.md:本文对应的原始官方文档
【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考