• postgresql源码学习(34)—— 事务日志⑩ - 全页写机制


    前几天看了阿里云关于全页写的直播,打算重新再整理下关于全页写的内容。

    一、 部分写/块不一致 问题

    对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前后日志号

    一共产生了43个日志

    2. 实验二:备份期间,full_page=off

    一共产生了134个日志,是之前的3倍以上

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

    五、 源码分析

    1. 修改操作

           前面提到,全页写是将整个数据页写入日志记录。而从日志写入流程我们知道,这一步主要就在组装日志记录的XLogRecordAssemble函数。

    会话1

    vscode设断点在跟踪8632进程

    会话1执行insert,模拟checkpoint后第一次块修改

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

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

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

    去除一些空洞

    设置标记位和计数器

    构造日志数据

    设置FPW头信息

    2. 备份阶段

    pg_start_backup命令,对应函数do_pg_start_backupxlog.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

    1. XLogRedoAction
    2. XLogReadBufferForRedoExtended(XLogReaderState *record,
    3. uint8 block_id,
    4. ReadBufferMode mode, bool get_cleanup_lock,
    5. Buffer *buf)
    6. {
    7. /* If it has a full-page image and it should be restored, do it. */
    8. if (XLogRecBlockImageApply(record, block_id))
    9. {
    10. Assert(XLogRecHasBlockImage(record, block_id));
    11. *buf = XLogReadBufferExtended(rnode, forknum, blkno,
    12. get_cleanup_lock ? RBM_ZERO_AND_CLEANUP_LOCK : RBM_ZERO_AND_LOCK);
    13. page = BufferGetPage(*buf);
    14. return BLK_RESTORED;
    15. }
    16. else
    17. {
    18. *buf = XLogReadBufferExtended(rnode, forknum, blkno, mode);
    19. if (BufferIsValid(*buf))
    20. {
    21. //页面LSN大于日志记录中的LSN,说明不需要再执行redo
    22. if (lsn <= PageGetLSN(BufferGetPage(*buf)))
    23. return BLK_DONE; //不需要执行redo
    24. else
    25. return BLK_NEEDS_REDO; //需要执行redo
    26. }
    27. else
    28. return BLK_NOTFOUND;
    29. }
    30. }

    参考

    阿里云直播《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博客

  • 相关阅读:
    java计算机毕业设计高校教学资源共享平台MyBatis+系统+LW文档+源码+调试部署
    笔记二:odoo搜索、筛选和分组
    windows server 2012 R2的C盘空间满了,但是找不到大文件的两种原因
    03-node基础
    腾讯云轻量应用服务器内网连接互通有什么限制?
    Linux—系统基础一
    常用注解@JsonField、@JsonFormat、@DateTimeFormat区别
    Redis入门完整教程:HyperLogLog
    sql:SQL优化知识点记录(九)
    ICC2: ICG clone与ICG merge
  • 原文地址:https://blog.csdn.net/Hehuyi_In/article/details/126073908