首页
产品
CLup:PostgreSQL高可用集群平台CData高性能数据库云一体机CBackup数据库备份恢复云平台CPDA高性能双子星数据库机
解决方案
数据库专业技术服务全栈式PostgreSQL解决方案Oracle分布式存储化数据库云
文章
客户及伙伴
中启开源
关于我们
公司简介 联系我们
中启开源

1.前言

目前很多同学使用ps aux常看PostgreSQL数据库主机的各个进程的内存时,如果把各个进程的内存加起来时,发现超过了总内存,很困惑。有些人认为是因为ps aux中显示的是虚拟内存,总虚拟内存是可以大于总内存的,这种认识可能不全面ps aux中显示的列VSZ代表的是虚拟内存,把这一列的值加起来是大于总内存的,但是ps aux中的列RSS代表的是真实的内存,把RSS列加起来,发现也是大于总内存的,这是为什么呢?

另有些同学使用top命令查看内存时,会发现有一些PostgreSQL进程占用了很大百分比的的内存,如超过了25%的内存,这一个进程就超过了总内存的25% ,这还了得?

中启乘数科技做为专业的数据库服务提供商,对此问题进行了深入研究,在这里给大家解惑。

2. 原理解释

我们用top查看时,有时会看到某个PostgreSQL进程占用的内存很大,如下图所示:
Top中看到某个PostgreSQL进程占用内存很大

从上图中可以看到进程(pid=45286)占到了机器总内存的25.9%,这台机器的总内存是128G,25.9%,基本就是33GB,从感觉上看觉得不可能?但是这里确实显示占用了这么大内存。进一步查看这个进程:

  1. [root@pg01 ~]# ps -ef|grep 45286 |grep -v grep
  2. postgres 45286 45282 0 2020 ? 05:33:30 postgres: checkpointer

发现这个进程是checkpointer进程,难道有内存泄露?但是查看了很多长时间运行的PostgreSQL数据库,发现checkpointer进程都占用了很高的内存。实际上这里看到的这个内存并不是这个进程实际占用的真实内存。原因是这里显示的内存包括了共享内存,而共享内存是在很多的进程之间共享的,不能都算到这个进程头上。另我们知道PostgreSQL数据库会分配总内存四分之一大小的共享内存做数据块的缓存。查看数据库的参数shared_buffers看到数据库的共享内存的大小:

  1. [postgres@pg01 ~]$ psql
  2. psql (11.7)
  3. Type "help" for help.
  4. postgres=# show shared_buffers;
  5. shared_buffers
  6. ----------------
  7. 32GB
  8. (1 row)

我们有时也会用ps aux查看进程的内存,如下:

  1. [root@pg01 ~]# ps aux --sort=-rss |head -10
  2. USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
  3. postgres 45286 0.0 25.8 35072428 33997064 ? Ss 2020 333:30 postgres: checkpointer
  4. postgres 45287 0.0 8.3 35067396 10929444 ? Ss 2020 49:22 postgres: background writer
  5. postgres 23784 30.4 0.8 35091120 1118520 ? Rs 21:42 4:13 postgres: inofa greenerp2018 192.168.2.193(62975) SELECT
  6. postgres 24074 41.8 0.8 35091636 1095144 ? Rs 21:47 4:04 postgres: inofa greenerp2018 192.168.2.193(63056) SELECT
  7. postgres 24737 38.2 0.8 35090844 1090260 ? Rs 21:54 0:51 postgres: inofa greenerp2018 192.168.2.193(63202) SELECT
  8. postgres 24837 70.3 0.8 35091552 1078752 ? Rs 21:55 1:03 postgres: inofa greenerp2018 192.168.2.193(63223) SELECT
  9. postgres 24368 44.3 0.8 35093552 1064208 ? Rs 21:50 2:50 postgres: inofa greenerp2018 192.168.2.193(63124) SELECT
  10. postgres 24835 52.3 0.8 35093740 1057660 ? Rs 21:55 0:47 postgres: inofa greenerp2018 192.168.2.193(63222) SELECT
  11. postgres 45282 0.0 0.8 35064744 1054188 ? S 2020 84:47 /usr/pgsql-11.7/bin/postgres -D /data/ssd_pg_data/sale_pgdata_11

注意上图中的列VSZ是虚拟内存并不是实际占用的内存,RSS虽然是实际的内存,但是包括了部分共享内存。而top命令中看到进程内存的百分比就是RSS内存占用总内存的百分比。

这里说RSS占用了部分共享内存而不是全部的共享内存,这又是为什么呢?原因是当进程访问共享内存中的部分缓存块时,这是操作系统会把这部分缓存块的共享内存的地址映射到此进程空间,这时此进程RSS中只记录这部分映射的共享内存的空间大小,而不是所有的共享内存空间大小。所以当一个数据库服务进程不断的访问缓存块时,这时有更多的共享内存的缓存块映射到服务进程中,这时服务进程的RSS会不断的增加,当然最大增加到所有的共享内存都映射到服务进程中。所以RSS的不断增大,可能并不是占用了更多的内存,而是更多的共享内存映射到了此服务进程中而已。

所以查看PostgreSQL进程占用的内存,正确的姿势应该是把共享的那部分内存给扣除掉,但是用ps命令没有办法扣除掉这部分内存。要想扣除掉这部分内存,需要查看/proc//smaps文件,查看Pss内存,Pss表示的内存是把共享内存平分到各个进程上的内存,这实际上是这个进程占用的实际内存:

  1. [root@pg01 ~]# cat /proc/12047/smaps |more
  2. 00400000-00ad1000 r-xp 00000000 fd:01 42123205 /usr/pgsql-11.7/bin/postgres
  3. Size: 6980 kB
  4. Rss: 528 kB
  5. Pss: 67 kB
  6. Shared_Clean: 512 kB
  7. Shared_Dirty: 0 kB
  8. Private_Clean: 16 kB
  9. Private_Dirty: 0 kB
  10. Referenced: 528 kB
  11. Anonymous: 0 kB
  12. AnonHugePages: 0 kB
  13. Swap: 0 kB
  14. KernelPageSize: 4 kB
  15. MMUPageSize: 4 kB
  16. Locked: 0 kB
  17. VmFlags: rd ex mr mw me dw sd
  18. 00cd0000-00cd1000 r--p 006d0000 fd:01 42123205 /usr/pgsql-11.7/bin/postgres
  19. Size: 4 kB
  20. Rss: 4 kB
  21. Pss: 0 kB
  22. Shared_Clean: 0 kB
  23. Shared_Dirty: 4 kB
  24. Private_Clean: 0 kB
  25. Private_Dirty: 0 kB
  26. ...
  27. ...
  28. ...

这个smaps看到的内存非常详细,有很多项,如果我们想查询出哪些进程占用内存多,不是很方便。所以在很多的操作系统中包括CentOS7.X下提供了命令smem,可以方便的查看Pss内存。

使用smem查看进程占用的内存

smem工具可以方便的查看Pss内存,这个工具实际是一个python脚本,通过读取/proc/XXX/smaps中的信息来显示Pss内存。

smem工具一般默认都没有安装上,这个包是在EPEL扩展包中,所以需要先安装epel扩展包:

  1. yum install epel-release

然后在安装smem:

  1. yum install smem

用smem命令查看占用内存最多的前20个进程:

  1. [root@PG02 ~]# smem -t -r | head -20
  2. PID User Command Swap USS PSS RSS
  3. 12035 postgres postgres: startup recover 0 120800 5012485 10186640
  4. 12047 postgres postgres: checkpointer 0 104684 4995433 10115820
  5. 12032 postgres /usr/pgsql-11.7/bin/postgre 0 510344 664110 1054460
  6. 28643 postgres postgres: inofa greenerp201 0 433236 512166 773388
  7. 27838 postgres postgres: inofa greenerp201 0 19568 282248 914964
  8. 27462 postgres postgres: inofa greenerp201 0 19820 279398 913364
  9. 27913 postgres postgres: inofa greenerp201 0 10812 271796 906096
  10. 12048 postgres postgres: background writer 0 168 68658 264096
  11. 27139 postgres postgres: inofa greenerp201 0 26880 67974 229700
  12. 27732 postgres postgres: inofa greenerp201 0 14272 36483 104760
  13. 27264 postgres postgres: inofa greenerp201 0 15512 29736 90616
  14. 28234 postgres postgres: inofa greenerp201 0 13904 23913 66956
  15. 27861 postgres postgres: inofa greenerp201 0 7216 21375 61788
  16. 27287 postgres postgres: inofa greenerp201 0 9236 18343 67220
  17. 28654 postgres postgres: inofa greenerp201 0 8124 15647 70764
  18. 493 root /usr/lib/systemd/systemd-jo 0 5760 13507 27548
  19. 1211 root ../pyenv/bin/python clup_ag 0 13424 13469 14524
  20. 780 root /sbin/dhclient -d -q -sf /u 0 12484 12680 14824
  21. 887 postgres postgres: inofa greenerp201 0 10104 12438 35852

上面列中PSS是把共享内存均摊到进程后每个进程占用的内存,而USS列则完全不包括共享内存之外占用的内存,所以有时看USS列也比较准。

最后把几类内存的名词解释一下:

3. 总结

对于基于共享内存的多进程数据库,查看各个进程占用的内存应该看PSS或完全不包含共享内存的USS。这个方法不仅适合PostgreSQL数据库,也适合Oracle数据库,因为他们都是基于共享内存的多进程数据库。