一文简单搞懂PostgreSQL中OOM几个重要的相关内核参数

文摘   科技   2024-10-09 06:20   北京  

我是【Sean】,  欢迎大家长按关注并加星公众号:数据库杂记。有好资源相送,同时为你提供及时更新。同时建了一个群: 数据库Hacker。 可以联系我微信邀请进群。已关注的朋友,发送0、1到7,都有好资源相送。
快速扫码入群, 纯技术交流:


前言

我们在生产环境当中,偶尔会遇到总体内存不够用了,然后相关应用程序,可能就被“杀死”。俗称OOM,然后被kill掉了。这背后隐藏的原理是什么样的?

一直以来,没怎么进行系统性的总结。本文试图从最基本的概念说起,把这个事情说清楚。

详细介绍

1、基本概念

虚存:指的是RAM和SWAP分区之和。

Overcommit: 分配超过可用虚存的大小。(超分)

Allocate: 在内存管理的背景下, 内存的分配可以被认为是对可用内存的一个“承诺“或”保证“。实际的物理内存并不立即分配,直到它真正使用的时候才进行分配。这种分配是在页一级进行的。如果新的页(通常8KB)需要的时候,系统会触发一个分页错误。

为何会出现超分?

Linux虚拟内存实现使用几种策略来优化所使用的内存量(其中一种策略称为“写时复制”,用于派生子进程)。这样做的结果是,实际使用的内存通常少于通过/proc文件系统(以及扩展名ps)报告的内存。

在这种情况下,轻微的超额提交是可以接受的,因为通常有足够的内存来提供服务。然而,这种方法可能导致在实际可用内存不足的情况下执行内存分配。

为了处理这种情况,Linux支持几种不同的复用策略,这些策略由配置vm.overcommit的整数值进行指定。

  • vm.overcommit_memory = 0

    这是Linux使用的默认策略,采用的是一种试探性的内存分配策略。在这种情况下,系统可以使用所有的虚拟内存进行分配,并且所有的分配都会被授予,除非它们看起来需要大量的超额提交。

    如果,当发生页面错误时,没有足够的可用内存(即,我们有一个超额提交),系统将触发“内存耗尽杀手”(OOM killer)。OOM killer将选择当前在系统上运行的进程并终止该进程。它使用一组启发式方法来选择要终止的进程。请注意,通常不可能预测何时需要此过程,也不可能预测哪些进程将被选择终止。

  • vm.overcommit_memory = 1

    此策略通常用于运行进程的系统,这些进程将分配非常大且稀疏填充的数组。在这种模式下,任何分配都将成功。如果检测到超分提交,检测到超分提交的进程将生成内存错误和灾难性的失败(不会执行清理; 进程只是终止)。

    请注意,由于只有在需要时才分配内存,因此失败的进程不一定是分配了最多内存的进程。由于内存使用的性质,预测哪个进程将由于内存过度提交而失败是不可能的。

  • vm.overcommit_memory = 2

    在这种模式下,Linux执行严格的内存记帐,只在所需内存确实可用的情况下才分配内存。由于这种检查是在分配内存时完成的,请求内存的程序可以优雅地处理失败(在GPDB生成“内存不足”错误的情况下),并清理遇到错误的会话。

    此策略还将严格分配一部分物理RAM供内核使用。限制的数量通过设置vm.overcommit_ratio来配置。这意味着程序可用的虚拟内存量(超过可提交内存量)实际上是: SWAP + (RAM ∗ (overcommit_ratio/100))

    保留的内存用于诸如IO缓冲区和系统调用之类的事情。

overcommit_memory =2的示例

  • 例1:

    4 GB RAM, 4 GB Swap, overcommit_memory = 2, overcommit_ratio = 50

    内存分配的限制:MemAllocLimit = 4GB Swap + 4GB RAM * (50%) = 6GB

  • 例2:

    4GB RAM, 8GB Swap, overcommit_memory = 2, overcommit_ratio = 50

    MemAllocLimit = 8GB Swap + 4GB RAM * 50% = 10GB

  • 例3:

    4GB RAM, 2GB Swap, overcommit_memory = 2, overcommit_ratio = 50

    MemAllocLimit = 2GB Swap + 4GB RAM * 50% = 4GB

注意,这是Linux将分配的内存总量。这包括所有正在运行的守护进程和其他应用程序。不要假设您的应用程序总能够分配总限额。Linux还将在/proc/meminfo中的CommitLimit字段中也提供了内存分配限制。

Overcommit的默认值:

直接grep过滤出当前值:
[17:46:59-root@centos2:/var/lib/pgsql/14/data]$ sysctl -a | grep overcommit
vm.nr_overcommit_hugepages = 0
vm.overcommit_kbytes = 0
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

[17:47:26-root@centos2:/var/lib/pgsql/14/data]$ sysctl -a | grep vm.swappiness
vm.swappiness = 30

[17:48:09-root@centos2:/var/lib/pgsql/14/data]$ sysctl -a | grep vm.oom-kill
修改swappiness值为0, 禁用swap
临时启用与禁用:swapoff -a , swapon -a
永久配置方法:sysctl -w vm.swappiness=0
echo vm.swappiness = 0 >> /etc/sysctl.conf
临时配置方法:sysctl -w vm.swappiness=0
手动更改/sys/fs/cgroup/memory下子目录对应的memory.swappiness值

sysctl -w vm.oom-kill=1
sysctl -w vm.swappiness=0
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=10

使用此模式,Linux会严格统计的内存使用情况,并且只会在物理内存可用时才会允许内存申请。由于检查是在分配时完成的,请求内存的程序可以正常处理该故障的情况下,并清理遇到该错误的会话。

overcommit 2会分配一部分物理RAM用于内核使用。分配的数量由设置vm.overcommit_ratio配置。这意味着可用于程序的内存的总量实际上是:

*RAM (overcommit_ratio / 100) + SWAP。

改变PG参数:

shared_buffers = 24MB 
work_mem=1MB

彻底禁用SWAP:

/etc/sysctl.conf

sysctl -w vm.overcommit_memory=2

[18:11:42-root@centos2:/var/lib/pgsql/14/data]$ sysctl -w vm.overcommit_ratio=90
vm.overcommit_ratio = 90
[18:11:49-root@centos2:/var/lib/pgsql/14/data]$ swapoff -a
[18:11:51-root@centos2:/var/lib/pgsql/14/data]$ sysctl -a | grep vm.overcommit_ratio
vm.overcommit_ratio = 30

sysctl -w vm.swappiness=0
sysctl -p /etc/sysctl.conf

另一种方式强写: /etc/fstab:

# UUID=4fef9250-05ce-49a1-b4c9-f3aff35d7626 swap                    swap    defaults        0 0

OOM killer的杀死原则

有两种选择,一种是直接panic系统,然后几秒钟后LINUX自动重启,这个行为可以通过kernel.panic_on_oom参数设置,不过我们一般来说不喜欢采用这种模式,LINUX自动重启对于很多应用来说存在太大的不确定性,会影响业务。另外一种选择就是杀死部分进程,从而释放足够的内存来让内存问题得到解决。LINUX的oom killer就应运而生了。oom killer通过oom_badness函数来找到可以杀掉的候选进程,然后直接干掉这个进程,尽快解决内存不足的问题。

oom_score_adj值,而这个查找过程中,其主要依据是每个进程的oom_score,其选择的算法比较简单,总是选择oom_score比较高,同时优先级比较低的进程去杀。而oom_score在新版本的Linux内核中计算起来十分简单,就是这个进程占用的(物理内存+SWAP)的总和乘以10,也就是说某个进程的最大oom_score=1000,最小oom_score=0。oom killer总是根据这个值从高到低排序来查找可以杀死的进程,这样确保杀死进程后,可以释放最多的内存。在这个函数中,不会返回oom_score为0的进程,**因此如果我们把某个进程的oom_score_adj设置为-1000(早期版本的linux oom_score的计算方法不同,因此调整adj的方法略有不同),那么这个进程的oom_score就会永远为0,也就是说这个进程永远不会被oom killer杀死。**这也是我们保护某个进程,不让oom killer杀掉的主要方法。

以前我们说过PG数据库在使用kill -9 /kill -SIGKILL杀死某个backend或者PG后台服务进程的时候可能会导致PG实例崩溃,因此和Oracle不同对的是,某个backend进程被oom killer杀掉的时候可能会引起更为严重的问题,因此我们在PG数据库上要尽可能避免oom killer杀死backend。同样,Mysql、达梦、opengauss等单进程多线程的数据库也存在类似的问题。

其实还有第二种方法,就是直接调整overcommit_memory参数为2,然后设置一个合理的overcommit_ratio,这样,在确保物理内存和SWAP足够的情况下,我们可以放心的运行相关的数据库。如果设置了overcommit_memory=2,那么我们一定要配置足够的SWAP,从而确保存在大量内存分配的时候,不会有进程因为无法分配内存而直接退出。

也可读一读:OOM终结者参数调优[1]

  • om_adj

正常范围是 -1615, 用于计算一个进程的OOM评分(oom_score)。这个分值越高, 该进程越有可能被OOM终结者给干掉。如果设置为 -17, 则禁止 oom_killer 杀死该进程。

说明: proc 文件系统是虚拟文件系统, 某个进程被杀掉, 则 /proc/pid/ 目录也就被销毁了。

修改其值:

$ cat /proc/12884/oom_adj
0

# 最终得分
$ cat /proc/12884/oom_score
161

$ cat /proc/12884/oom_score_adj
0

# change ...
$ echo -1000 > /proc/12884/oom_adj

$ cat /proc/12884/oom_adj
-1000

$ cat /proc/12884/oom_score
0
# 分值修正值
$ cat /proc/12884/oom_score_adj
-1000

#
$ echo 15 > /proc/12884/oom_adj

$ cat /proc/12884/oom_adj
15

$ cat /proc/12884/oom_score
1160

$ cat /proc/12884/oom_score_adj
1000

2、vm.dirty参数

vm.dirty_ratio

这决定了页面数量占系统总内存的百分比,之后pdflush后台回写守护进程将开始写脏数据。默认值是20。建议将其减少到2,以使刷新更频繁,但导致更少的IO峰值。

vm.dirty_background_ratio

这决定了pdflush后台回写守护进程将开始写脏数据的页数(占可用内存(包括缓存)的百分比)。默认值是10。建议将此值减小为1,以使刷新更频繁,但导致IO减少。

3、THP (transparent huge page, 透明大页)

为了获得更好的PostgreSQL性能,总是建议在Linux系统上禁用THP。禁用它: Enterprise Redhat Linux内核:

cat /sys/kernel/mm/redhat_transparent_hugepage/enabled

其它内核 :cat /sys/kernel/mm/transparent_hugepage/enabled

下面是一个示例输出,显示THP正在被使用,因为[always]标志是启用的:

[always] madvise never

如果输出结果为[always]表示透明大页启用了。[never]表示透明大页禁用、[madvise]表示(只在MADV_HUGEPAGE标志的VMA中使用THP这个状态就说明都是启用的。

3.1 在运行时禁用THP(Transparent HugePages)

运行以下命令即时禁用THP,该命令适用于其它Linux系统:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

3.2 永久禁用THP

[root@localhost ~]# vim /etc/rc.d/rc.local 

添加以下内容:
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi

保存退出,并确保相关权限
[root@localhost ~]# chmod +x /etc/rc.d/rc.local

在这之后重启系统,进行验证

[root@localhost ~]# cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]
[root@localhost ~]# cat /sys/kernel/mm/transparent_hugepage/defrag
always madvise [never]

另一种查看是否禁用:

是否为0? 如果是0,表示禁用:
cat /proc/sys/vm/nr_hugepages
sysctl vm.nr_hugepages

查看使用的内存,确认是否降下来:

cat /proc/meminfo && grep AnonHugePages /proc/meminfo

4、HugePage

使用软件和硬件机制的组合将虚拟内存映射到物理内存。这个虚拟内存特性允许操作系统将可寻址空间分散到物理RAM的不同区域。

但是,这个VM概念需要从虚拟地址转换为物理地址。此转换的信息存储在“页表”中。为了加快这些表中的查找/翻译速度,该表存储在一个称为翻译查找表Buffer (TLB)的缓存中。这个TLB缓存可以翻译的内存量称为“TLB缓冲”。如果漏了TLB,则会受到更大的惩罚。

根据x86架构,页面大小为4K字节。这意味着当一个进程使用1GB内存时,需要查找262144个条目! 这种影响会随着内存大小的增加而增加。大页面的想法通常是将4K字节增加到2MB。这将大大减少页面引用的数量。

明显的性能提升来自于更少的翻译,需要更少的周期。一个不太明显的好处是地址转换信息通常存储在L2缓存中。通常,数据库工作负载可以获得7%的即时性能提升。大页面的最大好处是数据库系统的性能更稳定。

相关文献参考

主要参考文章:内存管理中的overcommit和oom killer

参考书籍:PostgreSQL Configuration Best Practices for Performance and Security

Centos7禁用THP(Transparent HugePages)[2]

OOM终结者参数调优[3]

参考资料

[1]

OOM终结者参数调优: https://blog.csdn.net/renfufei/article/details/80468217

[2]

Centos7禁用THP(Transparent HugePages): https://blog.csdn.net/jiangbenchu/article/details/108727388

[3]

OOM终结者参数调优: https://blog.csdn.net/renfufei/article/details/80468217


个人微信:_iihero
CSDN: iihero
墨天轮:https://www.modb.pro/u/16258 (Sean)

pgfans: iihero

往期导读: 
1. PostgreSQL中配置单双向SSL连接详解
2. 提升PSQL使用技巧:PostgreSQL中PSQL使用技巧汇集(1)
3. 提升PSQL使用技巧:PostgreSQL中PSQL使用技巧汇集(2)
4. PostgreSQL SQL的基础使用及技巧
5. PostgreSQL开发技术基础:过程与函数
6. PostgreSQL中vacuum 物理文件truncate发生的条件
7. PostgreSQL中表的年龄与Vacuum的实验探索:Vacuum有大用
8. PostgreSQL利用分区表来弥补AutoVacuum的不足
9. 也聊聊PostgreSQL中的空间膨胀与AutoVacuum
10. 正确理解SAP BTP中hyperscaler PG中的IOPS (AWS篇)

数据库杂记
数据库技术专家,PostgreSQL ACE,SAP HANA,Sybase ASE/ASA,Oracle,MySQL,SQLite各类数据库, SAP BTP云计算技术, 以及陈式太极拳教学倾情分享。出版过三本技术图书,武术6段。
 最新文章