三招解決MongoDB的磁碟IO問題

pythontab發表於2014-08-19

三招解決MongoDB的磁碟IO問題


作者:nosqlfan on 星期六, 四月 28, 2012 · 3條評論 【閱讀:7,922 次】

有點標題黨的意思,不過下面三招確實比較實用,內容來自Conversocial公司的VP Colin Howe在London MongoDB使用者組的一個分享。

申請:下面幾點並非放四海皆准的法則,具體是否能夠使用,還需要根據自己的應用場景和資料特點來決定。

1.使用組合式的大文件

我們知道MongoDB是一個文件資料庫,其每一條記錄都是一個JSON格式的文件。比如像下面的例子,每一天會生成一條這樣的統計資料:

{ metric: "content_count", client: 5, value: 51, date: ISODate("2012-04-01 13:00") }

{ metric: "content_count", client: 5, value: 49, date: ISODate("2012-04-02 13:00") }

而如果採用組合式大文件的話,就可以這樣將一個月的資料全部存到一條記錄裡:

{ metric: "content_count", client: 5, month: "2012-04", 1: 51, 2: 49, ... }

透過上面兩種方式儲存,預先一共儲存大約7GB的資料(機器只有1.7GB的記憶體),測試讀取一年資訊,這二者的讀效能差別很明顯:

第一種: 1.6秒

第二種: 0.3秒

那麼問題在哪裡呢?

實際上原因是組合式的儲存在讀取資料的時候,可以讀取更少的文件數量。而讀取文件如果不能完全在記憶體中的話,其代價主要是被花在磁碟seek上,第一種儲存方式在獲取一年資料時,需要讀取的文件數更多,所以磁碟seek的數量也越多。所以更慢。

實際上MongoDB的知名使用者foursquare就大量採用這種方式來提升讀效能。見此

2.採用特殊的索引結構

我們知道,MongoDB和傳統資料庫一樣,都是採用B樹作為索引的資料結構。對於樹形的索引來說,儲存熱資料使用到的索引在儲存上越集中,索引浪費掉的記憶體也越小。所以我們對比下面兩種索引結構:

db.metrics.ensureIndex({ metric: 1, client: 1, date: 1})

db.metrics.ensureIndex({ date: 1, metric: 1, client: 1 })

採用這兩種不同的結構,在插入效能上的差別也很明顯。

當採用第一種結構時,資料量在2千萬以下時,能夠基本保持10k/s 的插入速度,而當資料量再增大,其插入速度就會慢慢降低到2.5k/s,當資料量再增大時,其效能可能會更低。

而採用第二種結構時,插入速度能夠基本穩定在10k/s。

其原因是第二種結構將date欄位放在了索引的第一位,這樣在構建索引時,新資料更新索引時,不是在中間去更新的,只是在索引的尾巴處進行修改。那些插入時間過早的索引在後續的插入操作中幾乎不需要進行修改。而第一種情況下,由於date欄位不在最前面,所以其索引更新經常是發生在樹結構的中間,導致索引結構會經常進行大規模的變化。

3.預留空間

與第1點相同,這一點同樣是考慮到傳統機械硬碟的主要操作時間是花在磁碟seek操作上。

比如還是拿第1點中的例子來說,我們在插入資料的時候,預先將這一年的資料需要的空間都一次性插入。這能保證我們這一年12個月的資料是在一條記錄中,是順序儲存在磁碟上的,那麼在讀取的時候,我們可能只需要一次對磁碟的順序讀操作就能夠讀到一年的資料,相比前面的12次讀取來說,磁碟seek也只有一次。

db.metrics.insert([

    { metric: 'content_count', client: 3, date: '2012-01', 0: 0, 1: 0, 2: 0, ... }

    { .................................., date: '2012-02', ... })

    { .................................., date: '2012-03', ... })

    { .................................., date: '2012-04', ... })

    { .................................., date: '2012-05', ... })

    { .................................., date: '2012-06', ... })

    { .................................., date: '2012-07', ... })

    { .................................., date: '2012-08', ... })

    { .................................., date: '2012-09', ... })

    { .................................., date: '2012-10', ... })

    { .................................., date: '2012-11', ... })

    { .................................., date: '2012-12', ... })

])

結果:

如果不採用預留空間的方式,讀取一年的記錄需要62ms

如果採用預留空間的方式,讀取一年的記錄只需要6.6ms


相關文章