当前位置:网站首页>postgresql源码学习(34)—— 事务日志⑩ - 全页写机制
postgresql源码学习(34)—— 事务日志⑩ - 全页写机制
2022-07-31 05:31:00 【Hehuyi_In】
前几天看了阿里云关于全页写的直播,打算重新再整理下关于全页写的内容。
一、 部分写/块不一致 问题
对pg来说,部分写可以发生在两种场景:
- pg异常宕机(或者出现磁盘错误)时,数据文件中的页只写入了一部分。

- 使用操作系统命令备份正在运行的数据库

备份途中源数据库可能被修改,此时得到的备份数据状态就是不一致的

无论是崩溃恢复还是备份还原的恢复,都无法基于不一致的数据块进行。
二、 如何解决部分写
思路就是我额外存一份数据页的信息到WAL中,WAL中的这份数据一定是正确的(如果写入WAL失败,事务就不能提交,相当于没有执行)。

官方文档介绍如下


发生时机:
- 对于修改操作,当启用全页写时,pg会在每个检查点之后、每个页面第一次发生变更时,将头数据和整个页面作为一条WAL记录写入WAL缓冲区。
- 对于备份操作,强制启用全页写,只要块发生变化,就会被整块写入WAL文件(不管是不是第一次,也不管有没有检查点)。因此,它写入的量是更大的。
三、 数据恢复

- 对于崩溃恢复,会通过checksum发现“部分写”的数据页,并将wal中保存的这个完整数据页覆盖当前损坏的数据页,然后再继续redo恢复整个数据库。
对于备份恢复,在restore阶段,会直接还原不一致的块;但在recover阶段,会直接用WAL中一致的块对其进行覆盖,然后开始应用日志。
小补充:在备份恢复中,为了最后能恢复到一致的状态,全备期间除了数据文件,也需要备份期间的归档日志,用于recover阶段的应用。由于recover阶段做的是redo而不是undo,因此全备最后可以恢复到的时间点应该是备份结束时间,而不是开始时间。
四、 全页写的优缺点
安全性与性能如何均衡
优点:
- 提高数据库的安全性,解决块不一致问题。
缺点:
- 导致WAL文件膨胀
- 导致额外的磁盘IO,从而影响数据库整体性能
导致主备延迟变大
一个XLOG记录长度是8字节,每个WAL段文件默认为16MB。一个WAL段可以记录将近200万事务。而如果存储8KB大小的数据块,只能储存2048个。
以下是课程直播是给的建议,可以参考
由于阵列一般都用raid技术做了处理,所以块物理损坏的机率大大减少了;而操作系统原因导致的逻辑错误机率也比较低; 或者使用支持原子写的文件系统( ZFS),故而建议关闭full-page写。
直播课中有一个小实验,对比关闭全页写和备份期间,大DML操作产生的wal日志量
1. 实验1:非备份,full_page=off
对大表执行update,看日志切换到了几号(切换了多少个)

Update前后日志号

一共产生了54个日志
2. 实验二:备份期间,full_page=off


一共产生了134个日志,翻倍还有多

还可以看到错误日志中,检查点过于频繁产生了告警(每次切换就产生检查点)

五、 源码分析
1. 修改操作
前面提到,全页写是将整个数据页写入日志记录。而从日志写入流程我们知道,这一步主要就在组装日志记录的XLogRecordAssemble函数。
会话1


vscode设断点在跟踪8632进程
会话1执行insert,模拟checkpoint后第一次块修改

首先进到了XLogInsert函数,这里我们进入XLogRecordAssemble函数

判断是否需要备份块(全页写):优先根据flag判断,否则根据GUC参数和是否处于backup状态判断,最终根据LSN判断


通过include_image再次判断是否需要全页写

去除一些空洞

设置标记位和计数器

构造日志数据

设置FPW头信息

2. 备份阶段
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的热备份操作

3. 恢复操作 —— StartupXLog函数
实际上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机制与原理》
《摸着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博客
边栏推荐
猜你喜欢
随机推荐
批量免费文字翻译
【云原生】-Docker容器迁移Oracle到MySQL
Oracle入门 09 - Linux 文件上传与下载
Exam Questions Previous True Questions Wrong Bills [The Fourth Session] [Provincial Competition] [Group B]
10.0 堆体系结构概述之元空间/永久代
芯塔电子斩获第十一届中国双创大赛芜湖赛区桂冠
Oracle入门 08 - Linux 系统远程登录维护
In-depth analysis of z-index
在级联选择器,根据不会重复的字段,来获取当前的对象
vmware搭建redis集群遇到问题
MySQL笔记下
软件测试之登录测试详解
项目-log4j2排查问题
网盘程序 ZFile安装
What is float?What is document flow?Several ways and principles of clearing floats?What is BFC, how to trigger BFC, the role of BFC
shell的脚本的基本用法
引导过程和服务控制
Skywalking安装部署
【编程题】【Scratch三级】2022.03 冬天下雪了
client









