精华 【分享】Mongodb线上真实事故案例
发布于 4 年前 作者 DoubleSpout 27169 次浏览 最后一次编辑是 3 年前 来自 分享

前几天,公司一个业务部门的 Mongodb 数据库副本集(1主2从)出现写入和更新延迟现象,最慢的一次更新长达22秒,平均的更新和插入操作在15秒左右,上报到我们公共部门,希望能够得到解决。

之前业务部门已经对这个 Mongodb 使用了一个多月,一直没出问题,又怎么会突然发生延迟这么长的故障呢?

由于 Mongodb 中本写入的是重要的价格政策信息,所以这个故障已经影响正常线上业务了,于是我就担任救火队员,负责解决这个问题了。

于是灾难开始了: 1、删除索引 我们在出问题的集合中发现了一个3个字段的组合索引,而这个组合索引并没有被查询使用到,为了加速插入,我们决定在主库删除这个不使用的索引,然后让它同步到从库上,因为删除索引通常很快。 删除很快,5分钟左右,就把几千万条记录的集合的索引删除了,在我们删除完索引之后,主库的写入速度有了一定下降。没有之前那么夸张的平均15秒了,下降到了4-5秒。

2、恢复索引 这时Mongodb的集群出现报警,从库的读取性能急剧下降,外网业务频繁出现 504(time out)的错误。这时候我们其中的一个DBA同事以为我误删了那个索引引起的全表扫描,导致查询超时,于是他在主库上又将我刚才删除的索引重建了。 这时候更大的灾难降临了,在主库重建索引的时候,所有的主库写操作阻塞了,当主库创建完索引,将oplog同步到从库,从库创建完索引之后,两台从库在集群里的状态分别是:recovering和unreachable。 这时候整个Mongodb集群无法正常提供数据服务了,业务部门职能临时切换备用 sql 方案,勉强恢复对外提供服务,整个故障历时2小时。

3、事故分析: A、删除无用索引肯定会加快写入,但是为什么我删除了这个没有查询命中的索引,会让从库读取性能下降呢? 主库删除索引,同步到从库之后,从库开始删除索引,这时候从库是无法从主库那边拉取同步数据的,因为删除索引会有一个写锁,所以当从库删除完索引之后,主库有大量的更新开始同步到从库,这时候从库压力倍增,导致读取性能下降。

B、恢复索引为什么会导致集群挂掉 DBA同事发现从库查询慢,以为是误删索引,其实并不是,而是由于之前从库删除索引停止同步引起的。 因为创建索引,会直接导致从库写入和读取的锁,并且耗时相比删除索引要长很久,所以两台从库就直接不提供服务了。 当从库创建完索引之后,从库和主库的数据不同步延迟相当大,大量的更新和外网的读取压力,导致整个Mongodb集群无法提供服务了。

4、解决问题 由于目前业务数据骤增,所以我们需要重新审视 Mongodb副本集 是否还能继续为我们提供服务,在Mongodb无法对外提供的时候,我们只能依靠多台redis和sqlserver数据库临时上线,应付业务。

A、在出故障的时候我们查看了Mongodb的状态,竟然有超过5000个连接,连接在主库上等待写操作。于是我们翻看了业务代码,发现10台服务器每台服务器的最大连接池设置了20000个,由于之前我们有测试Mongodb在大连接数下表现确实不佳,于是把代码改回默认的100个,继续跑数据,但是问题依旧,性能大约仍然在update操作 2000次/秒 左右。

B、因为业务的特殊性,集合中存储的当天之后的65天的价格政策信息,而之前没有定期清理垃圾数据,于是我们将过期的政策清理掉,大约清理掉了30%的数据,以为能够恢复原来的写入性能,无奈业务增长太快,就算清除了30%的垃圾数据,还是远远的超过以前的数据量,这个方式宣告失败。

C、翻阅网上Mongodb的资料,发现官方文档上有一些对于写操作的优化方案,文中提到使用SSD可以显著提升写操作的性能,最高可达20倍,于是折腾了运维,火急火燎的将3台Mongodb数据库机器全部加上SSD硬盘,由于换硬盘数据库要清楚,所以在换完硬盘前几小时写入性能非常高,但是当数据量上来之后,性能又跌回 2000次/秒,我们的数据量大约110G,内存当时配置了196G,所以SSD无法优化到写入操作。

D、再次翻阅Mongodb官方资料,发现Mongodb的文档是基于 usePowerOf2Sizes 这个规则来申请内存和空间的,当文档初始化插入4KB时,Mongodb申请的空间大小为8KB,当文档涨到8KB后,则申请16KB,以此类推。文档的增涨会带来数据块的迁移,带来性能损失,如果是文档初始化很小,会逐渐增涨,并增涨到一个相对初始大小很多倍的场景,那么官方推荐调整 usePowerOf2Sizes 这个设置,来初始化给文档分配一定的空间。我们的业务场景和这个很像,初始化只有1KB,以后的多次更新,最多可以将文档更新到80K。于是我们将文档初始化占用的空间设置为96KB,防止因为不断的数据块迁移带来的性能损耗,可惜这个配置还是然并卵。

E、无奈开始找其他出路,想到使用HBASE分布式kv存储方案来解决这个问题,了解到社区的@alsotang 正好非常熟悉HBASE这块,当天晚上就找 @alsotang 讨论解决方案,发现HBASE的查询条件太死,无法像Mongodb那样满足业务场景,经过这几天折腾,我得出结论就是因为并发写锁导致的性能低下,于是 @alsotang 给了我另外一个建议,批量写入,积攒大约几千条写的命令,批量插入。第二天的批量测试结果却是让人眼前一亮,性能从原来的2000次/秒 提升到了5000次/秒了,但是这样可能暂时能扛下来压力,过几个月业务增长之后,可能又存在瓶颈了。

F、由于我们线上使用的是Mongodb2.6,所以存在写入的库级锁,会不会是因为这个导致的并发写入性能低下呢?抱着吃螃蟹的心态,将数据库升级到3.0,还是之前批量写入的代码,空库的写入性能能达到 40000次/秒,在单表6000万数据量下,批量写入也能达到 18000次/秒,而且3.0的内存占用比2.6要少很多。 但是由于改成批量写入,业务端代码改动比较大,所以我们测试了非批量写入的情况,写操作性能也能达到8000次/秒。

G、最终我们还是决定一步到位,搭建了Mongodb3.0的集群,分6片存储之前单机Mongodb的数据,这样理论上能让我们的写性能翻5-6倍,就算不用批量写入,仍然可以达到40000次/秒,应该近几年业务是达不到这个指标的,而且一旦发现瓶颈,我们可以通过增加分片的方式提高集群性能。终于折腾了多天的Mongodb算是告一段落了,至于上线之后Mongodb3.0的坑是否会踩到,只能看脸了。

事故就分析到这里,期间研究了一些Mongodb的优化方案,传送门: http://snoopyxdy.blog.163.com/blog/static/6011744020157511536993/

53 回复

干货!赞!

感谢分享!收藏

用上了 3.0 啊,赞。是用的 WiredTiger 引擎吗?到时候等 WiredTiger 成为默认引擎的时候,我社区这边就升上去。听说后者性能比前者好很多,现在我们社区这边没有遇到性能瓶颈,所以暂时不动这里,还是用的 2.6。 hbase 这块,其实我也不是太熟悉,只是之前有项目用到了。我们是当做一个非内存的 kv 数据库在用,因为比较廉价嘛,支持的数据量大,自带数据分区支持,而且我们查询实时性要求不苛刻。但是如果有自有项目的话,除非搞大数据分析(已经超出我能力范畴)类的应用,否则我可能都不愿意去接触 hbase。它的使用场景太局限了,稍微一复杂就没法满足。

@alsotang 没办法啊,被逼着用3.0,是用的 WiredTiger 引擎,对数据是文档锁,不像以前是库锁的,所以插入和更新性能比之前好很多,很适用于写操作比较频繁的场景。hbase 最后还是没用上,下次有机会再用吧,确实感觉适用场景比较局限,先跑阵子看看吧,我们用的Mongodb 3.0.5,目前为止一切正常,不知道会不会踩到什么坑。。祈祷吧,哈哈~

干货啊,赞!

Good material!studying…

干货,收藏

来自炫酷的 CNodeMD

@DoubleSpout cnode这边我用上3.0了。在国内机房这边,调试成功。wiredtiger对于数据的压缩,让数据总量从 200MB 变成了 60MB。不过话说,mongodb 以前的引擎被人诟病确实很多,现在上了 wiredtiger 感觉放心多了。不过应该还有很多次升级才会慢慢稳定的,但至少我这边是单机使用,碰坑的几率相对少一点。

@alsotang 现在cnodejs.org也用上3.0了吗?我感觉比原来好不少,空间占用小了,写入性能提高了,适当的降低下读取性能也能接受,锁的情况明细改善了,感觉Mongodb越来越来成熟了,单机你没做副本集?数据丢了怎么办呢?

@DoubleSpout 磁盘有 raid 嘛。。

唯一感觉就是你司的人都是各自为战?一会你改一会别人又改回来

直接在production上做实验, 感觉很牛啊.

虽然很多字,但是还是看完了。总体下来的感觉是: 1、对mongodb关注度不高 2、以关系型数据库的思维部署mongodb 个人经历:使用mongodb存储通信系统的消息等数据,对于2.6版本来讲库级别锁真的是谁用谁知道,当时的解决方案是起多端口做到一个库一张表(当然有些夸张,尽量保证一个库不会超过3张表,就是为了尽可能减少库级锁的影响),终于在众人的期待中mongodb发布了3.0版本,锁粒度到collection,引擎使用WT后性能提升很大一截,存储的话与2.6相比磁盘占用量也减少了一些,对于内存的开销从2.6的有多少吃多少到3.0的可以设定。一系列的调优让人眼前一亮;终于在3.0.2版本后按耐不住,将线上项目升级到3.X。对于部署来讲即使在2.6版本的mongodb,官方也是不建议使用主从部署的,在3.0更是标注将在下个大版本取消掉这种部署模式,mongodb的核心点在于部署,分片则是部署的核心点,而片键的选择更是分片的核心。当然,很难推动分片部署的原因在于主从<复制集<分片(数据冗余度,白话就是磁盘空间大小)。 最后,建议使用mongodb做线上项目存储的人,基本的了解下mongodb的概要(《mongodb权威指南》),有时候类比的思想并不一定试用所有地方。

@haozxuan

请教两个问题

  1. 主从<复制集<分片。主从的冗余度为什么小于复制集?
  2. 以关系型数据库的思维部署mongodb体现在哪里?

@alsotang 主从一般会一主一从,或一主二从,而复制集部署最少是一个活跃节点和两个备份节点(原因在于如果活跃节点挂掉后,剩余的非活跃节点不仅要做数据备份,还要提供查询服务会挂的更快)。所以一般情况下复制集一份数据都要存储三份或更多。 关系型数据库的思维是指部署方案主从的选择以及不考虑死锁因素。对于主从方案,一般情况下为了容灾或是单点故障都会做数据的备份,很自然的会使用主从的模式,但是这种模式其实很需要外界的介入(比如主挂掉了自动漂移肯定是外部逻辑实现,不会像复制集一样自主选举),这样就是舍近求远的思路。对于死锁关注度不够,使用关系型数据库(比如mysql很自然的会将一些表存在同一个库下)习惯将多个表放在同一库中,这样一个表的逻辑出现锁将会影响本库(指2.6版本)所有表的业务逻辑。

@haozxuan 额,你说的都对,不过我们没有这样做 1、习惯上是说主从,其实我们搭建的就是3台mongodb的副本集,没有使用主从 2、那个锁库的数据库,只有一个集合,就是这个集合太过频繁的更新才导致的锁,其他集合都放在另外的一个库中,不过即使这样在2.6下还是锁现象很严重。

+1,也准备入3.0的坑

好东西,收藏

批量写入有数据出错的情况吗?没事务保证数据一致性啊。

感谢分享!收藏

DBA水平太差,线上建索引太随意了,而且建索引应该后台创建。

现在cnode的数据库文件有多大了?mongodb疯狂吃硬盘,真的太多数据冗余?

@haozxuan @DoubleSpout 感谢分享。已经点赞。不过。主库在使用时建立索引会导致阻塞所有IO这么一个低级错误。DBA居然犯了。。。。

几个问题: 1、DBA怎么可以在不知道业务逻辑的情况下随意增加索引?还有DBA居然可以直接修改生产环境DB,都不用测试一下。。 2、为什么Mongodb不使用分片集群,而采用不太可靠的副本集

干货,借鉴

不错。。。谢谢分享。。。

@shinygang 无所谓的,房间价格而已,出错不影响下单,用户不过就是不能以最新的价格下订单而已,如果贵了用户自己买单,如果便宜了,公司贴钱。

@inosqlorg 不能这么说,影响下单业务了,DBA也是心急,其实当时后台创建和前台创建关系不大,副本集已经收不了了

@struCoder DBA也算经验丰富的,就是当时情况紧急,慌乱了

@chemdemo 1、本来业务用什么语句就不会跟DBA说,DBA发现我删了索引之后,读库阻塞了,所以以为我索引误删了,慌了阵脚 2、Mongodb如果高可用分配,需要很多机器,以前酒店这方面的业务并没有这么大量,近期才飞速发展的,现在已经用了12台机器搭建了分片集群,一切ok

怎么没有收藏功能啊喂

我们也遇到了类似的问题,不过是MySQL,批量写入性能不到2000/秒。 由于做了分库所以受影响的只是单个客户,不过原因大多类似。 我们是读写没有分离,存在一个基础表(其实就是一个keyvalue的表)读写都很频繁,然后这里一锁,其它大部分功能响应很慢。 目前的思路就是把这一块独立出来,提供一个单独公司级的keyvalue服务。

谢谢分享,又见snoopy大神

马克 自豪地采用 CNodeJS ionic

如果存储这块可以再选一次的话,你会继续选择 mongodb,还是 mysql?

@alsotang 求2.6to3.0的服务升级方案 mongoskin和mongoose支持3.x了么

马克

来自酷炫的 CNodeMD

干货已收藏

时隔一年,又重看了一遍,感谢分享

看着有点像 某事故灾难现场~

回头再看,这个地方的性能提升主要是:

  1. 库锁成了行锁
  2. 从主从,到做了 sharding

如果存储这块可以再选一次的话,你会继续选择 mongodb,还是 mysql?

我 41 楼的这个问题我自己回答一下。

在文中所遇的场景下,用 mongodb 挺好的,主要是 sharding 方便,mongodb 还能自动数据迁移。

回到顶部