有點標題黨的意思,不過下面三招確實比較實用,內容來自Conversocial公司的VP Colin Howe在London MongoDB用戶群的一個分享。
申請:以下幾點並非放四海皆準的法則,具體是否能夠使用,還需要根據自己的應用場景和資料特性來決定。
我們知道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就大量採用這種方式來提升讀取效能。見此
我們知道,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欄位不在最前面,所以其索引更新經常是發生在樹狀結構的中間,導致索引結構會經常進行大規模的變更。
與第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
以上是MongoDB的磁碟IO問題的解決方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!