当前位置: 首页 > ORACLE > 正文

我们的文章会在微信公众号IT民工的龙马人生博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。

一个关于PostgreSQL的常见抱怨是每个连接使用了太多内存。这种抱怨经常出现在将PostgreSQL的连接模型与每个连接分配专用线程的模型进行比较时,而不是当前每个连接都有专用进程的模型。

需要明确的是:这是一个值得讨论的话题。我们可以做出几个重要的改进来减少内存使用。

话虽如此,我认为这些担忧的一个常见原因是,测量PostgreSQL后端内存使用的简单方法,如topps,都相当具有误导性。

准确测量额外连接增加的内存使用量出奇地困难。

在这篇文章中,我主要讨论在Linux上运行PostgreSQL,因为这是我最熟悉的。

我的大胆声明是,当准确测量时一个连接的开销只有不到2MiB(见结论)。

初步观察

仅使用常见的操作系统工具会使开销看起来比实际大得多。特别是当不使用huge_pages(不推荐)时,每个进程的内存使用看起来会很高。

让我们先看看在一个新启动的PostgreSQL集群中刚建立的连接:

andres@awork3:~$ psql
postgres[2003213][1]=# SELECT pg_backend_pid();
┌────────────────┐
│ pg_backend_pid │
├────────────────┤
│        2003213 │
└────────────────┘
(1 row)

andres@awork3:~/src/postgresql$ ps -q 2003213 -eo pid,rss
    PID   RSS
2003213 16944

大约16MiB。

大量泄漏!?幸运的是没有。

更糟糕的是,内存使用会随时间增长。为了展示这个问题,我将使用pgprewarm扩展将表中的所有页面加载到PostgreSQL的缓冲区池中:

postgres[2003213][1]=# SHOW shared_buffers ;
┌────────────────┐
│ shared_buffers │
├────────────────┤
│ 16GB           │
└────────────────┘
(1 row)

postgres[2003213][1]=# SELECT SUM(pg_prewarm(oid, 'buffer')) FROM pg_class WHERE relfilenode <> 0;
┌────────┐
│  sum   │
├────────┤
│ 383341 │
└────────┘

andres@awork3:~$ ps -q 2003213 -eo pid,rss
    PID   RSS
2003213 3169144

现在PostgreSQL的内存使用看起来大约是3GB。尽管单个连接实际上没有分配太多额外内存。增加的内存使用与触摸的共享缓冲区数量成正比:

postgres[2003213][1]=# SELECT pg_size_pretty(SUM(pg_relation_size(oid))) FROM pg_class WHERE relfilenode <> 0;
┌────────────────┐
│ pg_size_pretty │
├────────────────┤
│ 2995 MB        │
└────────────────┘
(1 row)

更糟糕的是,如果另一个连接也使用这些页面,它也会显示为具有巨大的内存使用:

postgres[3244960][1]=# SELECT sum(abalance) FROM pgbench_accounts ;
┌─────┐
│ sum │
├─────┤
│   0 │
└─────┘
(1 row)

andres@awork3:~$ ps -q 3244960 -eo pid,rss
    PID   RSS
3244960 2700372

当然,PostgreSQL在这种情况下实际上并没有使用3+2.7 GiB的内存。相反,发生的是,当huge_pages=off时,ps会将连接使用的共享内存量(包括缓冲区池)归因于每个连接。显然导致大大高估内存使用。

大页面意外地拯救了局面

许多CPU微架构通常使用4KiB的页面大小,但也可以选择使用更大的页面大小,最常见的是2MiB。

根据操作系统、配置和使用的应用程序类型,这些更大的页面可以被操作系统透明地使用,或者被应用程序明确使用。详情请参见例如Debian维基页面关于大页面的内容。

huge_pages=on重复之前的实验使它们看起来好很多。首先,看一个"新连接":

andres@awork3:~$ ps -q 3245907 -eo pid,rss
    PID   RSS
3245907  7612

所以,一个新连接现在看起来只使用大约~7MiB。这种内存使用的减少是由于页表需要更少的内存,因为它现在只需要包含之前条目的1/512,因为页面大小更大。

更重要的是,大量内存被访问的测试:

postgres[3245843][1]=# ;SELECT SUM(pg_prewarm(oid, 'buffer')) FROM pg_class WHERE relfilenode <> 0;
…
postgres[3245851][1]=# SELECT sum(abalance) FROM pgbench_accounts ;
…

andres@awork3:~$ ps -q 3245907,3245974 -eo pid,rss
    PID   RSS
3245907 12260
3245974  8936

与上面相比,这些连接现在看起来只使用12MiB和9MiB,而之前它们使用3GiB和2.7GiB。相当大的明显变化;)

这是由于Linux中较大页面使用实现方式,而不是因为我们使用了数量级更少的内存:大页面使用只是不显示为ps/top的RSS列的一部分。

变得更真实

自Linux 4.5以来,/proc/$pid/status文件将内存使用分为更细的子类别:

VmRSS		size of memory portions. It contains the three following parts (VmRSS = RssAnon + RssFile + RssShmem)
RssAnon		size of resident anonymous memory
RssFile		size of resident file mappings
RssShmem	size of resident shmem memory (includes SysV shm, mapping of tmpfs and shared anonymous mappings)

huge_pages=off查看这些统计:

andres@awork3:~$ grep -E '^(Rss|HugetlbPages)' /proc/3247901/status
RssAnon:	    2476 kB
RssFile:	    5072 kB
RssShmem:	    8520 kB
HugetlbPages:	       0 kB

postgres[3247901][1]=# SELECT SUM(pg_prewarm(oid, 'buffer')) FROM pg_class WHERE relfilenode <> 0;

andres@awork3:~$ ps -q 3247901 -eo pid,rss
    PID   RSS
3247901 3167164

andres@awork3:~$ grep -E '^(Rss|HugetlbPages)' /proc/3247901/status
RssAnon:	    3148 kB
RssFile:	    9212 kB
RssShmem:	 3154804 kB
HugetlbPages:	       0 kB

RssAnon是"匿名"内存的数量,即内存分配。RssFile是内存映射文件,包括PostgreSQL二进制文件本身。最后,RssShmem显示访问的非大页面共享内存。

这很好地表明,ps等显示的高内存使用主要是由于共享内存访问。

huge_pages=on

andres@awork3:~$ grep -E '^(Rss|HugetlbPages)' /proc/3248101/status
RssAnon:	    2476 kB
RssFile:	    4664 kB
RssShmem:	       0 kB
HugetlbPages:	  778240 kB

postgres[3248101][1]=# SELECT SUM(pg_prewarm(oid, 'buffer')) FROM pg_class WHERE relfilenode <> 0;

andres@awork3:~$ grep -E '^(Rss|HugetlbPages)' /proc/3248101/status
RssAnon:	    3136 kB
RssFile:	    8756 kB
RssShmem:	       0 kB
HugetlbPages:    3846144 kB

近似准确性

仅将非共享内存使用量相加仍然会高估内存使用。有两个主要原因:

首先,在测量PostgreSQL后端的内存使用时,包含RssFile实际上没有意义 – 对于PostgreSQL来说,这主要是PostgreSQL二进制文件和它使用的共享库(PostgreSQL不mmap()文件)。由于几乎所有这些都是系统中所有进程共享的,这不是每个连接的开销。

其次,即使只看RssAnon也会高估内存使用。原因是ps测量整个进程的内存,即使新连接的大部分开销在用户连接和监控进程之间共享。这是因为Linux在fork()新进程时不会复制所有内存,而是使用写时复制只在页面被修改时才复制。

没有好的方法来准确测量单个fork进程的内存使用,但自4.14版本以来,Linux内核至少在进程的/proc/[pid]/smaps_rollup文件中提供了近似值(带描述的提交)。Pss显示"此映射的进程比例份额"在所有进程映射中(搜索Linux文档页面smaps_rollup和Pss,不幸的是没有直接链接)。对于进程间共享的内存,它会将内存使用除以使用映射的进程数。

postgres[2004042][1]=# SELECT SUM(pg_prewarm(oid, 'buffer')) FROM pg_class WHERE relfilenode <> 0;
┌────────┐
│  sum   │
├────────┤
│ 383341 │
└────────┘
(1 row)

postgres[2004042][1]=# SHOW huge_pages ;
┌────────────┐
│ huge_pages │
├────────────┤
│ off        │
└────────────┘
(1 row)

andres@awork3:~$ grep ^Pss /proc/2004042/smaps_rollup
Pss:             3113967 kB
Pss_Anon:           2153 kB
Pss_File:           3128 kB
Pss_Shmem:       3108684 kB

Pss_Anon包含进程分配的内存,Pss_File包括链接到进程的共享库等,Pss_Shmem(如果不使用huge_pages)是共享内存使用量除以触摸相应页面的所有进程。

使比例值不完美的是除数取决于连接到服务器的连接数。这里我使用pgbench(scale 1000, -S, -M prepared -c 1024)启动大量连接:

postgres[2004042][1]=# SELECT count(*) FROM pg_stat_activity ;
┌───────┐
│ count │
├───────┤
│  1030 │
└───────┘
(1 row)

postgres[2004042][1]=# SELECT pid FROM pg_stat_activity WHERE application_name = 'pgbench' ORDER BY random() LIMIT 1;
┌─────────┐
│   pid   │
├─────────┤
│ 3249913 │
└─────────┘
(1 row)

andres@awork3:~$ grep ^Pss /proc/3249913/smaps_rollup
Pss:                4055 kB
Pss_Anon:           1185 kB
Pss_File:              6 kB
Pss_Shmem:          2863 kB

而用huge_pages=on

andres@awork3:~$ grep ^Pss /proc/2007379/smaps_rollup
Pss:                1179 kB
Pss_Anon:           1173 kB
Pss_File:              6 kB
Pss_Shmem:             0 kB

不幸的是,Pss值没有考虑应用程序不可见的资源。例如,页表的大小不包括在内。页表大小也在前面提到的/proc/$pid/status文件中可见。

据我所知 – 但我不确定 – VmPTE(页表大小)对每个进程完全私有,但大多数其他Vm*值,包括栈VmStk都以写时复制方式共享。

使用上述内容,页表导致的开销,用huge_pages=off

andres@awork3:~$ grep ^VmPTE /proc/2004042/status
VmPTE:      6480 kB

而用huge_pages=on

VmPTE:	     132 kB

当然,内核中每个进程还有一些额外的小开销,但它们小到不值得在status文件中表示。

结论

基于上述测量,我们可以近似地认为,运行非常简单的只读OLTP工作负载的连接,用huge_pages=off的开销约为7.6MiB,用huge_pages=on的开销约为1.3MiB,通过将Pss_Anon加到VmPTE

即使在为一些"不可见"开销、PostgreSQL缓冲区池中的更多数据等留出空间的情况下,我认为这支持了我早先的声明,即连接的开销不到2MiB。


原文链接: Measuring the Memory Overhead of a Postgres Connection

——————作者介绍———————–
姓名:黄廷忠
现就职:Oracle中国高级服务团队
曾就职:OceanBase、云和恩墨、东方龙马等
电话、微信、QQ:18081072613
个人博客: (http://www.htz.pw)
CSDN地址: (https://blog.csdn.net/wwwhtzpw)
博客园地址: (https://www.cnblogs.com/www-htz-pw)


PG系列:PostgreSQL连接内存开销测量:等您坐沙发呢!

发表评论

gravatar

? razz sad evil ! smile oops grin eek shock ??? cool lol mad twisted roll wink idea arrow neutral cry mrgreen

快捷键:Ctrl+Enter