mongodb数据文件格式

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; // 命名空间信息
};
View Code

哈希节点目前大小是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;
};
View Code

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 };
};
View Code

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];
};
View Code

每个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;
};
View Code

_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。

示例代码

把一个collection的所有记录dump出来

 

posted @ 2013-03-15 15:53  tripleH  阅读(7608)  评论(1编辑  收藏  举报