postgresql 手动清理wal日志的101个坑

       新年的第一天,总结下去年遇到的关于WAL日志清理的101个坑,以及如何相对安全地进行清理。前面是关于WAL日志堆积的原因分析,清理相关可以直接看第三部分。

       首先说明,手动清理wal日志是一个高风险的操作,尤其对于带主从的生产大库,能不用尽量不要用。

一、 常见的WAL 堆积原因

       其实之前有总结过(第8题) 《PostgreSQL面试题集锦》学习与回答-CSDN博客

      去年遇到的两次都是因为WAL日志生成量过大,而pg 10中归档进程又仅支持单进程串行归档,下面单独列一下这个问题。

二、 PG 10中WAL日志归档的瓶颈

1. 归档流程

每当WAL日志切换时,就可以通知对该日志进行归档:

  • 产生日志切换的进程在pg_wal/archive_status下生成与待归档日志同名的.ready文件
  • 发送信号通知archiver process(我们是pgbackrestarchive push,其只关心是否有.ready文件存在,不关心其内容
  • archiver processpgbackrestarchive push按照archive_command进行日志归档
  • 执行完成后,archiver process循环将.ready文件重命名为.done文件

2. 归档的瓶颈

       由于我们使用的是pgbackrest,在上面步骤中,前三步都可以并行完成,唯独第四步,需要靠archiver process单独工作(pg 15版本引入了新特性,可以64个文件为一组进行操作)。

/*
 * pgarch_ArchiverCopyLoop
 *
 * Archives all outstanding xlogs then returns
 */
static void
pgarch_ArchiverCopyLoop(void)
{
    char        xlog[MAX_XFN_CHARS + 1];
  
    /*
     * 循环处理.ready文件
     */
    while (pgarch_readyXlog(xlog))
    {
        int         failures = 0;
        int         failures_orphan = 0;
  
        for (;;)
        {
            struct stat stat_buf;
            char        pathname[MAXPGPATH];
            …
            /* 进行日志归档 */
            if (pgarch_archiveXlog(xlog))
            {
                /* successful,归档成功,将.ready改为.done文件 */
                pgarch_archiveDone(xlog);
  
                /*
                 * Tell the collector about the WAL file that we successfully archived
                 */
                pgstat_send_archiver(xlog, false);
  
            /* 开始处理下一个日志 */
                break;          /* out of inner retry loop */
            }
            /* 归档失败  */
...
        }
    }
}

在WAL日志量不大,archive_status中文件不多时,循环查找文件并重命名是很快的,经观察:

  • 在archive_status中约5000个文件时,archiver process每秒可以处理8~10个.ready文件
  • 而在问题期间,archive_status中日志多达百万个,每次循环异常耗时,每秒仅能处理3~4个.ready文件(对应每小时约168~225G WAL日志)
  • 在业务高峰期,每小时产生WAL日志基本都在200G以上,有时高达400G,远远高于其处理速度,导致archive_status目录中堆积文件更多,处理速度更慢
  • 由于archiver process进程处理速度远远落后pgbackrest执行的archive_command命令,pgbackrest也认为自己无需再工作,因此未再启动

         在pg中,只有已经归档的WAL日志(生成.done文件的)才能被回收或删除,由于归档速度过慢,导致可以被清理的WAL日志很少,而新增的WAL日志量又很大,最终导致WAL日志堆积,/data目录剩余空间持续下降。

...
            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);
            }
...

三、 WAL日志清理的101个坑

1. WAL日志的重命名

       非常危险的一点,概括而言,pg会将最旧的一些wal日志重命名为最新的名字,但利用ll -rth排序查看时,其时间并不会变化。而pg_archivecleanup工具只根据文件名进行判断,很可能导致需要的文件被删除。具体分析参考postgresql源码学习(58)—— 删除or重命名WAL日志?这是一个问题、-CSDN博客

       如下图,实际上FE和FF结尾的文件是重命名后的,但显示的时间并没有变化,按时间排序还是在最上面。

① 主库需要的日志被删除

这是最危险的情况,可能导致:

  • 主库crash:在某些情况下,可能会由于数据不一致或者持续性错误导致主库宕机。这点目前没有测试出来,但风险肯定是存在的。
  • 主库crash后无法启动删除wal日志后kill pg进程即可复现,结合前一点,如果主库真的异常崩溃,很可能根本都无法启动

不幸中的万幸主库还活着,也存在以下风险:

  • 主库数据丢失:未写入磁盘的事务可能会丢失,因为相关的WAL日志已经被删除。
  • 数据不一致:如果已经提交的事务所对应的 WAL 日志被删除,那么这些事务的修改可能无法被正确地应用到从库上,从而导致数据不一致。
  • 主从同步中断:相当于从库需要的日志被删除
  • 旧备份无法恢复到最新状态:理论上建议立即发起一个全备,因为之前的备份链已经断了

来整点高危操作模拟一下,测试版本为pg 14

  • 删除正在用的WAL日志,主库是否会crash
  • 删除正在用的WAL日志,正常停库,能否启动
  • 删除正在用的WAL日志,kill pg进程,能否启动

查看主库当前在用的wal日志

-bash-4.2$ pg_controldata
…
Latest checkpoint's REDO WAL file:    0000000500000008000000FD

试试将其删除会发生什么

可以看到pg进程还在(当然不保证每次都会还在)

造点数据执行checkpoint,会发现它重新生成了FD的文件,但其实原来文件里的数据已经丢失了。

挪走FD文件,尝试正常停库再启动

发现能起来,并且它切换到了用FE文件

挪走FE文件,kill pg进程再启动,这下悲剧了

看看日志报错

这只能走点更极端的路子,把pg_wal给reset掉,影响当然就更大了

pg_resetwal -f /data/postgres/pg5432/data

② 从库需要的文件被删除
  • 导致主从中断,由于pg原生不支持并行备份,大库的搭建耗时耗力。
  • 如果主库生成的wal日志量非常大(例如每天TB级),那更是雪上加霜,甚至需要停主库业务来搭建。

③ 新特性

       在pg 12中,新增了wal_recycle参数,可以关闭wal日志的重命名,并且不需重启生效。虽然引入的原因是在COW文件系统中新建日志会更快,但也在很大程度上规避了误删wal日志的风险。

2. 孤儿.ready文件

这部分特别感谢熊灿灿老师帮忙一起分析,又学到不少新知识:

小心!孤儿归档也可能将数据库整死!

① pg 12之前

       如果你只删除wal日志,会导致其对应的.ready变成孤儿文件,归档进程归档失败,会反复尝试处理第一个失败的wal日志,最终的结果就是wal日志仍然无法归档,出现堆积,空间被占满。

       以pg 10为例,相关代码如下。可以看到如果归档失败,它只会sleep一段时间然后重试,并不做额外处理,因此日志中会一直看到archiving write-ahead log file failed too many times, will try again later 的报错

static void
pgarch_ArchiverCopyLoop(void)
{
    char        xlog[MAX_XFN_CHARS + 1];

    /*
     * loop through all xlogs with archive_status of .ready and archive
     * them...mostly we expect this to be a single file, though it is possible
     * some backend will add files onto the list of those that need archiving
     * while we are still copying earlier archives
     */
    while (pgarch_readyXlog(xlog))
    {
        int         failures = 0;

        for (;;)
        {
            /*
             * Do not initiate any more archive commands after receiving
             * SIGTERM, nor after the postmaster has died unexpectedly. The
             * first condition is to try to keep from having init SIGKILL the
             * command, and the second is to avoid conflicts with another
             * archiver spawned by a newer postmaster.
             */
            if (got_SIGTERM || !PostmasterIsAlive())
                return;

            /*
             * Check for config update.  This is so that we'll adopt a new
             * setting for archive_command as soon as possible, even if there
             * is a backlog of files to be archived.
             */
            if (got_SIGHUP)
            {
                got_SIGHUP = false;
                ProcessConfigFile(PGC_SIGHUP);
            }

            /* can't do anything if no command ... */
            if (!XLogArchiveCommandSet())
            {
                ereport(WARNING,
                        (errmsg("archive_mode enabled, yet archive_command is not set")));
                return;
            }

            if (pgarch_archiveXlog(xlog))
            {
                /* successful */
                pgarch_archiveDone(xlog);

                /*
                 * Tell the collector about the WAL file that we successfully
                 * archived
                 */
                pgstat_send_archiver(xlog, false);

                break;          /* out of inner retry loop */
            }
            else
            {
                /*
                 * Tell the collector about the WAL file that we failed to
                 * archive
                 */
                pgstat_send_archiver(xlog, true);

                if (++failures >= NUM_ARCHIVE_RETRIES)
                {
                    ereport(WARNING,
                            (errmsg("archiving write-ahead log file \"%s\" failed too many times, will try again later",
                                    xlog)));
                    return;     /* give up archiving for now */
                }
                pg_usleep(1000000L);    /* wait a bit before retrying */
            }
        }
    }
}

② 新特性

       pg 12开始引入了一个检查,如果归档过程中发现仅有.ready文件,而没有对应wal日志,pg会在日志输出一条告警并删除这个.ready文件。

       以pg 14为例,它新增了一段代码

if (stat(pathname, &stat_buf) != 0 && errno == ENOENT)
            {
                char        xlogready[MAXPGPATH];

                StatusFilePath(xlogready, xlog, ".ready");
                if (unlink(xlogready) == 0)
                {
                    ereport(WARNING,
                            (errmsg("removed orphan archive status file \"%s\"",
                                    xlogready)));

                    /* leave loop and move to the next status file */
                    break;
                }

                if (++failures_orphan >= NUM_ORPHAN_CLEANUP_RETRIES)
                {
                    ereport(WARNING,
                            (errmsg("removal of orphan archive status file \"%s\" failed too many times, will try again later",
                                    xlogready)));

                    /* give up cleanup of orphan status files */
                    return;
                }

                /* wait a bit before retrying */
                pg_usleep(1000000L);
                continue;

3. 孤儿wal文件会有什么效果

       因为archive process的效率依赖于archive_status目录中的.ready文件数,当时还想了个办法——如果我不清wal日志,只把.ready mv走会怎么样?

       事实证明是不可行的,pg检查到wal日志没有.ready文件后,会给它重新生成一个对应的.ready文件,然后开始给它做归档,相当于归档是正常进行的。

四、 如何相对安全地手动清理wal日志

1. 准备工作

① pg 12及以上版本

整体操作是类似的,但对于高版本pg,相当于还有两道保险操作

  • 设置 wal_recycle 为off,调整不用重启,可以极大地避免误删除风险

  • wal日志和.ready文件不要求完全对齐

        虽说12版本开始pg会自动清理孤儿.ready文件,但当堆积量太大时,归档进程处理效率会极其低下,等它一个个报错清也要到猴年马月。不过好处是可以不必完全对齐,差个几十几百的,pg也能自己给你处理了。

② pg 12之前版本

      这就得靠自己了,我们准备了以下几道保险:

  • 与业务沟通停写入业务,尽量减少wal日志产生量。
  • 查看主从库当前在用的wal日志,待清理日志必须小于这个日志号。

以下图为例,主库当前发送的日志是1759xxxx,那么删除时,我们最多只删1758xxx的日志号

  • 以文件修改时间和文件名共同作为删除条件

       当wal日志被重命名后,虽然ll -rth查看其时间未变,但stat命令可以看到其change time和modify time都是变化的。因此我们实际应该用ll -rcth排序,查看最旧的日志名。

       理论上知道这个特点后,用pg_archivecleanup按文件名分批清理也是可以的,并且速度更快。但安全起见我们还是用find命令,以ctime,mtime,name条件共同限制

ll -crth |grep  0000000300174|more
ll -cth |grep  0000000300174|more
ll -cth |grep  0000000300174|wc -l

# 输出结果
Jan 29 04:55 - 10:29   0000000300174*
  • 输出待清理的文件名
find /data/postgres/pg5432/data/pg_wal -maxdepth 1 -mtime +0 -ctime +0 -name "0000000300174*" -exec ls -lhc {} \; > /tmp/old_wal_174.txt

检查输出的文件名及时间是否符合预期

2. 正式清理

  • 业务停写入操作
  • 停pgbackrest
pgbackrest stop
  • 删除对应wal日志
find /data/postgres/pg5432/data/pg_wal -maxdepth 1 -mtime +0 -ctime +0 -name "0000000300174*" -exec rm -rf {} \;
  • 挪走对应.ready文件

        这步若是使用find+rm,在文件量大时耗时过长,例如24万个文件,find+rm耗时超过15分钟,而mv在1分钟内可完成。

mkdir -p /data/postgres/pg5432/data/pg_wal/archive_status_bak0129
cd /data/postgres/pg5432/data/pg_wal/archive_status
mv 0000000300174*ready ../archive_status_bak0129/
  • 循环执行以上步骤,直至删完待清理文件
  • 启动pgbackrest
pgbackrest start
ps -ef|grep pgbackrest
  • 检查archiver process

应该开始处理剩余的wal日志并且逐步推进(正常每秒10个以上),没有报错

ps -ef|grep archiver
  • 检查.ready文件数,应该逐渐减少
cd /data/postgres/pg5432/data/pg_wal/archive_status
ll -h |grep  ready|wc -l
  • 检查wal日志数,应该逐渐减少
cd /data/postgres/pg5432/data/pg_wal/
ll -h |wc -l
  • 启动应用

一段时间后.ready文件及wal日志数应该能稳定在一个水平线而非持续走高,以我们的库为例,大概在7000个wal日志,120G左右

  • 新增告警线

新加了wal目录大小的监控及告警,及时发现日志堆积问题,尽早处理

select sum((pg_stat_file(file)).size) from (select dir||'/'||pg_ls_dir(dir) as file from (select setting as dir from pg_settings where name='log_directory') t)t where (pg_stat_file(file)).change>=current_date;

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

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

相关文章

《CSS 简易速速上手小册》第6章:高级 CSS 技巧(2024 最新版)

文章目录 6.1 使用 CSS 变量进行设计:魔法配方的调配6.1.1 基础知识6.1.2 重点案例:创建可定制的主题6.1.3 拓展案例 1:响应式字体大小6.1.4 拓展案例 2:使用 CSS 变量创建动态阴影效果 6.2 calc(), min(), max() 等函数的应用&am…

AES加密中的CBC和ECB

目录 1.说明 2.ECB模式(base64) 3.CBC模式 4.总结 1.说明 AES是常见的对称加密算法,加密和解密使用相同的密钥,流程如下: 主要概念如下: ①明文 ②密钥 用来加密明文的密码,在对称加密算…

CTF-PWN-沙箱逃脱-【侧信道爆破】(2021-蓝帽杯初赛-slient)

文章目录 侧信道攻击测信道爆破2021-蓝帽杯初赛-slient先自己准备个flag检查沙箱IDA源码mainsub_A60() 相关知识size_t getpagesize(void)void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);range(i,j)编写相关shellcode发现"的用法此时…

什么是宿主软件?宿主软件有哪些?

什么是宿主软件? 宿主软件就是专业的音乐制作软件,我们日常听到的大多数正规音乐作品都是用宿主软件制作出来的,这些软件一般需要安装各类插件,插件就像寄生虫需要在宿主软件里加载才可以工作。 插件主要分虚拟乐器插件和音频处理插件两类…

JZ36 二叉搜索树与双向链表

目录 题目描述 二叉搜索树与双向链表_牛客题霸_牛客网 题目解析 题目答案 最后 题目描述 二叉搜索树与双向链表_牛客题霸_牛客网 题目解析 这里采用的是采用前序遍历的思想,找到要转换的双向链表的头节点也就是这个二叉搜索树的最左节点,找到之后依…

MySQL篇----第十九篇

系列文章目录 文章目录 系列文章目录前言一、什么是存储过程?用什么来调用?二、如何通俗地理解三个范式?三、什么是基本表?什么是视图?四、试述视图的优点?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

力扣[面试题 01.02. 判定是否互为字符重排(哈希表,位图)

Problem: 面试题 01.02. 判定是否互为字符重排 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1:哈希表 1.若两个字符串长度不相等,则一定不符合题意; 2.创建一个map集合,先将字符串s1中的每一个字符与其对应的数量存入集合…

2024.2.10 DMS(数据库管理系统)初体验

数据库管理系统(Database Management System)是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户通过DBMS访问数据库中的数据,数据库管…

大年初一,送出最后8000个红包封面!

今天是大年初一,大家都是怎么安排的? 小灰最近在南方旅游,玩得很开心,后面会给大家分享自己的见闻。 过年期间,大家多少都会给亲朋好友发一些微信红包,拥有个性化的红包封面绝对是一件很酷的事情。 前一段时…

007集——数据存储的端序(大端序和小端序转换代码)——VB/VBA

VB/VBA存储的端序 1、要想制造高性能的VB/VBA代码,离了指针是很难办到的。 2、因为VB/VBA里,用Long来表示指针,而32位(包括64位兼容的)计算机里4字节整数的处理,是最快的方式! 3、要想用指针来处理数据,…

4. ⼤模型微调方法

到底有哪些微调⽅法呢? 第⼀类⽅法:借助OpenAI提供的在线微调⼯具进⾏微调;第⼆类⽅法:借助开源微调框架进⾏微调; 1. OpenAI在线微调⼯具 网址:https://platform.openai.com/docs/guides/fine-tuning/c…

无人机遥感技术应用分析,无人机遥感系统测绘技术详解

由于无人机具有机动快速、使用成本低、维护操作简单等技术特点,因此被作为一种理想的飞行平台广泛应用于军事和民用各个领域。尤其是进入二十一世纪以后,许多国家将无人机系统的研究、开发、应用置于优先发展的地位,体积小、重量轻、探测精度高的新型传感器的不断问世,也使无人…

让Python遇上Office--从编程入门到自动化办公实践

最近仔细的学习了这本《让Python遇上Office》的书,同时把我的学习进程与心得录制了同步视频。 到今天终于把全部90集完成,并且上传到下面的视频平台了,欢迎大家观看并指正! 西瓜视频:https://www.ixigua.com/7300628…

c语言中的隐式类型转换

数据类型转化 我们在实际编程中,不管你是有意的还是无意的,有时候都会让两个不同类型的数据参与运算,编译器为了能够生成CPU可以正常 执行的指令,往往会对数据做类型转换,将两个不同类型的数据转换成同一种数据类型。…

Popper.js:ElementUI 中采用弹出,提示框库,好用的没朋友。

Hi,我贝格前端工场,继续介绍经典的js库,ElementUI 中Tooltip、Select、Cascader、TimePicker等组件中怎么把提示框定位到目标元素的,是用 Popperjs 来实现。 一、Popper.js是什么? Popper.js是一个用于创建弹出式组件…

LLM之LangChain(七)| 使用LangChain,LangSmith实现Prompt工程ToT

如下图所示,LLM仍然是自治代理的backbone,可以通过给LLM增加以下模块来增强LLM功能: Prompter AgentChecker ModuleMemory moduleToT controller 当解决具体问题时,这些模块与LLM进行多轮对话。这是基于LLM的自治代理的典型情况,…

回归预测模型:MATLAB多项式回归

1. 多项式回归模型的基本原理 多项式回归是线性回归的一种扩展,用于分析自变量 X X X与因变量 Y Y Y之间的非线性关系。与简单的线性回归模型不同,多项式回归模型通过引入自变量的高次项来增加模型的复杂度,从而能够拟合数据中的非线性模式。…

卫星通讯领域FPGA关注技术:算法和图像方面(3)

最近关注的公众号提到了从事移动通信、卫星通讯等领域的FPGA、ASIC、信号处理算法等工程师可能需要关注的技术,有通感融合、RNSS授时、惯导,以下做了一些基础的调研: 1 通感融合 1)来自博鳌亚洲论坛创新报告2023:通感算融合已成…

Linux操作系统基础(三):虚拟机与Linux系统安装

文章目录 虚拟机与Linux系统安装 一、系统的安装方式 二、虚拟机概念 三、虚拟机的安装 四、Linux系统安装 1、解压人工智能虚拟机 2、找到解压目录中的node1.vmx 3、启动操作系统 虚拟机与Linux系统安装 一、系统的安装方式 Linux操作系统也有两种安装方式&#xf…

算法学习——LeetCode力扣栈与队列篇1

算法学习——LeetCode力扣栈与队列篇1 232. 用栈实现队列 232. 用栈实现队列 - 力扣(LeetCode) 描述 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQu…