MongoDB源码学习:catalog与storage

402 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

简介

为了方便的继续往下继续学习MongoDB的源码,这里先了解catalog与storage,因为无论是Command,还是Insert、Update等,最终都是与catalog和storage两个或其中一个交互。

catalog

catalog其翻译是目录的意思,目录在src/mongo/db/catalog。这个地方可以认为是一个为上面应用层和下面存储层连接的地方,里面封装了MongoDB的抽象对象,例如DataBase、Collection等。

下面简单看下catalog中Database、Collection、CollectionCatalog都定义了什么

// database.h 只列出了部分伪代码
class Database : public Decorable<Database> {
public:
    virtual Status userCreateNS(...) const = 0;
    virtual Collection* createCollection(...) const = 0;
    virtual Status dropCollection(...) const = 0;
    virtual Status renameCollection(...) const = 0;
}


// collection.h 同样只展示部分伪代码
class Collection : public DecorableCopyable<Collection> {
public:
    virtual bool findDoc(...) const = 0;
    virtual void deleteDocument(...) const = 0;
    virtual Status insertDocuments(...) const = 0;
}


// collection_catalog.h
class CollectionCatalog {
public:
    CollectionPtr lookupCollectionByUUID(OperationContext* opCtx, CollectionUUID uuid) const;
private:
    std::shared_ptr<Collection> _lookupCollectionByUUID(CollectionUUID uuid) const;
    
    CollectionCatalogMap _catalog;
    NamespaceCollectionMap _collections;
}

从上面的伪代码大概可以猜测出这3个对象的意思了:

  • Database - 数据库的抽象
  • Collection - 集合的抽象
  • CollectionCatalog - 集合目录的抽象(简单来讲就是Collection的集合)

storage

看名字就知道这个是持久化相关的,目录在src/mongo/db/storage。这个地方是对底层存储的一层抽象。下面看看几个具体的例子。

// storage_engine.h
class StorageEngine {
public:
    virtual std::vector<std::string> listDatabases() const = 0;
    
    virtual KVEngine* getEngine() = 0;
    virtual DurableCatalog* getCatalog() = 0;
}

// durable_catalog.h
class DurableCatalog {
public:
    virtual RecordStore* getRecordStore() = 0;
    virtual StatusWith<std::pair<RecordId, std::unique_ptr<RecordStore>>> createCollection(...) = 0;
    virtual Status createIndex(...) = 0;
}

// record_store.h
class RecordStore : public Ident {
public:
    virtual bool findRecord(OperationContext* opCtx, const RecordId& loc, RecordData* out) const
    virtual Status insertRecords(OperationContext* opCtx,
                                 std::vector<Record>* inOutRecords,
                                 const std::vector<Timestamp>& timestamps) = 0;
}

由上面伪代码不难看出:

  • StorageEngine - 作为底层存储引擎的抽象,提供一个getCatalog方法获取数据集合,另外还有提供一个getEngine的方法获取KVEngine。
  • DurableCatalog - 持久化集合,由StorageEngine::getCatalog获得。封装了底层操作Collection集合的方法。
  • RecordStore - 行存储,封装了行记录的CRUD操作。

AutoGetDb

MongoDB在创建DB的时候,其实只是在内存中创建了一个Database的数据结构,并没有对storage做实际的操作,这里讲一下MongoDB是如何“创建Database”的。

// src/mongo/db/catalog/create_collection.cpp
Status _createView(OperationContext* opCtx,
                   const NamespaceString& nss,
                   CollectionOptions&& collectionOptions) {
                   // ..... 省略
                   AutoGetDb autoDb(opCtx, nss.db(), MODE_IX);
                   auto db = autoDb.ensureDbExists();
                   // ..... 省略
}

上面是创建view的方法,其中看到通过一个AutoGetDb的对象来“创建Database”,这里涉及到两个catalog层的角色,分别是DatabaseHolderImpl和DatabaseImpl。

DatabaseHolderImpl

AutoGetDb这个对象逻辑比较简单,主要是:

  • 调用databaseHolder->getDb从databaseHolder获取Database
  • 如果获取到Database为null,则调用databaseHolder->openDb

因此我们主要看下DatabaseHolderImpl::openDb做了什么。

openDb其实并没有open什么DB,这一步骤并没有与storage层进行过交互,主要做了这些事情

  • 从自身维护的_dbs查找DB是否存在,存在直接返回。
  • 不存在DB的时候,创建DatabaseImpl对象,调用DatabaseImpl::init初始化
  • 将DatabaseImpl添加到_dbs中

下面看看详细逻辑

Database* DatabaseHolderImpl::openDb(OperationContext* opCtx, StringData ns, bool* justCreated) {
    // 已经存在db的时候直接返回
    if (auto db = _dbs[dbname])
        return db;
    
    // .....省略,这里使用锁,将_dbs[dbname]设置为null。
    // 因为在创建DB的时候有一个逻辑确保dbname不再_dbs中已经存在,防止并发导致明明检查_dbs[dbname]不存在但是创建的时候发现dbname冲突。
    
    // 这一步切实也是内存操作
    if (CollectionCatalog::get(opCtx)->getAllCollectionUUIDsFromDb(dbname).empty()) {
        audit::logCreateDatabase(opCtx->getClient(), dbname);
        if (justCreated)
            *justCreated = true;
    }
    
    auto newDb = std::make_unique<DatabaseImpl>(dbname);
    newDb->init(opCtx);
    
    auto it = _dbs.find(dbname);
    it->second = newDb.release();
    return it->second;
}

DatabaseImpl

在DatabaseHolderImpl::openDb中看到,“创建Database”的时候会调用DatabaseImpl::init,下面看看DatabaseImpl在init的时候做了什么。

void DatabaseImpl::init(OperationContext* const opCtx) const {
    // 调用validateDBName检查dbname
    Status status = validateDBName(_name);
    
    // 从CollectionCatalog中通过dbname找到collections列表(同样内存查找)。如果是新的dbname,这个循环是不会执行的,因为getAllCollectionUUIDsFromDb得到是空列表
    auto catalog = CollectionCatalog::get(opCtx);
    for (const auto& uuid : catalog->getAllCollectionUUIDsFromDb(_name)) {
        CollectionWriter collection(
            opCtx,
            uuid,
            opCtx->lockState()->isW() ? CollectionCatalog::LifetimeMode::kInplace
                                      : CollectionCatalog::LifetimeMode::kManagedInWriteUnitOfWork);
        invariant(collection);
        // If this is called from the repair path, the collection is already initialized.
        if (!collection->isInitialized()) {
            collection.getWritableCollection()->init(opCtx);
        }
    }
    
    // ...... 省略,非从库的时候调用ViewCatalog::reload重新加载试图
}

总结

看到这里应该对catalog层和storage层有大概了解,后面会开始看一下一些Command(例如CreateCollection、CreateIndex)的逻辑。当中也会涉及到AutoGetDb,由于与传统Mysql有区别(不需要创建Database),因此本篇也讲述了AutoGetDb。

to be continue