HybridDB · 源码分析 · MemoryContext 内存管理和内存异常分析

简介:

背景

最近排查和解决了几处 HybridDB for PostgreSQL 内存泄漏的BUG。觉得有一定通用性。 这期分享给大家一些实现细节和小技巧。

阿里云上的 HybridDB for PostgreSQL 是基于 PostgreSQL 开发,定位于 OLAP 场景的 MPP 架构数据库集群。它不少的内部机制沿用了 PostgreSQL 的实现。其中就包括了内存管理机制 MemoryContext。

一:PostgreSQL 内存管理机制

PostgreSQL 对内存的使用方式主要分两大块

1. shared_buffer 和同类 buffer。 简单的说 shared_buffer 用于存放数据页面对应数据文件中的 block,这部分内存是 PostgreSQL 中各进程共享。这部分不在本文讨论。 2. MemoryContext 以功能为单位组织起来的树形数据结构,不同的阶段使用不同的 MemoryContext。

1. MemoryContext 的作用

简单的说 MemoryContext 的存在是为了更清晰的管理内存

  • 合理管理碎片小内存。频繁的向 OS 申请和释放内存效率是很差的。MemoryContext 会以 trunk 为单位向 OS 申请成块的内存,并管理起来。当程序需求小内存时从 trunk 中分配,用完后归还给对应的 MemoryContext ,并不归还给 OS。
  • 赋予内存功能和生命周期属性以功能为单位管理内存。不同功能和阶段使用对应的 MemoryContext。
    • TopTransactionContext:一个事务的生命周期,事务管理相关数据放在 TopTransactionContext,当一个事务提交时该上下文被整个释放。
    • ExprContext PostgreSQL 以行为单位处理数据,每一行数据的表达式计算都会在 ExprContext 完成,每处理完一行都会重置对应的 ExprContext。
    • 树形的 MemoryContext 结构不同功能间的 MemoryContext 是以为树为单位组织起来的
  • 每个数据库后端进程顶层是 TopMemoryContext
    • TopMemoryContext 下有很多子 Context缓存相关的 CacheMemoryContext;
    • 本地锁相关的 LOCALLOCK hash;
    • 当前事务相关的 TopTransactionContext
    • 注意 CacheMemoryContext 为何不属于 TopTransactionContext,那是由于 Cache 是独立于事务存在的,事务提交不影响 Cache 的存在。
    • 删除或重置一个 MemoryContext,它的子 MemoryContext 也一并被删除或重置。
    • 2. 不同模块的 MemoryContext

你可能明白了,实现不同的模块时,对待内存的方式可能区别很大。 比如:

1. 执行器在做表达式计算时,一些诸如字符串类型数据处理的函数,大多会比较随意的使用 palloc 分配内存,但直到函数返回,却并没有释放它们。 2. 在处理缓存模块处理数据时,却倍加小心的释放内存。

这是由于:

1. 执行器对数据的处理是以行为单位,都在 ExprContext 中,每处理完一行,会重置 ExprContext,以此释放相关的内存。 2. 缓存的生命周期很长,不会定期重置整个 MemoryContext。哪怕少量的内存泄漏,积攒的后果都很严重。这部分的实现容易出问题,也不好排查。

3. 常见的内存问题

虽然有很好的内存管理机制,但进程中内存间没有强隔离,也可能出现内存问题。

造成内存泄漏的原因很大可能是:

1. 在较长生存周期的 MemoryContext 中正常处理流程中没有释放内存。 2. 由于发生了异常,跳转到在异常处理阶段没有释放内存。 3. 没有使用内存管理机制,使用 OS 调用 malloc,free 处理内存(某些实现不合理的插件中可能出现)。 4. 在不正确的 MemoryContext 分配了内存,导致内存泄漏或数据丢失。 5. 写内存越界,这是最难找的问题,很容易造成数据库崩溃。

4. 问题排查小技巧

针对内存泄漏,常用两种方法排查

1. valgrind 最常见的大杀器,开发人员都懂的。这里就不详细介绍了。

2. 使用 GDB 也能大致定位问题

2.1 这是一段脚本,我们把它保存成文本文件(pg_debug_cmd)

define sum_context_blocks
set $context = $arg0 set $block = ((AllocSet) $context)->blocks
set $size = 0 while ($block)
set $size = $size + (((AllocBlock) $block)->endptr - ((char *) $block))
set $block = ((AllocBlock) $block)->next
end
printf "%s: %d\n",((MemoryContext)$context)->name, $size
end

define walk_contexts
set $parent_$arg0 = ($arg1)
set $indent_$arg0 = ($arg0)
set $i_$arg0 = $indent_$arg0 while ($i_$arg0)
printf " " set $i_$arg0 = $i_$arg0 - 1
end
sum_context_blocks $parent_$arg0 set $child_$arg0 = ((MemoryContext) $parent_$arg0)->firstchild
set $indent_$arg0 = $indent_$arg0 + 1 while ($child_$arg0)
walk_contexts $indent_$arg0 $child_$arg0 set $child_$arg0 = ((MemoryContext) $child_$arg0)->nextchild
end
end

walk_contexts 0 TopMemoryContext

2.2 获得疑似内存泄漏的进程PID,定时触发执行下面的 shell

gdb -p $PID < pg_debug_cmd > memchek/MemoryContextInfo_$(time).log

2.3 分析日志文件

日志文件以 MemoryContext 树的形式展示了一个时间点该进程的内存分配情况。根据时间的积累,可以很容易判断出哪一些 MemoryContext 可能存在异常,从而为内存泄漏指明一个方向。

(gdb)
TopMemoryContext: 149616
 pgstat TabStatusArray lookup hash table: 8192
 TopTransactionContext: 8192
 TableSpace cache: 8192
 Type information cache: 24480
 Operator lookup cache: 24576
 MessageContext: 32768
 Operator class cache: 8192
 smgr relation table: 24576
 TransactionAbortContext: 32768
 Portal hash: 8192
 PortalMemory: 8192
 PortalHeapMemory: 1024
 ExecutorState: 24576
 SRF multi-call context: 1024
 ExprContext: 0
 ExprContext: 0
 ExprContext: 0
 Relcache by OID: 24576
 CacheMemoryContext: 1040384
 pg_toast_2619_index: 1024
 ....
 pg_authid_rolname_index: 1024
 WAL record construction: 49776
 PrivateRefCount: 8192
 MdSmgr: 8192
 LOCALLOCK hash: 8192
 Timezones: 104128
 ErrorContext: 8192 

最后,文章的参考资料中也提供了一种类似的方法,供各位参考。

总结

PostgreSQL 内存管理机制的实现比较复杂,但用起来确却很简单,有一种特别的美感,推荐大家了解一下。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
存储 弹性计算 缓存
阿里云服务器ECS经济型、通用算力、计算型、通用和内存型选购指南及使用场景分析
本文详细解析阿里云ECS服务器的经济型、通用算力型、计算型、通用型和内存型实例的区别及适用场景,涵盖性能特点、配置比例与实际应用,助你根据业务需求精准选型,提升资源利用率并降低成本。
854 3
|
11月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
276 1
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
981 4
AI代理内存消耗过大?9种优化策略对比分析
|
存储 Java
课时4:对象内存分析
接下来对对象实例化操作展开初步分析。在整个课程学习中,对象使用环节往往是最棘手的问题所在。
203 4
|
Java 编译器 Go
go的内存逃逸分析
内存逃逸分析是Go编译器在编译期间根据变量的类型和作用域,确定变量分配在堆上还是栈上的过程。如果变量需要分配在堆上,则称作内存逃逸。Go语言有自动内存管理(GC),开发者无需手动释放内存,但编译器需准确分配内存以优化性能。常见的内存逃逸场景包括返回局部变量的指针、使用`interface{}`动态类型、栈空间不足和闭包等。内存逃逸会影响性能,因为操作堆比栈慢,且增加GC压力。合理使用内存逃逸分析工具(如`-gcflags=-m`)有助于编写高效代码。
349 2
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
357 35
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
772 1
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3574 0
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1205 1