前言
昨天看到了一个问题,PostgreSQL如何清理Buffer Cache?。让我们来探讨一下这个问题。在Oracle数据库中清理Buffer Cache和Shared Pool有alter system的命令,而MySQL也有innodb_max_dirty_pages_pct
这样的参数可以进行控制,但在PostgreSQL中,您会发现基本上没有这些选项。
如何清理Buffer Cache
以前编写参数优化时曾发布过一个图,PostgreSQL使用自己的缓冲区,以及Linux操作系统的内核缓冲OS Cache。

因此,在清理缓存时,我们不仅要清理shared_buffer
中的数据,还要清除所有OS Cache
。在O'REILLY的网站上可以看到《PostgreSQL9.6 High Performance》提到的方法,即停止数据库,然后使用操作系统命令清除os缓存。

我们来按照这个方法测一下。为了方便观察,我们需要安装插件pg_buffercache
。
postgres=# create extension pg_buffercache;postgres=# CREATE TABLE test(id integer, name text);postgres=# INSERT INTO test SELECT i, 'name ' || i FROM generate_series(1, 100000) i;SELECT c.relname, pg_size_pretty(count(*) * 8192) as buffered, round(100.0 * count(*) / (SELECT setting FROM pg_settings WHERE name='shared_buffers')::integer, 1) AS buffer_percent, round(100.0 * count(*) * 8192 / pg_table_size(c.oid), 1) AS percent_of_relation FROM pg_class c INNER JOIN pg_buffercache b ON b.relfilenode = c.relfilenode INNER JOIN pg_database d ON (b.reldatabase = d.oid AND d.datname = current_database()) GROUP BY c.oid, c.relname ORDER BY 3 DESC LIMIT 10; relname | buffered | buffer_percent | percent_of_relation ---------------------------------+------------+----------------+--------------------- test | 4344 kB | 3.3 | 99.5 pg_statistic | 112 kB | 0.1 | 31.1 pg_operator | 112 kB | 0.1 | 77.8 pg_constraint_conparentid_index | 8192 bytes | 0.0 | 50.0 pg_amop | 48 kB | 0.0 | 60.0 pg_amproc | 32 kB | 0.0 | 50.0 pg_am | 16 kB | 0.0 | 40.0 pg_constraint | 8192 bytes | 0.0 | 16.7 pg_depend | 40 kB | 0.0 | 8.1 pg_cast | 16 kB | 0.0 | 33.3(10 rows)
你可以看到这里的test表,当前在缓冲区中,占比3.3%。
我们按照书中的方法,关闭数据库,然后清除os缓存后。
pg_ctl stopsyncecho 3 > /proc/sys/vm/drop_cachespg_ctl start
再次查看可以看到刚刚的test对象已经被清理出了缓冲区。

那么你可能会问,有没有什么别的办法,不用重启数据库也能清理Buffer Cache?
这是可以的,比如你可以下载pg_dropcache插件或者是pg_dropbuffers插件,这是两个不同的插件。但是实现原理都差不多,基本上利用了bufmgr.h
中的函数。但是这两个插件的缺点就是不支持13.2,直接编译就会报错,原因后面我会详细说明。这些插件也都基本3年没有更新了。
我先来说说这些插件实现的基本原理,清空全部内存利用了PG内置的DropDatabaseBuffers
函数。我们来看这个函数的定义。

DropDatabaseBuffers
函数将移除所有的buffer cache。但是要注意得一点是脏页只是被简单的丢弃了,没有先写入磁盘,所以这个动作的风险很大。
其次他还调用了一个函数DropRelFileNodeBuffers
按照表来清空内存。

这个函数的作用就是删除相关表所有在内存中缓冲的page,特别是firstDelBlock=0时,将删除所有页面。而这个操作也是很有风险的,也是直接丢弃脏页。
我们现在在看看它插件的代码。

这里的代码只有3个参数,而上面的有4个参数。所以编译就会失败。
extern void DropRelFileNodeBuffers(struct SMgrRelationData *smgr_reln, ForkNumber *forkNum,int nforks, BlockNumber *firstDelBlock);
PostgreSQL 13.2版本该函数的第一个入参是结构体SMgrRelationData
,SMgrRelationData
这个结构体代表了单个表的文件结构。而之前PostgrSQL版本这个函数的输入参数是RelFileNodeBackend
。
extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode,ForkNumber forkNum, BlockNumber firstDelBlock);
所以新版本编译不了。但是它的全库清理缓存是可以用的,那么我们就简单的把这个DropRelFileNodeBuffers
函数给删除,不使用单个清理的功能。所以这个代码就及其简单。

再次编译安装,这次没任何问题,这么简单的代码是不可能出问题的。

再次测试我编译的pg_dropcache,这次直接成功了。把一大堆的缓存全部清空出了buffer cache。

后记
针对这个数据库版本的问题,后续再研究一下,看看应该怎么把这个结构体传进去,自己开发一款高版本的插件。
参考链接
1.https://github.com/zilder/pg_dropcache/
2.https://maahl.net/pg_dropbuffers
3.https://www.oreilly.com/library/view/postgresql-96-high/9781784392970/b42a3be7-18cf-48e3-a51e-cb99cd075478.xhtml