MongoDB 实例 Crash 的故障现象问题

1故障现象
近日,朋友遇到一个 mongodb 实例 crash 的问题,找到我帮忙一起分析原因,事情经过以及分析过程如下,可供学习。
操作过程
运维人员在优化慢查询时针对性创建了一个索引,语句如下:
db.c1.createindex('name':1,background:true)  
随后又将表上一个没能用上的索引删除,语句如下:
db.c1.dropindex('idx_age')  
在主节点上很顺利的就完成了,但是不久后就发现从节点发生了 crash,日志中包含下列崩溃信息。
2023-04-13t0750.752+0000 e storage  [conn3569849] wiredtiger error (-31802) [1681369250:752455][9937:0x7fe740144700], wt_connection.open_session: __open_session, 2058: out of sessions, configured for 20030 (including internal sessions): wt_error: non-specific wiredtiger error raw: [1681369250:752455][9937:0x7fe740144700], wt_connection.open_session: __open_session, 2058: out of sessions, configured for 20030 (including internal sessions): wt_error: non-specific wiredtiger error2023-04-13t0750.752+0000 i network  [listener] connection accepted from xxx.xxx.xxx.xxx #3570023 (20576 connections now open)2023-04-13t0750.753+0000 f -        [conn3569849] invariant failure: conn->open_session(conn, null, isolation=snapshot, &_session) resulted in status unknownerror: -31802: wt_error: non-specific wiredtiger error at src/mongo/db/storage/wiredtiger/wiredtiger_session_cache.cpp 111  
其它信息
变更表是一张几千万的大表;
数据库架构为 mongodb 4.0.14 的 psa 架构;
应用开启了读写分离,从节点也存在大量只读请求。
2问题分析
根据日志信息,初步怀疑是连接打满了,检查最大连接数配置。
初步排查
shard1:primary> db.serverstatus().connections;{ current : 7, available : 29993, totalcreated : 7, active : 2 }  
最大连接数是由 maxincomingconnections 参数和 ulimit 决定的。
net:  maxincomingconnections: 30000  
在测试环境模拟连接数打满的情况,发现在连接数满了的情况下实例只会拒绝新的连接,而非直接 crash。
connecting to: mongodb://10.186.64.88:27017/admin?gssapiservicename=mongodb2023-04-19t1326.578+0000 i network  [js] dbclientconnection failed to receive message from xxx.xxx.xxx.xxx - hostunreachable: connection closed by peer2023-04-19t1326.579+0000 e query    [js] error: network error while attempting to run command 'ismaster' on host '10.186.64.88:27017'  :connect@src/mongo/shell/mongo.js17@(connect)6exception: connect failed  
根据 server-30462 描述怀疑是 wt_session[1] 打满的情况。
wt_session 是 mongodb server 和 wiredtiger[2] 存储引擎内部交互使用的会话,几乎所有操作都是在 wt_session 的上下文中执行的。因此 wt_session 在超过限制后将会触发较为严重的情况。
源码分析
在源码 mongo/wiredtiger_kv_engine.cpp[3] 中可以看到 wt_session 硬编码指定为 20000。
std::stringstream ss;    ss << create,;    ss << cache_size= << cachesizemb << m,;    ss << cache_overflow=(file_max= << maxcacheoverflowfilesizemb << m),;    ss << session_max=20000,;    ss << eviction=(threads_min=4,threads_max=4),;    ss << config_base=false,;    ss active)            break;    if (i == conn->session_size)        wt_err_msg(session, wt_error, out of sessions, configured for % priu32                                       (including                                       internal sessions),          conn->session_size);  
提出疑问
分析到这开始疑惑 wt_session 打满与索引操作存在什么样的关系?为什么相同的操作在主节点可以正常完成,而从节点会发生 crash?
在创建索引时指定 background:true 可以在后台构建索引,不会加锁阻塞集合上的其它操作,这也是我们日常添加索引常用的方式。
但在删除索引时,我们有一点需要注意,但又常常被忽略,在主节点删除索引后同步到从节点回放时,如果从节点正在跑同一个集合上后台创建索引的操作,那么删除索引的操作将会被阻塞,更严重的是这时候实例上所有 namespace 的访问都将会阻塞。针对这一现象在官网 dropindex[4] 文档中有提及:
avoid dropping an index on a collection while any index is being replicated on a secondary. if you attempt to drop an index from a collection on a primary while the collection has a background index building on a secondary, reads will be halted across all namespaces and replication will halt until the background index build completes.
当任何创建索引操作复制到 secondary 时,应避免在集合上删除索引。如果你试图在 primary 上删除一个索引,而该集合在 secondary 上有一个索引正在后台创建,那么所有 namespace 的访问将被停止,复制也会停止,直到后台索引建立完成。
回到错误日志中查找更多内容,就能发现从节点在后台创建索引时,又执行了同一个集合上的删除索引操作。
2023-04-13t0527.002+0000 i - [repl index builder 178] index build (background): 122873800/640018757 19% 2023-04-13t0530.002+0000 i - [repl index builder 178] index build (background): 122976300/640018769 19% 2023-04-13t0530.434+0000 i command [repl writer worker 11] cmd: dropindexes test.c1  
初步结论
到此,我们得出初步结论。事情起因是主节点在同一个集合上执行创建索引和删除索引后,在从节点回放时出现了很严重的阻塞,大量的只读请求开始不断积压,最后导致 wt_session 消耗殆尽,server 无法与 wiredtiger 进行内部通信,最终导致实例 crash。
3问题复现
下面的案例在测试环境复现 wt_session 超过限制的情况,dropindex 导致从节点锁阻塞的问题有兴趣可自己测试复现,这里就不做演示了。
wt_session 上限是由 wiredtiger_open 配置中的 session_max 决定的,但 mongodb 并未直接暴露 session_max 的配置方式,只能通过下列方式进行覆盖设置。
mongod -f /etc/mongod.conf --wiredtigerengineconfigstring=session_max=5  
然后在数据库内部发起一个全局排它锁。
mongo> db.fsynclock()  
编写下列 python 脚本模拟并发线程。
#!/usr/bin/python# -*- coding: utf-8 -*-import multiprocessingimport pymongodef find():    cnx_args = dict(username='root', password='abcd123#', host='127.0.0.1', port=27018, authsource='admin')    client=pymongo.mongoclient(**cnx_args)    db=client['test']    results=db.tab100.insert_one({name:jack})if __name__ == __main__:    x=1    while x db.fsyncunlock()  
错误日志如下,与生产日志相同。
4总结
net.maxincomingconnections 设置应小于 wt_session;
可以根据实际需求调整游标超时时间,避免出现大面积积压的情况;
避免创建索引和删除索引先后执行,特别是先执行后台创建索引的情况下;
4.2 版本中废弃了 background 选项,对索引创建过程进行了优化,只会在索引创建的开始和结束时持有 exclusive lock;并且 4.0 版本官方已经停止提供服务了,建议尽快升级。

无人机化身战“疫”奇兵 疏导宣传360度“无死角”
PI强推离线式开关电源InnoSwitch3
产品更新快培训成本高?华为云会议助力企业降本增效
用AI打击论文图像造假,仍有4000多篇医学“问题论文”
工业智能网关在机械设备数据采集中的应用
MongoDB 实例 Crash 的故障现象问题
受惠5G芯片订单涌入 台积电7nm抢破头,启动产能配置
“新基建”的推进为传统机场主动部署智能化技术
TiM:兼具高性能及低功耗的移动式平台防撞与导航方案
CEVA发布业界首个802.11ax Wi
如何使用Arduino构建室温监视器
锤子科技天猫官方旗舰店的手机已全线下架?
三星与高通续签合作,Galaxy S25 Ultra将搭载骁龙8 Gen 4
关于光纤测试阶段常见问题的解析
热像仪如何降低爆炸风险
5G R16标准迎来升级,未来行业发展将迎来怎样的改变
谷歌将停止收集IDFA以响应苹果政策
苹果13手机参数
铝合金工件气密性的测试原理
AOPT超光子3X4X5X嫩肤是什么?必须做满“全模式”?