最近做數據備份的時候發現了有個很嚴重的問題,那就是數據丟失(最后證明沒丟,是別的問題造成的)。
問題如下:
我通過兩種方式在兩個mongoDB集群中,對一組collection進行備份,最后2個備份數據的數據個數不相同,並且都小於原始collection的count結果。於是便開始尋求解決辦法,流程如下:
1、記錄3組數據,原始數據集按條件count有909217個數據,備份代碼如下,其中replaceOne備份下來的數據有907582條,而使用insertMany備份下來的結果有906281條(注釋是之后加的,之前用的是insertMany):
rdd.foreachPartition { x => { val mongoURI = new MongoClientURI(uri) val mongo = new MongoClient(mongoURI) val db = mongo.getDatabase("wenshu") val dbColl = db.getCollection("testbackup") val mongoURI2 = new MongoClientURI(uri2) val mongo2 = new MongoClient(mongoURI2) val db2 = mongo2.getDatabase("wenshu") val dbColl2 = db2.getCollection(backName) var count = 0 var resList = new ArrayList[Document] x.foreach(y => { // count = count + 1 // resList add y try{ dbColl.replaceOne(eqq("_id", y.get("_id")), y, new UpdateOptions().upsert(true)) dbColl2.insertOne(y) }catch{ case e: Throwable => e.printStackTrace() } // 使用這種方式插入會導致插入的數據和真實數據數量對應不上, 先注釋掉有機會再找原因 // if (count == 10000){ // try{ // dbColl2.insertMany(resList, new InsertManyOptions().ordered(false)) // }catch{ // case e: Throwable => e.printStackTrace() // } // resList.clear // count = 0 // } }) // if (count > 0) // try{ // dbColl2.insertMany(resList, new InsertManyOptions().ordered(false)) // }catch{ // case e: Throwable => e.printStackTrace() // }
2、通過查詢stackoverflow和jira發現數據丟失問題曾經存在過,但都是2.0之前的mongodb,現在商用化之后的mongodb基本沒人出現過數據丟失問題。
3、檢查代碼,發現不是插入代碼錯誤。
4、對抽出來的907582條數據的庫進行備份,還是用上述程序,發現replaceOne的數據有907582,而insertmany只有904291條數據。
5、結合上述條件,推測是insertMany導致部分數據丟失,所以才會出現insertMany結果和replaceOne不一樣。
6、對此結論進行測試,將insertMany改為上述代碼中的insertOne重新備份907582條數據。
7、結果正確,重新備份下來的2份數據都是907582條。目前解決了其中一個問題,就是備份出來的兩份數據不一樣多的問題,接下來考慮備份數據和從總庫中抽取的數據不一致的問題。
8、對mongo shell的count操作查找其工作原理,發現有一些報告count數據不准的問題,結合自身原因推測是count的問題,數據應該只有907582條。
9、通過多抽取幾遍對這個問題進行測試,按同樣條件抽了3遍返回的結果都是907582條,可以認定數據庫中只有907582條滿足此條件的數據。
結論:
1、MongoDB的Count操作有可能返回錯誤的結果。至少在Sharding Cluster,多個索引和2級索引的條件下會出現這種問題。
2、插入時不要使用InsertMany,會導致數據丟失。
3、同理,盡量不要使用updateMany,雖然不會導致數據丟失,但是按照結論2推測有可能出現某些數據更新失敗的情況。