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