Reference: http://dev.guanghe.tv/2016/06/recovering-a-wiredtiger-collection-from-a-corrupt-wt-file.html
常在河邊走,哪能不濕鞋。雖然說只要不使用kill -9
殺進程,一般不會導致MongoDB出問題(Mongo本身有對kill做處理),但是程序總有跑偏的時候,也許哪次服務器重啟或者遇到斷電之類的,沒准就會導致數據庫文件損壞。
當然一般的異常關閉后啟動不了時可能也就是刪除一下lock、pid文件或者tmp下的sock文件即可搞定,根本不是什么問題,偶爾的數據異常--repair也就可以了(數據量大要建一堆索引的時候慎用,等很長時間給你拋出一個修復失敗是最容易讓人崩潰的……),而且其實開啟了journal的情況下非正常關閉mongo時還有比較好用的數據文件自動修復功能,MongoDB的可靠性其實還不錯。
我們這次要處理的就是一個沒有開journal而且還遇到wt數據文件出現異常的數據庫。
根據MongoDB的啟動日志,WT_SESSION.open_cursor: unable to read root page from file:collection-3659--4168324323017494102.wt: WT_ERROR: non-specific WiredTiger error
,是3659這個wt文件頭出錯了,這個要怎么辦呢?如果按照某些「專業數據庫修復」專家的建議,我們可以把這個損壞的wt文件替換掉。然后你猜會發生什么?WT_SESSION.open_cursor: collection-3659--4168324323017494102.wt read error: failed to read 4096 bytes at offset 42864640: WT_ERROR: non-specific WiredTiger error
,肯定還會報錯嘛,Mongo怎么可能不對文件做校驗,暴力地替換肯定行不通啊……再往后怎么發展呢?這就到了專家們的營收環節了,只有到這一步才能體現出他們的『文件修復技術』是有價值的嘛……好了,話不多說,修復個小文件開口就四位數起步,說什么都不能忍……接下來我們自己動手,一步步開啟MongoDB數據庫修復從入門到放棄系列教程。
以下步驟可以通過直接讀取wt文件恢復出對應的集合。關於MongoDB的WT引擎數據的目錄布局可以到這里補習。
1.准備工作
1.1 wt實用工具包
wt實用工具包是本文用到的核心工具。下面是ubuntu下的操作示例,編譯過程嘛,無非就是根據報錯信息安裝一些必要的依賴,一般來只要有gcc、g++之類的編譯必備工具就OK了,唯一一個比較特殊的依賴是Google家的snappy,因為WT引擎默認的表數據壓縮方式是snappy(而不是zlib)。
wget http://source.wiredtiger.com/releases/wiredtiger-2.7.0.tar.bz2
tar xvf wiredtiger-2.7.0.tar.bz2 && cd wiredtiger-2.7.0
sudo apt-get install libsnappy-dev build-essential
./configure --enable-snappy
make
1.2 要恢復的文件
准備好要恢復的collection*****.wt
,以及讀取它必備的_mdb_catalog.wt
、sizeStorer.wt
、storage.bson
、WiredTiger
、WiredTiger.basecfg
、WiredTiger.lock
、WiredTiger.turtle
、WiredTiger.wt
。我們可以把這些文件放到一個新目錄,比如本例放到mongo_bak
下,目錄結構如下:
collection********.wt
_mdb_catalog.wt
sizeStorer.wt
storage.bson
WiredTiger
WiredTiger.basecfg
WiredTiger.lock
WiredTiger.turtle
WiredTiger.wt
2.開始干吧
2.1 『打撈』出可以被恢復的部分
./wt -v -h ../mongo-bak -C "extensions=[./ext/compressors/snappy/.libs/libwiredtiger_snappy.so]" -R salvage collection******.wt
這一步操作會讀取我們指定的collection*****.wt
,忽略所有無法被恢復的數據,然后把新數據覆蓋回去。當然你也可以修改參數讓它把salvage后的文件寫到另一個地方。
運行上述命令會輸出WT_SESSION.salvage 639400
這樣的結果,后面那個數量其實就是所有能被恢復的數據。但是你現在還不能把這個直接讀取到MongoDB。
2.2 做些必要的數據格式調整
因為上一步產生的wt文件還沒法直接用MongoDB讀,所以我們接下來這幾步就利用wt的dump和load工具想辦法把他們導入到MongoDB。
2.2.1 wt --> dump
./wt -v -h ../mongo-bak -C "extensions=[./ext/compressors/snappy/.libs/libwiredtiger_snappy.so]" -R dump -f ../collection.dump collection******
這一步會把我們剛打撈出來的健康的wt文件dump到上級目錄的collection.dump
文件(當然這個文件叫什么名、存哪里你自己定,下一步你還能找到就行)。注意這一步操作指定的collection不需要寫wt擴展名了,程序很貼心有木有,省下3次敲鍵盤的體力可以干好多事情呢……我知道你都是復制粘貼的……
還要注意的是這一步程序是沒有任何狀態輸出的(如果你看到了估計肯定是錯誤提示……),如果想看到進度的話可以用shell中的ls -l
等命令通過觀察collection.dump
文件的變化來揣測進度,以及帶給你一些程序確實運行成功了的安全感。
2.2.2 a new collection
這一步主要是為接下來的load做准備:我們要建立一個新的數據庫,然后把上一步dump的數據導入進來,然后還有幾個關鍵而鬼畜的步驟,后面都會提到,所以這一步還是老老實實跟着做一下吧。
來,我們先啟動一個新的Mongo實例,我舉個栗子,可以這么做:
mongod --dbpath tmp-mongo --storageEngine wiredTiger --nojournal
然后我們要連接這個實例並創建一個新集合
mongo
> use Recovery
> db.brokedCollection.insert({test: 1})
> db.brokedCollection.remove({})
> db.brokedCollection.stats()
我們創建了一個新的叫做Recovery的數據庫,並且插入移除過文檔,所以這些集合的數據文件會被生成。使用stats()方法可以查看集合所對應的wt文件名稱,當然了,因為我們只使用了一個集合,所以跑到tmp-mongo
目錄下ls
一下也就知道這個collecion對應的wt文件是哪個了……為什么要知道這個?當然是下一步要用了……
2.2.3 dump --> new wt
接下來是見證奇跡的時刻:我們的數據很快就可以重現Mongo了!
./wt -v -h ../tmp-mongo -C "extensions=[./ext/compressors/snappy/.libs/libwiredtiger_snappy.so]" -R load -f ../collection.dump -r collection******
這一步就是把前面轉出來的dump文件讀入上一步生成的collection文件,所以-h指定的當然是我們上一步用的新mongo實例的路徑。執行這一步的時候需要先把Mongo關上,不然mongo進程會霸占着這個wt文件不讓你操作。
這個操作是有一個進度展示的table:collection-******: 1386220
來來來,見證一下奇跡:現在可以再用2.2.2里的方式啟動這個Mongo實例了
mongo
> show dbs //應該可以看到Recovery有數據了
> use Recovery
> show collections //應該可以看到brokedCollection里有數據了
> db.brokedCollection.count() //是0?吶尼?
看到0的那一刻估計你又開始緊張了,別急我們慢慢來,如果到這一步就能解決問題的話我們何苦要折騰出2.2.2這一步。
> db.brokedCollection.find({}, {_id: 1})
接着執行這一條能看到數據,所以我估計你會再次燃起希望,接下來我們繼續,讓奇跡2出現吧:
2.3 完善一下
需要注意為了防止出錯執行下面的步驟要確保MongoDB版本不小於3.2(因為3.2版本才是基於WiredTiger2.7構建的)。
mongodump
mongorestore --drop
沒錯就是這么簡單,接下來我們就可以驗證奇跡了。
mongo
> show dbs
> use Recovery
> show collections
> db.brokedCollection.count()
這次都回來了吧,一切正常了吧……接下來可以restore到任何你想要restore的地方了,如何使用可自由發揮。
3 收工
收工吧,累死了。
也許你已經受到上面步驟的啟發想到更多有意思的恢復方法了,然而人生苦短,適可而止……
MongoDB使用建議
對重要數據庫所在機器操作前一定要提前backup一下,把萬萬沒想到的損失降到最低;還有,除非真的不care數據的高可用性,不要隨便關journal;單台機器上有多個庫的話最好在配置文件中設置下directoryPerDB:true,讓每個庫有一個單獨文件夾;有富余機器的話盡量做一下復制集……