SSTable은 다음과 같은 상황에서 만들어진다.
- MemTable로부터
Flush(Minor Compaction)
가 일어날 때 - Storage에서
Compaction
이 일어날 때
이 중 MemTable로부터 Flush
가 일어날 때에 초점을 맞춰 SSTable에 어떻게 만들어지는지 다룬다.
MemTable로부터 Flush가 일어날 때
이 때 MemTable로부터 Flush
가 일어나는 과정은 다음과 같다.
CompactMemTable
이 호출되고, 이로 인해 WriteLevel0Table
이 호출되면서 SSTable이 만들어지는데 이 때 BuildTable
이 실질적으로 SSTable을 만든다.
BuildTable
의 흐름은 다음과 같다.
전체 과정
순서
TableBuilder
인스턴스를 만든다TableBuilder
의Add
메소드를 통해 MemTable의 key-value pair들을 하나하나 추가한다.TableBuilder
의Finish
메소드를 통해 SSTable을 만드는 과정을 마무리한다.WritableFile
에 있는 내용들을 storage에 쓴다- storage에 저장한 SSTable을 cache에 올려서 사용가능한지 확인해본다.
Status BuildTable(const std::string& dbname, Env* env, const Options& options,
TableCache* table_cache, Iterator* iter, FileMetaData* meta) {
Status s;
meta->file_size = 0;
// Make that the Iterator points to the first element
iter->SeekToFirst();
std::string fname = TableFileName(dbname, meta->number);
if (iter->Valid()) {
WritableFile* file;
s = env->NewWritableFile(fname, &file);
if (!s.ok()) {
return s;
}
// 1. Create an instance of TableBuilder
TableBuilder* builder = new TableBuilder(options, file);
meta->smallest.DecodeFrom(iter->key());
Slice key;
// 2. Add key-value pairs of MemTable one by one via TableBuilder`s "Add" method
for (; iter->Valid(); iter->Next()) {
key = iter->key();
builder->Add(key, iter->value());
}
if (!key.empty()) {
meta->largest.DecodeFrom(key);
}
// 3. Complete the process of creating SSTable via TableBuilder's "Finish" method
s = builder->Finish();
if (s.ok()) {
meta->file_size = builder->FileSize();
assert(meta->file_size > 0);
}
delete builder;
// 4. Write the contents in the WritableFile to storage
if (s.ok()) {
s = file->Sync();
}
if (s.ok()) {
s = file->Close();
}
delete file;
file = nullptr;
if (s.ok()) {
// 5. Put the SSTable stored in storage into cache and check if it is available
Iterator* it = table_cache->NewIterator(ReadOptions(), meta->number,
meta->file_size);
s = it->status();
delete it;
}
}
// Check for Iterator related errors
if (!iter->status().ok()) {
s = iter->status();
}
if (s.ok() && meta->file_size > 0) {
// Keep it
} else {
env->RemoveFile(fname);
}
return s;
}
TableBuilder::Add
TableBuilder안에 있는 각각의 BlockBuilder들에게 Iterator가 현재 참조하고 있는 key-value pair를 전달하는 역할을 한다.
- 현재
BlockBuilder
로 만드는 Data Block이 비어있다면, 즉 새로운 Data Block을 구성하기 시작했다면 Index Block에 새 Entry를 추가한다. 이 때 추가되는 Entry는 현재 새로 만들기 시작한 Data Block에 대한 것이 아니라 바로 이전까지 만들던 Data Block에 대한 Entry이다. - Bloom Filter를 사용하는 경우 Filter Block도 업데이트한다.
- Data Block에 데이터를 추가한다.
- 만약 작성 중인 Data Block이 꽉 찼다면(option으로 지정한 block size이상이 된 경우)
Flush
를 호출한다.
void TableBuilder::Add(const Slice& key, const Slice& value) {
Rep* r = rep_;
// ...
// 1. If the Data Block that BlockBuilder is creating is empty,
// add a new entry to the Index Block
if (r->pending_index_entry) {
assert(r->data_block.empty());
r->options.comparator->FindShortestSeparator(&r->last_key, key);
std::string handle_encoding;
r->pending_handle.EncodeTo(&handle_encoding);
r->index_block.Add(r->last_key, Slice(handle_encoding));
r->pending_index_entry = false;
}
// 2. If using Bloom Filter, update the Filter Block as well
if (r->filter_block != nullptr) {
r->filter_block->AddKey(key);
}
r->last_key.assign(key.data(), key.size());
r->num_entries++;
// 3. Add data to the Data Block
r->data_block.Add(key, value);
const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
// 4. If the Data Block being created is full, call "Flush"
if (estimated_block_size >= r->options.block_size) {
Flush();
}
}
TableBuilder::Finish
MemTable의 모든 key-valur pair들에 대해
Add
가 끝났을 때 호출되며, 작성중인 SSTable을 마무리하는 역할을 한다.
Flush
를 호출한다.- Bloom Filter를 사용하는 경우
WritableFile
에 FilterBlockBuilder로 Filter Block을 추가한다. WritableFile
에 Meta Index Block을 추가한다.WritableFile
에BlockBuilder
로 만들고 있던 Index Block을 추가한다.WritableFile
에 Footer를 추가한다.
Status TableBuilder::Finish() {
Rep* r = rep_;
// 1. Call "Flush"
Flush();
assert(!r->closed);
r->closed = true;
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;
// 2. If using Bloom Filter, add the Filter Block to the WritableFile.
if (ok() && r->filter_block != nullptr) {
WriteRawBlock(r->filter_block->Finish(), kNoCompression,
&filter_block_handle);
}
// 3. Add the Meta Index Block to the WritableFile
if (ok()) {
BlockBuilder meta_index_block(&r->options);
if (r->filter_block != nullptr) {
std::string key = "filter.";
key.append(r->options.filter_policy->Name());
std::string handle_encoding;
filter_block_handle.EncodeTo(&handle_encoding);
meta_index_block.Add(key, handle_encoding);
}
WriteBlock(&meta_index_block, &metaindex_block_handle);
}
// 4. Add the Index Block to the WritableFile.
if (ok()) {
if (r->pending_index_entry) {
r->options.comparator->FindShortSuccessor(&r->last_key);
std::string handle_encoding;
r->pending_handle.EncodeTo(&handle_encoding);
r->index_block.Add(r->last_key, Slice(handle_encoding));
r->pending_index_entry = false;
}
WriteBlock(&r->index_block, &index_block_handle);
}
// 5. Add the Footer to the WritableFile.
if (ok()) {
Footer footer;
footer.set_metaindex_handle(metaindex_block_handle);
footer.set_index_handle(index_block_handle);
std::string footer_encoding;
footer.EncodeTo(&footer_encoding);
r->status = r->file->Append(footer_encoding);
if (r->status.ok()) {
r->offset += footer_encoding.size();
}
}
return r->status;
}
TableBuilder::Flush
BlockBuilder로 만들고 있는 Data Block을 storage에 쓰는 역할을 한다.
BlockBuilder
로 만들고 있는 Data Block의 contents를WritableFile
에 추가한다.WritableFile
에 쓴 내용을 storage에 쓴다.- Bloom Filter를 사용할 경우 새 Bloom Filter를 만든다.
void TableBuilder::Flush() {
Rep* r = rep_;
// ...
// 1. Add the contents of the Data Block being created to the WritableFile
WriteBlock(&r->data_block, &r->pending_handle);
if (ok()) {
r->pending_index_entry = true;
// 2. Write the contents of WritableFile to storage
r->status = r->file->Flush();
}
// 3. If using Bloom Filter, create a new Bloom Filter
if (r->filter_block != nullptr) {
r->filter_block->StartBlock(r->offset);
}
}