MongoDB的磁盤IO問題的解決方法

有點標題黨的意思,不過下面三招確實比較實用,內容來自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

? 版權聲明
THE END
喜歡就支持一下吧
點贊11 分享