PostgreSQL学徒

恼人的双缓存

Word count: 2.9kReading time: 12 min
2021/07/15
loading

PostgreSQL on Linux: what is cached?

在最近的一条推特中,我特意强调了去了解pgbench是如何进行测量的重要性,因为单看 “Transactions per second” 而不知道数据到底是在shared buffer里面命中了,或者在文件系统缓存里面命中了,或者在存储缓存里命中了,亦或是真正的发生了一次物理读,是没有任何意义的。pgbench工具自带的默认表的比例因子scale每个比例值大约15MB,主要是pgbench_accounts和对应的索引。默认的 select-only 工作负载模式会读取其中的90%的数据。

但是我也提到了文件系统缓存是这个大小的两倍,在推特中由于太过简短而无法阐述。这实际上是错误的,除了这是 free或着 /proc/meminfo 在cached指标中所反馈的。当您读取1GB数据时,您不会从文件读取中缓存2GB数据。但是,如果将shared_buffers设置的足够大,你会有1GB的文件缓存和1GB的共享内存,并且两者都会以cached所呈现,即使 free 命令也单独呈现了 shared 的部分。这就是为什么在这条推文中,我把绿色条放在黄色后面,而不是放在上面:cached includes shared

Linux的基本指标有一定的误导性,许多人在阅读它们时并不真正了解它们的测量内容。我鼓励每个人仔细阅读文档并查阅特定测试用例以加深理解。如下PostgreSQL的一个样例。

1
2
3
sudo yum install -y gcc readline-devel zlib-devel bison bison-devel flex
git clone --branch REL_13_STABLE https://github.com/postgres/postgres.git
( cd postgres && ./configure --enable-debug && make &&sudo make install && cd contrib && sudo make install )

我已经在实验环境里安装了PostgreSQL,并且是从源码进行安装的,因为我经常需要调试符号表。

1
2
git clone  https://github.com/yazgoo/linux-ftools
( cd linux-ftools/ && ./configure && make && sudo make install )

安装了ftools,它有一个有趣的 fincore 的实用工具,用于显示文件是如何映射到文件系统缓存中的

1
2
3
4
/usr/local/pgsql/bin/pg_ctl initdb -D ~/pgdata
/usr/local/pgsql/bin/pg_ctl -D ~/pgdata -l pglog start
/usr/local/pgsql/bin/pgbench -i -s 42 postgres
/usr/local/pgsql/bin/psql postgres <<<" create extension pg_prewarm; "

我创建了一个PostgreSQL数据库,初始化了一个pgbench模式,比例因子是42(大概630 MB),同时还安装了pg_prewarm扩展来快速将表加载进缓存中。

1
2
3
4
5
6
[opc@instance-20210203-1009 ~]$ df -h pgdata
652M pgdata/base

[opc@instance-20210203-1009 ~]$ grep ^shared pgdata/postgresql.conf
shared_buffers = 900MB
shared_memory_type = mmap

对于shared buffers我分配了900MB。

有个重点:此处我没有分配大页huge pages。所以使用free命令显示的指标都是关于小页small pages的。在大页中分配的shared buffers不会被计算到 shared这一指标当中。我之前有一篇关于这个的博客

1
2
3
4
5
6
7
8
9
10
[opc@instance-20210203-1009 ~]$ /usr/local/pgsql/bin/pg_ctl -D ~/pgdata -l pglog stop
waiting for server to shut down.... done
server stopped

[opc@instance-20210203-1009 ~]$ sudo su << "/proc/sys/vm/drop_caches"'

[opc@instance-20210203-1009 ~]$ free -mw
total used free shared buffers cache available
Mem: 14714 3443 11061 33 0 209 10989
Swap: 8191 3061 5130

停止实例后,并且 sync’d 和刷盘了,我在这个系统中几乎没有使用到共享内存,缓存了209M,空闲10GB。

启动实例后,没有发生任何改变。因为Linux对于SHM使用的是延迟分配:使用时才分配共享缓冲区,所以此处只有很少一部分其他区域使用到的共享内存。

1
2
3
4
5
6
7
8
9
10
11
[opc@instance-20210203-1009 ~]$ /usr/local/pgsql/bin/psql postgres <<<" select pg_size_pretty(current_setting('block_size')::int*pg_prewarm('pgbench_accounts','buffer')); "

pg_size_pretty
----------------
538 MB
(1 row)

[opc@instance-20210203-1009 ~]$ free -mw
total used free shared buffers cache available
Mem: 14714 3448 9917 611 0 1348 10378
Swap: 8191 3061 5130

我加载了大小为538MB的pgbench_accounts表。它首先会被读到文件系统缓存,cache会增加538MB,然后从文件系统缓存那里复制到共享缓冲区。由于它适合这里,所以共享缓存的大小现在从 72MB 提升到了 611MB 。然而,由于cache 包括 share,所以cache列显示为1.3G,但更有趣的数字是available,减少了562MB,因为共享缓冲区不能回收,但文件缓存可以,如果需要物理内存的话,文件缓存一旦在同步之后便可以被回收。

这就是PostgreSQL的特殊性。在用户空间中分配了一个共享内存段,以管理对页面的并发写操作,并从这个共享缓冲区缓存中读取这些页,用于频繁的读取数据。但是,与大多数 RDBMS 相反,这里的 RDBMS 代码没有做进一步的优化 (比如读取时的预取,写入时的重新排序)。这意味着我们必须为文件系统缓存提供足够的可用空间,上述的那些优化会在内核空间中进行。不适合放在shared_buffers里的频繁读取的数据应该会缓存在文件系统缓存那里。在检查点时,写入共享缓冲区的页面应该是文件系统缓存命中And the pages written to the shared_buffer should be filesystem cache hits at checkpoint time.。这便是PostgreSQL的双缓冲,在cache里面是可见的,并且两者都会被计算到cache

为了获得更好的性能可预测性,理解文件系统缓存中的内容是非常重要的,有一个实用工具可以给我们提供帮助。我在上面安装了linux-ftools,下面是linux-fincore来获取缓存文件的大致情况

1
2
3
[opc@instance-20210203-1009 ~]$ linux-fincore -s $(find pgdata/base -type f) | grep ^total

total cached size: 565,534,720

这是我的 539MB 的数据。不幸的是,您需要传递想要检查的文件列表。这就是为什么我开始只查看总数,以确保我的文件集代表了我在文件系统缓存中找到的内容。在这里,PGDATA目录下的所有文件中,我有539MB的缓存,我知道这是我已经读取的大小。

1
2
3
4
5
6
7
8
[opc@instance-20210203-1009 ~]$ linux-fincore -s -S 1048576  $(find pgdata/base -type f)

filename size total_pages min_cached page cached_pages cached_size cached_perc
-------- ---- ----------- --------------- ------------ ----------- -----------
pgdata/base/13581/16438 564,043,776 137,706 0 137,706 564,043,776 100.00
pgdata/base/13581/16446 94,363,648 23,038 -1 0 0 0.00
---
total cached size: 564,043,776

我在这里展示了细节,只针对cache中超过1MB的文件。当然,这可以与数据目录中的文件路径连接起来

1
2
3
4
5
6
7
8
9
10
[opc@instance-20210203-1009 ~]$ \
/usr/local/pgsql/bin/psql postgres <<<" select relname,relkind,current_setting('block_size')::int*relpages/1024/1024 as size_MB,current_setting('block_size')::int*buffers/1024/1024 as shared_mb,relfilenode,current_setting('data_directory')||'/'||pg_relation_filepath(oid) as file_path from pg_class c left outer join (select relfilenode, count(*) as buffers from pg_buffercache group by relfilenode) b using(relfilenode) where relpages>100;" | awk '/[/]base[/]/{"linux-fincore -s "$NF"* | grep ^total | cut -d: -f2" | getline f;printf "%6dM %s\n",gensub(/,/,"","g",f)/1024/1024,$0;next}{printf "%6s %s\n","",$0}'


relname | relkind | size_mb | shared_mb | relfilenode | file_path
-----------------------+---------+---------+-----------+-------------+-----------------------------------
537M pgbench_accounts | r | 537 | 537 | 16438 | /home/opc/pgdata/base/13581/16438
0M pgbench_accounts_pkey | i | 89 | | 16446 | /home/opc/pgdata/base/13581/16446

(2 rows)

这是一个快速而粗略的一行程序,用于获取文件系统中的大小,以及超过100页的对象的总大小和共享缓冲区中的大小(多亏了pg_buffercache扩展)。

1
2
3
4
5
6
[opc@instance-20210203-1009 ~]$ sudo su << "/proc/sys/vm/drop_caches"'    

[opc@instance-20210203-1009 ~]$ free -mw
total used free shared buffers cache available
Mem: 14714 3478 10438 612 0 797 10370
Swap: 8191 3040 5151

我刷新了缓存(注意在生产中不要这样做!),只剩下共享内存(大部分)

1
2
3
4
5
6
[opc@instance-20210203-1009 ~]$ /usr/local/pgsql/bin/pg_ctl -D ~/pgdata -l pglog stop                                                                         waiting for server to shut down.... done
server stopped
[opc@instance-20210203-1009 ~]$ free -mw
total used free shared buffers cache available
Mem: 14714 3477 11009 34 0 227 10945
Swap: 8191 3040 5151

当停止PostgreSQL实例时,共享内存会被释放。

您可能想知道仍然在缓存中的227MB是什么。这个实验环境里有很多东西在运行,但让我们检查一下:

1
2
3
4
5
6
7
8
9
10
11
12
[opc@instance-20210203-1009 ~]$ find / -type f -exec linux-fincore {} \; 2>/dev/null |  awk '/^[/]/{gsub(/,/,"");m=$(NF-1)/1024/1024;gsub(/  */," ");if(m>1)printf "%10d MB %s\n",m,$0}' | sort -h | tee all.log | sort -n | tail -10

73 MB /var/cache/yum/x86_64/7Server/ol7_developer_EPEL/gen/primary_db.sqlite 76955648 18788 0 18788 76955648 100.00
75 MB /home/opc/simulator_19.9.0.0.0/portainer_image.tar 78691328 19212 0 19212 78692352 100.00
83 MB /usr/bin/dockerd 87196536 21289 0 21289 87199744 100.00
93 MB /home/opc/simulator_19.9.0.0.0/jre/lib/amd64/libjfxwebkit.so 97667680 23845 0 23845 97669120 100.00
102 MB /usr/lib/locale/locale-archive 107680160 26290 0 26290 107683840 100.00
104 MB /var/lib/rpm/Packages 109428736 26716 0 26716 109428736 100.00
189 MB /var/cache/yum/x86_64/7Server/ol7_latest/gen/primary_db.sqlite 198237184 48398 0 48398 198238208 100.00
224 MB /home/opc/simulator_19.9.0.0.0/oda_simulator.tar.gz 235565495 57512 0 57512 235569152 100.00
229 MB /home/opc/simulator_19.9.0.0.0/oraclelinux_image.tar 240947200 58825 0 58825 240947200 100.00
486 MB /home/opc/pgdata/base/13581/16438 564043776 137706 1969 124652 510574592 90.52

这花了一段时间,但显示了缓存中的文件。那些 .tar 和 .tar.gz 似乎与我之前关于 ODA Simulator 的博文有关,并且和docker绑定挂在。 但是,lsof 和 fuser 告诉我它们没有被使用。我没有花太多时间在这上面,但我不知道如何将它们从缓存中逐出。

然而,回到主题,现在理解了Cache指标,这是我使用 pgbench 进行的初始测试:

所有操作都在shared_buffers=4GB的情况下运行的。橙色和绿色区域的值是pgbench运行后从free -wm中获取的值的大小增长情况(以MB为单位)。当scale小于70时,pg_bench数据库的大小是1054MB(黄色),我没有看到额外分配的SHM(橙色),文件系统“缓存”(绿色)随着从文件中读取的大小而增加。然后,当scale增加到270时,SHM开始分配内存以适应shared_buffers(橙色)中的这些页面。“cache”(绿色)包括SHM和文件系统的缓存。超过280,SHM不会再变高了,因为我们达到了shared_buffer设置的大小4GB。在操作系统级别仍然有空闲内存,然后“cache”会随着读取数据的大小而增加。我还添加了性能指标“tps”——每秒的事务量。在shared_buffers的范围内运行这个select-only的工作负载会显示出稳定的性能(在单线程中约为13500 tps)。当需要从文件系统缓存中拷贝出一些页面时,性能会缓慢降低。当到达超出物理RAM容量的范围时,就会涉及到物理读,tps就会降低很多。这里有14GB的RAM,当scale达到了570,除了4GB的shared_buffers外,还有8GB用于频繁读取数据。

以上说明了PostgreSQL双缓存的行为。为了获取最大性能,你需要了解需要频繁读取的数据。以及物理内存中可用的额外大小,以避免I/O延迟。这仅仅是select-only模式下的工作负载情况。现在假设有10%的数据被更新了…shared_buffers中新的元组,新的页面,在检查点后写入文件缓存和sync,还有通常开启了全页写的WAL也在那里。你可能需要更多的内存。所以在不知道这些大小的情况下查看pgbench结果是没有意义的。并且查看操作系统参数是必须的,因为理解并不仅仅是从名字中猜测。

原文链接:https://blog.dbi-services.com/postgresql-on-linux-what-is-cached/

CATALOG
  1. 1. PostgreSQL on Linux: what is cached?