之前写过一篇MongoDB 无法启动,如何恢复数据的文章,介绍了几种从无法启动的 MongoDB 节点恢复数据的方法,主要包括:
其中方法1-3比较简单,第4种方法对 WiredTiger 引擎的原理不了解,可能完全无从下手,本文将详细介绍如何通过 WiredTiger 的工具来提取有效数据。
每个 MongoDB 的集合/索引都对应一个 WiredTiger Table; 集合名跟 Table 名的映射关系保存在元数据里,只有通过这部分元数据,才能获得这个映射关系。
wt
工具可以将其提取出来。默认 MongoDB 的源码里不会编译出 wt 工具,可以自行下载 WiredTiger 源码编译,接下来的介绍假设你已经编译出了 wt 工具。假设你的数据库只有很少的集合,根据集合的大小,你可能很容易判断出集合名跟 WiredTiger 文件名的对应关系,比如
somedb.collection ===> somedb/collection-10--6822964274931136278 (如果没有指定 directoryPerDB 的选项,则没有 somedb/前缀
只要这个这个集合的数据还是完整的,没有被损坏,我们就可以通过如下步骤来恢复
./wt -v -h some_db_home -C "extensions=[./ext/compressors/snappy/.libs/libwiredtiger_snappy.so]" -R dump -f collection.dump somedb/collection-10--6822964274931136278
此时,这个集合的数据就已经导出到 collection.dump 文件了,如果这一步出错,说明这个文件 WiredTiger 已经无法解析了,则无法恢复了。
mkdir some_dest_db_home
mongod --dbpath some_dest_db_home --port some_port
mongo --port some_port
> use somedb
> db.createCollection("collection") // 创建临时集合
> db.collection.stats().uri // 查看该集合对应的 WiredTiger 表名
假设临时集合创建后,其在新的临时实例上集合名与 WiredTiger 表名对应关系如下
somedb.collection ===> somedb/collection-2--6822964274931136278 (如果没有指定 directoryPerDB 的选项,则没有 somedb/前缀
停掉临时实例,然后将 Step1 里 dump 的数据 load 到临时集合
./wt -v -h some_dest_db_home -C "extensions=[./ext/compressors/snappy/.libs/libwiredtiger_snappy.so]" -R load -f collection.dump -r somedb/collection-2--6822964274931136278
这时在目标实例上,访问 somedb.collection 时,访问的数据就是从「已经损坏的源实例」上 somedb.collection 的数据,只不过这个实例的 id 索引、统计元数据等是无法匹配的,但这并不影响全表扫描的数据访问。
通过 mongodump 从目标实例将 somedb.collection 备份出来,mongodump 只会触发对集合数据的顺序访问。
然后通过 mongorestore 重新导入,restore 后的数据即为恢复的目标数据。
上面介绍了如何恢复单个集合,如果损坏的 MongoDB 里有大量的集合,一个个按上面的流程恢复要搞到猴年马月了;要想自动化,需要解决的关键问题就是如果确定 MongoDB 集合名 与 WiredTiger 表名的映射关系,这里只需要稍加修改 MongoDB 源码,可以让 损坏的mongod 在 repair 模式下把映射关系输出
diff --git a/src/mongo/db/storage/kv/kv_database_catalog_entry.cpp b/src/mongo/db/storage/kv/kv_database_catalog_entry.cpp
index 91afa40026..523cb1fa95 100644
--- a/src/mongo/db/storage/kv/kv_database_catalog_entry.cpp
+++ b/src/mongo/db/storage/kv/kv_database_catalog_entry.cpp
@@ -260,6 +260,8 @@ void KVDatabaseCatalogEntry::initCollection(OperationContext* opCtx,
const std::string ident = _engine->getCatalog()->getCollectionIdent(ns);
+ log() << "metadata mapping " << ns << " " << ident;
+
RecordStore* rs;
if (forRepair) {
// Using a NULL rs since we don't want to open this record store before it has been
somedb.collection1 collection-2--4775156767705741267
somedb.collection2 collection-4--4775156767705741267
....
写个 shell 脚本很容易做到自动化,导出的文件名可以跟集合名保持一致,用于区分
写个 js 脚本,通过 mongo shell 批量创建
somedb.collection1 collection-6--4335156767705741253
somedb.collection2 collection-8--4335156767705741253
....
跟 Step2 类似,需要写个 shell 脚本
修正 id 索引、统计信息等
祝你成功 ^_^ ^_^ ^_^