mongodb大表数据归档
故障上报
- 今天 17:38 收到用户反馈:某系统访问很慢,经初步排查,发现 mongodb 服务器压力很大(16核-128G-2TB),阿里云监控信息如下:
- 登录 mongodb 服务器,查看同一时段的慢查询日志,发现有一张表存在大量慢日志,慢的时候超过 14390ms,分析如下:
[root@localhost ~]# cat /etc/mongod27017.conf | grep path
path: "/mnt/mongodb/data27017/mongod27017.log"
[root@localhost ~]# tail -n2000 /mnt/mongodb/data27017/mongod27017.log | grep "2026-09-01" | awk '{print $NF}' | grep ms | sort -nr | head -10
14390ms
14029ms
13986ms
13201ms
12961ms
12872ms
12704ms
11849ms
11556ms
11173ms
[root@localhost ~]# tail -n2000 /mnt/mongodb/data27017/mongod27017.log | grep "2026-09-01" | egrep "(14390ms|14029ms|13986ms)"
2026-09-01T17:36:31.999+0800 I COMMAND [conn87555] command xxx.note command: find { ... } protocol:op_msg 14390ms
2026-09-01T17:39:51.313+0800 I COMMAND [conn87388] command xxx.note command: find { ... } protocol:op_msg 14029ms
2026-09-01T17:40:51.453+0800 I COMMAND [conn87573] command xxx.note command: find { ... } protocol:op_msg 13986ms
[root@u-67177-mongo data27017]# /opt/mongodb/bin/mongo -usa -p123456 --authenticationDatabase admin --host 127.0.0.1 --port 27017 admin
set27017:PRIMARY> db.version(); // 查看版本
4.0.25
set27017:PRIMARY> use xxx;
set27017:PRIMARY> db.note.count(); // 查看行数
26337252
set27017:PRIMARY> (db.note.dataSize()/1024/1024/1024).toFixed(2) // 仅查看数据大小
122.41
set27017:PRIMARY> (db.note.totalIndexSize()/1024/1024/1024).toFixed(2) // 仅查看索引大小
11.28
set27017:PRIMARY> (db.note.totalSize()/1024/1024/1024).toFixed(2) // 查看存储大小(数据+索引,压缩后)
148.86
set27017:PRIMARY> (db.note.storageSize()/1024/1024/1024).toFixed(2) // 查看磁盘上实际占用的空间(含索引)
137.58
解决方案
- 针对慢查询日志多次涉及的 note 表(已有12个索引),这是一张核心业务表,很多业务都会用到,来不及做分片(申请资源需要费用、修改代码需要时间)
- 与业务方沟通下来,决定将2年前的历史数据迁移到归档库(以 createAt 字段、2024-09-01 为分割点),通过降低数据量来提升查询效率
- 说干就干,放到当晚 21:00 执行
维护窗口内的操作步骤
- 第1步,为 createAt 字段添加索引,方便后续删历史数据
实测:全表共26337252行、dataSize共122G、totalSize共148G,针对时间字段添加索引耗时 19分钟(22:15-22:34),数据目录仅增长1GB
use xxx;
db.note.ensureIndex({"createAt":1}, {"background":true});
db.note.getIndexes();
// 确认数据分布
var cutoffDate = new Date("2024-09-01T00:00:00");
db.note.find({ createAt: { $lt: cutoffDate } }).count(); // 20240901 之前有 15216141 行
db.note.find({ createAt: { $gt: cutoffDate } }).count(); // 20240901 之后有 11134334 行
db.note.find({}, {createAt:1,_id:0}).sort({createAt:1}).limit(1); // 最早的数据是 2015-08-20T14:26:56
db.note.find({}, {createAt:1,_id:0}).sort({createAt:-1}).limit(1); // 最新的数据是 2026-09-01T22:35:23
- 第2步,导出主表 note,方便后续导入到历史表
实测:全表共26337252行、dataSize共122G、totalSize共148G,备份耗时 19分钟(22:36-22:55),备份目录 123GB
mkdir -p /mnt/20260901
/opt/mongodb/bin/mongodump -h 127.0.0.1 --port 27017 -u sa -p 123456 --authenticationDatabase admin -d xxx -c note -o /mnt/20260901/
- 第3步,针对主表 note 执行分批删除 2024-09-01 以前的数据,每次删除1万行
实测:删1万条一开始仅1.5秒,但越删越慢,到后来删1万条需3秒,删1521万总耗时 98分钟(22:57-00:35)
use xxx;
var cutoffDate = new Date("2024-09-01T00:00:00");
var batchSize = 10000;
var totalDeleted = 0;
// lt 删除 < 20240901 以前的数据
while (true) {
var ids = db.note.find({createAt:{$lt:cutoffDate}},{_id:1}).limit(batchSize).toArray().map(d =>d._id);
if (ids.length === 0) break;
var res = db.note.deleteMany({ _id: { $in: ids } });
totalDeleted += res.deletedCount;
print("已删除: " + totalDeleted + " 条");
sleep(10); // 停顿10毫秒
}
print("删除完成,共删除: " + totalDeleted + " 条");
后续工作
- 第4步,将备份文件导入到归档库(不用提前创建)的历史表,示例 xxx_his.note_history
实测1:换台机器(4C-32G-500G)导入 123GB 备份文件,耗时 100分钟(10:09-11:49),其中 restoring data 耗费 27分钟,restoring indexes 耗费 73分钟
实测2:换回本机(16C-128G-2TB)导入 72GB 备份文件,耗时 84分钟(18:11-19:34),其中 restoring data 耗费 17分钟,restoring indexes 耗费 67分钟(行数: 15216141 | 存储: 78464.81MB | 数据: 73531.38MB | 索引: 6345.88MB | 膨胀率: 1.07)
注:默认情况下,通过 mongorestore 恢复出来的表是带有原表索引的,无需为该表再次创建 createAt 索引
/opt/mongodb/bin/mongorestore -h 127.0.0.1 --port 27017 -usa --authenticationDatabase admin -d xxx_his -c note_history /mnt/20260901/xxx/note.bson
注:在 mongorestore 的过程,由于 restoring indexes 耗时比较久,此时可以通过日志关注 Index Build 进度
[root@localhost ~]# tail -f /mnt/mongodb/data27017/mongod27017.log
2026-09-05T18:28:31.001+0800 I - [conn9517] Index Build: 178300/15216141 1%
...
2026-09-05T18:30:10.005+0800 I - [conn9517] Index Build: 1535600/15216141 10%
...
2026-09-05T19:23:14.137+0800 I - [conn9517] Index Build: 15195700/15216141 99%
- 第5步,针对历史表 note_history 表执行分批删除 2024-09-01 以后的数据,每次删除1万行
use xxx_his;
var cutoffDate = new Date("2024-09-01T00:00:00");
var batchSize = 10000;
var totalDeleted = 0;
// gte 删除 >= 20240901 以后的数据
while (true) {
var ids = db.note_history.find({createAt:{$gte:cutoffDate}},{_id:1}).limit(batchSize).toArray().map(d =>d._id);
if (ids.length === 0) break;
var res = db.note_history.deleteMany({ _id: { $in: ids } });
totalDeleted += res.deletedCount;
print("已删除: " + totalDeleted + " 条");
sleep(10); // 停顿10毫秒
}
print("删除完成,共删除: " + totalDeleted + " 条");
遇到的问题
- 1、对主表删除 2024-09-01 以前的数据,越删越慢,暂无好的思路
- 2、对历史数据归档结束之后,发现 note 表的膨胀率达到 2.71,经沟通建议执行
db.runCommand({compact:'note',force:true});对该表进行碎片整理,才发现 compact 是单核运行(示例 %Cpu0 使用率达到 100%),执行 40 分钟(00:36-01:16)仍未结束,查看日志发现大量 connection refused 报错(MongoDB 4.4 之前的版本执行compact会导致该数据库的读写操作被阻塞)。随后执行 ctrl +c 取消 compact,当前连接被迫断开,随后无法登录mongo,本寄希望 compact 能后台完成,但干等无望,决定重启一个java服务释放一些连接(02:01),登录mongo执行db.currentOp({"command.compact":{$exists:true}});发现 compact 进程"opid" : -1227504189仍在,且"killPending" : true,随后连接很快用完,尝试重启更多java服务,连接数从6632 降到 3846(02:25),执行多次db.killOp(-1227504189);仍无法杀掉该进程(应是在回滚中),且某个 CPU 单核使用率还是 100%、mongo数据库时常登录不上。最后 kill mongo 也无法结束进程(02:27),只能 reboot 重启服务器才恢复(02:32)。
注:compact 是一个本地化的维护命令,它的作用是整理当前节点的磁盘文件,因此它不会被复制到副本集的其他节点,不用担心服务重启或故障转移之后新主库发生回滚。
// 行数: 11134848 | 存储: 140440.81MB | 数据: 51868.55MB | 索引: 14126.09MB | 膨胀率: 2.71
var stats = db.note.stats();
var ratio = stats.storageSize / stats.size;
print("行数: " + stats.count + " | 存储: " + (stats.storageSize/1024/1024).toFixed(2) + "MB | 数据: " + (stats.size/1024/1024).toFixed(2) + "MB | 索引: " + (stats.totalIndexSize/1024/1024).toFixed(2) + "MB | 膨胀率: " + ratio.toFixed(2));
[root@localhost ~]# top
top - 01:13:34 up 567 days, 23:38, 6 users, load average: 1.87, 1.58, 1.47
Tasks: 237 total, 1 running, 236 sleeping, 0 stopped, 0 zombie
%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 1.3 us, 2.3 sy, 0.0 ni, 96.1 id, 0.3 wa, 0.0 hi, 0.0 si, 0.0 st
...
%Cpu15 : 0.7 us, 2.0 sy, 0.0 ni, 97.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17139 root 20 0 138.0g 83.8g 7688 S 117.9 66.6 26969:38 mongod
27229 root 20 0 239984 82660 21936 S 3.0 0.1 135:22.08 AliYunDunMonito
15523 root 20 0 2436588 8284 5644 S 1.7 0.0 5705:22 argusagent
[root@localhost ~]# tail -n5 /mnt/mongodb/data27017/mongod27017.log
2026-09-02T01:19:48.804+0800 I NETWORK [listener] connection refused because too many open connections: 8001
2026-09-02T01:19:48.875+0800 I NETWORK [listener] connection refused because too many open connections: 8001
2026-09-02T01:19:48.933+0800 I NETWORK [listener] connection refused because too many open connections: 8001
2026-09-02T01:19:49.260+0800 I NETWORK [listener] connection refused because too many open connections: 8001
2026-09-02T01:19:49.277+0800 I NETWORK [listener] connection refused because too many open connections: 8001
- 后续建议:考虑到版本过低,不建议再尝试在主库上执行 compact。如果确实需要整理碎片,应选择业务低峰期,先在从库操作 → 等待完成 → 切换主从 → 再操作原主库
