本文是公开报告的摘要与解读,事实以原始报告为准。
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。报告把备份测试被忽视归因于“没有人负责测试这个流程”,这是组织问题,不只是技术问题。
合上页面,先回答,再展开对照。
小程序版
微信扫码,随时查命令