2. 诊断数据采集体系

MongoDB 性能故障诊断的第一道门槛不是“看懂某个指标”,而是“在正确的时间拿到正确的数据”。mongostat、mongotop、serverStatus、db.currentOp、日志与 profiler 这六类工具,分别覆盖了全局吞吐、集合级热点、内部计数器、实时操作、事件轨迹、慢操作明细六个不同粒度。把它们当成六个孤立命令去背,往往在故障现场手忙脚乱;把它们理解成一条从“宏观异常”到“微观根因”的采集链,才能形成稳定的诊断节奏。

六类工具的分工与粒度

mongostat 是秒级快照,回答“现在整体忙不忙、有没有排队”。它输出 insert/query/update/delete/getmore/command 的每秒次数、dirty used 百分比、used 内存、vsize、res、qr|qw(读写队列)、ar|aw(活跃读写客户端数)、netIn/netOut、conn、time。其中 qr|qw 持续非零说明请求在排队,dirty 接近 20%说明 WiredTiger 缓存脏页比例偏高,conn 突增常与连接泄漏或客户端重试风暴相关。

mongotop 是集合级读写耗时排行,回答“时间花在哪些集合上”。它按 ns 维度输出 total/read/write 的毫秒数,默认每秒刷新。当 mongostat 显示整体繁忙但看不出热点时,mongotop 能快速把范围缩小到具体命名空间。

serverStatus 是内部计数器的全量快照,回答“系统内部发生了什么”。关键字段包括:opcounters(各类操作累计数)、opcountersRepl、connections(current/available/totalCreated)、wiredTiger.cache(bytes currently in the cache、tracked dirty bytes、pages read into cache、pages written from cache)、wiredTiger.concurrentTransactions(read/write 的 out 与 available)、globalLock(activeClients、currentQueue)、metrics.cursor(open、timedOut)、metrics.document(deleted、inserted、returned、updated)、asserts、extra_info.page_faults。它适合做前后差值对比,而不是单点读数。

db.currentOp() 是实时操作视图,回答“此刻谁在跑、跑了多久、卡在哪”。重点字段:opid、secs_running、microsecs_running、op、ns、command、planSummary、numYields、waitingForLock、locks、client、desc。配合 db.killOp(opid) 可以终止失控操作,但必须先确认它不是关键写入。

日志是事件时间线,回答“什么时候发生了什么”。关注 slow query 行(含 planSummary、docsExamined、keysExamined、nreturned、execStats)、连接建立/断开、选举与复制状态变化、WiredTiger 的 eviction 与 checkpoint 记录。

profiler 是慢操作明细库,回答“慢操作长什么样”。通过 db.setProfilingLevel(1, {slowms: 100}) 开启,数据落在 system.profile 集合,可按 ts、millis、ns、op、planSummary、execStats 聚合分析。

诊断数据采集链:从宏观异常到微观根因 ① 宏观吞吐层 mongostat qr|qw · dirty% · conn mongotop ns 级 read/write 耗时 serverStatus opcounters · WT cache db.currentOp() secs_running · yields 定位到具体集合/操作 ② 明细追踪层 日志 slow query planSummary · docsExamined profiler / system.profile millis · execStats · ts db.killOp(opid) 确认后终止失控操作 提取根因证据 ③ 根因判读层 索引缺失/低效 COLLSCAN docsExamined≫nreturned 锁/队列阻塞 waitingForLock qr|qw 持续非零 缓存压力 dirty≥20% eviction 频繁 连接/游标泄漏 conn 突增 cursor.timedOut 协同采集原则 • 先 mongostat 判断“是否异常”,再 mongotop 缩小“哪个集合”,最后 serverStatus + currentOp 定位“为什么” • serverStatus 用两次快照做差值,避免累计值误读;profiler 仅在排查窗口开启,避免自身开销 • 日志与 profiler 时间戳对齐,才能把“慢操作”与“系统事件”关联成完整证据链

协同采集的实操节奏

假设某业务反馈“晚高峰查询变慢”。第一步,在故障节点执行 mongostat --host rs0/127.0.0.1:27017 --rowcount 30 1,观察 30 秒。若看到 qr 从 0 升到 30 以上、dirty 从 5% 升到 18%、conn 从 200 升到 900,可以判断存在排队与缓存压力,但还不知道热点在哪。

第二步,执行 mongotop 5,发现 app.orders 的 read 时间稳定在 800ms/s 以上,而其他集合都在 50ms/s 以下,热点锁定。

第三步,取两次 serverStatus 做差值。示例(教学假设数据):间隔 10 秒,opcounters.query 从 120000 增至 138000,即 1800 次/秒;wiredTiger.cache 的 “tracked dirty bytes” 从 1.2GB 增至 2.1GB,“pages read into cache” 增加 45000;metrics.cursor.open 从 300 增至 1200。这说明查询量本身不算极端,但缓存读放大严重、游标打开数激增,怀疑存在未关闭游标或全表扫描。

第四步,执行 db.currentOp({ "secs_running": { $gt: 3 }, "ns": "app.orders" }),看到若干 op 的 planSummary 为 COLLSCAN,numYields 高达数千,waitingForLock 为 false,说明不是锁等待,而是扫描量过大导致 CPU 与缓存被拖垮。

第五步,开启 profiler 采集窗口:db.setProfilingLevel(1, { slowms: 200 }),运行 2 分钟后关闭。查询 db.system.profile.find({ ns: "app.orders", millis: { $gt: 200 } }).sort({ ts: -1 }).limit(10),典型结果如下(示例):

{
  op: "query",
  ns: "app.orders",
  millis: 1842,
  planSummary: "COLLSCAN",
  docsExamined: 1284000,
  keysExamined: 0,
  nreturned: 12,
  execStats: { executionTimeMillis: 1830, totalDocsExamined: 1284000 }
}

docsExamined 与 nreturned 的比值超过 10 万倍,是典型的索引缺失信号。此时再回到日志,搜索同一时间窗口的 slow query 行,确认 planSummary 与 profiler 一致,证据链闭合。

数据解读中的常见误判

第一,把 serverStatus 的累计值当瞬时值。opcounters 是自启动以来的累计计数,必须做差值或除以时间间隔,否则会得出“查询量巨大”的错误结论。

第二,把 dirty 高直接等同于故障。dirty 反映的是脏页占缓存比例,短时接近 20% 属于正常波动;只有当它持续高位且伴随 eviction 频繁、qr 排队时,才构成缓存压力。

第三,忽略 numYields 的含义。numYields 高说明操作多次让出锁,通常与扫描量大或并发高相关,但它本身不是错误,需要结合 docsExamined 与 secs_running 一起判断。

第四,profiler 长期开启。profiler 会写入 system.profile,带来额外写放大与存储占用,只应在排查窗口内开启,并设置合理的 slowms 阈值。

建议把六类工具的采集命令固化为一份排查脚本:mongostat 抓 30 秒、mongotop 抓 10 秒、serverStatus 前后各一次、currentOp 过滤 secs_running>3、profiler 按需开启。这样在故障现场可以直接按顺序执行,减少决策成本。

一份可直接使用的采集清单

顺序工具关键参数/字段判断依据
1mongostatqr|qw、dirty、connqr>0 持续、dirty≥20%、conn 突增
2mongotopns、read、write某 ns 耗时显著高于其他
3serverStatusopcounters、WT cache、cursor两次差值判断增长速率
4db.currentOp()secs_running、planSummary、numYieldsCOLLSCAN、长耗时、高 yields
5日志slow query、planSummary与 profiler 时间戳对齐
6profilermillis、docsExamined、nreturned扫描/返回比异常

这套体系的价值不在于工具本身,而在于采集顺序与证据对齐。mongostat 负责发现异常,mongotop 负责缩小范围,serverStatus 负责量化内部状态,currentOp 负责锁定实时操作,日志与 profiler 负责提供可复现的明细证据。六者按“宏观→集合→内部→实时→明细”的顺序协同,才能把一次性能故障从现象还原到根因。