
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 1500 字,预计阅读需要 8 分钟。

用户反馈,应用访问 OceanBase 数据库时报错,报错内容为:ErrorCode = 4013, SQLState = HY001, Details = No memory or reach tenant memory limit,重启 OBServer 后暂时恢复,但过一段时间又会报相同的错误。
开发同学通过 ODC[1] 执行查询也报同样错误,在 ODC 的执行记录中可以直接看到 SQL 的 TRACE_ID 信息。

点击异常 SQL 的 TRACE_ID,会弹出如下界面,依次点击全链路诊断,鼠标移动到com_query_process 右侧的蓝色耗时区域,会出现该 Span 的具体内容,其中节点字段,即为 SVR_IP(如下是本地环境举例说明)。

获取执行过的 SQL 或者黑屏执行的 SQL 的 TRACE_ID 和 SVR_IP 方法,请查看文末参考资料[2]。
根据报错的时间范围和 TRACE_ID,过滤 OBServer 日志,在 SVR_IP 节点(实际为:113 节点)过滤到了详细的报错内容,显示租户占用内存加上租户新申请内存超过租户内存限制(tenant_hold+alloc_size>tenant_limit)。
#[OOPS]: alloc failed reason is that tenant memory has reached the upper limit(tenant_id: 1010, tenant_hold: 11595448320, tenant_limit: 11596411700, alloc_size: 2097152)
过滤 113 节点 的 OBServer 日志,查看 1010 租户 的各个上下文的内存使用,显示上下文 DEFAULT_CTX_ID 的内存占用最大,接近租户的内存使用限制了。
# grep 'malloc_allocator.*tenant: 1010' observer.log.20250522105401599 -A 20

过滤 113 节点 的 OBServer 日志,查看 1010 租户 的上下文 DEFAULT_CTX_ID中各个mod的内存使用,显示 SqlSessionSbloc 这个 mod 占用内存最大,几乎用了整个租户 80% 以上的内存。通过一段时间观测内存使用会持续增加,最终导致租户内存用满。
分析到这,我们已经定位到导致租户内存用满的 mod 是谁了。通过查阅官方文档,并没有看到对这个 mod 的介绍,但我们知道,从 4.1 版本开始,提供了虚拟表来记录 mod 内存分配的相关的关键信息,请继续往下看。
grep '1010 ctx_id= DEFAULT_CTX_ID' observer.log.20250522105401599 -A 10

从 OceanBase 4.1.0 版本开始,提供了 __all_virtual_malloc_sample_info 虚拟表[3],此虚拟表常态化记录内存分配相关的关键信息,当 OBServer 的某个模块分配内存超过 GB,基于此虚拟表的信息,通常有能力定位到问题的根因。
有关 __all_virtual_malloc_sample_info 表的字段说明如下:
列明 | 类型 | 含义 |
|---|---|---|
svr_ip | varchar:MAX_IP_ADDR_LENGTH | IP 地址 |
svr_port | int | 端口号 |
tenant_id | int | 租户 ID |
ctx_id | int | CTX ID |
mod_name | varchar:OB_MAX_CHAR_LENGTH | 模块名称 |
back_trace | varchar:DEFAULT_BUF_LENGTH | 内存分配的堆栈 |
ctx_name | varchar:OB_MAX_CHAR_LENGTH | CTX 名称 |
alloc_count | int | 内存分配次数 |
alloc_bytes | int | 内存分配的总大小 |
注意:虚拟表
__all_virtual_malloc_sample_info只能在sys租户和 x86_64 系统架构下查询。
select * from oceanbase.__all_virtual_malloc_sample_info where mod_name='SqlSessionSbloc' order by alloc_bytes DESC ;

# addr2line -pCfe bin/observer 0x1f5951a0 0x788cfac 0x7ce43e0 0x7ce3a48 0x15111873 Oxf35e3e8 0x79304ee 0x792e9af 0x791e66b 0xf054727 Ox1f6509be

通过调用栈分析,直接调用内存分配的函数是 set_login_info,怀疑内存泄漏发生在 oceanbase::sql::ObSQLSessionInfo::set_login_info 函数中。
OceanBase 社区版是 MySQL 租户兼容模式,所以我们可以看开源代码进行问题确认。
打开 OceanBase 社区版[4],选择最新发布版本,通过 ObSQLSessionInfo 关键字,找到代码文件ob_sql_session_info.cpp,点击进入。

切换查看方式为 Blame,通过关键字 set_login_info,搜索函数的代码变更记录。

打开包含 fix memory leak 的代码提交的链接,可以看到该 commit 修复了一个由于 OceanBase 内部在复用一个会话时,直接分配内存(而不是尝试复用已有的内存),导致了内存泄露问题(fix memory leak caused by calling set_login_info() when reusing a session),并且可以看到该 Bug 在 4.3.5 及之后的版本被修复了。
PS:如果这里没有查到函数的 bug fixed,需要研发根据
set_login_info函数代码逻辑进一步分析内存泄露的原因。

最后和研发团队确认后,对当前数据库版本进行了升级(建议升级到 v4.3.5)。
升级后,在业务租户进行查询(租户中内存占用排名靠前的 mod 中,找不到 SqlSessionSbloc 这个 mod 了),升级后租户内存爆的问题解决。
SELECT svr_ip,ctx_name,mod_name ,sum(hold) FROM oceanbase.__all_virtual_memory_info WHERE tenant_id = 1010 group by svr_ip,ctx_name,mod_name order by sum(hold) desc limit 10;

本文主要分享了当租户内存用满时,如何通过分析 OBServer 日志,找到引起租户内存用满的 mod,遇到 mod 的内存占用持续增长的情况,如何进一步结合代码进行问题根因的定位,希望对你有帮助。
参考资料
[1]
开发者工具 ODC: https://www.oceanbase.com/product/odc
[2]
获取 SQL 执行的 svr_ip、trace_id 的方法: https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000225641
[3]
使用 __all_virtual_malloc_sample_info 诊断 OceanBase 数据库内存模块问题: https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000002393827
[4]
社区版 Github 仓库地址: https://github.com/oceanbase/oceanbase/tree/v4.3.5_CE_BP2_HF1
本文关键字:#OceanBase #故障分析