MongoDB运行一段时间后变慢且频繁锁库问题排查求助
我正在执行一项定时Cron任务,任务内容为完成业务处理并发送通知(邮件和短信),执行间隔为每8分钟。该任务需覆盖25000个商家,每个商家对应4-5个子任务,每个商家需执行60-70次MongoDB查询,其中15-20次为插入、更新操作,40-50次为查询操作。
我采用Goroutines实现的Worker Pool运行该Cron任务,设置Worker数量为200,即同时处理200个商家的任务。数据库采用MongoDB,为每个商家分配独立数据库,当前MongoDB使用默认配置。
服务器配置
- 数据库服务器:内存192GB,数据库总大小570GB,操作系统Ubuntu 22.04
- Cron任务服务器:内存16GB,操作系统Ubuntu 22.04
遇到的问题
启动Cron服务后,初期数据库增删改查速度正常,但运行一段时间后,所有数据库操作(含Cron任务及其他业务操作)均变慢,MongoDB频繁进入锁库状态,且锁库时长持续递增——初期锁库后1-2秒恢复,运行2-3小时后,锁库时长超5分钟,仅能正常运行1分钟就再次锁库。
相关日志信息
锁库警告日志
{"t":{"$date":"2023-03-31T06:38:04.021+00:00"},"s":"W", "c":"COMMAND", "id":20525, "ctx":"conn60701","msg":"Failed to gather storage statistics for slow operation","attr":{"opId":2317177,"error":"lock acquire timeout"}}
慢查询日志(锁恢复后)
{"t":{"$date":"2023-03-31T06:40:34.908+00:00"},"s":"I", "c":"COMMAND", "id":51803, "ctx":"conn59118","msg":"Slow query","attr":{"type":"command","ns":"ausloc678_bk_db.providers","command":{"find":"providers","filter":{"uid":7},"limit":1,"projection":{"_id":1,"show_payment_method_and_price":1,"show_payment_method_and_price_for":1,"is_team_member":1,"who_see_payment_method_and_price":1,"team_lead_id":1,"hide_provider_payments":1,"hidden_provider_payments":1,"show_booking_price":1,"show_booking_price_for":1,"who_see_booking_price":1},"singleBatch":true,"lsid":{"id":{"$uuid":"c6c4c42b-216c-48c4-92bf-8ca3b1db93f7"}},"$db":"ausloc678_bk_db"},"planSummary":"COLLSCAN","keysExamined":0,"docsExamined":52,"cursorExhausted":true,"numYields":1,"nreturned":0,"queryHash":"B89C5911","planCacheKey":"B89C5911","reslen":114,"locks":{"FeatureCompatibilityVersion":{"acquireCount":{"r":2}},"ReplicationStateTransition":{"acquireCount":{"w":2}},"Global":{"acquireCount":{"r":2}},"Database":{"acquireCount":{"r":2}},"Collection":{"acquireCount":{"r":2}},"Mutex":{"acquireCount":{"r":1}}},"storage":{"data":{"bytesRead":28496,"timeReadingMicros":13},"timeWaitingMicros":{"handleLock":122143,"schemaLock":15285487}},"protocol":"op_msg","durationMillis":15899}}
疑问
- 导致该问题的原因是什么?
- 需要进行哪些调整来解决该问题?
1. 问题原因分析
- 元数据锁竞争过载:每个商家对应独立数据库,200个Worker同时操作200个不同数据库,MongoDB处理跨数据库操作时,元数据(数据库、集合的schema信息)锁的争抢急剧上升。日志中
schemaLock等待时长超15秒,说明元数据锁已成为核心瓶颈。 - 无索引引发全表扫描:慢查询显示
planSummary: COLLSCAN,即使单集合数据量小,大量并发全表扫描会持续占用资源,加剧锁竞争,导致锁等待时间不断累积。 - 默认配置不匹配高并发场景:MongoDB默认的锁机制、连接池大小、缓存策略未针对大规模跨数据库并发操作优化,随着任务运行,资源逐步饱和,锁无法及时释放。
- 任务并发密度过高:每8分钟同时处理200个商家,单周期总操作数达12000次以上,远超MongoDB默认处理能力,引发资源耗尽、锁排队。
2. 调整方案
数据库层面
- 添加缺失索引:针对所有查询的过滤字段(如慢查询中的
uid)创建索引,消除全表扫描。执行命令:
可编写脚本遍历所有商家数据库批量完成索引创建。db.providers.createIndex({uid: 1}) - 优化MongoDB并发配置:
- 调整WiredTiger引擎并发事务数,提升读写并行能力:
storage: wiredTiger: engineConfig: concurrentTransactions: read: 128 write: 64 - 增大客户端连接池
maxPoolSize至200左右(按需调整),避免连接等待加剧锁竞争。
- 调整WiredTiger引擎并发事务数,提升读写并行能力:
- 合并数据库/集合:每个商家独立数据库会大幅增加元数据管理开销,可将多个商家数据合并到同一数据库的不同集合,或按业务维度分组,减少数据库数量,降低元数据锁争抢。
任务调度层面
- 降低Worker并发数:将Worker数量从200下调至50-100,减少同时操作的数据库数量,缓解锁竞争。
- 拆分任务批次:将每8分钟处理200个商家改为分批次执行,比如每8分钟处理500个商家,分50批次完成全量商家处理,避免瞬间并发过高。
- 合并单商家操作:将每个商家的60-70次操作合并为批量操作,比如用
bulkWrite替代多次单条更新,减少数据库交互次数。
监控与维护
- 实时监控性能:用
mongostat、mongotop监控锁状态、连接数、读写吞吐量,及时定位资源瓶颈。 - 清理存储碎片:定期运行
compact命令整理集合碎片,提升存储效率(注意业务低峰期操作)。 - 流量分离:条件允许时,为Cron任务分配独立MongoDB只读副本节点处理查询,主节点仅处理写入,降低主节点锁压力。
内容的提问来源于stack exchange,提问作者sahil garg

