1. 概述
研发反馈MongoDB 写入慢,偶尔会无响应。
2. 环境
系统
- Centos 7.9
- Mongodb 7.0.23
3. 现象
3.1 磁盘IO
bash
iostat -dx 2
bash
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 852.00 29.50 41222.00 274.00 94.15 4.69 5.33 5.52 0.02 1.13 99.70
dm-0 0.00 0.00 852.00 29.50 41186.00 274.00 94.07 4.69 5.33 5.52 0.02 1.13 99.65
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 816.50 29.00 45676.00 288.75 108.73 4.54 5.37 5.56 0.05 1.18 100.00
dm-0 0.00 0.00 818.50 29.00 45732.00 288.75 108.60 4.54 5.35 5.54 0.05 1.18 100.00
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 770.15 29.85 41305.47 254.98 103.90 4.51 5.64 5.85 0.03 1.25 99.65
dm-0 0.00 0.00 767.66 29.85 41309.45 254.98 104.24 4.51 5.65 5.87 0.03 1.25 99.65
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.00 836.00 30.00 44912.00 260.00 104.32 4.29 4.96 5.13 0.05 1.14 98.90
dm-0 0.00 0.00 837.00 30.00 44902.00 260.00 104.18 4.29 4.96 5.13 0.05 1.14 98.85
dm-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
3.2 iotop
- 当前服务器只有mongodb进程有IO读写
bash
iotop -oP
bash
Total DISK READ : 42.98 M/s | Total DISK WRITE : 285.27 K/s
Actual DISK READ: 43.00 M/s | Actual DISK WRITE: 652.33 K/s
PID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
85778 be/4 mongod 42.98 M/s 285.27 K/s 0.00 % 1.48 % mongod -f /etc/mongod.conf
4. 排查
4.1 排查当前全表扫描
- 找出运行时间超过 5 秒且不是空闲的操作
bash
# db.currentOp({ "active": true, "secs_running": { "$gt": 5 }, "op": { "$nin": ["none"] } })
db.currentOp({
"active": true,
"secs_running": { "$gt": 5 },
"op": { "$nin": ["none"] }
})
bash
# 排除干扰,除掉各种检测、心跳包(排除了"admin.$cmd"空间的操作)
# db.currentOp({ "active": true, "secs_running": { "$gt": 5 }, "op": { "$nin": ["none"] }, "ns": { "$ne": "admin.$cmd" } })
db.currentOp({
"active": true,
"secs_running": { "$gt": 5 },
"op": { "$nin": ["none"] },
"ns": { "$ne": "admin.$cmd" }
})
bash
# 改用微秒查看。排除干扰,除掉各种检测、心跳包(排除了"admin.$cmd"空间的操作)
db.currentOp({ "active": true, "microsecs_running": { "$gt": 0 }, "op": { "$nin": ["none"] }, "ns": { "$ne": "admin.$cmd" } })
- 更专注于 I/O 密集型操作(全表扫描、大批量写入)
bash
# db.currentOp({ "planSummary": "COLLSCAN", "secs_running": { "$gt": 2 } })
db.currentOp({
"planSummary": "COLLSCAN",
"secs_running": { "$gt": 2 }
})
4.2 检查慢日志-问题1
- 开启业务数据库慢日志,设置超过1秒记录
bash
test> use deduplication
deduplication> db.setProfilingLevel(1, { slowms: 1000 })
- 等待1-2分钟关闭(可同步观察iostat输出结果判断)
bash
deduplication> db.setProfilingLevel(0)
- 查看慢日志结果
bash
deduplication> db.system.profile.find().sort({ts: -1}).limit(1).pretty()
- db.system.profile:MongoDB 的系统集合,用于存储数据库的分析记录(profile data)。
当数据库的 profiling 级别为 1(只记录慢查询)或 2(记录所有操作)时,符合条件的操作会被写入该集合。 - .find():查询该集合中的所有文档(也就是所有已记录的操作)。
- .sort({ts: -1}):按 ts 字段(操作的时间戳)降序排序。-1 表示从大到小,即最新的记录排在前面。
- .limit(1):只取排序后的第一条记录,也就是最新的一条记录。
- .pretty():以格式化的 JSON 形式显示结果,更易阅读。
json
[
{
op: 'command',
ns: 'deduplication.car_data_dedup_202604',
command: {
aggregate: 'car_data_dedup_202604',
pipeline: [
{ '$match': { strategyType: 'URL' } },
{ '$group': { _id: 1, n: { '$sum': 1 } } }
],
cursor: {},
'$db': 'deduplication',
lsid: { id: UUID('7281ab9f-9a95-413f-bfee-38a4d5c20900') }
},
keysExamined: 0,
docsExamined: 70257706,
cursorExhausted: true,
numYield: 70262,
nreturned: 1,
queryHash: 'D37AE614',
planCacheKey: 'D37AE614',
queryFramework: 'classic',
locks: {
FeatureCompatibilityVersion: { acquireCount: { r: Long('70265') } },
Global: { acquireCount: { r: Long('70265') } }
},
flowControl: {},
storage: {
data: {
bytesRead: Long('12400934334'),
timeReadingMicros: Long('1723888')
}
},
responseLength: 159,
protocol: 'op_msg',
cpuNanos: Long('55042459731'),
millis: 55410,
planSummary: 'COLLSCAN',
planningTimeMicros: 123,
ts: ISODate('2026-05-27T01:37:42.446Z'),
client: '192.168.28.63',
allUsers: [ { user: 'root', db: 'admin' } ],
user: 'root@admin'
}
]
- 基本操作信息
| 字段 | 值 | 含义 |
|---|---|---|
| op | 'command' |
操作类型为数据库命令,这里是 aggregate |
| ns | kol_deduplication.kol_hw_car_data_dedup_202604 |
操作集合:kol_hw_car_data_dedup_202604 |
| command | aggregate: ... |
聚合管道: { $match: { strategyType: 'URL' } }, { KaTeX parse error: Expected '}', got 'EOF' at end of input: ...p: { _id:1, n:{sum:1} } } |
| millis | 55410 |
耗时 55.41 秒(远超 5 秒阈值) |
| ts | 2026-05-27T01:37:42.446Z |
UTC 时间,北京 09:37:42 |
| client | 192.168.28.63 |
客户端 IP |
| user | root@admin |
执行用户 |
- 执行统计信息
| 字段 | 值 | 含义 |
|---|---|---|
| keysExamined | 0 | 没有使用任何索引,所以扫描索引键为 0 |
| docsExamined | 70,257,706 | 扫描了约 7026 万条文档(全表扫描) |
| nreturned | 1 | 最终只返回 1 条文档(聚合结果:{ _id: 1, n: 计数 }) |
| numYield | 70,262 | 让出锁 7 万多次,说明长时间扫描中频繁让位,避免阻塞写入 |
| planSummary | COLLSCAN | 全集合扫描(Collection Scan),这是慢的根本原因 |
| storage.data.bytesRead | 12,400,934,334 ≈ 12.4 GB | 从磁盘读取了约 12.4 GB 数据 |
| storage.data.timeReadingMicros | 1,723,888 微秒 ≈ 1.72 秒 | 实际磁盘 I/O 等待时间只有 1.72 秒(说明大部分时间花在 CPU 处理和锁等待上?) |
| cpuNanos | 55,042,459,731 纳秒 ≈ 55.04 秒 | CPU 消耗时间接近总耗时,表明查询主要是 CPU 密集型(逐条判断 strategyType 并累加) |
| locks | Global 锁获取 70265 次 | 每次让出锁后重新获取,开销很大 |
- command等价于以下语句并且没有索引
bash
SELECT COUNT(*) FROM kol_hw_car_data_dedup_202604 WHERE strategyType = 'URL';
- 排查结果
- 磁盘取了约 12.4 GB 数据(storage.data.bytesRead),读取只有 1.7 秒(storage.data.timeReadingMicros),但 CPU 耗时 55 秒(cpuNanos)
- 结论:这不是导致磁盘IO的主要原因
4.3 解决-问题1
4.3.1 创建索引
- 创建索引
bash
deduplication> db.car_data_dedup_202604.createIndex({ strategyType: 1 })
- 查看创建进度
bash
# db.currentOp({ $and: [ { op: "command" }, { "command.createIndexes": { $exists: true } }, { msg: { $regex: /^Index Build/ } } ] })
deduplication> db.currentOp(
{
$and: [
{ op: "command" },
{ "command.createIndexes": { $exists: true } },
{ msg: { $regex: /^Index Build/ } }
]
}
)
json
{
inprog: [
......
msg: 'Index Build: scanning collection Index Build: scanning collection: 59439696/70257706 84%',
progress: { done: 59439696, total: 70257706 },
numYields: 59994,
locks: {
FeatureCompatibilityVersion: 'w',
ReplicationStateTransition: 'w',
Global: 'w',
Database: 'w',
Collection: 'w'
},
......
waitingForFlowControl: false,
flowControlStats: { acquireCount: Long('59996') }
}
],
ok: 1
}
- msg 显示当前创建进度 84%
- 查看已创建的索引
bash
db.car_data_dedup_202604.getIndexes()
4.4 排查IO高时全局负载
bash
# 对应iostat -dx2
mongostat --host 127.0.0.1 --port 27017 -u "root" -p "yourpassword" --authenticationDatabase "admin" 2
bash
insert query update delete getmore command dirty used flushes vsize res qrw arw net_in net_out conn time
599 147 372 *0 0 543|0 0.5% 59.8% 0 146G 143G 1|0 0|0 841k 1.45m 214 May 27 17:11:10.592
565 146 597 *0 0 769|0 0.4% 59.8% 1 146G 143G 1|0 2|0 1.03m 1.98m 214 May 27 17:11:12.591
586 143 515 *0 0 684|0 0.2% 59.8% 0 146G 143G 0|0 1|0 960k 1.77m 214 May 27 17:11:14.593
427 95 164 *0 0 299|0 0.1% 59.7% 0 146G 143G 0|0 0|0 517k 799k 214 May 27 17:11:16.592
204 44 588 *0 0 647|0 0.1% 59.6% 0 146G 143G 0|0 2|0 710k 1.59m 214 May 27 17:11:18.591
46 24 75 *0 0 108|0 0.1% 59.6% 0 146G 143G 0|0 1|0 120k 298k 214 May 27 17:11:20.593
46 8 113 *0 0 132|0 0.1% 59.6% 0 146G 143G 0|0 2|1 143k 337k 214 May 27 17:11:22.594
18 14 226 *0 0 252|0 0.1% 59.6% 0 146G 143G 0|1 2|0 222k 620k 214 May 27 17:11:24.591
7 5 106 *0 0 140|0 0.1% 59.6% 0 146G 143G 0|0 2|1 110k 312k 208 May 27 17:11:26.592
68 19 301 *0 0 332|0 0.1% 59.6% 0 146G 143G 0|1 3|0 324k 811k 208 May 27 17:11:28.591
| 指标 | 观察值 | 含义 | 评价 |
|---|---|---|---|
| dirty | 0.1% ~ 0.5% | WiredTiger 缓存中脏数据比例 | ✅ 健康(建议 <20%),说明刷盘压力很小 |
| used | ~59.6% | WiredTiger 缓存使用率 | ✅ 正常,还有 40% 缓存可用 |
| vsize / res | 146G / 143G | 虚拟内存 / 常驻物理内存 | ✅物理内存256G,MongoDB 占用了 143 GB 物理内存 |
| qr|qw | 0|0 ~ 1|0 | 读/写请求队列长度 | ✅ 无积压,请求被立即处理 |
| arw | 0|0 ~ 2|1 | 活跃读/写客户端数 | ✅ 并发很低,当前压力不大 |
| insert/query/update | 逐渐下降 | 操作吞吐量 | 业务压力在逐步回落,可能进入低峰期 |
| net_in/net_out | ~几 MB/s | 网络吞吐 | ✅ 正常 |
4.5 排查IO高时的集合
bash
# 对应iostat -dx2
mongotop --host 127.0.0.1 --port 27017 -u "root" -p "yourpassword" --authenticationDatabase "admin" 2
bash
ns total read write 2026-05-27T17:02:05+08:00
kol_deduplication.kol_hw_car_test_data_dedup_202604 1100ms 1100ms 0ms
kol_deduplication.kol_hw_car_data_dedup_202604 876ms 876ms 0ms
kol_deduplication.kol_hw_phone_test_data_dedup_202605 456ms 247ms 208ms
kol_deduplication.kol_hw_car_data_dedup_202603 409ms 409ms 0ms
kol_deduplication.kol_hw_car_test_data_dedup_202605 376ms 359ms 17ms
kol_deduplication.kol_hw_car_test_data_dedup_202603 231ms 231ms 0ms
kol_deduplication.kol_hw_car_data_dedup_202605 40ms 13ms 27ms
admin.$cmd.aggregate 0ms 0ms 0ms
admin.atlascli 0ms 0ms 0ms
admin.system.users 0ms 0ms 0ms
ns total read write 2026-05-27T17:02:08+08:00
kol_deduplication.kol_hw_phone_test_data_dedup_202604 2881ms 2881ms 0ms
kol_deduplication.kol_hw_car_data_dedup_202604 1186ms 1186ms 0ms
kol_deduplication.kol_hw_phone_test_data_dedup_202605 856ms 88ms 768ms
kol_deduplication.kol_hw_car_test_data_dedup_202604 608ms 608ms 0ms
kol_deduplication.kol_hw_phone_test_data_dedup_202603 573ms 573ms 0ms
kol_deduplication.kol_hw_car_test_data_dedup_202603 252ms 252ms 0ms
kol_deduplication.kol_hw_car_data_dedup_202603 152ms 152ms 0ms
kol_deduplication.kol_hw_car_test_data_dedup_202605 148ms 90ms 57ms
kol_deduplication.kol_hw_car_data_dedup_202605 72ms 22ms 50ms
xhs_cache.url_sign 1ms 0ms 0ms
4.6 排查IO高时iotop
bash
[root@centos7-172-028-002-003 script]# iotop -botq --iter=5
17:35:24 Total DISK READ : 0.00 B/s | Total DISK WRITE : 0.00 B/s
17:35:24 Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 9.70 M/s
TIME TID PRIO USER DISK READ DISK WRITE SWAPIN IO COMMAND
17:35:24 144586 be/4 root 0.00 B/s 0.00 B/s 0.00 % 99.99 % [kworker/u609:2]
17:35:24 85801 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 78.22 % mongod -f /etc/mongod.conf [Checkpointer]
17:35:25 Total DISK READ : 11.55 K/s | Total DISK WRITE : 223.23 K/s
17:35:25 Actual DISK READ: 11.55 K/s | Actual DISK WRITE: 10.36 M/s
17:35:25 85801 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 99.99 % mongod -f /etc/mongod.conf [Checkpointer]
17:35:25 144586 be/4 root 0.00 B/s 0.00 B/s 0.00 % 81.77 % [kworker/u609:2]
17:35:25 254095 be/4 mongod 11.55 K/s 0.00 B/s 0.00 % 51.00 % mongod -f /etc/mongod.conf [conn76238]
17:35:25 85800 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 45.72 % mongod -f /etc/mongod.conf [JournalFlusher]
17:35:25 85793 be/4 mongod 0.00 B/s 223.23 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
17:35:26 Total DISK READ : 22.86 K/s | Total DISK WRITE : 213.32 K/s
17:35:26 Actual DISK READ: 22.86 K/s | Actual DISK WRITE: 12.79 M/s
17:35:26 144586 be/4 root 0.00 B/s 0.00 B/s 0.00 % 99.99 % [kworker/u609:2]
17:35:26 85801 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 82.54 % mongod -f /etc/mongod.conf [Checkpointer]
17:35:26 85800 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 48.48 % mongod -f /etc/mongod.conf [JournalFlusher]
17:35:26 254095 be/4 mongod 22.86 K/s 0.00 B/s 0.00 % 39.24 % mongod -f /etc/mongod.conf [conn76238]
17:35:26 85793 be/4 mongod 0.00 B/s 156.18 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
17:35:26 85802 be/4 mongod 0.00 B/s 57.14 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf [ftdc]
17:35:27 Total DISK READ : 11.43 K/s | Total DISK WRITE : 217.10 K/s
17:35:27 Actual DISK READ: 11.43 K/s | Actual DISK WRITE: 12.49 M/s
17:35:27 85801 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 99.99 % mongod -f /etc/mongod.conf [Checkpointer]
17:35:27 254095 be/4 mongod 11.43 K/s 0.00 B/s 0.00 % 94.63 % mongod -f /etc/mongod.conf [conn76238]
17:35:27 144586 be/4 root 0.00 B/s 0.00 B/s 0.00 % 93.15 % [kworker/u609:2]
17:35:27 85800 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 66.00 % mongod -f /etc/mongod.conf [JournalFlusher]
17:35:27 85793 be/4 mongod 0.00 B/s 45.70 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
17:35:27 85797 be/4 mongod 0.00 B/s 99.03 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
17:35:27 85802 be/4 mongod 0.00 B/s 72.37 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf [ftdc]
17:35:28 Total DISK READ : 0.00 B/s | Total DISK WRITE : 399.63 K/s
17:35:28 Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 12.04 M/s
17:35:28 144586 be/4 root 0.00 B/s 0.00 B/s 0.00 % 99.99 % [kworker/u609:2]
17:35:28 85801 be/4 mongod 0.00 B/s 0.00 B/s 0.00 % 93.77 % mongod -f /etc/mongod.conf [Checkpointer]
17:35:28 85800 be/4 mongod 0.00 B/s 15.22 K/s 0.00 % 51.98 % mongod -f /etc/mongod.conf [JournalFlusher]
17:35:28 85793 be/4 mongod 0.00 B/s 216.94 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
17:35:28 85797 be/4 mongod 0.00 B/s 167.47 K/s 0.00 % 0.00 % mongod -f /etc/mongod.conf
- -b:批处理模式,将结果输出到控制台。
- -o:只显示实际执行I/O的进程。
- -t:增加时间戳,这对分析周期性峰值至关重要。
- -q:移除列名,使输出更简洁。
- --iter=5:指定输出5次后退出。
4.7 确定问题
4.7.1 问题原因
MongoDB 导致的周期性高 I/O 写入
| 时间 | 进程 (TID) | 线程名/功能 | 磁盘写入速率 | I/O占用 |
|---|---|---|---|---|
| 17:35:24 | 144586 | kworker/u609:2 (内核工作线程) | 0 B/s | 99.99% |
| 17:35:24 | 85801 | Checkpointer (MongoDB检查点) | 0 B/s | 78.22% |
| 17:35:25-28 | 85801 | Checkpointer | 0 B/s | 82~99% |
| 17:35:25-28 | 85800 | JournalFlusher (日志刷写) | 0~15 KB/s | 45~66% |
| 17:35:25-28 | 254095 | conn76238 (客户端连接) | 11~22 KB/s (读) | 39~94% |
| 17:35:25-28 | 85793/85797 | 其他mongod线程 | 45~217 KB/s (写) | 0% |
- Actual DISK WRITE 始终高达 9~13 MB/s,但Total DISK WRITE 只有几百 KB/s。
这表示大量写入来自内核/文件系统的缓存刷写,而非应用程序直接写入。
具体来说:
- Total DISK WRITE = 进程直接发出的写请求(如 write() 系统调用)。
- Actual DISK WRITE = 实际下沉到块设备的写入(包含内核 page cache 回写)。
差值巨大说明:MongoDB 写入速度快于磁盘处理速度,导致内核 page cache 积压,然后由 kworker 线程负责回刷到磁盘。
-
Checkpointer 线程占用了 80~99% 的 I/O 时间,这是 MongoDB WiredTiger 存储引擎的检查点(checkpoint)线程。默认每 60 秒触发一次,将内存中的脏数据刷到磁盘上的数据文件。这就是你观察到"每过几分钟出现一次"的直接原因(间隔可能是 60 秒,但受写入负载影响会有波动)。
-
conn76238 线程表明有一个客户端连接(可能是应用程序或脚本)正在执行读取操作(少量读),但触发了后续的写入(可能是写入数据后需要落盘)。
-
kworker 高 I/O 是正常现象:当文件系统写压力大时,内核的 flush 线程(kworker)会负责将 page cache 数据真正写入磁盘。
4.7.2 优化方案
因为数据写入是正常业务行为,所以考虑从系统层面优化。但这无法解决性能瓶颈问题,最合理的方式应该是更换SSD硬盘。
- 系统层面
bash
cd /etc/ && cp fstab fstab.orig
bash
vim fstab
#
# /etc/fstab
# Created by anaconda on Thu Feb 27 13:21:51 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/centos-root / xfs defaults,noatime,nodiratime 0 0
UUID=1fceca49-0d70-424c-830c-d991d7f94659 /boot xfs defaults 0 0
UUID=66B7-F344 /boot/efi vfat umask=0077,shortname=winnt 0 0
/dev/mapper/centos-swap swap swap defaults 0 0
- 修改根分区(/)记录,增加 noatime,nodiratime 。减少每次读取文件时更新访问时间戳的元数据写入开销 。
bash
# 重新挂载根分区(在线,不重启)
mount -o remount,noatime,nodiratime /
bash
# 通过以下命令验证是否生效,应该显示 noatime, nodiratime
cat /proc/mounts | grep " / "