2023-12-03 服务器事故
2023 年 12 月 3 日,ACM 服务器硬盘发生严重故障,导致从当晚 20 时 10 分开始,服务器部分数据已经无法写入,最终硬盘导致服务器虚拟机的文件系统损坏。此次事件造成数十个提交无法完全恢复。我们将在这篇文章中分享故障、修复故障及恢复数据的过程,以及我们对此次事故造成数据丢失的总结与反思。
注:本文中的所有时间,除非特别注明,否则默认为 2023 年 12 月 3 日,时区为北京时间(UTC+8)。
故障公告
如果你在 2023 年 12 月 3 日之后的一周里登录过 OJ,那么你应该看到过下面这个公告:
2023 年 12 月 3 日晚上,ACM 服务器发生故障,服务现已恢复。我们已经尽可能多地恢复数据,但故障仍然导致数十发提交(从 20:18:15 之后到 20:48 左右服务器停机)无法恢复,请同学们检查自己的提交是否受到影响。
涉及的提交包括以下题目:丑数 (1142)、Travelling Salesman Problem (1856)、unique_ptr (template version) (2066)、Basic Interpreter 2023 (2068)、二哥的优先队列 [改] (1856)。我们恢复了所有提交的提交代码,但是无法确认每个提交对应的用户。如果有获取代码的需求,请发邮件给 acmclassoj@googlegroups.com,并提供您的用户名、姓名学号、提交编号(可以从浏览器的历史记录里找)、题目编号。
对于无法恢复的数据,我们深表歉意。
2023 年 12 月 3 日
ACM服务器维护组
实际上,对于 ACM OJ 来说,停止服务几个小时的情况非常常见(至少在最近的一年里,这种情况发生了很多次),根本不值一提,更不需要发布故障公告。
这次的情况之所以需要发布故障公告,是因为我们发现故障造成了数据丢失。停止服务是小事情,因为这种情况不会造成用户与平台的不一致——用户只需要在 OJ 上线后再提交代码即可;但是如果造成了数据丢失,就会出现信息的不一致——用户觉得自己已经提交了,但是助教并不认为你提交了。因此,如果造成了数据丢失,我们必须第一时间通知用户,让用户有时间重新提交代码。如果故障导致已经结束的比赛中的有效提交丢失,那么我们还需要联系到相关的助教,并让助教允许用户重新提交,计入有效提交中。
故障时间线
- 20:10 物理机的日志不再写入。注意这个时间是我们重启服务后发现的最晚日志时间,即我们重启服务后看到最晚的一条日志在当晚 20 时 10 分。我们只能断言,20 时 10 分之后的某个时间,物理机尝试写入日志失败。
- 20:18:15 数据库数据不再写入。获得该时间的原理同上。这也是造成数据出问题的最早时间点,因此后来写在公告上的起始时间就是这个时间点。
- 20:39 服务器日志不再写入。获得该时间的原理同上。
- 20:48 左右,服务中断。
- 21:10 运维组到达物理机所在机房,发现机器都没有响应(VGA 输出、键盘均没有任何显示)。
- 21:13 强制重启服务器。此时 OJ 所在虚拟机的虚拟磁盘中的文件系统处于错误状态,OJ 服务无法启动。
- 21:26 对 OJ 虚拟磁盘进行 fsck 操作完成,OJ 服务启动。此后进行了其他虚拟机的检查。
- 21:26 OJ 重启后的第一发提交。
- 21:55 在检查日志的过程中发现,日志中提到了不存在的提交编号,发现数据丢失,立即停止了 OJ 服务。
- 22:59 从评测机缓存中恢复了全部丢失的代码。
- 23:40 在对 OJ 完成全面检查后,服务重新上线。
故障恢复及数据恢复
待物理机启动后,我们尝试重启各虚拟机(ACM 的服务全运行在各个虚拟机里,虚拟机为 VirtualBox),然而有几个虚拟机并不能正常启动,需要修复文件系统。待文件系统修复完成,我们便启动了所有的服务。
然而,当我们查看大家的提交是否可以正常评测时,我提了一个问题,我们的数据是完整的吗?——并不是!查看修复硬盘的日志,发现记录提交的 S3 服务储存目录有一个「指向未来」的错误。时间倒转肯定是不可能的(这是相对论保证的),出现这种情况的原因肯定是有真实存在过提交被忽略(丢失)了。
不好!
我们立刻停止了服务,以免更多的数据遭到破坏。我们需要尽可能多地恢复数据。但是最后的一个提交是多少呢?我们要从什么地方找回数据呢?数据库已经罢工了,日志也不完整,存放提交的 S3 服务储存目录倒是有一部分数据,但是仍然不够。
此外,我们还有一个备份服务器,会每天执行备份,但是备份时间点并不在故障发生时,也就是说,没有备份到丢失的数据。
这时,我们意识到提交一旦开始评测,提交的数据就会被抓取到评测机1,而我们的评测机在另一台物理机上,并没有丢失数据!于是,我们花费了将近两个小时,试图从残缺的日志和评测机数据中获得更多数据。
可惜的是,由于我们的用户数据与提交数据分离设计,评测机无法获得用户的数据,因此我们无法确认提交用户,因此我们无法创造出一个提交,而是必须由用户手动确认。
数据丢失原因
正常情况下,数据库的数据是不可能出现不一致的。因为数据库的设计保证了只有数据的确被写入了,才会返回成功。那么为什么会出现在 20 时 18 分后的数据被接受了,但是并没有真正写入呢?
这实际上是虚拟机的问题,在各家虚拟机性能竞争下,任何一点可以压榨的时间都是重要的,于是 VirtualBox 在默认情况下不会在收到 flush 指令2后真的将缓存的数据写回硬盘3。这直接打破了操作系统的抽象,导致了数据库的设计失效了。
至于为什么虚拟机的文件系统会发生问题,这很可能是背后的储存媒介——物理机硬盘存在问题。由于物理机的硬盘是硬件 RAID,有很多东西我们都无法获知。
总结与反思
造成这次事件的原因有很多:
- 没有为虚拟机正确配置 flush 指令;
- 数据硬盘多次故障,但是没有采取更多措施;
- 没有在上线前检查数据完整性;
- 提交数据没有实时备份;
等等。但是这些原因中,最重要的原因是我们没有在上线前检查数据完整性,这也是我们当时完全可以做的。
鉴于此次事件,我们:
- 为虚拟机虚拟硬盘强制开启了 flush;
- 设置了故障检查单,以减小数据损失的可能性;
- 计划更换服务器物理机。
目前的评测机的实现是,数据不会在评测完成后立刻删除,而是当数据在一天内没有被访问后才会被删除。 ↩︎
flush 指令是操作系统向硬盘发送的指令,要求硬盘将硬盘缓存中的数据写入硬盘,写入后才能返回,因此这个操作需要花费较长的时间。在虚拟机的情况下,这应当意味着需要将缓存的数据(可能缓存在内存,也可能缓存在物理机的硬盘缓存上)写入物理机的硬盘。 ↩︎
见 https://www.virtualbox.org/manual/UserManual.html#ts_ide-sata-flush。事实上,这个行为在绝大多数与商业相关的虚拟机里都存在。不过,KVM/QEMU 并没有这个问题,我们会考虑是否需要切换到 KVM/QEMU。 ↩︎