LevelDB源碼解析15. Version的數據結構2
Version的更改
Version
固定的無論在內存裡面,還是在磁碟上都是不更改的。
class Version {
// List of files per level
// 前面已經介紹過了
std::vector<FileMetaData*> files_[config::kNumLevels];
// Next file to compact based on seek stats.
FileMetaData* file_to_compact_;
int file_to_compact_level_;
// Level that should be compacted next and its compaction score.
// Score < 1 means compaction is not strictly needed. These fields
// are initialized by Finalize().
double compaction_score_;
int compaction_level_;
};
在當前Version
中,有可能也需要知道如何生成下一個版本。在LevelDB
中主要是通過合併操作來完成。因此,需要在當前version
中指定如何合併。當前level
的一個文件需要與下一級裡面的文件合併生成多個新文件。
那麼以下信息就是用來說明合併信息的。
class Version {
// Next file to compact based on seek stats.
FileMetaData* file_to_compact_;
int file_to_compact_level_;
// Level that should be compacted next and its compaction score.
// Score < 1 means compaction is not strictly needed. These fields
// are initialized by Finalize().
double compaction_score_;
int compaction_level_;
};
也就是說,當前版中需要記錄如何生成下一個版本。在LevelDB
中是通過合併操作
來生成下一個版本的。那麼當前版本應該合併哪些文件呢?
在LevelDB
中,就是通過compact
各種變數來記錄如何生成下一個版本。
VersionEdit
即然Version
中記錄了如何合併,那麼在VersionEdit
中就同樣會記錄合併後的操作,那麼就是:合併之後,那些文件需要刪除,哪些文件需要添加。
class VersionEdit {
std::string comparator_;
uint64_t log_number_;
uint64_t prev_log_number_;
uint64_t next_file_number_;
SequenceNumber last_sequence_;
bool has_comparator_;
bool has_log_number_;
bool has_prev_log_number_;
bool has_next_file_number_;
bool has_last_sequence_;
std::vector< std::pair<int, InternalKey> > compact_pointers_;
DeletedFileSet deleted_files_;
std::vector< std::pair<int, FileMetaData> > new_files_;
};
由於Version
在合併時,生成的新文件都會使用統一的序號。所以每當生成新文件的時候,序號也會發生變化。那麼,也需要將這些變化的序號做為更改的一部分記錄下來。各種has_xxx
指示符就是說明這個序號是否發生了變化。
序號的更改,並不會累加到展現到Version
上,而是直接作用於VersionSet
。
Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) {
if (edit->has_log_number_) {
assert(edit->log_number_ >= log_number_);
assert(edit->log_number_ < next_file_number_);
} else {
edit->SetLogNumber(log_number_);
}
if (!edit->has_prev_log_number_) {
edit->SetPrevLogNumber(prev_log_number_);
}
edit->SetNextFile(next_file_number_);
edit->SetLastSequence(last_sequence_);
// .. 這裡還有一堆代碼
return s;
}
壓縮指針
每一個Version
需要合併的時候,都會經過一定的計算得到每個Level
上下一次合併的位置。
class VersionEdit {
std::vector< std::pair<int, InternalKey> > compact_pointers_;
};
每一層合併的位置是記錄在VersionEdit
上。而Version
中並不記錄這個信息。那麼當要Apply
這個合併位置的指針實際上就是internalKey
的位置時候,則是通過。
// VersionSet::Builder來完成
// Apply all of the edits in *edit to the current state.
void Apply(VersionEdit* edit) {
// Update compaction pointers
// compact_pointers_只是一個vector
// 裡面的元素是一個pair,first元素表示level, second表示相應層裡面合併到的
// key
for (size_t i = 0; i < edit->compact_pointers_.size(); i++) {
// 取得層數
const int level = edit->compact_pointers_[i].first;
// version_set_裡面的compact_pointers_定義是不一樣的
// std::string compact_pointer_[config::kNumLevels];
// 所以這裡直接使用level作為索引
// 由於是版本的apply,那麼總歸是後面的一個versionEdit去覆蓋前面的
// version edit的內容。
// 所以這裡直接採用賦值
vset_->compact_pointer_[level] =
edit->compact_pointers_[i].second.Encode().ToString();
}
....
}
不出意外,VersionSet
裡面也會有記錄相應信息的數據結構。
class VersionSet {
// Per-level key at which the next compaction at that level should start.
// Either an empty string, or a valid InternalKey.
std::string compact_pointer_[config::kNumLevels];
};
至此,VersionSet
,Version
,VersionEdit
,VersionSet::Builder
幾個數據結構裡面的聯繫到這裡就介紹完成。
總結
從這裡也可以看出LevelDB
對數據結構的安排處理得並不十分好。
Version
按理說,應該是一個靜態的數據結構,但是卻放置了一些需要動態計算合併層,以及合併InternalKey
的數據變數。其中可能說得過去的一個理由可能是:在當前Version
中,由於sst
文件的排列在版本給定的情況下,如何合併肯定是固定的。如何合併也就固定了。但是這種說法是站不住腳的。這裡面的原因在於:sst
文件的合併也需要考慮到文件被讀取的次數。不同的讀落在不同的文件上,得到的結果也不會相同。那麼如何合併,就成了一個動態信息。- 如何合併的動態信息,一部分放在
Version
裡面,一部分放到VersionEdit
裡面。然後在Apply
的時候,一會是通過LogAndApply
這個介面,一會兒是通過Apply
介面。
推薦閱讀: