上周五晚上十一点,我正在家追剧,手机突然震个不停。运维群里炸了锅——MongoDB挂了,主节点宕机,从节点升主失败,整个数据库集群直接瘫痪。我当时嘴里还叼着薯片,瞬间就没了胃口。这场景太熟悉了,做过后端的都知道,数据库出问题的时候,那种无力感就像眼睁睁看着自己的车在高速上爆胎,你知道该修,但手边连个扳手都没有。

我赶紧连上VPN,登录服务器一看,日志里全是"failed to create file"的报错。这问题我见过——磁盘满了。但奇怪的是,df -h显示还有30%的空间。再仔细看,原来是MongoDB的journal日志把inode吃光了。inode是什么东西?简单说,每个文件都有一个索引节点,就像每个人的身份证号。inode用完了,就算磁盘还有空,你也写不了新文件。当时我脑子里只有一个念头:为什么没人给inode做监控?这个问题其实很普遍,很多团队只盯着磁盘容量,却忽略了inode这个隐形杀手。那天晚上我花了整整两个小时才清理完日志,重启服务,数据总算保住了。
但真正让人头疼的不是这种显性问题。上个月有个客户找我,说他们的MongoDB突然变慢了,查询时间从几毫秒飙升到几十秒。我远程上去一看,索引没问题,内存充足,CPU也不高。后来翻了半天慢查询日志,才发现是有人在凌晨跑了一个全表扫描的聚合任务,直接导致整个库的缓存被冲垮了。MongoDB的缓存机制跟MySQL不太一样,它用的是内存映射文件,一旦缓存被大量新数据覆盖,热点数据的命中率就会骤降。这就像你家冰箱本来放满了常吃的菜,突然有人塞进来一箱冻鱼,把常吃的菜挤到最里面,你每次开门都得翻半天。修复这种问题,光重启没用,得把那个聚合任务优化成增量模式,同时给关键查询加上hint,强制走索引。
说到修复数据库,很多人第一反应就是跑repair命令。MongoDB确实提供了这个操作,但很多人不知道,这个命令不是万能药。它本质上是在创建一个新的数据库副本,过程中会重建所有文档,丢弃掉损坏的记录。这意味着什么?如果你的数据文件有物理损坏,repair可能会直接丢掉部分数据。更坑的是,执行repair需要两倍的磁盘空间。我见过有人在一个只有100GB空闲的服务器上跑repair一个200GB的库,结果跑了一半就报错,数据库彻底不能用了。所以,跑repair之前一定要先做两件事:一是确认磁盘空间足够,二是先备份。而且最好在从节点上跑,别在主节点上搞,否则影响面太大。
还有一种更隐蔽的问题,就是WiredTiger存储引擎的内部校验错误。MongoDB 3.2之后的版本默认用WiredTiger,这个引擎会在数据读写时做校验和检查。如果硬件层面出现内存位翻转或者磁盘坏道,WiredTiger会检测到校验和不匹配,然后直接报错退出。有一次一个金融客户遇到这个问题,他们的MongoDB每隔两天就自动崩溃一次,重启后又正常。我一开始怀疑是内存问题,但跑了一整晚memtest没报错。后来检查系统日志,发现服务器发生了多次ECC纠错,说明内存已经在默默修复错误了,但MongoDB对这种错误特别敏感。换了内存条,问题解决。这种硬件层面的问题最难排查,因为你总以为是软件bug,结果却是硬件在撒谎。
很多人忽视的一个修复技巧是善用MongoDB的日志级别。当数据库出现异常时,默认的日志往往不够详细。你可以动态调整日志级别而不需要重启:。这能让你看到WiredTiger的详细操作日志,包括每个数据页的读写情况。有次我帮一个电商客户排查数据不一致的问题,就是靠调高日志级别,发现了一个副本集成员之间的oplog同步延迟导致的数据覆盖。那个客户的问题很典型:主节点写入一条记录,还没来得及同步到从节点,主节点挂了。从节点升主后,这条记录就丢了。后来我们启用了write concern为majority的写入策略,才彻底解决。但注意,这会影响写入性能,需要根据业务场景权衡。
还有一个经常被忽略的修复场景,就是索引损坏。MongoDB的索引是B-Tree结构,理论上很稳定,但在极端情况下——比如意外断电——索引文件可能会损坏。表现形式是查询时突然返回错误结果,或者扫描索引时卡死。修复方法很简单:删除索引再重建。但问题在于,如果你用的是复制集,删除索引的操作会在所有节点上执行,如果索引很大,重建过程会消耗大量IO,可能引发连锁反应。我建议的做法是,先在从节点上逐个重建索引,然后切主,再重建原来的主节点。整个过程需要精心编排,但能避免业务中断。
说到这,我想强调一个观点:修复数据库的终极方案不是技术,而是流程。我见过太多团队在数据库出问题时手忙脚乱,就是因为没有预案。你最好提前准备一个MongoDB故障恢复的SOP文档,把常见问题的排查步骤、修复命令、回滚方案都写清楚。比如磁盘空间不足怎么处理、主从切换失败怎么办、数据文件损坏怎么恢复,每一步都要有具体的命令和预期结果。然后定期做演练,真出事的时候才不至于抓瞎。数据库这东西,平时看起来稳如老狗,一旦出问题就是火葬场。但只要你把修复变成一种肌肉记忆,再大的火也能扑灭。那个周五晚上,我后来三点才睡,但第二天一早就把监控系统加上了inode告警。下次再出类似问题,至少警报会比客户的投诉来得早。


