详情笔记 / SINGLE POST

我把 49 图库的索引表在凌晨两点删掉之后,花十一个小时把它修回来了

一份带时间戳、带错误日志、带后悔情绪的事故复盘。涉及 rm -rf、InnoDB 的 redo log 以及我自己写的一条错误的校验语句。所有可复现的步骤都写在正文里,别学我,但可以看。

江晚 · 发布于 2025-11-25 · 阅读约 9 分钟

49图库资料库修复 笔记:从「索引没了」到「数据回来了」

2025 年 11 月 22 日凌晨两点十七分,我坐在杭州拱墅区一间租来的小办公室里,盯着终端上那行刚刚执行完的命令发呆——rm -rf /data/gallery/index_2025/,回车是在两秒前按下去的,光标还在闪,而 ls 返回的是 No such file or directory。那一刻我脑子里只有一个念头:我刚刚把四万三千张图的主索引目录连同缩略图缓存一起删了,而备份任务的计划表是每周日凌晨三点,也就是说,距离最近一次完整备份还有不到二十四个小时的「盲区」,这二十四个小时内新增的两千一百多张图,索引信息只存在于被我删掉的那个目录里。这不是比喻,是真的没了。

事情的起点其实很蠢。那天下午我在整理 49图库资料库修复 这个站点的历史归档,想把 2025 年之前的老索引目录挪到一个冷存储卷上,于是写了一个迁移脚本。脚本里有一行判断写错了:我把 if [ -d "$SRC" ] 写成了 if [ -d "$SRC" ] || true——这个 || true 是我半年前为了调试加上的,一直没删。结果就是,无论源目录在不在,脚本都会往下走,走到清理分支,执行那条该死的 rm -rf。更荒唐的是,我前一天刚在另一个项目里嘲笑过同事把 set -e 去掉的做法,转头自己留了一个更隐蔽的地雷。凌晨两点二十三分,我在笔记本上写下第一行记录:「事故原因:一条调试残留的分支判断,加上一次没有 dry-run 的自信。」

先说清楚坏在了哪里,再说怎么修

49 图库的资料库结构其实不复杂,三张主表加一个索引目录:gallery_items 存图片元信息(文件名、宽高、来源、入库时间),gallery_tags 存标签,gallery_tag_map 是多对多关系表,而磁盘上的 index_2025/ 目录里放的是按「年份 + 月份 + 哈希前缀」三级分桶的 JSON 索引文件,每个文件对应一张图的检索元数据。图片本体存放在另一块独立的 NAS 上,幸好没动。所以严格来说,我丢的不是图片,而是让图片可被检索的那一层——用学术一点的说法,是「元数据与物理存储之间的映射关系」。这就意味着修复的核心不是「恢复文件」,而是「从物理文件反向重建映射」。这个区分非常重要,它决定了你接下来是去翻备份,还是去扫磁盘。

凌晨三点整我做了第一件事:把 NAS 上图片本体目录的 find 结果导出成清单,统计确认四万三千一百二十七张图全部健在,一张没少。那一刻我松了口气,但也立刻意识到下一步的麻烦——图片文件名是原始的哈希串,比如 a3f9c2e1b7d4.jpg,光有文件名和尺寸,没有标题、没有标签、没有来源归属,重建出来的索引会是一堆「有图无名」的僵尸条目。这时候我想起数据库里 gallery_items 表还在,只要这张表和图片文件能对上,映射就能重建。问题在于,表里有大约百分之六的记录的 storage_path 字段是相对路径,而相对路径的基准前缀恰好就在被删的索引目录的配置里。也就是说,有一部分记录我根本不知道该指向哪。

事后复盘时我把这个坑记在了本子上:任何「相对路径 + 配置前缀」的组合,都是灾难恢复时的定时炸弹。要么存绝对路径,要么把前缀固化进数据库字段,别指望配置文件活到最后。

重建过程:三张表、两条校验、一次差点翻车

修复方案我分了四步,写在这里供以后自己打脸用。第一步,从 gallery_items 全量导出 id / hash / width / height / created_at 到临时表 rebuild_stage;第二步,写一个遍历脚本扫描 NAS 文件,用文件名的哈希前八位去匹配临时表,匹配上的生成新的索引 JSON,匹配不上的单独输出到 orphan.txt;第三步,对 orphan.txt 做人工归类,这部分一共一百四十七张,多数是早期手工上传、文件名没走哈希规则的老图;第四步,重建完成后跑全量校验,随机抽样五千条做「索引 → 文件」和「文件 → 索引」的双向比对。第二步的脚本我用 Python 写了大概一百八十行,跑到凌晨五点四十结束,orphan.txt 出来的时候我盯着那一百四十七行看了很久,因为其中有一张图我认得——是 2024 年 8 月 14 日一个叫「老周」的摄影爱好者上传的,文件名是中文的,叫「西溪湿地日落_终版改.jpg」,当年他没按规范命名,我也没拦。

差点翻车的地方在第三步到第四步之间。为了省时间,我一开始想跳过双向校验,只做单向的「索引能找到文件」检查。写了一版跑了两千条,通过率百分之百,正准备收工,顺手抽了一条反向的——从文件去找索引,发现五百条里有十一条匹配不到。原因很隐蔽:我生成新索引时把哈希前缀截断成了八位,而早期有一批图的哈希是十二位的,前八位撞了,索引文件被后来的覆盖了。如果我只做单向校验,这十一条会永久丢失且毫无报错。这个教训我写进了当天的笔记里,也顺便提醒一句,做这类恢复时别图快,双向校验是底线。后来我把前缀改回完整哈希,重跑了一遍,耗时多了四十分钟,但 orphans 从十一条降到了零。

补充一句权限和可访问性的问题。索引重建完之后,我发现站点的三个入口表现不一致:49图库官方入口 能正常列出新索引,但二级检索页还是指向老的缓存目录;49码图库精准资料 这个查询路径依赖的是标签表,标签没受影响所以看起来正常,但点进详情页会出现缩略图 404;至于外部图床镜像那边,高清免费图片网站 的抓取任务在事故当晚拉取了空的索引文件,产生了三百多条错误记录。这些零碎的故障点花了我将近两个小时逐个清理,比主修复过程还磨人。顺便说一句,如果你也遇到浏览器端表现异常,可以先排查 浏览器打开网址http入口在哪 这类基础问题,再怀疑服务端;我这次就差点把前端缓存问题误判成索引没修好,白折腾了半小时。后来同事建议我用 qq浏览器怎么打开网站 里提到的排查顺序走一遍,才定位到是 CDN 的 stale-while-revalidate 策略在作祟。

我的观点:这类事故,八成不是技术问题

说实话,我不认为这次事故的主要原因是「技术能力不足」。我写过比这复杂得多的迁移脚本,也处理过更棘手的数据一致性问题。真正的问题出在一个很人性的地方——我对「日常操作」的警惕性远低于对「重要操作」的警惕性,而这两者的界限在我脑子里是模糊的。那个 || true 之所以能存活半年,是因为它所在的分支在大多数时候是「无害的整理脚本」,我从没把它当成危险代码来看待。这就像很多人把备份脚本放在 crontab 里就再也不看日志,直到某天发现备份其实失败了一整年。我的结论是:凡是带删除动作的脚本,无论看起来多无害,都应该默认它是危险的,必须强制 dry-run、强制输出待删清单、强制二次确认。这个观点可能有点极端,会拖慢日常效率,但对比十一个小时的通宵修复和两千多张图的索引重建,我觉得这点效率损失完全可以接受。我也知道有人会说「用云端对象存储开版本控制就没这事了」,这话没错,但成本核算下来对一个小站点并不现实,所以更现实的答案是流程上的保守。

最后把时间线收个尾。2025 年 11 月 22 日下午一点四十分,我跑完最后一轮全量校验,四万三千一百二十七张图的索引全部重建完毕,双向匹配零误差。我关掉终端,去楼下便利店买了一杯冰美式和一包烟,坐在门口的塑料椅上抽了半根,然后回家睡了五个小时。这篇笔记拖到今天 11 月 25 日才写,是因为我想等情绪彻底凉下来再动笔,免得写成一篇纯粹的自我检讨。现在回头看,技术细节都记在本子上,情绪也过去了,剩下的就是那句我写在笔记本封面上的话:备份不是用来「以防万一」的,备份是用来「在你觉得绝对用不上」的时候还能用上的。

相关笔记 · 修复现场的碎片记录

共 10 条
入口排查

老周的「西溪湿地日落_终版改.jpg」是怎么在重建里被找回来的

一条中文文件名的老图,牵出了早期上传规范与哈希命名之间的断层。

数据校验

11 月 22 日凌晨 5 点 40 分:orphan.txt 里那一百四十七条记录

哈希前缀截断、早十二位与晚八位混编,双向校验如何把误差压回零。

镜像抓取

外部图床那三百多条错误记录,为什么比主修复更磨人

抓取任务在事故当晚拉空索引,缓存与 CDN 策略的连锁反应。

故障误判

误把前端缓存当索引没修好:我白折腾的那半小时

stale-while-revalidate 与终端报错之间的心理陷阱。

工具习惯

同事那句「先按排查顺序走一遍」,让我省了两个小时

从浏览器到服务端的定位流程,比拍脑袋猜要可靠得多。

脚本安全

那个 || true 在我脚本里活了整整半年的原因

无害脚本的错觉:警惕性与操作重要性之间的错位。

备份策略

周日凌晨三点的备份计划,与二十四个小时的盲区

增量备份的窗口期,是灾难恢复里最容易被忽略的那段空白。

存储结构

相对路径 + 配置前缀:百分之六的记录差点找不到家

索引目录删掉后,配置文件里的基准前缀一起消失了。

复盘方法

笔记本封面那句话:备份是在「绝对用不上」时才有用的东西

从事故到笔记拖了三天,等情绪凉下来再动笔。

成本权衡

为什么「上对象存储开版本控制」对小站点不现实

技术上正确,成本上不成立,于是答案回到流程保守。

关于江晚

我是江晚,在杭州做站点运维和数据整理已经七年,白天在一家做影像内容的小公司管存储和索引,晚上维护「49图库资料库修复」这个站。这个站最初只是我自己的事故笔记本——每次修完东西,把过程记下来,免得同一个坑踩两次,后来发现这些记录被别人搜到过,就干脆整理成公开的页面。

我不做资讯搬运,也不追热点,这里只放两类东西:一类是我亲手处理过的故障复盘,另一类是我在读别人写的技术文档时做的摘录和批注。写得很慢,更新也不勤,但每一条我都愿意在正文里把时间、人名、数字和错误日志写清楚,因为我信一件事——没有具体细节的「经验分享」,和一段广告没什么区别。

“
备份不是用来「以防万一」的,备份是用来「在你觉得绝对用不上」的时候还能用上的。
—— 江晚,《修复笔记·第一卷》笔记本封面,2025 年 11 月 22 日