標籤:
轉自:http://quentinxxz.iteye.com/blog/2149440
一、正常情況下,不應該有這種需求
首先,大家應該有個概念,標題中的這個問題,在大多情況下是一個偽命題,不應該被提出來。要知道,對於一般較大資料量的資料庫,全表查詢,這種操作一般情況下是不應該出現的,在做正常查詢的時候,如果是範圍查詢,你至少應該要加上limit。
說一下,我的應用程式情境:用於全量建立搜尋引擎的索引。這就是一種需要用到全表掃描的非一般情況。對於全表掃描的結果,我們沒有排序要求。
二、情況說明
既然有如此大的資料量,那儲存所佔空間基本都是上T的了。所以肯定是使用了mongodb叢集,而且配置了分區的分布式環境。
公司的伺服器,效能比較好,應該是24核,96g記憶體的。所以讀者們使用不同機器,測出來的用時,跟我這的結果可能不一定相符。
三、第一種方法,利用chunk資訊作劃分。
原理:我們知道,在分區環境下,mongodb使用chunk塊來組織資料。同一個chunk塊的資料肯定存在於同一個分區上。假設,我們以 “_id”作分區時所用的片鍵。Mongodb為了保證範圍尋找的效率,一定會將一定範圍內的_id值的document方在同一個chunk中。而且通過mongodb自身提供的方法,我們可以很方便的擷取,每一個chunk的中maxKey與minKey。[minKey,maxKey) 這個範圍內的資料肯定在同一個chunk內,也並定在同一個分區中。
做法:1、先擷取所有chunk資訊,得到他們的maxKey與minKey。
2、多線程執行,將這些chunk資訊,分發給這些執行線程。
3、各線程,根據當前的chunk與 maxKey與minKey資訊,做範圍尋找。
4、最後將結果匯總。
這樣做的好處在於:
1、每次對一個chunk做的範圍尋找,肯定是只在一個分區(意味著同一塊硬碟)中進行的,不會分散到多個分區。這樣很高效。
2、可以方便的利用多線程,提高效率。
這種方法我沒試過,公司的前輩嘗試過,據說,最終用時3小時以內。是最為理想的效果。
四、改用散列片鍵後的全表掃描方法
用上面的方法,有一個前題,就是分區策略採用的是mongodb預設的升序片鍵的方法。這樣才保證,升序的_id,會按排序範圍分布在chunk塊中。這樣這策略,存在一個明顯的問題,就是會造成所以新增的doucument的寫入肯定都會命中到具有當前最大_id的chunk上。造成寫入的分發的不平衡。
前文中,說過,全表掃描,應該是一個正常情況下不被允許的情況。所以資料庫策略的制定也不應該以考慮全表掃描的效率為優先,當前情況下,就應以寫入效為優先考慮。公司正在使用的片鍵策略,是片鍵策略散列片鍵(hashed shard key),這樣的話,寫入請況會被很好地分發到多個分區上,但是不利用進行範圍尋找。上面用的全表掃描方法就沒法再用了。
做法:1、獲得全域的最大id maxID與全域的最小id minID。
2、設定一個stepSize ,比如5000。將[minID,maxID]按5000為一個chunk(我們定義的一個邏輯chunk)作切分,那麼第一個區塊範圍[minID,minID+5000),
3、各線程,根據分配到的chunk塊的maxID與minID資訊,做範圍尋找。
4、最後將結果匯總。
這樣做法的問題:
1、對一個chunk進行查詢,會命中多個分區進行查詢,查詢效率大幅降低。
2、 如果_id分布稀疏,查詢變得更快。因為以5000為stepSize, 其中可能有不少_id是不存在的。測試同學幫我搭線下測試資料時,2000w條資料,由於計算錯誤,_id的範圍分布劇然從10億起,到130億止。導致的線程幾乎一起在空跑chunk。所以_id分布稀疏的情況下,這種查詢方式完全不適用。
3、 _id分布不均。可能某個chunk中幾乎5000個滿載,有些chunk只有很少幾個_id有效。那麼就會導致計算資源分布不均。
最後的結果不理想,我一開始,由於涉及一些聯表join操作,用時16多個小時。後來各種調整,加線程,去掉一些操作,差不多仍需10小時。
五、改用散列片鍵後的較高效全表掃描方法
上面的那種方法,當然是不理想的。光查資料就需要16個小時,加上後續處理,肯定更久,這樣我們的搜尋引擎索引建立,就不可能當前完成了。但一直苦於沒有更好的方法。
最終這個問題,還是被我的頭解決了。 先說一下,最終效果吧。20w條/秒,約3小時完成。
其實,最終的方法很簡單,但必須放棄多線程(多cursor),至多一個分區一個線程。
做法:使用單線程掃描,不加適合可能影響排序的條件。
這樣做的目的是使用mongodb中的自然排序。掃描時,必然是依次命中某一個分區讀取,不會帶來磁碟震蕩問題。
怎麼對10億資料量級的mongoDB作高效的全表掃描