compaction 分成两类:
1. minor compaction:
minor compaction,当immemtable写满以后,需要将其落盘成SSTable,这个操作即为minor compaction。
**2. major compaction:**又分成三类
主动compaction:
size compaction:
seek compact:
BackgroundCompaction函数
所有的compaction操作都是由后台线程进行调起的,优先级:minor compaction > size compaction > seek compaction。下面详细介绍后台线程需要运行的函数 (BackgroundCompaction)。
void DBImpl::BackgroundCompaction(){
mutex_.AssertHeld();
// minor compaction,即将immemtable中的内容进行落盘
if (imm_ != nullptr) {
CompactMemTable();
return;
}
}
CompactMemTable 函数
首先详细讲解一下函数CompactMemTable(),该函数的目的是:将immemtable落盘到SStable中,即 minor compaction。
/**
将immemtable落盘到SStable中,即 minor compaction。
**/
void DBImpl::CompactMemTable() {
...
VersionEdit edit;
// 获取当前磁盘SSTable的最新版本
Version* base = versions_->current();
base->Ref();
// step1: 构造VersionEdit
Status s = WriteLevel0Table(imm_, &edit, base);
base->Unref();
...
// Replace immutable memtable with the generated Table
if (s.ok()) {
edit.SetPrevLogNumber(0);
edit.SetLogNumber(logfile_number_); // Earlier logs no longer needed
// step2: 构造新的version,并将新version添加到versions_中
// versionEdit + version = new version
s = versions_->LogAndApply(&edit, &mutex_);
}
...
}
我的理解,所有的SSTable以文件元信息进行记录,即FileMetaData,而每个version对象中都维护这一系列的FileMetaData。
WriteLevel0Table函数
/**
将imm真实地写入到磁盘中,并构造出对应的文件元信息 meta
然后记录 meta
**/
Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,
Version* base) {
...
// 创建一个FileMetaData作为该SSTable句柄,方便之后查找
// 即记录新SStable的详细信息
FileMetaData meta;
meta.number = versions_->NewFileNumber();
pending_outputs_.insert(meta.number);
// 取得MemTable的迭代器
Iterator* iter = mem->NewIterator();
...
Status s;
{
mutex_.Unlock();
// 将immemtable中的信息写入到磁盘文件中,用meta记录SSTable信息
s = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta);
mutex_.Lock();
}
...
delete iter;
pending_outputs_.erase(meta.number);
// Note that if file_size is zero, the file has been deleted and
// should not be added to the manifest.
int level = 0;
if (s.ok() && meta.file_size > 0) {
// 向SSTable句柄写入所有Key的最小值
const Slice min_user_key = meta.smallest.user_key();
// 向SSTable句柄写入所有Key的最大值
const Slice max_user_key = meta.largest.user_key();
if (base != nullptr) {
// 计算新创建的SSTable应该属于哪一层
level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
}
// 将SSTable文件序列号对应的层,Key等信息写入manifest中
edit->AddFile(level, meta.number, meta.file_size, meta.smallest,
meta.largest);
}
CompactionStats stats;
stats.micros = env_->NowMicros() - start_micros;
stats.bytes_written = meta.file_size;
stats_[level].Add(stats);
return s;
}
// 文件元信息结构
struct FileMetaData {
FileMetaData() : refs(0), allowed_seeks(1 << 30), file_size(0) {}
int refs;
int allowed_seeks; // Seeks allowed until compaction
// 元信息,通过该number字段与磁盘中的SSTable进行关联,
uint64_t number; // 即 file_number
uint64_t file_size; // SStable的大小
InternalKey smallest; // Smallest internal key served by table
InternalKey largest; // Largest internal key served by table
};
WriteLevel0Table函数操作:
- 在当前version下,构建新的文件元信息 meta;
- 将mem中的内容,真实地写到磁盘文件中,并与meta进行关联(其实创建文件时,文件名字是基于meta的number字段创建的,e.g. number = 10, 则文件存储为 dbtest/10.ldb,其中dbtest为数据库名称);
- 判断该SSTable属于哪一层level;
- 基于level和meta 填充 VersionEdit
图1 FileMetaData与本地SStable之间的关系