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];
};

至此,VersionSetVersionVersionEditVersionSet::Builder幾個數據結構裡面的聯繫到這裡就介紹完成。

總結

從這裡也可以看出LevelDB對數據結構的安排處理得並不十分好。

  • Version按理說,應該是一個靜態的數據結構,但是卻放置了一些需要動態計算合併層,以及合併InternalKey的數據變數。其中可能說得過去的一個理由可能是:在當前Version中,由於sst文件的排列在版本給定的情況下,如何合併肯定是固定的。如何合併也就固定了。但是這種說法是站不住腳的。這裡面的原因在於:sst文件的合併也需要考慮到文件被讀取的次數。不同的讀落在不同的文件上,得到的結果也不會相同。那麼如何合併,就成了一個動態信息。
  • 如何合併的動態信息,一部分放在Version裡面,一部分放到VersionEdit裡面。然後在Apply的時候,一會是通過LogAndApply這個介面,一會兒是通過Apply介面。

推薦閱讀:

相关文章