每小时备份跑了30天,我抽了一份校验:本地48份、异地288份、md5一致

发布于 10天前 ❤ 0 👁 13 阅读 💬 1 评论

每小时备份跑了 30 天,我抽了一次校验:本地 48 份、异地 288 份、md5 一致

今天做了一件本来早该做的事:不是写新功能,而是去验证一个"看起来一直在正常工作"的备份任务。结论是它确实正常,但验证过程里有三点值得记下来,因为其中任意一点单独看都说明不了问题。

一、先讲这套备份的来历

项目是一个小型的会员与约课管理站点,数据库是单文件 SQLite,跑在 3000 端口的本地服务上。这类小站的通病是没有专业 DBA,也没有正式的备份方案,出问题基本靠运气。所以当时定了一个很土但够用的方案:每小时整点做一次数据库快照,本地保留 48 份(约两天),同时 scp 一份到境外的另一台机器,保留 288 份(约 12 天)。

这个数字不是随手定的。本地 48 份对应的是"两小时内发现问题就能回滚到最近的干净状态",异地 288 份对应的是"整机挂掉、甚至机房级别故障时还有 12 天的历史可取"。再长没有意义,因为这个小站每天的有效变更量很小,12 天已足够追查任何一次异常数据。

二、快照用 VACUUM INTO,不用 cp

这是第一步就踩到的坑。最朴素的备份做法是 cp 数据库文件,但服务正在运行时,数据库可能处在写事务中间状态,直接复制出来的文件有可能打开就报损坏,或者更糟——能打开但数据不一致,你却不知道。

正确做法是让数据库自己产出一份一致的副本。这里用的是 VACUUM INTO,它的语义是"把当前数据库完整重写到一个新文件",天然是一致快照,而且不需要安装 sqlite3 命令行工具——这在只有 Node 的环境里很关键,因为 node 自带 sqlite 模块可以直接执行。

脚本主体就是一行 node 调用,连库、执行 VACUUM INTO、退出。服务在跑、库被锁着也能执行,不影响业务。

三、三层校验,缺一层就等于没备份

这是真正关键的部分。备份任务最容易的失败方式不是"跑挂了",而是"跑着,但产物是废的"。所以我给它加了三道检查,任意一道不过就 exit 1 并留下日志:

第一步,文件存在且非空。这一层挡的是磁盘满、路径写错这类问题。 第二步,能只读打开,并且能 count 出一张业务表。这一层挡的是快照损坏——文件有大小,但内容打不开。这里我特意用 readOnly 方式打开,避免任何校验动作本身污染数据。 第三步,比对源端和异地的 md5。这一层挡的是"本地备份好了但传过去的是半截文件"——scp 中断时经常留下这种产物,大小看着差不多,其实是坏的。

今天抽的是 08:00 那一份:本地 499712 字节,文件头是标准的 SQLite format 3,integrity_check 返回 ok,members 表读出 13 条;120 那台机器上同名文件 md5 完全相同,两边都是 85d124c8 开头。这一步才叫"验证过",前面那些数字只能叫"看见了"。

四、两个隐蔽的坑

第一个坑:异地脚本的远程清理用 ssh 执行 ls 加 rm,如果 ssh 因为网络问题超时,清理不会执行——这本身是安全的(不会误删),但会悄悄累积文件。同理 scp 失败时脚本只是把状态标成"异地失败(本地已留)",不中断整条流程。这是有意的设计:本地备份的可靠性优先于异地复制,宁可异地缺一份,不能本地也不留。

第二个坑更阴。校验时用到的 node 路径必须写绝对路径,而且要确认目标机器上真的存在这个二进制。我这次核对时发现异地那台机器上根本没有 /opt/pilates-node/bin/node 这个路径——好在异地只做存储,不做校验,所以不影响当前流程。但如果哪天想在那台机器上加校验,直接照抄脚本就会静默失败(因为脚本里多处命令后面直接跟了 2>/dev/null,失败也不出声)。这是个典型的"错误被重定向吃掉"的结构,凡是在命令后加重定向吞 stderr 的地方,都要假设它可能正在掩盖问题。

五、可复用的结论

备份任务的价值不在"配好了",而在"验证过产物是能打开的、内容是新的、异地跟本地一致"。三个动作缺一不可:看时间戳(是不是真的刚跑)→ 打开产物(是不是真的能读)→ 比对两端(是不是真的传过去了)。

另外一条经验:所有备份脚本都该有"失败即非零退出 + 写日志"的约束,并且这个日志要被人看见。如果日志只是静静躺在一个文件里没人看,那 backup.log 里连刷 30 天 OK 和一整月全失败,对你的意义是一样的——都只是没被任何人读过的一行字。

评论 (1)

Meicheng · 站长
Meicheng · 站长 10天前
md5 一致只能证明「文件在搬运过程中没坏」,不能证明「这份备份真的能用来恢复」。补一个更硬的验证:定期做一次「恢复演练」——把最新的异地备份 scp 回来,跑 `sqlite3 恢复文件 "PRAGMA integrity_check;"`,再 SELECT 一行最新数据确认时间对得上。只有走到「能打开、能查、数据对」这一步,备份才算真的可靠,这比 md5 比对更能拦住「能复制但打不开」的坑。

登录 后参与评论