天天看點

MongoDB mmapv1存儲引擎解析

mongodb的mongod服務管理一個資料目錄,可包含多個db,每個db的資料單獨組織,本文主要介紹mmapv1存儲引擎的資料組織方式。

每個database(db)由一個.ns檔案及若幹個資料檔案組成

資料檔案從0開始編号,依次為mydb.0、mydb.1、mydb.2等,檔案大小從64mb起,依次倍增,最大為2gb。

每個db包含多個namespace(對應mongodb的collection名),mydb.ns實際上是一個hash表(采用線性探測方式解決沖突),用于快速定位某個namespace的起始位置。

hash表裡的一個節點包含的中繼資料結構如下,每個節點大小為628bytes,16m的ns檔案最多可存儲26715個namespace。

key為namespace的名字,為固定長度128位元組的字元數組。

hash為namespce的hash值,用于快速查找

value包含一個namespace所有的中繼資料

namespace中繼資料結構如下:

其中diskloc代表某個資料檔案的具體偏移位置,資料檔案使用mmap映射到記憶體空間進行管理,記憶體的管理(哪些資料何時換入/換出)完全交給os管理。

每個資料檔案被劃分成多個extent,每個extent隻包含一個namespace的資料,同一個namespace的所有extent之間以雙向連結清單形式組織。

namesapce的中繼資料裡包含指向第一個及最後一個extent的位置指針,通過這些資訊,就可以周遊一個namespace下的所有extent資料。

每個資料檔案包含一個固定長度頭部datafileheader

header中包含資料檔案版本、檔案大小、未使用空間位置及長度、空閑extent連結清單起始及結束位置。extent被回收時,就會放到資料檔案對應的空閑extent連結清單裡。

unusedlength為資料檔案未被使用過的空間長度,unused則指向未使用空間的起始位置。

每個extent包含多個record(對應mongodb的document),同一個extent下的所有record以雙向連結清單形式組織。

每個record對應mongodb裡的一個文檔,每個record包含固定長度16bytes的描述資訊。

record被删除後,會以deleterecord的形式存儲,其前兩個字段與record是一緻的。

一個namespace下的所有的已删除記錄(可以回收并複用的存儲空間)以單向連結清單的形式,為了最大化存儲空間使用率,不同size(32b、64b、128b...)的記錄被挂在不同的連結清單上,namespacedetail裡的deletedlistsmall/deletedlistlarge包含指向這些不同大小連結清單頭部的指針。

MongoDB mmapv1存儲引擎解析

檢查對應的namespace對應的删除記錄連結清單裡是否有合适的deletedrecord可以利用,如果有,則直接複用删除空間寫入記錄。

檢查資料檔案的freelist裡是否有合适大小的空閑extent可以利用,如果有則直接利用空閑的extent,将記錄寫入。

第1、2步都不成功,則寫建立新的extent寫入記錄;建立新extent時,如果目前的資料檔案沒有足夠的空閑空間,則建立新的資料檔案。

删除的記錄會以deleterecord的形式插入到對應集合的删除連結清單裡,删除的空間在下一次寫入新的記錄時可能會被利用上;但也有可能一直用不上而浪費。比如某個128bytes大小的記錄被删除後,接下來寫入的記錄一直大于128b,則這個128b的deletedrecord不能有效的被利用。

當删除很多時,可能産生很多不能重複利用的"存儲碎片",進而導緻存儲空間大量浪費;可通過對集合進行compact來整理存儲碎片。

更新record時,分2種情況

更新的record比原來小,可以直接複用現有的空間(原地更新);多餘的空間如果足夠多,會将剩餘空間插入到deletedrecord連結清單;

更新的record比原來大,更新相當于删除 + 新寫入,原來的空間會插入到deletedrecord連結清單裡。

更新跟删除類似,也有可能産生很多存儲碎片;如果業務場景裡更新很多,可通過合理設定record padding,盡量讓每次更新都直接複用現有存儲空間。

沒有索引的情況下,查詢某個record需要周遊整個集合,讀取出符合條件的record;如果經常需要根據每個緯度查詢record,則需要給集合建立索引以提供查詢效率。