oss

GitLab 2017 年数据库丢失事故:一份用于预发布环境的快照成了恢复方案

6 条来源 4 条一手来源 已翻译 2026年7月23号

正文
GitLab 联合创始人 Dmitriy Zaporozhets 与 Sid Sijbrandij 身穿蓝色 GitLab T 恤,并肩站在一条住宅街道上。

GitLab 联合创始人 Dmitriy Zaporozhets(左)与 Sid Sijbrandij 摄于 2016 年 12 月 27 日,距数据库中断事故还有 5 周。这张照片用于交代组织背景,并非事故或恢复现场。[6]

2017 年 1 月 31 日 23:30 UTC 左右,一名 GitLab 工程师发现 rm -rf 命令正在生产主库 db1.cluster.gitlab.com 上运行,原定目标其实是从库 db2。他在 1 至 2 秒后中止命令,约 300 GB 数据已经被删除。从库此时也无法接管服务:在修复复制的过程中,它的数据目录刚刚被清空。[1]

这条命令是事故中最容易被记住的部分,真正有助于理解事故的内容却在命令之外。

GitLab.com 中断约 18 小时,并永久丢失了约 6 小时内产生的数据库变更。项目方估计,至少 5,000 个项目、5,000 条评论和 700 个新用户账户受到影响。Git 仓库与 wiki 存放在别处,因而保留下来;丢失时段内的问题、合并请求、代码片段、账户以及其他存入 PostgreSQL 的工作则无法恢复。[1][2]

这起事件的主线是恢复系统失效,操作错误位于故障链的中段。负载、复制、运行手册、主机标识、备份工具及其版本、告警送达、存储位置和恢复吞吐能力,逐层削弱了安全余量。工程师为另一项实验手动创建并导入预发布环境的一份快照,最后成了唯一可用的恢复来源。复盘留下的长期教训很清楚:备份是一项端到端服务,定时命令只是其中一环。

图片背景:GitLab 联合创始人 Dmitriy Zaporozhets 与 Sid Sijbrandij 于 2016 年 12 月合影,时间就在事故发生前不久。照片中没有涉事工程师、数据库或恢复过程;它呈现的是一家年轻的开源公司,而这家公司随后公开应对事故,让自身基础设施以及对待操作人员的方式都以少见的透明度进入公众视野。[6]

一次普通的负载问题耗尽了退路

故障链在删除发生数小时前已经启动。17:20 UTC 左右,一名工程师为生产环境创建 LVM 快照,并将其载入预发布环境,以测试 pgpool-II 能否把查询分散到多台 PostgreSQL 服务器。常规快照任务每 24 小时运行一次,这项实验需要时间点更近的数据。后来,正是这份手动副本把永久数据丢失的时间窗口从接近一整天缩短到约 6 小时。[1]

19:00 左右,GitLab.com 的数据库负载开始攀升。垃圾信息是成因之一,GitLab 后来还发现了另一项消耗大量资源的任务:一名恶意用户举报 GitLab 员工账户后,滥用处置流程误将这名员工及其关联数据排入删除计划。工程师着手降低负载时,问题和合并请求下的评论已开始提交失败。[1]

23:00 左右,热备库的复制延迟已经十分严重,主库已经清理掉从库仍然需要的预写式日志(WAL)段。GitLab 当时没有归档这些 WAL 段,从库因此无法直接追上主库,只能使用 pg_basebackup 重建;整个重建流程先清空从库的数据目录。[1]

修复过程随即陷入判断困难。pg_basebackup 没有显示可供判断的进度,工程师一度判断进程已经挂起。工程师把 max_wal_senders 从 3 调到 32;PostgreSQL 随后无法重启,因为 max_connections 一直设为 8,000,于是该值又降到 2,000。strace 显示备份进程阻塞在 poll 调用上,却没有解释原因。事后复盘记录指出,这种无输出等待有时是主库准备发送数据时的正常表现,GitLab 当时的运行手册却没有写明这一行为。[1]

工程师进入 shell 时,原定工作始终针对从库。持续数小时的负载处置逐渐演变成手动重建复制;从库的数据目录已按设计清空,下一个合理的排查步骤也以空目录为前提。疲劳让这套流程中原本就薄弱的区分变成决定性因素,危险则早已存在于工作流程之中。

db1db2 在最危险的时刻太容易混淆

23:30 左右,工程师怀疑先前一次 pg_basebackup 尝试已将文件写回从库的数据目录。再次尝试前,需要先把这个目录清空。破坏性命令最终却落在主库上。[1]

独立报道记录了最直接的损失规模:删除中止时,约 300 GB 的目录只剩约 4.5 GB。报道还引用了 GitLab 在事故期间的坦率说明:5 种备份或复制手段要么不可靠,要么配置有误。[4] 这个数字很适合写进醒目的标题,暴露出的深层缺陷却是两台数据库主机共用一套操作权限。一名操作人员在高压修复过程中,能够对这对数据库主机中的任意一台执行同样的破坏性步骤。

GitLab 的纠正工作包括修改 shell 提示符,让不同主机和环境在视觉上更易区分;改进复制运行手册;以及把依赖手动命令的恢复操作自动化。[1] 这些控制措施分别作用于不同的失效时刻。醒目的提示符帮助操作人员在按下 Enter 前确认当前环境;经过演练验证的运行手册可以减少临场摸索;自动化则能把“清空从库并重建”变成一项受检查的操作,在删除任何内容之前核验角色、主机名、复制状态与允许的目标。

每项措施都只能覆盖一部分风险。红色的生产环境提示符久而久之会融入背景。文档负责指路,粘贴命令仍需其他控制措施拦截。自动化也会以机器速度放大建立在错误假设上的操作。更强的设计会叠加身份检查:生产环境与预发布环境使用不同的访问通道;主库与副本角色由查询确认,避免仅凭名称推断;破坏性工具要求输入与目标绑定的 token;例行重新同步操作也不会给同一名操作人员开放两台主机的通用删除入口。

复制留下了数据副本,也把故障边界带到了另一台主机

热备库是一种可用性手段。遇到普通硬件故障时,可用的从库可以升为主库。可这起事故的修复流程先主动清空了从库,它自然无法充当独立恢复副本。

这一区别很容易被忽视,因为复制看上去像连续备份:同一批数据在另一块磁盘、另一台主机上也有一份。然而,复制以保持节点一致为目标,删除操作和错误更新也会同步过去;针对整组复制节点的管理操作,会让两台主机同时受一次变更影响。本次事故中,从库所需的 WAL 段已经被主库清理,团队只能执行完整的重新同步;清空从库就在主库被删前移除了故障转移通道。[1]

PostgreSQL 9.6 的文档描述了当时缺失的补充手段。将已经完成的 WAL 段归档后,便能从文件系统备份启动,再重放变更,恢复到选定的时间点。[5] 因此,GitLab 的纠正事项除重建副本外,也包括研究连续归档与时间点恢复(PITR)。复制与 PITR 回答的是两类问题:另一台服务器现在能否接管服务? 以及 能否重建破坏性事件发生前的状态?

一项存储隔离确实发挥了作用。Git 仓库和 wiki 存放在受影响的 PostgreSQL 数据库之外。GitLab.com 中断期间,它们同样无法访问,但内容没有随数据库一起丢失。拥有本地克隆的开发者仍可继续修改代码,托管平台上的协作则陷入停顿。[1] 这种隔离保住了 Git 对象,却没能保住记录协作上下文的问题、评论、合并请求、代码片段、账户和 webhook。

因此,“代码还在”不足以概括事故影响。在开源项目中,一段审查说明、一份问题历史或一个新贡献者账户,都可以成为代码为何需要改动的唯一持久记录。分离二进制大对象存储可以限制影响范围,但每种数据存储仍需配套恢复方案,并覆盖该存储独自保存的用户工作。

文档列出的四套恢复手段,最终只有一份副本可用

GitLab 的最终复盘列出 4 种恢复手段:每日运行并上传至 Amazon S3 的 pg_dump 备份、每日复制到预发布环境的 LVM 快照、部分服务器使用的 Azure 磁盘快照,以及用于故障转移的 PostgreSQL 复制。真正开始恢复时,只有当天下午手动制作并存入预发布环境的 LVM 快照能够使用。[1]

逻辑备份通道在事故前已经失效。pg_dump 由一台普通应用服务器运行;这台主机缺少数据库集群的 PG_VERSION 文件,GitLab 的 Omnibus 软件包因而选用了 PostgreSQL 9.2 工具,目标数据库却是 PostgreSQL 9.6。主版本不匹配导致 pg_dump 退出。Cron 发出了失败邮件,但接收系统因这些自动邮件没有经过 DMARC 身份验证而将其拒收。S3 存储桶一直空着,团队无人知晓。[1]

NFS 服务器上已有 Azure 磁盘快照,数据库主机却没有启用。团队认定其他数据库流程已经足够,同时也清楚,跨存储账户恢复耗时可达数小时甚至数天。LVM 快照确实存在,其用途是刷新预发布环境,最初并未用于灾难恢复。其中一份创建于近 24 小时前,另一份之所以保存下来,仅因工程师在删除发生 6 小时前为测试创建了它。[1]

复盘中的根因分析追问:备份为何没有定期测试?答案直截了当:这项测试没有负责人。[1] 这句话串起了空存储桶、遭拒邮件、版本不匹配和从未计时的恢复流程。各个组件都有自己的配置,却没人负责验证恢复服务整体是否有效:生产环境的一次写入能否依次被捕获、存储、监测、恢复,并通过应用验证。

只监控进程是否成功退出,依然无法证明恢复可用。即便 pg_dump 正常生成了备份,文件仍面临多种风险:传输途中遭到截断、用于解密的密钥缺失、保留时间过短,或因可用 PostgreSQL 版本不兼容而无法载入。真正有价值的证据,是按计划把备份恢复到隔离环境中,再从应用层检查近期数据行、仓库、webhook、权限,以及数据库引用的所有外部对象。

恢复吞吐量决定了中断时长

就当时可用的副本而言,6 小时前的快照已经是最佳恢复点;恢复时间问题仍留在眼前。

GitLab 必须把预发布环境数据库从缓慢的 Azure 网络磁盘复制回生产环境。复盘记录的有效存储吞吐上限接近 60 Mbps,整个复制过程耗时约 18 小时;处理器和网络容量都没有成为瓶颈。2 月 1 日 17:00 UTC,数据库恢复上线,webhook 尚未恢复。工程师随后从该快照的另一份副本单独恢复 webhook,并在 18:00 左右完成最终检查。[1]

恢复点目标(RPO)与恢复时间目标(RTO)这两个常见缩写由此变成了具体损失。恢复点落后约 6 小时,17:20 UTC 之后的数据库工作无法重建;恢复耗时约 18 小时,在数据穿过缓慢存储的这段时间里,即便保存下来的 Git 仓库也无法经 GitLab.com 访问。[1] 快照频率限定历史丢失窗口的长度,恢复吞吐能力则限制保留下来的数据还要多久才能重新使用。

可信的恢复演练必须测量这两项指标。应复制规模和组成都接近生产环境的数据集,方便的小样本无法替代它;还要分别计时快照发现、权限变更、传输、数据库启动、WAL 重放、机密信息获取、应用迁移和面向用户的验证。应确认应急计算资源确实能够挂载备份所在的存储。假如方案写着“恢复 300 GB”,却从未让 300 GB 数据完整走过恢复通道,那么它的时间目标只是一个带着单位的愿望。

公开透明应让系统问题显形,同时保护操作人员

事故期间,GitLab 持续维护公开工作文档、发布状态更新,并直播恢复过程。最终复盘称,直播观众人数峰值接近 5,000。服务无法使用时,这种开放让用户得以了解影响范围与恢复进度;独立报道也得以区分被删除的数据库、未受影响的代码仓库,以及未受影响的自行托管 GitLab 实例。[1][4]

公开文档最初写出了涉事工程师的姓名。GitLab 后来表示,今后遇到同类事件会隐去姓名,因为其他人未必愿意承受这种曝光。[1] 在随后一篇专门讲述“team-member-1”的文章中,公司把注意力从个人归责转向系统,确认这名工程师状态良好,并表示事故前已经批准的晋升仍会照常推进。[3]

这份回应以工程整改为基础,同时保护当事人。GitLab 公开了命令执行过程、配置错误、职责缺位、存储限制和纠正事项,如此充分的细节十分少见。保护当事人,也让这种坦率的技术披露更容易延续。操作人员一旦预期单次错误足以断送职业生涯,便有理由延迟披露、隐瞒险情,也会回避那些能够暴露系统弱点的恢复工作。

不归咎个人的复盘原则仍要求落实整改。纠正措施针对的是让一次错误演变成灾难的条件:难以区分的主机、手动执行的破坏性修复、缺失的 WAL 归档、缺乏监控的备份、未经测试的恢复,以及职责无人认领。问责的落点,是改变这些条件并验证改动已经生效。

最小可信恢复系统

小团队即使没有 GitLab.com 在 2017 年的规模,也会继承同样的故障形态。一台主库、一台流复制副本、一份每夜转储和一个对象存储桶,足以凑出 4 个让人安心的名词,实际仍拿不出经过验证的恢复能力。

最低限度的可信系统要求更集中。首先为备份与恢复指定负责人。使用版本匹配的工具制作逻辑或物理备份,并把备份存放在数据库主机及其日常管理权限范围之外。WAL 的保留时长应满足明确的恢复点目标;失败消息经由独立于受监控任务的通道送达,且有人值守该通道。按计划自动恢复并验证应用数据,同时记录最近一次成功测试所用备份的年龄与恢复耗时。

操作流程也要增加防混淆措施。生产环境使用独立的凭据和终端,在提示符中显示数据库角色,用带检查的自动化封装副本重建;事故已经持续很久或团队疲劳时,生产环境的破坏性操作需要第二人复核。状态通道与运行手册应保存在其所描述的服务之外。完整恢复的基准测试,则要采用生产环境实际需要的数据量和存储类别。

无法为这些例行工作配备人员的团队,应缩减服务承诺:接受更长且有文档记录的恢复点目标;使用恢复行为已经验证的托管数据库;或减少需要作为权威来源保存的状态量。复制和未经测试的备份任务,不能列为彼此独立的恢复手段。

GitLab 的这次中断之所以留在人们记忆中,是因为一条命令在数秒内删除了数据库。真正的警示在随后 18 小时里逐渐显现:许多手段从未完成最后一步,也就是把已知完好的数据送回正常运行的服务。那份原本用于预发布环境的快照后来意外承担了恢复任务,正因为事故发生前,只有它在这条通道上走得足够远。

来源

  1. GitLab,《Postmortem of database outage of January 31》,2017 年 2 月 10 日——最终时间线、数据库拓扑、失效的恢复通道、恢复性能、影响、根因分析与纠正措施。
  2. GitLab,《GitLab.com database incident》,2017 年 2 月 1 日——同期事故记录、受影响的数据范围、初始恢复状态与 6 小时前的预发布环境副本。
  3. Marcia Ramos,GitLab,《How is team-member-1 doing?》,2017 年 3 月 17 日——项目方对操作人员状态、透明度与事故学习的后续说明,以及不撤销此前已批准晋升的决定。
  4. Simon Sharwood,The Register,《GitLab.com melts down after wrong directory deleted, backups fail》,2017 年 2 月 1 日——关于删除范围、备份状态与存放在预发布环境中的可用快照的同期独立报道。
  5. PostgreSQL 9.6 文档,《Continuous Archiving and Point-in-Time Recovery》——与 GitLab 当时所缺恢复层相关的 WAL 归档与重放模型。
  6. GitLab,《Gitlab-founders》,Wikimedia Commons——GitLab 官方发布的联合创始人 Dmitriy Zaporozhets 与 Sid Sijbrandij 合影,摄于 2016 年 12 月 27 日。
Previous OpenTitan 把信任根变成可检查的构建产物

Recommended In oss

Matched by subject and format