StarRocks BE Jemalloc 堆剖析实战:ADMIN EXECUTE ON 开启 Heap Profile 并可视化内存分配热点
2026/9/16 10:36:08 网站建设 项目流程

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_confprof相关选项与 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中确认:查询NAMEjemalloc_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 BACKENDSSHOW 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);

有两处工程细节值得一提:

  1. 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_enablesnapshotto_dot_formatdump_dot_snapshot则直接透传HeapProf方法。
  2. 避免死锁的单向依赖。源码注释说明: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恒返回falsesnapshot返回空串(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::ParseRpcMessage0.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排查完成后及时关闭以消除性能开销

排错要点:

  1. enable_prof报错/无效:先查information_schema.be_configsNAME='jemalloc_conf'VALUE是否包含prof:true;由于prof是启动期选项,若缺失需修改 be.conf 中的jemalloc_conf并重启 BE。
  2. has_enable()恒为false:按上文 2.2 节说明,读取失败(EINVAL)同样会被实现保守地报告为 false,应结合 BE 日志(try to enable the heap profiling等 LOG 输出)确认切换是否真正发生。
  3. macOS 开发机:功能不可用(见 2.4 节平台限制),请在 Linux BE 节点上操作。
  4. 性能影响:剖析期间每次分配都会记录栈信息,建议遵循“开启 → 复现/观察 → 采集 → 立即关闭”的最小窗口原则。

相关源码与文档索引

  • 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询