备份策略的演进:从全量到增量到快照
2017 年我接手数据库时,备份是一行定时任务:每天凌晨一点 mysqldump 全量导出,落到一台四盘位的存储上。库不大,两百来 G,导一次五个半小时。
这套东西跑了三年,中间没人动过。直到 2020 年做恢复演练,我们才发现备份文件里有一份是坏的,而坏的那份刚好是三个月前的一份"重要节点"备份。
后来策略改过两轮,从全量加增量,再到加存储快照,现在定成三层混用。这篇写清楚每一层解决什么问题、哪一层是我们曾经误解的。
一、每天全量那三年
1.1 五个半小时的窗口
库两百 G,mysqldump 单线程导,压缩后一百二十 G,写完再传到另一台机器,前后五个半小时。凌晨一点开始,六点半结束,正好赶在早班之前。
窗口看着宽裕,其实很紧。有一次业务侧做了一次批量补数,库涨到两百六十 G,导出跑到七点四十,早班同事上班时数据库还在全表扫描,页面卡了半小时。
1.2 恢复演练发现备份是坏的
2020 年 4 月的那次演练,我们按流程挑了一份三个月前的备份恢复。导到一半报错,提示某个表的数据文件截断。查下来是那次导出时磁盘写满,进程被系统杀掉,但定时任务的退出码被管道吞掉了,日志里只留下一行"完成"。
这件事改变了我们看待备份的方式。备份做完了不等于备份能用,中间隔着一道验证,而这道验证此前没有任何人负责。
1.3 全量的边界在哪
全量不是不好,它的问题是成本随时长线性增长。库每涨一百 G,窗口就多两个多小时,而窗口是有上限的,业务不可能等你到中午。
另一个问题是恢复粒度。全量只能整体恢复到导出那一刻,导完之后的所有写入都没了。对我们这种白天一直在下单的系统,丢掉七个小时的数据等同于事故。
二、加增量之后的新问题
2.1 binlog 链一断就全废
2020 年下半年上了增量:每周日全量,周一到周六每天备份 binlog。恢复时先恢复周日的全量,再按顺序重放六天的增量。
头一次真恢复就出事了。周三那天的 binlog 备份失败,原因是磁盘配额到了,告警发到了一个已经离职同事的邮箱。缺了周三,周四和周五的重放全部无效,等于回到周日。
2.2 恢复要先算一遍时间
全量恢复两个半小时,重放一天的 binlog 四十分钟。要恢复到周三下午三点,就得重放三天半,加起来五个多小时。这个数字业务侧接受不了,但当时我们没有别的办法。
于是我们做了一个取舍:把全量周期从七天缩到三天。全量频率上去,增量链变短,恢复时间就压下来了。代价是备份窗口从每周一次变成每周两次,白天的 I/O 压力往上走了一截。
压缩参数也调过。原来是单线程压缩,CPU 打满,备份跑不完就撞上早高峰。改成四线程、低优先级 I/O,导出时间从五个半小时降到四小时十分钟。这类调整的收益比换工具直接,但前提是先量过一次真实的耗时分布,知道时间花在读、在写、还是在压缩上。
2.3 保留期不是越长越好
起初我们留三十天全量加三十天增量,存储用了四十多 T。后来梳理发现,真正被恢复过的只有两类:二十四小时内的误操作,和一个月前的数据核对。
于是保留期改成:日备留十四天,周备留八周,月备留十二个月。存储降到十八 T,恢复需求一个没漏。保留期这件事,写下来比想清楚重要,因为接手的人不会知道你当初为什么留三十天。
三、快照解决的是另一件事
3.1 快照快在存储层
2021 年存储换成了支持快照的设备,打一次快照十几秒,对业务几乎无感。我们起初很兴奋,觉得备份问题解决了。
用了一个月才发现,快照和备份不是一回事。快照依赖同一套存储,存储本身坏了、被误删了、机房断电了,快照跟着一起没。它能解决"手滑删库",解决不了"设备报废"。这两类事故在概率上差得很远,但一旦发生,后果的量级完全不在一个档次。
3.2 一致性要应用配合
快照的另一个坑是一致性。数据库在跑的时候,存储层看到的数据文件是"撕裂"的:索引写了一半,数据页还没落盘。直接对这样的文件打快照,恢复出来大概率起不来。
我们的做法是先让数据库进入只读并刷盘,再打快照,打完立刻恢复读写。整个动作控制在二十秒内,业务侧表现为一次短暂的重试。这个流程后来固化成了脚本,人不用记步骤。
3.3 三层怎么搭配
现在是这样分工的。快照负责小时级的回退,每六小时一次,留八份,覆盖两天。增量负责天级,binlog 每十五分钟归档一次,保留十四天。全量负责周级和月级,每周一次,月备留一年。
三层不是互相替代,是把恢复点目标切成三档。误删一张表,用快照,十分钟。误删一个库,用增量加全量,两小时。要查一年前的数据,翻月备,半天。
四、备份计划该写成什么样
4.1 一张表说清六件事
我们把所有备份任务收进一张配置文件,每条任务写六件事:备份什么、什么时候备、用什么方法、留多久、怎么校验、出问题通知谁。
前四件是常规,后两件是这三年加出来的。校验和通知如果不写进配置,就会变成"记得的时候做一下",而人会忘。
4.2 校验比备份更费心
校验分三层。文件层算校验和,确认传输没坏。对象层抽样恢复三张核心表,确认能查询。业务层每月做一次全量恢复演练,恢复到一台隔离机器上,跑一遍对账。
三层里,业务层花钱花时间,但只有它能发现"备份能用但数据不对"这类问题。我们遇到过索引丢了的情况,文件校验和对象抽样都过,查询性能差十倍。
那次是重建索引时中断,备份文件里少了两个二级索引。恢复出来能查,慢查询从每天三条涨到四百多条。后来把"恢复后跑一遍慢查询基线"写进了演练清单,基线固定十条 SQL,耗时超过两倍算失败。这个检查比看日志管用,因为它直接量的是业务感受。
4.3 配置长这样
下面是我们现在用的备份计划配置,删掉了环境相关的字段,结构和字段名保持原样。
project: order-db
owner: dba-oncall
timezone: "+08:00"
targets:
- name: order-primary
engine: mysql
size_gb: 820
schedule:
full:
cron: "0 2 * * 6" # 每周六 02:00
method: xtrabackup
compress: zstd
keep: 6
incremental:
cron: "0 2 * * 1-5" # 工作日 02:00
base: latest-full
keep: 14
snapshot:
cron: "0 */6 * * *" # 每 6 小时
keep: 8
consistency: readonly-flush-snapshot
max_freeze_seconds: 20
binlog:
archive_interval_minutes: 15
keep_days: 14
verify:
checksum: sha256
sample_tables: [orders, order_item, payment]
restore_drill_cron: "0 4 1 * *" # 每月 1 日 04:00
drill_target: isolated-vm
objectives:
rpo_minutes: 15
rto_minutes: 120
offsite:
enabled: true
delay_hours: 6
immutable_days: 7
alerts:
- on: backup_missed
after_minutes: 30
to: dba-oncall
- on: verify_failed
after_minutes: 0
to: dba-oncall
delay_hours 那一项是被勒索软件逼出来的:异地的副本延迟六小时才可写,出了事还有一份没被污染的。immutable_days 是七天不可删,防止有人拿着管理员权限把备份一起清了。
五、还剩两个洞
5.1 恢复演练的机器是借的
演练要一台隔离机器,我们一直是临时从测试环境借。测试环境忙的时候借不到,演练就往后推,去年有两次推到下个月。
后来定了一条:给备份留一台固定的小规格机器,只做恢复演练用,平时关机。这条写进了资产清单,谁都不能挪。
5.2 文件类数据的备份没人管
数据库的备份是我在管,但业务上传的附件、报表导出文件存在对象存储里,这块的备份一直没人认领。理论上对象存储有多副本,实际上误删是同步的,删了就是删了。
现在的过渡办法是每周做一次增量同步到另一个存储桶,版本保留三十天。这件事还没定责任人,是今年要补的。方案写了两版,难点不在同步,在于删掉的文件三十天内要能找回,而版本控制和访问权限分属两套系统,对不上。
常见问题
问:小库也要做增量吗?
答:一百 G 以下、每天写入量不大的库,全量加快照就够,增量带来的复杂度不划算。判断标准是恢复时间:如果全量恢复能在业务容忍的窗口内完成,就不必引入 binlog 重放这一层。
问:快照能不能代替备份?
答:不能。快照和源数据在同一套存储上,设备故障、误删卷、账号被盗这几种情况下会一起丢。快照适合做小时级回退,异地备份负责灾难场景,两者管的是不同的事故类型。
问:备份文件要校验到什么程度?
答:文件校验和只能证明文件没坏,证明不了数据可用。我们加了两层:抽样恢复核心表并跑一次查询,每月做一次完整恢复演练并跑对账。只做校验和的话,索引损坏、字符集错误这类问题查不出来。
问:恢复演练多久做一次比较合适?
答:数据量小、变更少的系统,一季度一次可以。核心交易库我们做每月一次,因为表结构和数据分布一直在变,半年前的演练流程放到今天可能已经跑不通。演练记录要写下恢复耗时,这个数字比"成功"两个字有用。
浙公网安备 33010602011771号