Foursquare 的 MongoDB 宕机事件

阅读数:26 2010 年 11 月 2 日

话题:架构DevOpsAI

Foursquare最近经历了长达 11 小时的宕机。宕机是由于他们的 MongoDB 出现了数据的不均衡增长,而这一点并没有被事先检测到。由于数据分散的原因,当 Foursquare 试图增加一个分区时没有成功,需要数据库离线才能对数据进行压缩。这也导致了系统宕机时间的延长。这篇文章提供了更多细节,记录当时发生了什么,为什么系统会宕机,Foursquare 和 10Gen 对事故的响应。

Foursquare 是一个成长极为迅速的基于位置服务的社交网络,8 月份注册用户已经达到三百万。10 月 4 日,由于快速增长的数据,Foursquare 经历了 11 个小时的宕机。Foursquare 的运营总监 Nathan Folkman 写了一篇博客,一方面向用户道歉,一方面提供了事故发生的一些技术细节。随后 10gen 的首席技术官 Eliot Horowitz 写了更为详细的文章,发布到 MongoDB 的用户邮件列表上。10gen 开发了 MongoDB,并为 Foursquare 提供技术支持。这篇分析文章引发了热议,包括 Foursquare 的工程师 Harry Heymann,提供了更多细节。

基础系统架构

本次受到影响的关键系统是 Foursquare 的用户登入数据库。不像许多历史数据库只有小部分数据需要随时访问,10gen 的首席执行官告诉我们,“由于种种原因整个数据库被频繁访问时,会导致工作集与整个数据库的大小差不多”。因此,数据库对内存大小的需求就等于数据库中所有数据量的大小。假如数据库的大小超过了机器内存,机器性能就会出现很大的摇摆,4 块硬盘不能再负载更多的 I/O 请求。对于这个的问题,他说,“被频繁访问文档,其频率远远高于你们的预期”。

最初数据库运行在一个单实例 EC2 节点上,66G 内存。大约两个月之前,Foursquare 几乎耗尽了所有内存,于是他们把系统迁移到了具备两个 Shard 节点的集群环境中。每个 Shard 有 66G 内存,为了冗余,数据被复制到 Slave 节点。经过这次迁移之后,每个 Shard 大概有 33G 的数据。由于 Shard 上的数据是“通过用户 ID 平均分成 200 片进行存储的”,结果就是,给定用户的所有数据会被保存在一个独立 Shard 上。

宕机

由于用户持续增长,分区以一种不均衡的方式在增长,Horowitz 指出:

很容易想到会发生什么:假如某个用户子集的活跃程度高于其他人,可以想象,这些更新都是在同一个 Shard 进行的。

MongoDB 会对片进行分割,每到 200M 就切分为 2 个 100M。最后的结果就是当整个系统的数据超过 116GB 时,一个分区数据是 50G,另一个会达到极限 66G,超出的请求会分散到磁盘上,性能大幅下降,从而导致系统宕机。

运营团队试图修复系统,为数据库增加了第三个 Shard,希望把系统数据的 %5 转移到新的 Shard 上,这样就和内存相匹配了。他们仅仅迁移了 5% 的数据,Horowitz 说,“我们试图迁移最少的数据,让网站尽可能快的恢复”。但是,这种方式并没有缓解整个 Shard 的性能问题。正如 Horowitz 所言:

...... 我们最终发现问题出在 Shard0 的碎片上。从本质上来说,虽然我们把 5% 的数据从 Shard0 上迁移到了第三个新的 Shard 上,但是数据文件,碎片状态,仍然需要占用相同数量的内存。这是因为 Foursquare 的 Check-in 文档很小(每个 300 字节),很多文档才能填满一个 4KB 的页(Page)。移走了 5% 的碎片,只是让每个页变得稀疏了一些,而不是整个删除 Page。

迁移数据的稀疏是因为数据太小了,而且由于“在 Shard 中 Key 的顺序和插入顺序是不一样的,妨碍了在连续的块中迁移数据”。为了解决性能问题,他们不得不压缩整个 Shard。目前 MongoDB 仅支持对 Shard 的离线压缩。数据的压缩,加上 EBS(Elastic Block storage)的缓慢,导致整个过程花费了 4 小时。在为 Shard 释放了 5% 的空间之后,系统终于重新上线,至此,本次宕机事故持续了整整 11 个小时。在这次突发事件里,没有数据丢失。

跟进

系统恢复之后,Foursquare 增加了多台额外的 Shard,保证分布数据的均衡。为了解决碎片问题,他们对每个 Slave 节点的分区进行压缩,然后把 Slave 节点切换为 Master 节点,再对 Master 节点进行压缩。最后每个分区使用大约 20GB 的空间。

Horowitz 表示:

10gen 团队正在实现在线增量式压缩的功能,压缩对象包括数据文件和索引。我们知道这是非 Shard 系统也会关注的功能。未来几周内会有更多详细信息。

Horowitz 指出:

需要记住的是,一旦你的系统处于最大负载,而且系统对象又非常小的情况下,就很难不停机增加更多容量。

但是,如果你能提前去做这件事,就可以在不需要停机的情况下在线为系统增加更多的 Shard。

Foursquare 小组也做出响应,承诺加强沟通,改进操作流程,如 Folkman 所言:

我们当然希望这种情况能够逐渐减少。很显然,将来当我们的系统过载时,关掉某些功能比整个系统停机要好得多。

Heymann 表示:

整体而言,我们仍然会为 Foursquare 的 MongoDB 提供大型风扇,希望能用更久一些。 

社区反应

针对这篇文章,社区提出了一系列问题:

  1. Nat 问:

    RepairDatabase() 能否利用 CPU 的多核能力?考虑到数据已经被分割为块,是否可以进行并行处理?在互联网领域,4 小时的停机时间让人感觉太久了。



    Horowitz 答:

    现在还不行。我们正在进行后台压缩功能的开发,以后就不用离线压缩了。

  2. Alex Popescu 问:

    有没有真正的解决方案去处理块迁移和 Page 大小的问题?



    Horowitz 答:

    是的,我们正在实现在线压缩。

  3. Suhail Doshi 问:

    我觉得最明显的问题是:如何避免 MongoDB 节点的冗余?什么时候该提供新节点?

    假定你们能监控所有事情,我们该看哪些部分?我们怎么知道呢?如果你是一家规模和功能会不断变化的公司,会发生什么情况?

    刚开始启动是似乎很难规划容量。

    Horowitz 答:

    这取决于应用系统。在某些案例中你需要把所有的索引都放在内存里,而在其他案例中可能只需要一个小工作集。

    一个好的方式是,计算出你在 10 分钟之内需要处理多少数据,索引和文档。确认可以把数据存储在内存,或者在同一时间从磁盘读取。

  4. Nat 还问了反向压力问题:“看起来当数据的增长超过内存时,性能会明显降低”。

    对于该问题 Roger Binns 补充如下:





还有一些讨论,是关于固态硬盘驱动能否改善性能,但并没有确切的结论说明固态硬盘能够影响性能。还有人想知道为什么用户 ID 分区会以不均衡的方式增长。通过用户 ID 划分的分区大体上来说是均衡的──可能是有倾向性的分区(例如把旧用户放到一个 Shard 上)会导致不均衡的数据增长。

监控和未来的方向

为了更好的理解这个案例中的问题,我们采访了 10gen 的 CEO Dwight Merriman。我们问了如何更好的监控大规模部署的 MongoDB,他回答说需要依赖很多监控工具,Munin 是很常用的工具,而且它有 MongoDB 的插件。我们问道:

根据以上描述,应该能够对 MongoDB 进程使用的常驻内存进行监控,进而在 Shard 内存很低时告警,是这样吗?

假如数据库比内存大,MongoDB 会像其他数据库一样,倾向于把所有内存当作 Cache 使用。那么使用全部内存就不是什么问题。相反的情况,我们就需要知道什么时候工作集最接近内存大小。对于所有数据库来说这都很难。有一个不错的方式就是是监控其物理 I/O 的读写,并注意其增长情况。

在 Foursquare 的案例中,Merriman 同意,所有数据可以驻留在内存中,通过监控驻留内存或判断整个数据库的大小就足以预先监测问题。这就意味着,在 Shard 被用完之前,我们可以简单的定位到问题所在。事实上,无论监控是否到位,能否定位问题,好像没有人希望出现不均衡增长的情况。

我们还问到了由于这个案例的经验,10gen 是否会在开发重点上做一些改变,Merriman 的回答是他们会尽快完成后台压缩的功能。另外,Horowitz 表示 MongoDB 应该“变得更加优雅。我们会尽快完成这些增强的功能”。Merriman 指出,MongoDB 将允许对象重新集群,把非活跃的对象放入硬盘的 Page 中,而且他们相信内存映射文件会运行的很好。Horowitz 表示:

最大的问题是并发。虚拟机已经运行的很好了,问题是读写锁的粒度太粗。线程可能会引起比预期更大的故障。我们会通过以下几种方式处理这个问题:创建策略更加智能、真正的内部集合并发等。

查看英文原文:Foursquare's MongoDB Outage