2.5.2 BgWriter后台写进程
BgWriter是PostgreSQL中在后台将脏页写出到磁盘的辅助进程,引入该进程主要为达到如下两个目的:首先,数据库在进行查询处理时若发现要读取的数据不在缓冲区中时要先从磁盘中读入要读取的数据所在的页面,此时如果缓冲区已满,则需要先选择部分缓冲区中的页面替换出去。如果被替换的页面没有被修改过,那么可以直接丢弃;但如果要被替换的页已被修改,则必需先将这页写出到磁盘中后才能替换,这样数据库的查询处理就会被阻塞。通过使用BgWriter定期写出缓冲区中的部分脏页到磁盘中,为缓冲区腾出空间,就可以降低查询处理被阻塞的可能性。其次,PostgreSQL在定期作检查点时需要把所有脏页写出到磁盘,通过BgWriter预先写出一些脏页,可以减少设置检查点(CheckPoint,数据库恢复技术的一种)时要进行的IO操作,使系统的IO负载趋向平稳。通过BgWriter对共享缓冲区写操作的统一管理,避免了其他服务进程在需要读入新的页面到共享缓冲区时,不得不将之前修改过的页面写出到磁盘的操作。不过,当BgWriter无法维护足够的干净共享缓冲区时,其他服务进程仍然可以自行完成将脏页写回磁盘的操作。BgWriter同时也负责处理所有的检查点,它也会定期地发出一个检查点请求,当然也可以由其他进程通过信号要求BgWriter执行一个检查点。
BgWriter是PostgreSQL 8.0以后新加的特性,但在8.2以前版本中,使用BgWriter需要管理员进行很复杂的配置。在PostgreSQL 8.4中,数据库配置文件postgresql.conf中与BgWriter相关的配置选项有3个:bgwriter_delay、bgwriter_lru_maxpages、bgwriter_lru_multiplier。系统每隔bgwriter_delay指定的时间启动BgWriter。BgWriter从后向前扫描缓冲区的LRU链表,写出至多bgwriter_lru_multiplier*N个脏页,并且不超过bgwriter_lru_maxpages值的限制。其中N是最近一段时间在两次BgWriter运行期间系统新申请的缓冲页数。在BgWriter参数的配置中,如果BgWriter过于频繁地将脏页写出,则经常被更新的数据页很可能会被一次又一次地写出到磁盘上,反而增加了数据库的IO次数,进而导致系统性能下降。另一方面,若BgWriter写周期过长,又不能起到优化数据库写IO操作的作用。因此确定BgWriter以什么速率将脏页写出才能达到最佳效果需要综合考虑系统的实际运行状态,默认将bgwriter_delay设置为200毫秒,bgwriter_lru_maxpages设置为100,bgwriter_lru_multiplier设置为2.0。
BgWriter辅助进程在Postmaster中启动,入口为StartBackgroundWriter函数,BgWriter和WalWriter两个辅助进程采用相同的模式启动。BgWriter实际的工作函数是BackgroundWriterMain。其处理流程如图2-8所示。
图2-8 BgWriter处理流程 |
1)变量初始化:定义局部变量,完成部分全局变量的赋值操作,记录进程PID。
2)注册信号处理函数:SIGHUP信号,在信号响应函数中设置got_SIGHUP=true;SIGINT信号,在信号响应函数中设置checkpoint_requested=true;信号SIGUSR2,在信号响应函数中设置shutdown_requested=true;最后注册SIGQUIT用于快速退出处理。在后续的其他辅助进程中,信号的处理方式与此相同,都是在响应的信号处理函数中设置对应的标志变量为true。在循环处理中根据标志变量的取值来执行相应的处理操作。
3)运行环境初始化:通过ResourceOwnerCreate函数创建一个名称为“Background Writer”的资源跟踪器(见3.6节)。然后为BgWriter创建运行内存上下文,并将运行环境切换到新创建的内存上下文中。
4)注册异常处理:结合系统调用setjmp和longjmp完成异常处理,实现进程的错误处理过程。在setjmp设置的异常处理结构中,会向系统日志中写错误信息、清理正在运行的缓冲区I/O、释放初始化中创建的资源跟踪器,同时清理事务相关资源、更新ckpt状态标识checkpoint执行状态。最后清理ErrorContext并重置BgWriter内存上下文、关闭所有存在的SmgrRelation对象。使用setjmp和longjump是C语言编程中常用的一种错误恢复方法,有兴趣的读者可以参考相关的文献。
5)处理写磁盘请求:调用AbsorbFsyncRequests函数,处理fsync请求(内存中的数据刷新到磁盘文件中)队列,将请求发送到本地的SMGR(见第3章),该函数必须在执行检查点创建工作时执行。
6)处理信号分支:在每次循环中检测信号对应的标志变量进行相应的处理。got_SIGHUP代表重新读取配置文件;shutdown_requested表示关闭数据库并退出BgWriter;checkpoint_requested表明有创建检查点请求,设置do_checkpoint标志,将检查点请求计数器加1。
7)创建检查点:如果没有创建检查点请求或者创建请求点时间间隔未到,则调用函数BgBufferSync按照BgWriter相关的配置参数把“脏”缓冲块刷回磁盘中。否则执行创建检查点操作。检查点的创建分为检查点类型设置、执行检查点创建操作、检查检查点是否创建成功三个阶段。与检查点创建相关的状态标志集合为ckpt_started、ckpt_failed、ckpt_done、ckpt_active、ckpt_performed,与检查点类型相关的标志集合为do_checkpoint(创建检查点)、do_restartpoint(创建恢复点)、flags。检查点的创建步骤如下:
①当有创建检查点信号或者是到达创建检查点时间间隔时,设置do_checkpoint标志为需要创建检查点,否则调用函数BgBufferSync按照BgWriter相关的配置参数把“脏”缓冲块刷回磁盘中。
②根据系统运行状态,以及在发送创建检查点请求时设置的检查点标志类型,来确定是创建检查点还是重启点。当系统处于恢复状态且不是WAL恢复的结束时刻时,将设置创建重启点do_restartpoint标志,完成检查点类型设置阶段。
③在执行创建检查点阶段,将根据do_restartpoint的状态创建检查点或者重启点。在检查点创建后将直接设置ckpt_performed为真;而在重启点中将根据创建检查点成功与否标志来设置ckpt_performed。
④当ckpt_performed为真时,即检查点或者重启点创建成功时,将最近检查点创建时间设置为当前时间。否则设置最近检查点创建时间为15秒后再试。
⑤完成上述工作后,如果需要将进行XLog日志切换操作,最后将休眠配置中设定的时间或中途被信号唤醒继续执行上述过程。
检查点创建流程的一次处理过程如图2-9所示。
(点击查看大图)图2-9 检查点创建流程 |
BgWriter后台写进程最理想的情况是后台写进程负责刷回所有的缓冲区。但是,如果后台写进程不能保证有足够多干净的缓冲区情况下,常规后台进程仍然有权刷回缓冲区。
【责任编辑:云霞 TEL:(010)68476606】