MongoDB oplog是一个 capped collection ,创建capped collection时, createCollection 可以设置size(最大字节数)和max(最大文档数)的参数,当这个集合的『总大小超过size』或者『总文档数超过max』时,在新插入文档时就会自动删除一些集合内最先插入的文档,相当于一片环形的存储空间。
oplog(local.oplog.rs集合)默认情况下配置为可用磁盘空间的5%,当oplog写满时,就会开始删除最先写入的oplog,一次正常的insert操作包含如下步骤:
MongoDB 3.2为了提升写入性能,使用wiredtiger引擎时,针对local.oplog.rs这个集合的删除策略进行了优化,主要改进:
monogd启动时,会根据oplog的最大字节数将整个集合分为10-100个Stone(可以理解为oplog的一段数据,包含多个文档,Stone的具体个数oplogSizeMB的配置相关)。
WiredTigerRecordStore::OplogStones::OplogStones(OperationContext* txn, WiredTigerRecordStore* rs) : _rs(rs) { //... unsigned long long maxSize = rs->cappedMaxSize(); const unsigned long long kMinStonesToKeep = 10ULL; const unsigned long long kMaxStonesToKeep = 100ULL; unsigned long long numStones = maxSize / BSONObjMaxInternalSize; _numStonesToKeep = std::min(kMaxStonesToKeep, std::max(kMinStonesToKeep, numStones)); _minBytesPerStone = maxSize / _numStonesToKeep; // ... }
其中 _numStonesToKeep
为oplog应该保持的Stone个数,而 _minBytesPerStone
代表每个Stone的最小字节数。
接下来,会根据oplog当前的大小以及 _minBytesPerStone
来估算下,当前的oplog大致包含的Stone数量,并通过采样的方式来获取每个Stone的起始位置(不能保证每个Stone的大小跟预期完全一样),然后将所有的Stone按顺序存储到一个队列中。
mongod在服务写请求的过程中,每次都会记录下新产生oplog的大小,当新产生的oplog的总量超过 _minBytesPerStones
时,就会产生一个新的Stone加入到队列中。
void WiredTigerRecordStore::OplogStones::createNewStoneIfNeeded(RecordId lastRecord) { if (_currentBytes.load() < _minBytesPerStone) { // Must have raced to create a new stone, someone else already triggered it. return; } // ... OplogStones::Stone stone = {_currentRecords.swap(0), _currentBytes.swap(0), lastRecord}; _stones.push_back(stone); _pokeReclaimThreadIfNeeded(); // 唤醒后台回收oplog空间的线程 }
当队列中的Stone数量超过 _numStonesToKeep
,后台线程就会删除最老的Stone里的数据,来回收oplog的存储空间。