前几天看了阿里云关于全页写的直播,打算重新再整理下关于全页写的内容。
对pg来说,部分写可以发生在两种场景:
备份途中源数据库可能被修改,此时得到的备份数据状态就是不一致的
无论是崩溃恢复还是备份还原的恢复,都无法基于不一致的数据块进行。
思路就是我额外存一份数据页的信息到WAL中,WAL中的这份数据一定是正确的(如果写入WAL失败,事务就不能提交,相当于没有执行)。
官方文档介绍如下
发生时机:
对于备份恢复,在restore阶段,会直接还原不一致的块;但在recover阶段,会直接用WAL中一致的块对其进行覆盖,然后开始应用日志。
小补充:在备份恢复中,为了最后能恢复到一致的状态,全备期间除了数据文件,也需要备份期间的归档日志,用于recover阶段的应用。由于recover阶段做的是redo而不是undo,因此全备最后可以恢复到的时间点应该是备份结束时间,而不是开始时间。
安全性与性能如何均衡
优点:
缺点:
导致主备延迟变大
一个XLOG记录长度是8字节,每个WAL段文件默认为16MB。一个WAL段可以记录将近200万事务。而如果存储8KB大小的数据块,只能储存2048个。
以下是课程直播是给的建议,可以参考
由于阵列一般都用raid技术做了处理,所以块物理损坏的机率大大减少了;而操作系统原因导致的逻辑错误机率也比较低; 或者使用支持原子写的文件系统( ZFS),故而建议关闭full-page写。
直播课中有一个小实验,对比关闭全页写和备份期间,大DML操作产生的wal日志量
对大表执行update,看日志切换到了几号(切换了多少个)
Update前后日志号
一共产生了43个日志
一共产生了134个日志,是之前的3倍以上
还可以看到错误日志中,检查点过于频繁产生了告警(每次切换就产生检查点)
前面提到,全页写是将整个数据页写入日志记录。而从日志写入流程我们知道,这一步主要就在组装日志记录的XLogRecordAssemble函数。
会话1
vscode设断点在跟踪8632进程
会话1执行insert,模拟checkpoint后第一次块修改
首先进到了XLogInsert函数,这里我们进入XLogRecordAssemble函数
判断是否需要备份块(全页写):优先根据flag判断,否则根据GUC参数和是否处于backup状态判断,最终根据LSN判断
通过include_image再次判断是否需要全页写
去除一些空洞
设置标记位和计数器
构造日志数据
设置FPW头信息
pg_start_backup命令,对应函数do_pg_start_backup(xlog.c文件),其中开启强制全页写:
同理在pg_stop_backup对应的函数do_pg_stop_backup,有一句关闭强制全页写:
因此手动执行pg_start_backup命令之后,备份完一定要执行pg_stop_backup,避免WAL暴增。
另外pg_basebackup备份工具可以自动进入备份模式进行数据库备份,备份完成后自动从备份模式退出,不需执行pg_start_backup()和 pg_stop_backup()显式声明进入和退出备份模式,极大简化了原有方式,推荐使用。
这两个命令有点类似oracle的热备份操作
实际上StartupXLog函数内容很多,包括崩溃恢复、PITR、standby等。这里我们只简单看下崩溃恢复中全页写相关的内容,其他的后面再学习。
heap_redo函数会根据不同的日志记录类型,调用不同的redo函数。以insert为例,就会调用heap_xlog_insert。
heap_xlog_insert函数会调用XLogReadBufferForRedo,再调用XLogReadBufferForRedoExtended。
对FPW模式下产生的记录,在redo时直接覆盖页面。对普通日志记录,则判断该操作在故障前是否已落盘;如果已落盘,则不需要redo。
- XLogRedoAction
- XLogReadBufferForRedoExtended(XLogReaderState *record,
- uint8 block_id,
- ReadBufferMode mode, bool get_cleanup_lock,
- Buffer *buf)
- {
- …
- /* If it has a full-page image and it should be restored, do it. */
- if (XLogRecBlockImageApply(record, block_id))
- {
- Assert(XLogRecHasBlockImage(record, block_id));
- *buf = XLogReadBufferExtended(rnode, forknum, blkno,
- get_cleanup_lock ? RBM_ZERO_AND_CLEANUP_LOCK : RBM_ZERO_AND_LOCK);
- page = BufferGetPage(*buf);
- …
- return BLK_RESTORED;
- }
- else
- {
- *buf = XLogReadBufferExtended(rnode, forknum, blkno, mode);
- if (BufferIsValid(*buf))
- {
- …
- //页面LSN大于日志记录中的LSN,说明不需要再执行redo
- if (lsn <= PageGetLSN(BufferGetPage(*buf)))
- return BLK_DONE; //不需要执行redo
- else
- return BLK_NEEDS_REDO; //需要执行redo
- }
- else
- return BLK_NOTFOUND;
- }
- }
参考
阿里云直播《pg-full-page机制与原理》
《PostgreSQL技术内幕:事务处理深度探索》第4章
《摸着Oracle过河 ---- 大幅提升PostgreSQL性能分享 吕海波(VAGE)》
PostgreSQL中的full_page_writes的理解_weixin_33861800的博客-CSDN博客
http://mysql.taobao.org/monthly/2015/11/05/
PostgreSQL 源码解读(111)- WAL#7(Insert&WAL - XLogRecordAssemble-FPW) - 代码先锋网
pg 崩溃恢复篇(一)—— WAL的作用与全页写机制_Hehuyi_In的博客-CSDN博客_pg wal
postgresql源码学习(23)—— 事务日志④-日志组装_Hehuyi_In的博客-CSDN博客_postgresql事务日志
postgresql源码学习(33)—— 事务日志⑨ - 从insert记录看日志写入整体流程_Hehuyi_In的博客-CSDN博客