凌晨的一条命令,能毁掉多少东西?GitLab给出的答案是:约300GB生产数据,18小时服务中断,6小时数据永久丢失。
2017年1月31日,一名工程师在深夜执行了一条删除命令。他以为自己在操作副本,实际上敲在了主库上。一两秒之内,GitLab.com的生产数据库被清空了大半。
更糟的还在后面。当团队去找备份时,五个备份方案,没有一个能用。
命令本身没有回头路
那条命令是 rm -Rvf。rm 负责删除文件,-r 让它递归,删掉目录和目录下的一切,-f 强制生效,不问确认、不报缺失错误,-v 则把删掉的每个文件都打印出来。
GitLab的实时事故记录里,这条命令的完整形态是:
rm -Rvf /var/opt/gitlab/postgresql/data
rm 没有回收站。当时CEO建议尝试恢复被删文件,实时文档里的回答很直接:"不可能!rm -Rvf"。另一位工程师问起打开的文件描述符,也行不通——PostgreSQL不会把所有文件都保持打开状态。数据目录一旦被解除链接,唯一的出路就是备份。
副本为什么先坏了
事情的起点是复制延迟。PostgreSQL的复制机制是把预写日志(WAL)从主库流式传到副本。主库只保留有限量的WAL,副本落后太多,它需要的日志段就会被回收,靠流式复制永远追不上。
标准的安全网是WAL归档:主库把每个完成的日志段复制到别处,副本或恢复操作就能取回旧段。GitLab.com当时没有启用WAL归档,所以唯一的修复办法是清空副本的数据目录,用 pg_basebackup 把整个数据库重新拷一遍。
问题就出在这个夜里。pg_basebackup 先抱怨 max_wal_senders 不够,工程师把它从3调到32。PostgreSQL又因为信号量过多拒绝重启——max_connections 被设成了8000,这个值用了将近一年,于是降到2000。然后 pg_basebackup 就静静地卡在那里,没有任何输出。
按照事故复盘的说法,pg_basebackup 会"静静地等待"主库,另一位生产工程师说最长可达10分钟。这一点既不在操作手册里,文档里也没写清楚。这位工程师此前说过自己大概在当地时间23点左右下班,他当时想,也许 pg_basebackup 只是对"数据目录必须为空"这件事过于较真,于是决定把目录删掉。
他正在输入的终端,连的是 db1。
两个主机名,只差一个字符
db1.cluster.gitlab.com 和 db2.cluster.gitlab.com,区别只有一个字符。复盘里的原话是:"不幸的是,这个操作被执行在了主库上。工程师在意识到错误后一两秒内终止了进程,但此时约300GB数据已经被删除。"
约310GB里,只剩下4.5GB。
五个备份,一个都没用
真正让这起事故出名的,是备份环节。复盘写道:"这意味着在一切都太晚之前,我们从未意识到备份一直在失败。"
每晚发往S3的 pg_dump 因为PostgreSQL版本不匹配而静默失败,失败通知邮件又被退回。次日发布的文章用一句话总结,后来被反复引用:"换句话说,部署的五种备份/复制技术中,没有一种在可靠运行,或者根本就没有配置好。我们最终恢复的是一份六小时前的备份。"
恢复过程是把暂存的数据目录拷回生产环境,走的是Azure经典的非高级网络磁盘,速度被限制在约60Mbps。这一拷就是大约18个小时。恢复完成后,数据库序列被整体加了100000。
丢了什么,没丢什么
大约17:20到23:25 UTC之间写入的内容全部丢失:
- 约5000个项目
- 约5000条评论
- 700个新注册账号
Git仓库和wiki存放在数据库之外,没有受影响;自管理的GitLab安装也完全没受影响。
GitLab把整个恢复过程放在了公开场合进行。事故记录是一份公开的Google文档,实时更新,恢复过程在YouTube上直播,峰值约5000人观看。复盘里提到,它"有好几个小时是YouTube上排名第二的直播"。
实时文档里还有一句相当有人情味的话:这位工程师"说自己今天最好不要再运行任何带sudo的命令了",把恢复工作交给了同事。
复盘没有把矛头指向工程师
事故复盘对这位工程师做了匿名处理,并表示GitLab"在未来的案例中会隐去姓名"。实时文档里之所以出现姓名缩写,是因为他自己加了上去。
复盘的五问分析最终落在流程上:"为什么备份流程没有定期测试?——因为没有归属,结果没有人负责测试这个流程。"
两个主机名只差一个字符,终端看起来又几乎一样。这条命令至今仍躺在很多人的基础设施里,等着被敲下去。
热门跟贴