使用MongoDB一段時間的同學肯定會發現,MongoDB往往會占用比實際數據大小多不少空間的問題。
如果利用db.stats()命令去查看,會發現MongoDB會報告幾種不同的空間大小信息,如dataSize, storageSize以及fileSize。這些大小到底指的是什么意思呢?讓我們來通過了解MongoDB的存儲機制來解析這幾個數值的含義。
數據庫文件類型
MongoDB的數據庫文件主要有3種:
- journal 日志文件
- namespace 表名文件
- data 數據及索引文件
日志文件
跟一些傳統數據庫不同,MongoDB的日志文件只是用來在系統出現宕機時候恢復尚未來得及同步到硬盤的內存數據。日志文件會存放在一個分開的目錄下面。啟動時候MongoDB會自動預先創建3個每個為1G的日志文件(初始為空)。除非你真的有持續海量數據並發寫入,一般來說3個G已經足夠。
命名文件 dbname.ns
這個文件用來存儲整個數據庫的集合以及索引的名字。這個文件不大,默認16M,可以存儲24000個集合或者索引名以及那些集合和索引在數據文件中得具體位置。通過這個文件MongoDB可以知道從哪里去開始尋找或插入集合的數據或者索引數據。這個值可以通過參數調整至2G。
數據文件 dbname.0, dbname.1,… dbname.n
MongoDB的數據以及索引都存放在一個或者多個MongoDB數據文件里。第一個數據文件會以“數據庫名.0”命名,如 my-db.0。這個文件默認大小是64M,在接近用完這個64M之前,MongoDB 會提前生成下一個數據文件如my-db.1。數據文件的大小會2倍遞增。第二個數據文件的大小為128M,第三個為256M。一直到了2G以后就會停止,一直按這個2G這個大小增加新的文件。
當然MongoDB還會生成一些臨時文件如 _tmp 和 mongod.lock等, 不過他們跟我們的討論都沒有太大相關性。
數據文件結構
Extent
在每一個數據文件內,MongoDB把所存儲的BSON文檔的數據和B樹索引組織到邏輯容器“Extent”里面。如下圖所示(my-db.1和my-db.2 是數據庫的兩個數據文件):
- 一個文件可以有多個Extent
- 每一個Extent只會包含一個集合的數據或者索引
- 同一個集合的數據或索引可以分布在多個Extent內。這幾個Extent也可以分步於多個文件內
- 同一個Extent不會又有數據又有索引
Record 記錄
在每個Extent里面存放有多個”Record“, 每一個記錄里包含一個記錄頭以及MongoDB的BSON文檔,以及一些額外的padding空間。Padding是MongoDB在插入記錄時額外分配一些未用空間,這樣將來文檔變大的時候不至於需要把文檔遷移到別處。 記錄頭以整個記錄的大小開始,包括該記錄自己的位置以及前一個記錄和后一個記錄的位置。可以想象成一個Double Linked List。
數據庫大小參數
在之前的基礎上,我們可以來理解一下db.stats()里面關於空間大小參數的含義。
如下圖截圖所示:
dataSize
dataSize是最接近真實數據大小的一個參數。你可以用來檢查你的數據有多少。這個大小包括了數據庫(或者集合)的每條記錄的總和。注意每條記錄除了BSON文檔外還有header及padding這些額外開銷。所以實際大小會比真正數據所占空間會稍大。
當刪除文檔的時候,這個參數會相應變小因為它是所有文檔數的大小總和。如果你的文檔沒有刪除,只是文檔內部的字段被刪除或縮小,則不會對dataSize 有影響。原因就是因為文檔所在記錄還在,並且整條記錄所占空間並無改動,只不過記錄內的未用空間變多了而已。
storageSize
這個參數等於數據庫或者某個集合所有用到的Data Extents的總和。注意這個數字會大於dataSize因為Extent里面會有一些刪除文檔之后留下來的碎片(deleted)。及時你的storageSize大出dataSize很多,這個也不一定就是很糟糕的情況。 如果有新插入的文檔小於或等於碎片的大小,MongoDB會重新利用這個碎片來存儲新的文檔。不過在這之前這些碎片將一直會被保留在那里占用空間。由於這個原因,你刪除文檔的時候這個參數不會變小。
碎片問題會因為運行的時間變長而變得嚴重。你可以通過 compact 命令來進行碎片清理或者通過新架一台從機復制所有數據,然后變成主節點的方式來解決這些碎片。
fileSize
這個參數只在數據庫上有效,指的是實際文件系統中用到的文件的大小。它包括所有的數據Extents的總和,索引Extent的總和,以及一些未被分配的空間。之前提到MongoDB會對數據庫文件創建時候進行預分配,例如最小就是64M,哪怕你只有幾百個KB的數據。所以這個參數可能會比實際的數據大小會大不少。 這些額外未用空間是用來保證MongoDB可以在新的數據寫入時候快速的分配新的Extent,避免引起磁盤空間分配引起的延遲。
值得注意的是,當你刪除文檔,或甚至集合和索引,這個參數不會變小。換句話說,數據庫所使用的硬盤空間只會上升(或者不變),而不會因為刪除數據而變小。當然需要知道的是這並不就意味着浪費,只是說有很多預留空間而已。