mongodb的數據文件存在dbpath選項指定的目錄里。每個庫(database)都有一系列的文件:dbname.ns, dbname.0, dbname.1, ...數據文件也叫pdfile,意思是Portable Data File。
dbname.ns文件
dbname.ns文件存儲命名空間信息。在mongodb里,每個collection都具有一個命名空間,名字為dbname.collection_name。dbname.ns文件存儲哈希表節點數組。
struct Node { int hash; // 根據key計算出來的hash值。如果大於0,則表示已經使用;等於0,則表示未使用 Namespace key; // 命名空間的名字,為128字節的char數組 NamespaceDetails value; // 命名空間信息 };
哈希節點目前大小是628字節,dbname.ns文件的默認大小是16M,一共可以存放26715個命名空間。nssize選項可以設置dbname.ns文件的大小。
如何查找?
n為數組大小,maxChain = (int) (n * 0.05),chain = 0
a.根據key計算hash值(為一個大於0的整數)
b.i = hash % n(n為數組大小),start = i,
c.比較下標為i的節點的hash和key,如果相同則找到;如果不同,則i = (i + 1) % n,chain++, 繼續比較
d.如果i == start或者chain >= maxChain,則查找失敗
尋找空閑節點?
a.根據key計算hash值(為一個大於0的整數)
b.i = hash % n(n為數組大小),start = i
c.如果下標為i的節點空閑,則返回。否則,i = (i + 1) % n,chain++,繼續找
d.如果i == start或者chain >= maxChain,則找不到
實際上,mongodb把上述的2個操作合成一個操作,如果找不到節點,則返回第一個空閑節點。所以,當插入新的節點時,會查找maxChain次。
示例代碼
dbname.<#>系列文件
dbname.<#>系列文件存儲了每個庫的所有數據,其文件格式為
--------------------------------------------
DataFileHeader
--------------------------------------------
Extent (for a particular namespace)
Record
...
Record (some chained for unused space)
--------------------------------------------
more Extents...
--------------------------------------------
DataFileHeader是數據文件的頭部,后面的部分為Extent。
DiskLoc
/** represents a disk location/offset on disk in a database. 64 bits. * it is assumed these will be passed around by value a lot so don't do anything to make them large * (such as adding a virtual function) */ struct DiskLoc { int _a; // this will be volume, file #, etc. but is a logical value could be anything depending on storage engine int ofs; };
DiskLoc表示數據文件的位置,_a為dbname.<#>文件的編號,ofs為在文件中的偏移,從0開始。
DataFileHeader
class DataFileHeader { public: int version; int versionMinor; int fileLength; DiskLoc unused; /* unused is the portion of the file that doesn't belong to any allocated extents. -1 = no more */ int unusedLength; DiskLoc freeListStart; DiskLoc freeListEnd; char reserved[8192 - 4*4 - 8*3]; char data[4]; // first extent starts here enum { HeaderSize = 8192 }; };
unused字段是未分配空間的位置,unusedLength為未分配空間的大小。freeListStart和freeListEnd這2個字段比較特殊,只在dbname.0文件中才有效,存儲了空閑Extent鏈表的頭部Extent的位置和尾部Extent的位置。對於一個庫來說,被刪除的collection的所有Extent都會掛到這個空閑Extent鏈表中。
可以利用這個特性來恢復被刪除的collection,示例代碼
Extent
class Extent { public: enum { extentSignature = 0x41424344 }; unsigned magic; DiskLoc myLoc; DiskLoc xnext, xprev; /* next/prev extent for this namespace */ /* which namespace this extent is for. this is just for troubleshooting really and won't even be correct if the collection were renamed! */ Namespace nsDiagnostic; int length; /* size of the extent, including these fields */ DiskLoc firstRecord; DiskLoc lastRecord; char _extentData[4]; };
每個Extent本身是一個雙向鏈表節點,xnext和xprev字段指向后繼和前驅節點。Extent內的所有Record也組成一個雙向鏈表,firstRecord指向頭部Record,lastRecord指向尾部Record。
Record
class Record { public: enum HeaderSizeValue { HeaderSize = 16 }; private: int _lengthWithHeaders; int _extentOfs; int _nextOfs; int _prevOfs; /** be careful when referencing this that your write intent was correct */ char _data[4]; }; class DeletedRecord { private: int _lengthWithHeaders; int _extentOfs; DiskLoc _nextDeleted; };
_extentOfs字段表示Record所在Extent在數據文件中的偏移。屬於同一個Extent的Record組成一個雙向鏈表,_nextOfs和_prevOfs分別指向后繼和前驅。
DeletedRecord是一種特殊的Record,被刪除的Record或者Extent中沒有分配的空間,都會作為DeletedRecord。根據DeletedRecord的大小,形成19個單向鏈表,每個鏈表的表頭存在命名空間信息里。
文件空間的分配以Extent為單位。每個命名空間的所申請的Extent形成一個雙向鏈表,表頭和表尾存在命名空間信息里。Record在Extent里分配,每個Extent里的所有Record形成一個雙向鏈表,表頭和表尾存在Extent頭部。可以想到,對命名空間的所有Record的遍歷方法為:遍歷Extent鏈表,對每個Extent,遍歷其Record鏈表。空閑的Record(Extent里剩余的空間、或者Record被刪除),稱作DeleteRecord,根據其大小,形成19個單向鏈表(表頭也存在命名空間里)。可以想到,申請一個Record的方法:先從空閑的Record里面找;如果找不到,則分配新的Extent。

示例代碼
