我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢!
由于博客中有大量代码,通过页面浏览效果更佳。
一个关于PostgreSQL的常见抱怨是每个连接使用了太多内存。这种抱怨经常出现在将PostgreSQL的连接模型与每个连接分配专用线程的模型进行比较时,而不是当前每个连接都有专用进程的模型。
需要明确的是:这是一个值得讨论的话题。我们可以做出几个重要的改进来减少内存使用。
话虽如此,我认为这些担忧的一个常见原因是,测量PostgreSQL后端内存使用的简单方法,如top
和ps
,都相当具有误导性。
准确测量额外连接增加的内存使用量出奇地困难。
在这篇文章中,我主要讨论在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连接内存开销测量:等您坐沙发呢!