PostgreSQL内存上下文[翻译]

news/2024/7/9 20:52:50 标签: postgresql, 数据库

PostgreSQL内存上下文

PG使用共享内存在多进程之间进行数据共享。使用动态共享内存段dynamic shared memory segments在并行workers之间进行数据交换,这个内存在启动时分配固定大小。但是PG后端进程必须管理私有内存用于处理SQL语句。本文,介绍PG如何使用memory context,即内存上下文,来管理私有内存;以及如何检查内存使用情况。这对于编写服务器代码的人来说很有意思,但我要重点关注用户如何理解和调试SQL语句的内存消耗。

1.什么是内存上下文

PG由C语言编写,C语言的内存管理比较棘手,必须显式释放所有动态分配的内存。这就特别容易造成内存泄漏了,导致不断增加内存消耗。对于PG后端这样长期存在的进程来说是致命的。

为了减少内存泄漏,PG使用内存上下文管理自己的内存。内存上下文是可以按需增长的内存块。在PG中不直接调用malloc申请内存,而是从内存上下文中申请。根据需要,PG会扩展内存上下文。

内存上下文的优势:可以通过删除内存上下文,一次性释放所有内存。这就意味着不再需要追踪分配的内存,关注什么时候释放了,简化了内存管理,降低了内存泄漏的风险。PG查询执行器在开始处理一个语句时,创建ExecutorState context。如果需要申请内存,则从该内存上下文中申请。语句执行完时,执行器会删除ExecutorState,在查询执行结束后,不必担心内存泄漏。源码src/backend/utils/mmgr/README中详细介绍了内存上下文的设计与使用。

2.内存上下文的组织

内存上下文形成一个层次结构。最顶层的内存上下文是TopMemoryContext,存在于后台进程的整个生命周期。其他任何一个内存上下文都有一个父节点。当删除一个内存上下文时,会递归删除所有后继内存上下文。因此,不需要频繁明确地释放内存。如果在较短时间内需要几个内存块,例如处理执行计划的某个步骤,可以在ExecutorState中再创建一个内存上下文,在该步骤执行完时将其删除。如果执行器在此之前终止,则该内存上下文中任何内存都不会泄漏。

重要的内存上下文

TopMemoryContext

内存上下文的最顶层,不需要删除。

CacheMemoryContext

包含数据库元数据的缓存以及执行计划的换岑。如果数据库包含多个对象(例如表分区),或者有许多prepared语句,则会占用更多空间

MessageContext

包含来自客户端的语句,有时还包含执行计划和解析数据

PortalContext

与当前语句关联的内存(称为portal或者cursor)

3.一个SQL语句使用多少内存

理论上执行计划的每一步都会被work_mem限制,但是不足以评估内存的使用。

1、单个语句可能有很多内存密集型执行步骤,因此会分配work_mem多次;

2、如果语句使用并行查询,会创建动态共享内存段,work_mem并不统计这个;

3、PG13之前,bytea二进制数据或者大PostGIS几何图形,会驻留在内存中,也不被work_mem限制

有一些方法可以帮助查看内存上下文中存储了多少内存。

3.1 pg_backend_memory_contexts查看内存上下文使用

pg_backend_memory_context视图限制了当前会话拥有的所有内存上下文。只能在语句之间查询该视图,但在执行SQL时查看才会更有用。为此,可以创建一个函数,将其构建到SQL语句中:

CREATE FUNCTION dump_my_mem() RETURNS void
   LANGUAGE plpgsql AS
$$DECLARE
   r record;
BEGIN
   FOR r IN
      SELECT name, ident, level, total_bytes
      FROM pg_backend_memory_contexts
   LOOP
      RAISE NOTICE '% % % %',
         repeat('  ', r.level - 1),
         r.name,
         r.total_bytes,
         r.ident;
   END LOOP;
END;$$;

3.2 pg_log_backend_memory_contexts()记录内存上下文使用

pg_log_backend_memory_context(integer)函数可以将任意会话的内存上下文当前状态写入日志文件。参数是进程ID,可以通过pg_stat_activity查看。默认仅超级用户可以调用整个函数,但是你可以GRANT EXECUTE权限给其他用户。

通过这种方法,可以方便地检查长时间运行SQL的内存使用。问题是一个消耗大量内存的语句不需要长时间运行。捕捉一个简短的语句比较棘手。

3.3 debug来记录内存使用

如果想要检查内存使用,可以通过debug的方式精确控制语句的执行点。但需要熟悉PG代码,并gdb一个进程。

首先看下进程ID,我们使用12345作为一个例子:

gdb /path/to/postgresql/bin/postgres 12345
GNU gdb (GDB) Fedora Linux 13.1-3.fc37
Copyright (C) 2023 Free Software Foundation, Inc.
[...]
(gdb)

然后打个断点,一个有用的函数是ExecutorEnd:PG处理一个语句结束点:

(gdb) break ExecutorEnd
Breakpoint 1 at 0x783271: file execMain.c, line 471.
(gdb) cont
Continuing.

执行有问题的语句,一旦执行到断点,就会触发内存上下文的dump:

Breakpoint 1, ExecutorEnd (queryDesc=0x2333fd8) at execMain.c:471
471 if (ExecutorEnd_hook)
(gdb) print MemoryContextStats(TopMemoryContext)
$1 = void

这会将内存上下文转储到日志文件。然后可以detach该进程,退出GDB:

(gdb) detach
Detaching from program: /path/to/postgresql/bin/postgres, process 12345
[Inferior 1 (process 12345) detached]
(gdb) quit

4.评估PG总内存使用

一个繁忙的数据库将有许多会话同时运行,很难说会有多少连接,以及他们执行的是简单还是复杂的语句。因此很难预测到PG使用多少内存。恰当地说,你所知道的work_mem的一切都是错误的,很显然Christophe Pettus提出了自己的公式:

50%的free memory + 文件系统buffers/连接数

可以看到,连接数有着至关重要的作用。如果想获得良好性能,需要使用大小合适的连接池。毕竟,足够大的work_mem是non-trival SQL语句良好性能的重要条件。

5.PG内存不足

我们当然不想遇到内存不足的情况,但是一旦发生,后果很大程度上取决于如何配置操作系统内核。使用默认配置,Linux将在内存耗尽时调用“out-of memory killer”。这个不友好的内核组件将向某些后台进程发送SIGKILL信号,无条件终止进程并释放内存。PG进程过早死亡,会断开所有连接,并导致崩溃恢复。崩溃恢复意味着直到PG恢复到上次最近的checkpoint,才能对外服务。

避免这种崩溃的正确方法是:设置内核参数vm.overcommit_memory到2和调整vm.overcommit_ratio。然后回得到一个常规“out of memory”错误,PG会将内存上下文dump到日志文件。该内存上下文转储非常有用,有助于理解后格SQL在哪里分配了所有的内存。

6.总结

拥有PG如何使用内存上下文管理私有内存的概念非常重要,即使你不是一个内核开发者。正确配置有助于理解内存上下文,同时也介绍了一些视图和函数来帮助检查内存上下文。

原文

https://www.cybertec-postgresql.com/en/memory-context-for-postgresql-memory-management/


http://www.niftyadmin.cn/n/1399704.html

相关文章

九度 题目1144:Freckles

题目描述:In an episode of the Dick Van Dyke show, little Richie connects the freckles on his Dads back to form a picture of the Liberty Bell. Alas, one of the freckles turns out to be a scar, so his Ripleys engagement falls through. Conside…

九度 题目1154:Jungle Roads

题目描述:The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads between villages some years ago. But the jungle overtakes roads relentlessly, so the large road network is too expen…

throws和try catch的区别

说一下个人理解: 1.throws是表明方法抛出异常,需要调用者来处理,如果不想处理就一直向外抛,最后会有jvm来处理; 2.try catch 是自己来捕获别人抛出的异常,然后在catch里面去处理; 一般情况下…

java static和final的用法总结

final: 1.final变量: 在变量前面加上final关键字,这个变量一旦被初始化就不可以改变,这里不可改变是对基本类型来说是其值不可变,而对于对象变量来说是其引用不可以变。初始化可以在两个地方,一是其定义处…

九度 题目1008:最短路径问题

最短路径问题,首先想到了贪心算法实现的dijkstra算法;这道题我用了链表的存储方式,其实用邻接矩阵也可以,主要为了练手,并且链表比矩阵要节约空间; 题目描述:给你n个点,m条无向边&am…

如何将二维数组作为函数的参数传递

声明: 如果你是得道的大侠,这篇文章可能浪费你的时间,如果你坚持要看,我当然感觉很高 兴,但是希望你看完了别骂我!如果你发现我这篇文章有错误的话,你可以提出批评以及 指正,我将很乐…

九度 题目1448:Legal or Not

题目1448:Legal or Not时间限制:1 秒 内存限制:128 兆 特殊判题:否 提交:1071 解决:485 题目描述:ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmoniou…

java 受检异常和非受检异常

总是记不住什么是受检异常,什么是非受检异常,有时甚至弄混淆,特此简单记录以便复习之用。 异常的分类: java.lang.Throwable 1.Error错误:JVM内部的严重问题。无法恢复。程序人员不用处理。 2.Exception异常&#xff1…