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出來

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM