从一道PG知识的选择题谈起,你悟到了些什么?
创始人
2025-07-09 17:11:27
0

昨天一个网友问我一道关于PG的选择题:Postgresql数据库中哪些进程可以将shared buffers中的脏数据回写到数据文件?A) BACKEND B) BGWRITER C)CHECKPOINTER D) WALWRITER。

稍微懂点PG数据库的人不难回答,答案是A、B、C。一些Oracle DBA可能会觉得这个答案有点出乎意料。因为在Oracle数据库中,回写DB CACHE脏数据的只有DBWR。可能这些人不太清楚的CKPT负责回写部分脏数据是80年代早期关系型数据库的共同特点,Oracle数据库中,CKPT也曾经负责过写脏块。后来随着数据库规模的增大,CKPT的功能被独立出来了,只负责CKPT的推进工作,不再负责写脏块了。在Oracle数据库中,为了更快的将脏块回写,通过将DB CACHE分区,采用多个DBWR进程可以并发写脏块,从而满足大型数据库的需要。

PG在刷脏块的算法方面的设计比较传统,这种设计是从90年代一脉相承的,改起来成本较大,所以就一直沿用下来了。只不过让backend也去写脏块,这未免也有点太出乎一般人的想象了。这会产生几个比较奇怪的现象,一个是某条SQL语句,可能在执行计划相同、数据量相同的情况下,不同的时间执行,其执行效率有较大的不同。另外一个奇怪现象是某条SELECT语句可能会产生较多的写操作。实际上Oracle数据库中也会存在类似的现象,这种情况是由延迟块清理产生的。当一个大型事务结束后,ITL的状态清理可能并没有完成,如果马上有SELECT操作访问这些数据,那么执行SELECT的前台进程将负责完成这些块的清理,这时候select操作也会产生大量的写操作,产生大量的REDO,同时这条SELECT比起平时会慢很多。    

图片图片

通过pg_stat_bgwriter系统表中的buffers_backend和buffers_backend_fsync可以查询到系统中的backend写脏块和fsync的计数。

图片图片

从我们的一个D-SMART的PG数据库中看到一个十分神奇的情况,按理说被用来写脏块的bgwriter居然很小(buffers_clean),大多数脏块都是checkpointer和backend写的 。如果你去检查一下你们的PG数据库,可能bgwriter写脏块的比例也不是很高。

为什么会出现这种情况,为什么大量的写脏块工作不由本应该处理脏块的 bgwriter去做,而让Backend去写脏块呢?这方面的资料很少,我们只能从 PG的代码上去找找答案。首先我们看一下src/backend/storage/bufmgr.c,在这里可以看到backend从shared buffers中分配空闲buffer的算法。一般我们都能理解从shared buffers中查找空闲的buffer,如果找不到非脏的空闲块,那么就有可能找到一个存储了脏数据的数据块,这时候才需要backend去写脏块。在Oracle数据库中,前台进程是顺着lru链的冷端去查找空闲缓冲块的,如果前台进程发现了某个没有被Pin住的块是脏块,就会把这个数据块移到lru-w中,然后继续往下搜索,如果连续搜素到了N个脏块,无法获得所需要的空闲块的时候,就会发出一个free buffer requests的事件,让DBWR加快刷脏块,然后再去重试。从PG的代码上看,PG的大体思想类似,不过策略要复杂得多。    

图片图片

BufferAlloc里包含了backend查找某个buffer的顶层逻辑。不阅读一下还真没发现PG这方面的代码逻辑会搞得如此复杂。要想访问某个buffer,先要生成一个BUFFER TAG(关于这方面的详细算法请参考我2021年写过的一篇8000多字的长篇《PG SHARED BUFFER POOL的优化》)。然后查找这个BUFFER。    

图片图片

如果BUFFER存在,还有两种可能性,一种是成功的PIN住了这个BUFFER,那么就可以返回这个BUFFER了。不过BUFFER存在还有一种可能性是无法PIN住,无法PIN住的原因是可能被其他的会话PIN住了,也可能是一些其他的原因。这种情况,Oracle被称为read by other session或者buffer busy waits。这个部分不是我们今天分析的重点,我们继续往下看代码。    

图片图片

GetVictimBuffer函数通过时钟扫描算法去找一个空闲的buffer。这里就涉及到查找空闲shared buffers了。我们下钻到这个函数的代码中去继续分析。

图片图片

首先调用StrategyGetBuffer去找一个BUFFER。    

图片图片

如果发现找到的是一个脏块,那么就把脏块刷盘,这就是BACKEND也需要刷脏块的原因之一。作为数据库缓冲的算法,我们肯定应该尽可能的找到非脏块来复用,总是让BACKEND写脏块肯定会降低数据库的整体性能。    

StrategyGetBuffer函数在src/backend/buffer/freelist.c中定义。首先,它会检查是否有一个策略对象(strategy),如果有,就调用GetBufferFromRing函数,从策略对象的环形缓冲区(ring buffer)中获取一个缓冲区。如果获取成功,就返回这个缓冲区,并设置from_ring标志为true。如果没能正常找到free buffer,它会尝试唤醒bgwriter,让它刷新脏的缓冲区到磁盘,以便释放一些空间。接下来,backend会检查StrategyControl->firstFreeBuffer变量,如果大于等于0,就表示有空闲的缓冲区,那么就通过一个循环从空闲链表中获取一个缓冲区。这部分算法与Oracle的free buffer requests十分类似。

此时如果空闲链表为空,backend会进入另一个循环,尝试从victim pool中选择一个缓冲区,victim pool是一个循环队列,存储了最近被访问过的缓冲区的编号。从nextVictimBuffer的当前位置开始,顺时针扫描victim pool,寻找一个既不被锁定,也没有被引用,也不是脏的缓冲区。如果找到了,就返回这个缓冲区,并将其从victim pool中移除。如果没有找到合适的缓冲区,它会继续扫描victim pool,寻找一个既不被锁定,也没有被引用,但是是脏块的缓冲区。如果找到了,就将这个缓冲区的内容写入磁盘,然后返回这个缓冲区(这是代码中backend中另外一个写脏块的地方),并将其从victim pool中移除,并添加到策略对象的环形缓冲区中。

我们先不去吐槽PG在这块代码的质量问题,仅仅从算法来看,backend直接刷脏块的机会也应该是比较小的。那么为什么我们会在pg_stat_bgwriter中看到如此奇葩的数据呢?这里面其实也有一个十分有意思的逻辑。

首先是关于buffers_backend这个指标,本身这个指标就有一定的误导性,我们今天看的代码上包含了处写脏块的地方,其实不用看代码我们都能想到第三处,那就是VACUUM。因为backend中还包含了auto vacuum,vacuum操作等写脏块的统计数据,因此我们可能会被这个指标误导。PG社区中十多年前就有人希望PG代码中把这些情况区分开来,从而让buffers_clean更有指向性,不过没有获得PG社区核心研发的认同。    

另外一点是PG数据库的策略是尽可能让数据在内存中多存放一段时间,而不急着把脏数据写盘。因此在PG数据库中,还是将检查点进程作为写脏块的主力,如果你的系统中的buffers_alloc增长很缓慢的话,那么只要按照checkpointer的节奏慢慢写脏块就可以了,backend总是能够找到所需要的buffer,因此也就没必要让bgwriter去写脏块了。这种算法对于早些年比较缓慢的IO子系统来说是十分友好的,不过对于当今高性能的IO系统来说,不够高效,比较适合目前IO性能一般的云上小型数据库,而对于采用高性能IO设备的大型数据库来说,并不一定是很优化的。

基于上面的分析我们可以了解到,如果你看到你的系统中的buffers_clean总是为0或者总是慢速增长,那么并不说明系统存在问题,而是说明你的系统写负载还不算太高,bgwriter还犯不着去帮你刷盘而已。对于IO性能还不错的系统,或者说规模不算太大的数据库来说,PG的这种刷脏块的方法还是可以胜任的,在一些超大型系统中,可能这方面会成为瓶颈。我看到Polardb-PG、openGauss等基于PG代码的数据库产品中,对这方面都做了一些优化,引入了专门的机制来替换BGWRITER。目前还没有对这些代码进行分析,因此不知道这方面的改善如何。

今天分析PG这方面源代码的另外一个收获是从中学到一些PG的SHARED BUFFERS相关的优化策略的。首先shared buffers不能设置得太少,否则backend真正开始大量刷脏块了,那么SQL的性能是会受到很大的影响的。其次是CHECKPOINTER的相关参数设置要合理,根据底层IO的能力配置合适的参数,让CHECKPOINTER刷盘的速度能够跟得上buffers_alloc的速度。如果我们发现buffers_clean的增长比较快了,那么说明目前系统的负载对shared buffers 有一定的压力了,那么我们就需要考虑调整bgwriter相关的参数了。       

最后的源码链接是我两年多前写的一篇关于PG SHARED BUFFERS的内部结构的分析文章,文章很长,有8000多字,有兴趣的朋友可以阅读一下。文中有些观点可能和今天的文章有些不大一致了,如果存在这方面的观点,那么就以今天的文章为准吧。对PG数据库的理解都是一点一点的从模糊到清晰,从不大准确到相对准确的。认知的提升是从一个个案例,一段段源码的分析中逐渐完成的。

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
P2P的自白|我不生产内容,我... 现在一提起P2P,人们就会联想到正在被有关部门“围剿”的互联网理财服务。×租宝事件使得劳...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...