postgresql源码学习(58)—— 删除or重命名WAL日志?这是一个问题

       最近因为WAL日志重命名踩到大坑,一直很纠结WAL日志在什么情况下会被删除,什么情况下会被重命名,钻研一下这个部分。

一、 准备工作

1. 主要函数调用栈

       首先无用WAL日志的清理发生检查点执行时,检查点执行核心函数为CreateCheckPoint。其中核心调用栈为CreateCheckPoint->RemoveOldXlogFiles->RemoveXlogFile。

      CreateCheckPoint函数很复杂,从外往里看会很容易晕,所以我们倒过来,先从最内层的RemoveXlogFile开始研究。

2. Debug准备

因为debug跑了很多次,后文各日志段号等不会完全一致,但本质是相同的。

  • 先把wal_keep_size和max_wal_size调大(例如800M)
alter system set wal_keep_size='800MB';
alter system set max_wal_size='800MB';
  • 插入数据,生成一批wal日志(超过80M,即5个)
truncate table tmp003;
insert into tmp003 select * from pgbench_accounts limit 100000;
insert into tmp003 select * from pgbench_accounts limit 100000;
  • 然后调小(例如80M)
alter system set wal_keep_size='80MB';
alter system set max_wal_size='80MB';
  • Vscode跟踪checkpoint进程,在RemoveOldXlogFile函数打断点

  • 客户端执行checkpoint命令,将触发日志回收操作
checkpoint ;

注意这个跟踪属于高危操作,db进程有可能挂掉,千万别在生产环境随便执行

二、 RemoveXlogFile函数

1. 源码学习

       RemoveXlogFile中进行日志回收以及删除,回收是从不需要保留的日志中选择一部分重命名给未来使用(回收数量和两次checkpoint间产生wal量有关),其余的会被删除掉。

       三个入参:* segname为正在处理的wal日志名;recycleSegNo为最大可重命名的段号;endlogSegNo为当前(最新)的wal日志end段号。

/*
 * Recycle or remove a log file that's no longer needed.
 * segname为正在处理的wal日志名;recycleSegNo为最大可重命名的段号;endlogSegNo为当前(最新)的wal日志段号。
*/
static void
RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
               XLogSegNo *endlogSegNo)
{
    char        path[MAXPGPATH];
#ifdef WIN32
    char        newpath[MAXPGPATH];
#endif
    struct stat statbuf;
 
    snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);
 
    /*
     * 首先判断是回收还是直接删除日志。
* 如果启用了wal_recycle、并且最新wal日志号<最大可回收号、中间的条件排除符号链接并确保待重命名文件为普通文件,InstallXLogFileSegment函数回收日志,并增加ckpt_segs_recycled和endlogSegNo
     */
    if (wal_recycle &&
        *endlogSegNo <= recycleSegNo &&
        lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
        InstallXLogFileSegment(endlogSegNo, path,
                               true, recycleSegNo, true))
    {
/* 服务器日志级别为debug2时,会提示当前正在回收wal */
        ereport(DEBUG2,
                (errmsg_internal("recycled write-ahead log file \"%s\"",
                                 segname)));
        CheckpointStats.ckpt_segs_recycled++;
        /* Needn't recheck that slot on future iterations */
        (*endlogSegNo)++;
    }
    /* 否则删除文件 */
    else
    {
        int         rc;
 
        ereport(DEBUG2,
                (errmsg_internal("removing write-ahead log file \"%s\"",
                                 segname)));
/* 如果是windows */
#ifdef WIN32
    
        snprintf(newpath, MAXPGPATH, "%s.deleted", path);
        if (rename(path, newpath) != 0)
        {
            ereport(LOG,
                    (errcode_for_file_access(),
                     errmsg("could not rename file \"%s\": %m",
                            path)));
            return;
        }
        
/* 删除日志文件 */
        rc = durable_unlink(newpath, LOG);
/* 否则直接删除 */
#else
/* 删除日志文件 */
        rc = durable_unlink(path, LOG);
#endif
        if (rc != 0)
        {
            /* Message already logged by durable_unlink() */
            return;
        }
        CheckpointStats.ckpt_segs_removed++;
    }
 
    /* 清除.ready, .done标签 */
    XLogArchiveCleanup(segname);
}

2. Debug过程(rename

  • *segname为正在处理的wal日志名:000000050000000300000092,对应日志段号为914
  • recycleSegNo为最大可重命名的段号:937
  • endlogSegNo为当前(最新)的wal日志段号:933

段号与日志名是怎么转换的?

Segname:000000050000000300000092 -> 392(16进制) -> 914(10进制)

recycleSegNo:937(10进制) -> 397(16进制) -> 000000050000000300000097

endlogSegNo:933(10进制) -> 3A5(16进制) -> 0000000500000003000000A5,这里它对应的是3A5而不是最新的日志名3A4,因为它对应的是日志段的结尾

       符合*endlogSegNo <= recycleSegNo,将调用InstallXLogFileSegment为segname重命名。当前最新为0000000500000003000000A4,因此新名字为0000000500000003000000A5。

重命名后

      随后跳出函数将ckpt_segs_recycled和endlogSegNo加一,调用XLogArchiveCleanup清理.ready和.done文件。

       最后跳出RemoveXlogFile函数,回到RemoveOldXlogFiles(也可能下一次循环又进到RemoveXlogFile)。

3. Debug过程(delete

        多轮重命名后,segname=0x1b13c3b "00000005000000030000009B",*endlogSegNo=938,不再符合*endlogSegNo <= recycleSegNo,进入else,本文件将被删除。

删除前后对比

       下一轮segname=0x1b13d8b "000000050000000300000095",此后endlogSegNo不会再增加,将一直删除多余的文件。

这里有个问题,endlogSegNo和recycleSegNo是怎么来的?需要回到上层函数去看。

三、 RemoveOldXlogFiles函数

RemoveOldXlogFiles函数主要作用

  • 获取endptr对应的日志段号endlogSegNo
  • 调用XLOGfileslop函数,计算可预分配(重命名)的最大段号recycleSegNo
  • 根据segno(最新可删除段号)构建日志名lastoff
  • 读取pg_wal目录,循环读取其中日志文件(xlde->d_name),与最新可删除日志名lastoff对比
  • 对未开启归档,或归档已完成的日志,调用UpdateLastRemovedPtr在共享内存中更新已被删除的位置
  • 再调用RemoveXlogFile函数,真正进行删除(unlink)或重命名
/*
 * Recycle or remove all log files older or equal to passed segno.
 * endptr is current (or recent) end of xlog, and lastredoptr is the redo pointer of the last checkpoint. These are used to determine whether we want to recycle rather than delete no-longer-wanted log files.
 */
static void
RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr)
{
    DIR        *xldir;
    struct dirent *xlde;
    char        lastoff[MAXFNAMELEN];
    XLogSegNo   endlogSegNo;
    XLogSegNo   recycleSegNo;
 
    /* 获取endptr对应的日志段号endlogSegNo */
    XLByteToSeg(endptr, endlogSegNo, wal_segment_size);
    /*在本次检查点,有多少 WAL 段需要作为预分配的未来 XLOG 段回收?返回可预分配(重命名)的最大段号*/
    recycleSegNo = XLOGfileslop(lastredoptr);
 
    /*构建一个XLog日志名,用于判断,该文件之前的xlog可以删除。用不到时间线,所以可以使用0 */
    XLogFileName(lastoff, 0, segno, wal_segment_size);
 
    elog(DEBUG2, "attempting to remove WAL segments older than log file %s",
         lastoff);
 
    /* 获取XLog目录 */
    xldir = AllocateDir(XLOGDIR);
    /* 读取目录中的文件 */
    while ((xlde = ReadDir(xldir, XLOGDIR)) != NULL)
    {
        /* 忽略非XLog文件 */
        if (!IsXLogFileName(xlde->d_name) &&
            !IsPartialXLogFileName(xlde->d_name))
            continue;
 
    	/* 跳过时间线部分比较日志文件名,对比当前段号是否<=回收点段号 */
        if (strcmp(xlde->d_name + 8, lastoff + 8) <= 0)
        {
/* 如果没有开启归档:总是TRUE;否则,检查日志是否归档完成(即pg_wal/archive_status目录下是不是已经存在对应的.done文件) */
            if (XLogArchiveCheckDone(xlde->d_name))
            {
                /* Update the last removed location in shared memory first,首先在共享内存中更新已被删除的位置 */
                UpdateLastRemovedPtr(xlde->d_name);
                 /* 调用RemoveXlogFile函数真正进行删除或重命名,函数里使用unlink删除日志 */
                RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo);
            }
        }
    }
    FreeDir(xldir);
}

四、 XLOGfileslop函数

       在本次检查点,有多少 WAL 段需要作为预分配的未来 XLOG 段回收,返回应预分配的最大段号。函数参数为最近一次redo点位置。预分配的过程是,为所有不再需要的旧文件重命名一个未来的日志号,直到预分配的文件数量达到XLOGfileslop返回的recycleSegNo。

/*
 * At a checkpoint, how many WAL segments to recycle as preallocated future XLOG segments? Returns the highest segment that should be preallocated.
 */
static XLogSegNo
XLOGfileslop(XLogRecPtr lastredoptr)
{
    XLogSegNo   minSegNo;
    XLogSegNo   maxSegNo;
    double      distance;
    XLogSegNo   recycleSegNo;
 
    /* 根据min_wal_size和max_wal_size参数设置,计算最小和最大段号 */
    minSegNo = lastredoptr / wal_segment_size +
        ConvertToXSegs(min_wal_size_mb, wal_segment_size) - 1;
    maxSegNo = lastredoptr / wal_segment_size +
        ConvertToXSegs(max_wal_size_mb, wal_segment_size) - 1;
 
    /*估算下一次checkpoint结束时日志位置*/
    distance = (1.0 + CheckPointCompletionTarget) * CheckPointDistanceEstimate;
    /* add 10% for good measure. */
    distance *= 1.10;
 
    recycleSegNo = (XLogSegNo) ceil(((double) lastredoptr + distance) /
                                    wal_segment_size);
    /* recycleSegNo不能小于minSegNo,也不能大于maxSegNo */
    if (recycleSegNo < minSegNo)
        recycleSegNo = minSegNo;
    if (recycleSegNo > maxSegNo)
        recycleSegNo = maxSegNo;
 
    return recycleSegNo;
}

       所以本质上recycleSegNo是根据两次检查点之间的“距离”估算出来的。这里其他的值都是由pg参数指定的,例如CheckPointCompletionTarget就对应checkpoint_completion_target(默认0.9),唯独CheckPointDistanceEstimate还不知道是从哪来的,需要去外层找。这个外层其实还不在RemoveOldXlogFiles函数,而在更上层的CreateCheckPoint函数里

五、 CreateCheckPoint函数

       终于我们又回到了开头,从整体看一下checkpoint在执行WAL日志清理时到底会干什么,以及CheckPointDistanceEstimate是在哪里算出来的。

       checkpoint的核心作用之一——计算哪些WAL日志是过时可以清理的,并将其清理(删除或重命名):

  • 根据两次checkpoint之间产生的wal日志量,计算CheckPointDistanceEstimate的值
  • 获取redo点的日志段号,作为最旧的需要保留的_logSegNo(redo点之前的数据均已落盘,此前的wal日志就可以删除)
  • 但实际中还会有一些参数控制wal日志保留量,因此需要用KeepLogSeg函数再次调整_logSegNo
  • _logSegNo是最旧的需要保留的段号,因此减1则是最新的可以清理的段号
  • 调用RemoveOldXlogFiles,清理已无用的日志文件
/*
     * 如果前一个检查点存在,更新检查点之间的平均距离
     */
    if (PriorRedoPtr != InvalidXLogRecPtr)
/* 估算两次checkpoint之间产生的wal日志量,假如上次估算量比这次估算的小,则更新为这次的估算量,否则适量增加CheckPointDistanceEstimate =(0.90 * CheckPointDistanceEstimate + 0.10 * (double) nbytes); */
        UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);
 
    /* 获取redo点的日志段号,作为最旧的需要保留的_logSegNo */
    XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);
 
    /* 根据max_slot_wal_keep_size和wal_keep_size两个参数设置,再次调整最旧的需要保留的_logSegNo */
    KeepLogSeg(recptr, &_logSegNo);
 
    /* 如果_logSegNo是已经过时的复制槽,需要重新计算 */
    if (InvalidateObsoleteReplicationSlots(_logSegNo))
    {
        /*
         * Some slots have been invalidated; recalculate the old-segment
         * horizon, starting again from RedoRecPtr.
         */
        XLByteToSeg(RedoRecPtr, _logSegNo, wal_segment_size);
        KeepLogSeg(recptr, &_logSegNo);
    }
 
    /* 前面_logSegNo是最旧的需要保留的段号,因此减1则是最新的可以删除的段号 */
    _logSegNo--;
 
    /* 清理已无用的日志文件 */
    RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr);

六、 UpdateCheckPointDistanceEstimate函数

       经过上面的分析,很明显CheckPointDistanceEstimate就是在这个函数算出来的,并用于后面recycleSegNo的计算。

UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);

1. 函数代码

/*
 * Update the estimate of distance between checkpoints.
 *
 * The estimate is used to calculate the number of WAL segments to keep preallocated, see XLOGfileslop().
 */
static void
UpdateCheckPointDistanceEstimate(uint64 nbytes)
{    
/* 本次产生的日志量 */
    PrevCheckPointDistance = nbytes;
/* 如果上次估算量CheckPointDistanceEstimate比这次实际产生的要小,则将估算值更新为这次产生的量 */
    if (CheckPointDistanceEstimate < nbytes)
        CheckPointDistanceEstimate = nbytes;
    else
/* 否则,按下面的算法估算 */
        CheckPointDistanceEstimate =
            (0.90 * CheckPointDistanceEstimate + 0.10 * (double) nbytes);
}

例如上次估算值为100,实际值为50,则本次估算值应为:0.9*100+0.1*50=95,缓缓缩小。

2. 函数debug

UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);

  • RedoRecPtr= 17997616128
  • PriorRedoPtr= 17800674440
  • nbytes=17997616128- 17800674440 = 196941688
  • CheckPointDistanceEstimate= 105898150.9891763
  • 符合CheckPointDistanceEstimate< nbytes,因此CheckPointDistanceEstimate被赋值为196941688

  • lastredoptr=17997616128
  • wal_segment_size=16777216
  • min_wal_size_mb=80
  • max_wal_size_mb=80
  • 因此 minSegNo= maxSegNo= 17997616128/16777216 + 80/16 -1= 1072+5-1=1076

  •  因此distance=(1+0.9)*196941688*1.1=216635856.8
  • recycleSegNo=(17997616128+216635856.8)/16777216=1085

  • ​​​​​​​1085>1076,因此recycleSegNo=1076

最后,如果想看从上至下顺序的分析,可以参考:https://blog.csdn.net/Hehuyi_In/article/details/126209094

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/43204.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为OD机试真题 Java 实现【经典屏保】【2023 B卷 100分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、补充说明四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、再输入4、再输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&…

Mysql基础(下)之函数,约束,多表查询,事务

&#x1f442; 回到夏天&#xff08;我多想回到那个夏天&#xff09; - 傲七爷/小田音乐社 - 单曲 - 网易云音乐 截图自 劈里啪啦 -- 黑马Mysql&#xff0c;仅学习使用 &#x1f447;原地址 47. 基础-多表查询-表子查询_哔哩哔哩_bilibili 目录 &#x1f982;函数 &#x1f3…

使用Plist编辑器——简单入门指南

本指南将介绍如何使用Plist编辑器。您将学习如何打开、编辑和保存plist文件&#xff0c;并了解plist文件的基本结构和用途。跟随这个简单的入门指南&#xff0c;您将掌握如何使用Plist编辑器轻松管理您的plist文件。 plist文件是一种常见的配置文件格式&#xff0c;用于存储应…

docker - prometheus+grafana监控与集成到spring boot 服务

一、Prometheus 介绍 1.数据收集器&#xff0c;它以配置的时间间隔定期通过HTTP提取指标数据。 2.一个时间序列数据库&#xff0c;用于存储所有指标数据。 3.一个简单的用户界面&#xff0c;您可以在其中可视化&#xff0c;查询和监视所有指标。二、Grafana 介绍 Grafana 是一…

Kubernetes对象深入学习之四:对象属性编码实战

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 本文是《Kubernetes对象深入学习》系列的第四篇&#xff0c;前面咱们读源码和文档&#xff0c;从理论上学习了kubernetes的对象相关的知识&#xff…

spring复习:(50)@Configuration注解配置的singleton的bean是什么时候被创建出来并缓存到容器的?

一、主类&#xff1a; 二、配置类&#xff1a; 三、singleton bean的创建流程 运行到context.refresh(); 进入refresh方法&#xff1a; 向下运行到红线位置时&#xff1a; 会实例化所有的singleton bean.进入finisheBeanFactoryInitialization方法&#xff1a; 向下拖动代…

(双指针) 剑指 Offer 57 - II. 和为s的连续正数序列 ——【Leetcode每日一题】

❓ 剑指 Offer 57 - II. 和为s的连续正数序列 难度&#xff1a;简单 输入一个正整数 target &#xff0c;输出所有和为 target 的连续正整数序列&#xff08;至少含有两个数&#xff09;。 序列内的数字由小到大排列&#xff0c;不同序列按照首个数字从小到大排列。 示例 1…

【CMU15-445 FALL 2022】Project #1 - Buffer Pool

About 实验官网 Project #1 - Buffer Pool在线评测网站 gradescope Lab Task #1 - Extendible Hash Table 详见——【CMU15-445 FALL 2022】Project #1 - Extendable Hashing 如果链接失效&#xff0c;请查看当前平台我之前发布的文章。 Task #2 - LRU-K Replacement Polic…

Hadoop——DataGrip连接MySQL|Hive

1、下载 DataGrip下载&#xff1a;DataGrip: The Cross-Platform IDE for Databases & SQL by JetBrains 2、破解 破解链接&#xff1a;https://www.cnblogs.com/xiaohuhu/p/17218430.html 3、启动环境 启动Hadoop&#xff1a;到Hadoop的sbin目录下右键管理员身份运行…

计算机服务器被devos勒索病毒攻击怎么解决,数据库解密恢复方式

科学技术的发展为企业的生产运行提供了极大的便利性&#xff0c;但随之而来的网络安全也应该引起人们的重视。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的计算机服务器内的数据库被devos后缀勒索病毒攻击&#xff0c;导致企业许多工作无法正常运行。Devos后缀勒…

每天五分钟计算机视觉:单卷积层的前向传播过程

什么是单卷积层? 一张图片(输入)经过多个卷积核卷积就会得到一个输出,而这多个卷积核的组合就是一个单卷积层。 这些卷积核可能大小是不一样的,但是他们接收同样大小是输入,他们的输出必须是一般大小,所以不同的卷积核需要具备不同的步长和填充值。 单层卷积网络前向传…

大数据分析案例-基于LightGBM算法构建乳腺癌分类预测模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

微服务如何治理

微服务远程调用可能有如下问题&#xff1a; 注册中心宕机&#xff1b; 服务提供者B有节点宕机&#xff1b; 服务消费者A和注册中心之间的网络不通&#xff1b; 服务提供者B和注册中心之间的网络不通&#xff1b; 服务消费者A和服务提供者B之间的网络不通&#xff1b; 服务提供者…

安装redis,适配阿里云服务器,Liunx安装redis

下载redis以及编译安装 下载redis文件 wget http://download.redis.io/releases/redis-6.0.8.tar.gz #下载redis压缩文件 tar xzf redis-6.0.8.tar.gz #解压缩 cd redis-6.0.8 make 查看是否安装了gcc编译输入gcc --version如果没有…

数据仓库表设计理论

数据仓库表设计理论 数仓顾名思义是数据仓库&#xff0c;其数据来源大多来自于业务数据(例如:关系型数据库)&#xff0c;当设计数仓中表类型时(拉链表、增量表、全量表、流水表、切片表)时&#xff0c;应先观察业务数据的特点再设计数仓表结构 首先业务数据是会不断增长的-即…

STM32外设系列—TB6612FNG

本文涉及到定时器和串口的知识&#xff0c;详细内容可见博主STM32速成笔记专栏。 文章目录 一、TB6612简介二、TB6612使用方法2.1 TB6612引脚连接2.2 控制逻辑2.3 电机调速 三、实战项目3.1 项目简介3.2 初始化GPIO3.3 PWM初始化3.3 电机控制程序3.4 串口接收处理函数 一、TB66…

基于SPDK-vhost的云原生Kubevirt虚拟化存储IO的优化方案

摘要 本文主要介绍针对云原生kubernetes虚拟化IO的应用场景&#xff0c;在Kubevirt中引入SPDK-vhost的支持&#xff0c;来加速虚机中IO存储性能。同时基于Intel开源的Workload Service Framework[1]平台集成部署一套端到端虚拟化IO的应用场景做基本的性能对比测试。 云原生Kube…

Hadoop——Windows系统下Hadoop单机环境搭建

为了便于开发&#xff0c;我在本地Windows系统进行Hadoop搭建。 我使用的版本&#xff1a;hadoop-2.7.0。其他版本也可&#xff0c;搭建流程基本一样&#xff0c;所以参考这个教程一般不会有错。 1、下载安装包和插件 安装包hadoop-2.7.0.tar.gz 必要插件winutils-master 2、…

RocketMQ教程-安装和配置

Linux系统安装配置 64位操作系统&#xff0c;推荐 Linux/Unix/macOS 64位 JDK 1.8 Maven3.0 yum 安装jdk8 yum 安装maven 1.下载安装Apache RocketMQ RocketMQ 的安装包分为两种&#xff0c;二进制包和源码包。 点击这里 下载 Apache RocketMQ 5.1.3的源码包。你也可以从这…

详细介绍Matlab中线性规划算法的使用

Matlab中提供了用于线性规划的优化工具箱&#xff0c;其中包含了多种算法&#xff0c;如单纯形法、内点法等。线性规划是一种优化问题&#xff0c;旨在找到一组变量的最佳值&#xff0c;以最大化或最小化线性目标函数&#xff0c;同时满足一组线性约束条件。 下面将详细介绍Ma…