You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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. 导致该问题的原因是什么?
  2. 需要进行哪些调整来解决该问题?

问题解答

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左右(按需调整),避免连接等待加剧锁竞争。
  • 合并数据库/集合:每个商家独立数据库会大幅增加元数据管理开销,可将多个商家数据合并到同一数据库的不同集合,或按业务维度分组,减少数据库数量,降低元数据锁争抢。

任务调度层面

  • 降低Worker并发数:将Worker数量从200下调至50-100,减少同时操作的数据库数量,缓解锁竞争。
  • 拆分任务批次:将每8分钟处理200个商家改为分批次执行,比如每8分钟处理500个商家,分50批次完成全量商家处理,避免瞬间并发过高。
  • 合并单商家操作:将每个商家的60-70次操作合并为批量操作,比如用bulkWrite替代多次单条更新,减少数据库交互次数。

监控与维护

  • 实时监控性能:用mongostat、mongotop监控锁状态、连接数、读写吞吐量,及时定位资源瓶颈。
  • 清理存储碎片:定期运行compact命令整理集合碎片,提升存储效率(注意业务低峰期操作)。
  • 流量分离:条件允许时,为Cron任务分配独立MongoDB只读副本节点处理查询,主节点仅处理写入,降低主节点锁压力。

内容的提问来源于stack exchange,提问作者sahil garg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 12:32:14