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。
示例代码