首页 > 数据库 > 数据库开发技术 > 生产案例 | 费解的备份corrupt
最近又双叒叕在处理备份这个老大难的事情了,对于云上环境我们采用的是pg_start_backup + 存储快照的方式,而对于传统环境,我们使用的是pg_probackup,pg_probackup是一款不错的备份工具,美中不足的是寻址空间不能超过4GB,为此我们专门修改了这一块的源代码,另外就是NAS频繁导致主机夯死,去年大半年都在折腾这个事情,进行大库改造。
好景不长,最近又碰到了一堆关于备份的问题,更准确的说是关于pg_probackup,总是会出现很多corrupt状态的备份。
corrupt,顾名思义,备份损坏了,是无法用于恢复的。我本地模拟了一下,如下RA5O25(部分进行了删减)
[postgres@xiongcc log]$ pg_probackup show -B /pgdata/probackup/ --format=plain
BACKUP INSTANCE 'mypg'
======================================================================================================================================
Instance Version ID Recovery Time Mode WAL Current/Parent TLI Time Data Start LSN Stop LSN Status
======================================================================================================================================
mypg 11 RA5VTM 2022-04-11 14:19:32+08 FULL STREAM 1 / 0 542s 42GB C/98000028 C/98000198 OK
mypg 11 RA5O25 2022-04-11 11:29:24+08 DELTA STREAM 1 / 0 411s 2314MB B/F205DA38 C/6BC8E2B8 CORRUPT
mypg 11 RA5MW2 ---- FULL ARCHIVE 1 / 0 304s 0B A/D8000028 0/0 ERROR
为什么会出现这种状态呢?观察了生产中几个报错的备份日志,都存在类似的报错:Backup file xxx is not found
• WARNING: Backup file "/pgdata/probackup/backups/mypg/RA5O25/database/pg_logical/snapshots/B-DC21FDC0.snap" is not found,这个出现频率最高,这个snap文件是logical decoding下会存在的,存储的是脏数据,在checkpoint的时候会进行回收,而生产中报错的环境都存在DTS链路
• WARNING: Backup file "/pgdata/probackup/backups/mypg/RA5O25/database/pg_clog/0BC0" is not found
• WARNING: Backup file "/pgdata/probackup/backups/mypg/RA5O25/database/base/17047/28557586" is not found
当发现了某些文件不存在的时候,就会将备份置为corrupted的状态。我最开始以为是,pg_probackup会先扫描一下数据目录,然后将所有待备份的文件信息记录到某个文件中,接着进行实际备份的时候发现文件不在了,就会报错。后面发现并不是这样,其实仔细思考pg_probackup不应该这么笨,我备份的时候删了一个表就报错岂不是太low了。
于是我采用了另外一种方式,备份完成后,手动将某个文件删除,再手动validate校验一次,就会出现一样的错误。
那么什么情况下会导致这种错误?
我本身是极其不爱去看代码的,可是翻遍了Google也没能找到类似的错误能够解决我的困惑,没法只能硬着头皮,本着"我行我上"的态度去看pg_probackup这一个十分陌生的代码。生产中使用的pg_probackup版本是2.0.27,最新的已经是2.5.5的版本了。
当面对一个包含几十万行代码工程的时候,我们不可能直接逮着一个backup.c就开始看,这样效率过低,一般可以采用的方式大概有这么几种:
• 关键字搜索
• 错误信息搜索
• 通过GDB进行定位
• 通过文件名大致搜索
• 通过Google、文献
• GPROF
这里我们使用最原始的方式:关键字搜索。pg_probackup是可以设置日志级别和是否记录日志的
[postgres@xiongcc ~]$ pg_probackup show-config -B /pgdata/probackup/ --instance mypg | grep log-
log-level-console = OFF ---是否输出到屏幕
log-level-file = VERBOSE ---日志级别
log-filename = pg_probackup.log ---日志文件
log-directory = /pgdata/probackup/log ---日志目录
log-rotation-size = 0TB ---日志文件的最大尺寸
log-rotation-age = 0d ---
此处我将日志级别设置成了VERBOSE,同时将备份信息输出到了文件里,简单看下日志。
[postgres@xiongcc log]$ cat pg_probackup.log | head -n 10
2022-04-11 14:10:34 CST: INFO: command: pg_probackup backup -B /pgdata/probackup/ --instance mypg -p 5435 -b full --stream
2022-04-11 14:10:34 CST: INFO: Backup start, pg_probackup version: 2.0.27, backup ID: RA5VTM, backup mode: full, instance: mypg, stream: true, remote: false
2022-04-11 14:10:34 CST: VERBOSE: (query) SELECT pg_catalog.current_setting($1)
2022-04-11 14:10:34 CST: VERBOSE: (param:0) = block_size
2022-04-11 14:10:34 CST: VERBOSE: (query) SELECT pg_catalog.current_setting($1)
2022-04-11 14:10:34 CST: VERBOSE: (param:0) = wal_block_size
2022-04-11 14:10:34 CST: VERBOSE: (query) SELECT pg_catalog.pg_is_in_recovery()
2022-04-11 14:10:34 CST: VERBOSE: (query) SELECT pgpro_edition()
2022-04-11 14:10:34 CST: VERBOSE: (query) SHOW data_checksums
此处我们根据这么一段关键字去搜索
INFO: Backup start, pg_probackup version: 2.0.27, backup ID: RA5VTM, backup mode: full, instance: mypg, stream: true, remote: false
最终定位到是do_backup这个函数,这个函数是pg_probackup 命令行的入口
简单分析一下重点流程
pgNodeInit()会做一些最基本的初始化
PGDATA目录下可能会包含一些额外的目录等,也会保存下来
接着创建备份需要的相关数据目录,这个在日志中也可以窥探到一些
这一步很简单,更改备份状态,置为RUNNING,也就是将backups/$instance_name/$backup_id/backup_control里面的status置为RUNNING
接着调用write_backup() -> pgBackupWriteControl()修改控制文件backup_control,主要记录一些备份信息
接着就是备份文件了,是do_backup_pg()这个函数做的事情,逻辑有点长,前面逻辑先过滤了,大致流程是
• 做基本的初始化
• 调用pg_start_backup(),pg_start_backup(label, smooth_checkpoint, ¤t, nodeInfo, backup_conn);
• 然后做一些判断,比如时间线,是否采用ptrack模式等
• 接着更新backup.control里面的start_lsn
• 遍历需要备份的文件,在备份之前会再次判断一下目录
• 然后创建backup_content.control,以及修改backup.control
• 然后开始备份,日志里会打印一条Start transferring data files,这里会根据设置的参数-j,采取并行备份
• 传输完成后,打印一条Data files are transferred, time elapsed,然后调用pg_stop_backup()
• 接着记录备份文件详细信息到backup_content.control里面,这一步很重要
• 修改backup.control的data-bytes字段,为备份大小
• 最后进行sync,刷盘,会打印Syncing backup files to disk和Backup files are synced, time elapsed
至此,备份的流程完成,接着回到主函数
这里,可以看到进行了关键的一个步骤,使用pgBackupValidate()对备份进行校验,除非显示指定no validate,也就是这里,pgBackupValidate() - > pgBackupValidateFiles(),pgBackupValidateFiles里面打印了Backup file "%s" is not found
因此,我们来看一下什么时候会打印Backup file xxx is not found,根据我们前面的分析,传输文件完成之后,会将备份的文件信息写入到backup_content.control里面,这里也可以根据日志来分析
• backup.control是14:24创建的,同时14:24备份正式完成
• backup_content.control是14:19:36创建的,而文件传输完成时间是在14:19:32,因此符合我们之前的判断逻辑,即文件传输完成之后,再写入到backup_content.control,接着做validate校验
那再仔细看看校验的流程,看下什么情况下会将状态设为corrupted,即arguments->corrupted = true,代码进行了截取
这个也是生产中报的错,Backup file xx is not found,这个file_fullpath就是文件的全路径,即base/xxx/xxx,方法很简单,使用stat命令,判断该文件是否存在,ENOENT即文件不存在
• ENOENT 参数file_name 指定的文件不存在
• ENOTDIR 路径中的目录存在但却非真正的目录
• ELOOP 欲打开的文件有过多符号连接问题, 上限为16 符号连接
• EFAULT 参数buf 为无效指针, 指向无法存在的内存空间
• EACCESS 存取文件时被拒绝
• ENOMEM 核心内存不足
• ENAMETOOLONG 参数file_name 的路径名称太长
到这里似乎陷入到了僵局,我传输成功了,接着将所有传输完成的文件记录到backup_content.control里,然后再做validate,结果validate的时候,发现文件又不在了,和backup_content.control里面记录的信息不一致!
正如我之前复现的那样,备份完成之后,手动删除snap,然后再手动做一个validate校验,就也会提示xxx is not found,备份变成corrupt。
正当我急得直抠脑袋的时候,我才发现我又犯了一个低级错误,我一直在看的代码是2.5.5的,但是生产是2.0.27的!
没法,我又去大概过了一遍2.0.27的流程,因为前面分析过了一遍,这次的分析速度要快得多,流程基本一样的,即传输文件完成之后,再将备份的文件信息写入到backup_content.control里面,然后再做validate,因此,还是回到那个问题,什么情况下会出现我传输完了写完了backup_content.control之后,去校验的时候文件又没了?我去看release的时候,发现一些端倪,在2.3.2的时候可能存在backup control file不会刷盘的情况。
既然提到了sync,去看一下,这个是2.0.27的代码
下面是2.3.2的代码
最新的2.5.5代码里面也是类似,在do_backup_pg()函数里面做的sync,也就是说在传输完成之后,然后写backup_content.control文件,接着立马对所有的文件(backup_content.control里面记录的所有文件)做sync(fio_sync(to_fullpath, FIO_BACKUP_HOST)),最后做校验。
分析到这里,可以看到,高版本会多一个逻辑,即刷盘sync的动作,更加可靠。那么接下来便有几条路线去同步验证这个问题了,即什么情况下,会出现文件传输完成后,再去做校验的时候文件丢失了?
• 针对生产环境,pg_probackup开启全日志,验证是否存在backup_content.control记录了某个文件,但是实际日志中并没有去备份这个文件的情况,但是按照我们之前的分析,backup_content.control是已经传输完成之后写的,这个概率不大
• backup_content.control可能没有刷盘,最后落盘的时候出错了?这个概率感觉更不大可能
• 假如backup_content.control记录了,并且日志里也记录了确实传输了,那就可能和没有做sync有关,我们是备份到NAS的,默认是异步,写入到cache里面,可能数据文件在传输到NAS server端的时候丢了出问题了,最后校验,这种情况就得merge最新的代码了
• NAS丢数据了?google了一下,好像也有可能
直接翻译一下,
原文作者:PostgreSQL学徒;如有侵权,请联系管理员删除。