只活在运行中的数据库里的结构
这个服务所做的一切都在版本控制里。至少代码是。它运行其上的数据库是 32 张表,其中 15 张在仓库里任何地方都没有定义 — 没有 CREATE TABLE,没有结构文件,什么都没有。如果服务器丢了,代码会一字不差地回来,而数据的形状只能从代码碰巧要什么去反推。
这些数字来自把运行中的数据库与仓库里的 736 个文件、5,671,710 个字符做比对。它们不是估计。
半套结构是怎么丢的
这不是马虎,而这正是它值得写下来的原因。单看每一步都是合理的。
在现有代码之前建的表从来没被写下来,因为当时它们就是在那儿。此后新增的表是通过迁移脚本来的,而那些脚本确实在仓库里 — 那 17 处定义就是从这来的。更晚加上的列,是在命令行敲一行 ALTER TABLE 加的,因为敲它比为一个一秒钟的改动写脚本要快。
索引是最糟的情形。索引是在某个东西变慢的时候加的,就在它变慢的那一刻,而且立刻就解决了问题。它不留下任何产物,没有东西可审,忘了也不会有什么失败。这里三十二个二级索引中有十一个正是这么来的,而且哪里都没有记录。
存放计数器本身的那张表,166,438 行,就在这十五张没有定义的表里。
真正会坏的是什么
不多,直到某个特定时刻:数据库从转储恢复的时候。
转储带着结构,所以直接恢复没问题。危险的是任何重建表而不是恢复表的路径 — 迁到另一台机器、只导入部分表、从脚本重新建一张。数据回来了,索引悄悄地没回来。什么都没有失败。查询返回正确的答案。它们只是变慢了,而原因不可见,因为关于那个索引是什么的唯一记录,就在那台已经没有它的机器上。
这是值得起个名字的失效方式:缺失的索引不产生错误,它产生一个更慢的正确答案。没有测试能抓住它,因为测试都通过。
在此之前我们做什么
诚实的说法是:这件事没有修好。结构今天不在仓库里。现有的东西比一个修复要小,但比没有强:
在任何破坏性操作之前先做转储,存在网站根目录之外。一次真正执行过、而不是假定过的恢复。以及一份短名单,每次恢复之后核对一遍,列出已知只存在于运行中数据库里的那些索引 — 因为机器是可以被问「你有哪些索引」的,而答案可以和上一次的对照。
一般化的说法不止适用于数据库。如果系统的一部分只存在于运行中的系统里,你手上没有它的副本,你手上有一名人质。对任何一块基础设施值得问的问题,不是「有没有备份」,而是「如果这台机器消失了,有什么是无法从仓库重建的」。这里的答案是十五张表和十一个索引,弄清楚它花了一个下午。