系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

一次本意重建从库的目录清空操作落在主库上,约 300 GB 数据被删,GitLab.com 停摆约 18 小时并丢失数小时数据。 本文是公开报告的摘要与解读,事实以原始报告为准

本文是公开报告的摘要与解读,事实以原始报告为准。

事件概要

2017 年 1 月 31 日,GitLab.com 发生数据库故障。当天 17:20 UTC,一名工程师对生产数据库做了 LVM 快照并加载到 staging,用于 pgpool-II 负载测试;这是当天 01:00 UTC 日常快照之外的额外快照。

约 19:00,数据库负载急剧上升,issue 和 merge request 的评论功能受到影响,控制负载花了数小时。报告把负载的一部分归因于垃圾信息,另一部分归因于一个后台任务:一名 GitLab 员工被滥用举报后,该任务开始删除其数据。

约 23:00,从库复制延迟并最终失败,原因是它需要的 WAL 段已从主库移除;由于没有启用 WAL 归档,从库只能手工重建。23:30 左右,工程师在重建过程中清空了主库的 PostgreSQL 数据目录,而不是从库;发现后一两秒内终止了进程,但约 300 GB 数据已被删除。

2 月 1 日 17:00 UTC,GitLab.com 数据库在没有 webhook 的情况下恢复;约 18:00 UTC 完成 webhook 和最终校验。报告称 GitLab.com 宕机约 18 小时。

时间线

时间(UTC) 事件
1 月 31 日 17:20 工程师对生产数据库做 LVM 快照并加载到 staging,用于 pgpool-II 负载测试
约 19:00 数据库负载急剧上升,评论功能受影响;控制负载耗时数小时
约 23:00 从库复制延迟并失败,所需 WAL 段已从主库移除;未启用 WAL 归档,只能手工重建
约 23:00–23:30 清空从库数据目录并运行 pg_basebackup;进程挂起,工程师调整 max_wal_senders 与 max_connections,并用 strace 排查
约 23:30 本意清理从库目录,实际清空主库目录;一两秒内终止,约 300 GB 数据已删除
2 月 1 日 17:00 数据库恢复,但未包含 webhook
2 月 1 日约 18:00 webhook 与最终校验完成,报告称宕机约 18 小时

影响面

GitLab.com 停机约 18 小时。Git 仓库和 wiki 在故障期间不可用,但没有丢失;GitLab Enterprise、GitHost 和自建 CE 用户不受影响。

数据丢失窗口按报告“数据丢失”一节的表述为 1 月 31 日 17:20 至 23:30 UTC;报告开头也出现过 17:20 至 00:00 UTC 的表述,两者并不完全一致。报告估计约 5,000 个项目、5,000 条评论和 700 个新用户账号受到影响;17:20 之后创建的项目只能从 Git 仓库恢复基本信息,issue 和 merge request 无法恢复。

更严重的是恢复能力同时失效:S3 备份桶是空的;备份任务用 pg_dump 9.2 连接 PostgreSQL 9.6,主版本不匹配导致任务失败;cron 失败邮件因未配置 DMARC 被拒收;Azure 磁盘快照只为 NFS 服务器启用,没有覆盖数据库服务器;两块 LVM 快照分别约为 24 小时和 6 小时前;从库数据也已被清空,无法作为恢复源。

根因

直接原因是操作落在了错误的主机:工程师以为在清理从库,实际清空的是主库。报告没有给出被执行的完整命令,只描述了“清空 PostgreSQL 数据目录”这一动作;因此不应把根因简化为某一条具体的删除命令。

复制为何需要重建,是事故链的前一环:负载上升后,从库需要的 WAL 段已从主库移除,复制中断;由于没有启用 WAL 归档,无法通过归档补齐,只能手工重建从库。报告还提到 max_wal_senders 当时使用默认值 3,工程师将其提高到 32;随后 PostgreSQL 又因信号量限制无法启动,max_connections 从 8000 降到 2000 后才恢复。

备份体系的多重失效让事故从“复制中断”升级为“数据丢失”。pg_dump 版本不匹配、S3 桶为空、失败邮件被拒收、数据库服务器没有 Azure 磁盘快照、LVM 快照不是为灾难恢复设计、从库也已被清空,这些因素叠加,使可用的恢复点只剩下约 6 小时前的 LVM 快照。报告还指出,没有人负责测试备份恢复流程。

处置与恢复

团队选择约 6 小时前的 LVM 快照,以尽量减少数据丢失。他们没有直接复制 LVM 快照,而是把 staging 的 PostgreSQL 数据目录复制到生产主机;staging 使用的是限速约 60 Mbps 的 Azure classic storage,复制耗时约 18 小时。

恢复后的数据库对应 1 月 31 日 17:20 UTC 的状态。团队先用不含 webhook 的数据库副本恢复生产,再用另一份副本恢复 webhook 表,并把数据库序列增加 100,000 以避免 ID 重用。2 月 1 日 17:00 UTC 生产恢复但无 webhook,约 18:00 UTC 完成 webhook 和最终校验。

GitLab 同时公开了恢复文档、直播了恢复过程,并在 Twitter 上持续更新状态。报告列出的改进包括:在 shell 提示符中更清楚地标识主机和环境;用 Prometheus 监控备份并提供公开的备份仪表盘;设置合理的 max_connections;启用时间点恢复和持续 WAL 归档;每小时 LVM 快照,并为生产数据库增加 Azure 磁盘快照;把 staging 迁到 ARM 环境并重建生产从库;自动化测试 PostgreSQL 备份恢复;完善复制文档和 runbook;评估 pgbarman、WAL-E 和流式恢复;明确数据持久性的负责人;增加多个从库和 pgpool-II 负载均衡;改进滥用举报流程;为非数据库基础设施制定灾难恢复计划。

教训

第一,目标不是让人永远不犯错,而是让人犯错后能快速恢复。报告明确说,理想环境是“人们可以犯错,但能轻松、快速地恢复”;只靠封禁危险命令,无法替代可恢复性设计。

第二,备份只有经过恢复验证才算备份。版本不匹配、空桶、告警邮件被拒收、快照未覆盖数据库,这些问题在事故前都没有暴露,因为恢复流程没有被定期测试。

第三,主机身份必须一眼可辨。生产与从库的提示符、颜色、跳板机和确认步骤,都应该降低“在错误主机上执行正确命令”的概率。

第四,WAL 归档和时间点恢复是数据库的基本能力。没有归档,复制一旦断裂就只能重建;没有 PITR,恢复点就被快照间隔限制。

第五,数据持久性需要明确 owner。报告把备份测试被忽视归因于“没有人负责测试这个流程”,这是组织问题,不只是技术问题。

对本站读者的检查清单

恢复手册与同类复盘

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么一次本意重建从库的操作,最终删掉了主库数据?报告里有哪些防护缺口?
  2. 为什么 GitLab 最终只能恢复到 1 月 31 日 17:20 UTC 的状态?
  3. 从备份与恢复角度,这次事故最重要的三个教训是什么?
答案
  1. 工程师在错误的主机上清空了 PostgreSQL 数据目录,报告没有给出完整命令;防护缺口包括主机身份不清晰、缺少目标确认、从库重建流程不熟、复制中断后只能手工重建。
  2. 因为可用的恢复源只有约 6 小时前的 LVM 快照:S3 备份为空、pg_dump 版本不匹配、失败邮件被拒收、数据库服务器没有 Azure 磁盘快照,从库也已被清空。
  3. 备份必须定期做恢复演练;主机身份和危险操作要有可验证的防呆设计;数据持久性要有明确 owner,并补齐 WAL 归档、PITR 和多副本能力。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令