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 提供大型风扇,希望能用更久一些。
社区反应
针对这篇文章,社区提出了一系列问题:
-
Nat 问:RepairDatabase() 能否利用 CPU 的多核能力?考虑到数据已经被分割为块,是否可以进行并行处理?在互联网领域,4 小时的停机时间让人感觉太久了。
Horowitz 答: 现在还不行。我们正在进行后台压缩功能的开发,以后就不用离线压缩了。
-
Alex Popescu 问: 有没有真正的解决方案去处理块迁移和 Page 大小的问题?
Horowitz 答: 是的,我们正在实现在线压缩。
-
Suhail Doshi 问:
我觉得最明显的问题是:如何避免 MongoDB 节点的冗余?什么时候该提供新节点?假定你们能监控所有事情,我们该看哪些部分?我们怎么知道呢?如果你是一家规模和功能会不断变化的公司,会发生什么情况?
刚开始启动是似乎很难规划容量。
Horowitz 答:
这取决于应用系统。在某些案例中你需要把所有的索引都放在内存里,而在其他案例中可能只需要一个小工作集。一个好的方式是,计算出你在 10 分钟之内需要处理多少数据,索引和文档。确认可以把数据存储在内存,或者在同一时间从磁盘读取。
-
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
评论