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

news/2024/2/24 8:11:54/文章来源:https://blog.csdn.net/Hehuyi_In/article/details/106168905

       新年的第一天,总结下去年遇到的关于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;

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

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

相关文章

前端vite+vue3——自动化配置路由布局

文章目录 ⭐前言💖vue3系列文章 ⭐ 自动化配置路由💖引入vite版本自定义目录映射💖自动化读取文件下的路由💖main入口加载路由💖入口app.vue配置💖layout基础布局配置💖效果 ⭐总结⭐结束 ⭐前言…

搜索二维矩阵[中等]

一、题目 给你一个满足下述两条属性的m x n整数矩阵: 【1】每行中的整数从左到右按非严格递增顺序排列。 【2】每行的第一个整数大于前一行的最后一个整数。 给你一个整数target,如果target在矩阵中,返回true;否则,返…

【Linux技术宝典】Linux入门:揭开Linux的神秘面纱

文章目录 官网Linux 环境的搭建方式一、什么是Linux?二、Linux的起源与发展三、Linux的核心组件四、Linux企业应用现状五、Linux的发行版本六、为什么选择Linux?七、总结 Linux,一个在全球范围内广泛应用的开源操作系统,近年来越来…

树莓派编程基础与硬件控制

1.编程语言 Python 是一种泛用型的编程语言,可以用于大量场景的程序开发中。根据基于谷歌搜 索指数的 PYPL(程序语言流行指数)统计,Python 是 2019 年 2 月全球范围内最为流行 的编程语言 相比传统的 C、Java 等编程语言&#x…

生成树技术华为ICT网络赛道

9.生成树 目录 9.生成树 9.1.生成树技术概述 9.2.STP的基本概念及工作原理 9.3.STP的基础配置 9.4.RSTP对STP的改进 9.5.生成树技术进阶 9.1.生成树技术概述 技术背景:二层交换机网络的冗余性与环路 典型问题1:广播风暴 典型问题2:MA…

《UE5_C++多人TPS完整教程》学习笔记10 ——《P11 设置加入游戏会话(Setup for Joining Sessions)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P11 设置加入游戏会话(Setup for Joining Sessions)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版,UP主&…

Linux开发:PAM1 介绍

PAM(Pluggable Authentication Modules )是Linux提供的一种通用的认证方式,他可以根据需要动态的加载认证模块,从而减少认证开发的工作量以及提供认证的灵活度。 1.PAM的框架 PAM的框架由一下几个部分构成 1)应用程序,即需要使用认证服务的程序,这些应用程序是使用抽象…

单例模式 C++

6 种 单例 的手写,都是懒汉(饿汉代码在 “懒汉 / 饿汉的区别”) 目录 ✊前言 🌼GPT解析 🌼概念解析 RAII 懒汉 / 饿汉的区别 特点 举例 单例 -- 伪代码 适用场景 单例 -- 实现方式 优缺点 🎂手…

【Iceberg学习二】Branch和Tag在Iceberg中的应用

Iceberg 表元数据保持一个快照日志,记录了对表所做的更改。快照在 Iceberg 中至关重要,因为它们是读者隔离和时间旅行查询的基础。为了控制元数据大小和存储成本,Iceberg 提供了快照生命周期管理程序,如 expire_snapshots&#xf…

《Linux 简易速速上手小册》第9章: 备份与恢复策略(2024 最新版)

文章目录 9.1 理解备份的重要性9.1.1 重点基础知识9.1.2 重点案例:数据中心遭受火灾9.1.3 拓展案例:个人电脑硬盘故障9.1.4 企业级数据库被恶意软件加密 9.2 实施备份策略9.2.1 重点基础知识9.2.2 重点案例:为中小企业实施备份策略9.2.3 拓展…

springboot165科研工作量管理系统的设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计,课程设计参考与学习用途。仅供学习参考, 不得用于商业或者非法用途,否则,一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

基于SpringBoot+Vue的服装销售商城系统

末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 目录 一、项目简介 二、开发技术与环…

bert-vits2本地部署报错疑难问题汇总

环境: bert-vits2.3 win 和wsl 问题描述: bert-vits2本地部署报错疑难问题汇总 解决方案: 问题1: Conda安装requirements里面依赖出现ERROR: No matching distribution found for opencc1.1.6 解决方法 需要在 Python 3.11 上使用 Op…

linux服务器如何提高游戏帧率?

在Linux服务器上,由于硬件配置和系统的限制,提高游戏帧率变得更加困难。但是通过一些优化和调整,我们仍然可以提升Linux服务器上的游戏性能。 首先我们需要了解游戏帧率与服务器性能之间的关系。游戏帧率是指游戏每秒渲染的帧数,…

《统计学简易速速上手小册》第6章:多变量数据分析(2024 最新版)

文章目录 6.1 主成分分析(PCA)6.1.1 基础知识6.1.2 主要案例:客户细分6.1.3 拓展案例 1:面部识别6.1.4 拓展案例 2:基因数据分析 6.2 聚类分析6.2.1 基础知识6.2.2 主要案例:市场细分6.2.3 拓展案例 1&…

【XR806开发板试用】轻松连上华为云实现物联网

本文为极术社区XR806试用活动文章。 一.开始 偶然的机会在网上看到了鸿蒙开发板的试用,作为一个"老鸿蒙"岂能放弃这个机会,报名之后不出意料地得到了使用名额,在此感谢极术社区. 收到开发板之后其实还有点失望了,就那么一个小小的核心板,其他啥也没有,连一根数据线…

【精选】java进阶——包和final

🍬 博主介绍👨‍🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收藏…

【CC++】内存管理2:new + delete

前言 今天继续来学new和delete operator new 与operator delete函数 new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通…

Android Studio无法安装Git问题解决(折中方案)

安装配置好studio,往往会使用git克隆github上面的项目,但是却发现git无法正确安装,本文将介绍如何解决git无法安装这一问题。 对于git安装,实际比较复杂,可以参考这一篇博客。 Git 详细安装教程(详解 Gi…

13 年后,我如何用 Go 编写 HTTP 服务(译)

原文:Mat Ryer - 2024.02.09 大约六年前,我写了一篇博客文章,概述了我是如何用 Go 编写 HTTP 服务的,现在我再次告诉你,我是如何写 HTTP 服务的。 那篇原始的文章引发了一些热烈的讨论,这些讨论影响了我今…