ITPub博客

首页 > 数据库 > 数据库开发技术 > 生产案例 | 费解的备份corrupt

生产案例 | 费解的备份corrupt

原创 数据库开发技术 作者:u_a9af4a561c3b 时间:2022-04-11 19:53:07 0 删除 编辑

前言

最近又双叒叕在处理备份这个老大难的事情了,对于云上环境我们采用的是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, &current, 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 的路径名称太长

文件大小不匹配

CRC不一致

到这里似乎陷入到了僵局,我传输成功了,接着将所有传输完成的文件记录到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学徒;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2020-07-14

  • 博文量
    306
  • 访问量
    139324