原文链接:https://www.depesz.com/2012/06/09/how-much-ram-is-postgresql-using/,译者(guobo507)水平有限,不足之处,请将就看吧,哈哈。文中所有命令及输出,全部照搬原文,看官请各自验证,轻喷。
免责声明:本文中涉及的所有数据和示例都在 Linux 上,类似的数据也可能在其他系统上通过相应的方法获取,只是我通常在 Linux 上工作,对其他系统不是十分了解。
大家可能会遇到这样的一个问题,PostgreSQL 有时会使用了很多的系统内存。为什么会这样呢?又如何缓解呢?
在我们进行必要的“优化”之前,我们应该真正的了解问题所在。但是我们并不一定能真正了解问题所在,因为包括ps
和top
在内的两个标准系统工具报告的信息都不一定准确,这到底是为什么呢?让我们来细细了解。
我的桌面系统上运行了一个非常简单的 PostgreSQL 实例,配置了 4 GB 的共享缓冲区和 100MB 的work_mem
。并且,我确实运行了一些工作负载,现在我通过ps
工具观察到了下面的输出:
=$ ps -u pgdba uf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
pgdba 32324 0.0 0.0 79788 1900 ? S 14:26 0:00 sshd: pgdba@pts/13
pgdba 32325 0.0 0.0 25844 5788 pts/13 Ss+ 14:26 0:00 \_ -bash
pgdba 27502 0.0 0.8 4344112 109724 ? S 14:18 0:00 /home/pgdba/work/bin/postgres
pgdba 27506 0.0 0.0 24792 620 ? Ss 14:18 0:00 \_ postgres: logger process
pgdba 27508 1.5 34.7 4346688 4274752 ? Ss 14:18 0:14 \_ postgres: checkpointer process
pgdba 27509 0.2 12.1 4346164 1495780 ? Ss 14:18 0:02 \_ postgres: writer process
pgdba 27510 0.3 0.1 4346164 17292 ? Ss 14:18 0:03 \_ postgres: wal writer process
pgdba 27511 0.0 0.0 4347168 2408 ? Ss 14:18 0:00 \_ postgres: autovacuum launcher process
pgdba 27512 0.0 0.0 26888 856 ? Ss 14:18 0:00 \_ postgres: archiver process last was 00000001000000060000004D
pgdba 27513 0.0 0.0 27184 1160 ? Ss 14:18 0:00 \_ postgres: stats collector process
pgdba 27713 5.6 34.8 4347268 4285716 ? Ss 14:19 0:51 \_ postgres: depesz depesz [local] idle
pgdba 27722 2.6 3.1 4347412 392704 ? Ss 14:19 0:23 \_ postgres: depesz depesz [local] idle
pgdba 27726 15.8 35.0 4352560 4309776 ? Ss 14:19 2:25 \_ postgres: depesz depesz [local] idle
同时,top
命令也报告了大致相同的结果。
结果非常的可疑,因为free
命令显示了下面的结果:
=$ free
total used free shared buffers cached
Mem: 12296140 12144356 151784 0 26828 10644460
-/+ buffers/cache: 1473068 10823072
Swap: 0 0 0
即,目前仅仅使用了 1.5GB 的内存(磁盘缓存用了 10GB 内存,但如果有任何应用需要更多内存,这一部分将被随时释放)。
那么,我怎么会在ps
命令中看到那么庞大的数字呢?
首先,我们需要忽略 VSZ 列。重要的是 RSS。但是它却没有什么实际的意义:
=$ ps -u pgdba o pid,rss:8,cmd | awk 'NR>1 {A+=$2} {print} END{print "Total RSS: " A}'
PID RSS CMD
27502 109724 /home/pgdba/work/bin/postgres
27506 620 postgres: logger process
27508 4274752 postgres: checkpointer process
27509 1755420 postgres: writer process
27510 17292 postgres: wal writer process
27511 2408 postgres: autovacuum launcher process
27512 856 postgres: archiver process last was 00000001000000060000004D
27513 1160 postgres: stats collector process
27713 4285716 postgres: depesz depesz [local] idle
27722 392700 postgres: depesz depesz [local] idle
27726 4309776 postgres: depesz depesz [local] idle
32324 1900 sshd: pgdba@pts/13
32325 5788 -bash
Total RSS: 15158112
从上可知,所有 PostgreSQ 进程的 RSS 列之和为 15GB,比我在此机器上的总的物理内存还多。那么,真正的内存使用量是多少呢?假如我杀死 PG 服务会释放多少内存呢?
幸运的是,在 Linux 系统中,通过/proc
文件系统中的filesystem/mountpoint/fairy
,我们可以查看 RAM 的确切用途。
Linux 上的每个进程在/proc
中都有一个目录。在此目录中,有许多文件和目录。它们都显示为“0”字节大小,但是请不要被所报告的文件大小所迷惑,但它们确实包含了许多信息,这很神奇。
我们感兴趣的一个文件是“smaps”,它的内容看起来像这样:
=$ sudo head -n 20 /proc/27713/smaps
00400000-00914000 r-xp 00000000 09:00 3545633 /home/pgdba/work/bin/postgres
Size: 5200 kB
Rss: 964 kB
Pss: 214 kB
Shared_Clean: 964 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 964 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
00b13000-00b14000 r--p 00513000 09:00 3545633 /home/pgdba/work/bin/postgres
Size: 4 kB
Rss: 4 kB
Pss: 0 kB
Shared_Clean: 0 kB
...
对于此特定的进程,smaps 文件具有 2000 行以上的内容,因此我将不向您显示所有内容。
不管怎样,通过ps
命令的报告,ID 为 27713 的进程使用了 4285716 KB 的 RAM。那么,它为什么这么大?使用grep
工具,我们看到:
=$ sudo grep -B1 -E '^Size: *[0-9]{6}' /proc/27713/smaps
7fde8dacc000-7fdf952d6000 rw-s 00000000 00:04 232882235 /SYSV005a5501 (deleted)
Size: 4317224 kB
只有一个“块”的大小超过了 100MB,并且它的大小非常接近该进程内存使用量的总大小。
有关这个进程内存的完整信息如下:
7fde8dacc000-7fdf952d6000 rw-s 00000000 00:04 232882235 /SYSV005a5501 (deleted)
Size: 4317224 kB
Rss: 4280924 kB
Pss: 1245734 kB
Shared_Clean: 0 kB
Shared_Dirty: 4280924 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 4280924 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
这些信息大部分都感觉比较神秘,但我们看到了几个事实:
- 它是共享内存(第一行包含 rw-s,其中“s”代表共享)
- 从输出上看(/SYSV005a5501 (deleted)),共享内存似乎是使用 mmaping 删除的文件完成该操作的,因此该内存将包含在
free
命令的“Cached”(缓存)列中,而不是在“Used”(已使用)列中。 - 共享块的大小为 4317224 KB,而共享块中的 4280924 KB 实际上驻留在了物理内存中。
因此,没有关系,那是shared_buffers
内存。但事实上,大多数后端进程都会使用共享缓冲区(shared buffers)。而且,更糟的是,它们的使用程度是不同的。例如,下面是来自进程 27722 的相同的共享缓冲区使用情况数据:
=$ sudo grep -A14 7fde8dacc000-7fdf952d6000 /proc/27722/smaps
7fde8dacc000-7fdf952d6000 rw-s 00000000 00:04 232882235 /SYSV005a5501 (deleted)
Size: 4317224 kB
Rss: 388652 kB
Pss: 95756 kB
Shared_Clean: 0 kB
Shared_Dirty: 388652 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 388652 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
在这里,我们看到这个进程仅请求/使用了 388MB 的内存。
因此计算将很复杂。例如,我们可能有两个进程,每个进程使用 400MB 的shared_buffers
,但它没有告诉我们它实际使用了多少内存,因为它们可能使用了 100MB 的相同的缓冲区和各自 300MB 的不同缓冲区,因此总的内存使用量将为 700MB。
我们确实知道此 shared_buffers
块的总大小为 4317224 KB。这很好,但是其他事情呢?例如,库文件又占用了的内存呢,要知道内核是可以在多个进程之间共享它们的。
幸运的是,在 2007 年的时候,Fengguang Wu 发布了(以前写过)一个非常酷的内核补丁,在 smaps 信息中添加了“Pss”信息。
基本上,Pss 最大可以达到 Rss,但是如果多个进程使用相同的内存页,则 Pss 会低一些。
这就是为什么上面的例子中,Pss 远远低于 Rss/Size 的原因。例如,在最后一个例子中。 Rss 为 388652 KB,而 Pss 只有 95756 KB,这意味着该后端进程使用的大多数页面也被其他 3 个后端使用。
因此,了解了 Pss 之后,我们终于可以得到正在运行的 PG 集群的实际内存使用情况了:
=$ ps -u pgdba o pid= | sed 's#.*#/proc/&/smaps#' | xargs sudo grep ^Pss: | awk '{A+=$2} END{print A}'
4329040
如果您不禁说“WTF,这 tm 是什么命令?!”,请让我来解释一下。第一个命令:
=\$ ps -u pgdba o pid=
27502
...
32325
只返回 pgdba 用户的所有进程的 pids(通常您可能需要用 postgres 用户,但我就是与众不同,我喜欢用 pgdba 的身份运行 PostgreSQL)。
第二个命令,sed
将 pids 更改为 smaps 文件的路径:
=$ ps -u pgdba o pid= | sed 's#.*#/proc/&/smaps#'
/proc/27502/smaps
...
/proc/32325/smaps
然后我对sed
命令处理后的文件中的以 Pss 字符开始的行做简单的grep
处理。可能返回很多行,例如:
/proc/32325/smaps:Pss: 0 kB
/proc/32325/smaps:Pss: 4 kB
/proc/32325/smaps:Pss: 4 kB
然后,使用awk
命令汇总第二列(即大小)。我得到的结果为 4329040,以千字节(KB)为单位。
因此,从理论上讲,如果我停止 PG 集群,我将回收大量的 RAM。让我们看看是否正确:
=$ free; pg_ctl -m IMMEDIATE stop; free
total used free shared buffers cached
Mem: 12296140 12145424 150716 0 40708 10640968
-/+ buffers/cache: 1463748 10832392
Swap: 0 0 0
waiting FOR server TO shut down.... done
server stopped
total used free shared buffers cached
Mem: 12296140 7781960 4514180 0 40856 6325092
-/+ buffers/cache: 1416012 10880128
Swap: 0 0 0
已用内存从 12145424 KB 下降到 7781960 KB,意味着我释放了 4363464 KB 的 RAM。甚至比预期的 4329040 KB 还略高一点,但是已经非常接近预估值了。而且大多数释放都来自磁盘缓存,这一点也符合我的预期,因为它用在shared_buffers
中。
一切都很好,但是可以使用这种方法来评估杀死单个后端进程后,将释放多少 RAM 吗?
是的,但也不是。关闭整个 PG 意味着可以释放它正在使用的所有共享内存。在正常环境中,当您杀死一个后端进程时,最终只能释放该后端专用的内存(Pss)。但是这部分内存通常比较小。
例如,在另一台具有令人印象深刻的硬件的机器上:
=> ps uxf | grep USER.db_name | sort -nk6 | tail -n 1 | tee >( cat - >&2) | awk '{system("cat /proc/"$2"/smaps")}' | grep ^Private | awk '{A+=$2} END{print A}'
postgres 5278 8.2 0.3 107465132 1727408 ? Ss 13:21 0:03 \_ postgres: USER db_name aa.bbb.cc.dd(eeeee) idle
52580
如上所示,该进程具有 1.7GB 的 RSS(在ps
输出中可以看到),但是其中只有 52MB 是专用内存(PSS),如果该进程被杀死,它将仅仅释放 52MB 的 PSS 内存,因为其他进程仍然可能在使用它所使用的那部分共享内存(RSS)。
So no, you can't use the Pss for this, but you can use Private_*
data from smaps to get the number.
综上所述,PostgreSQL 使用的内存比乍看之下要少得多,并且您只需要执行一些 Shell 命令/脚本,就可以获得相当准确的内存使用量数据。
现在,我准备向大家征求意见,以指出这篇文章中的所有技术错误,或者(更糟糕的是)错别字。