目前对于 GetOverlappingInputsAddBoundaryInputs 两部分的区别还是不太明白。

本质是对于两者的具体实现还有所不知。

下面进行一个浅析。

首先需要清楚的是。

InternalKey 的组成为:UserKey + SequenceNumber + ValueType

在 MemTable 中,即这个 SkipList 中,本质上 Node 中只存储了 key ,
这个 key = InternalKeySize + InternalKey + ValueSize + Value

通过 SequenceNumber 就可以保证每个 InternalKey 都一定不同。

这里注意的是,SkipList 中所使用的 Comparator 是 InternalKeyComparator 。

InternalKeyComparator 所采用的比较方式是,先 UserKey ,UserKey 相同的情况下比较 SequenceNumber 。

GetOverlappingInputs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 传入的 begin 和 end 是 InternalKey
void Version::GetOverlappingInputs(int level, const InternalKey* begin,
                                   const InternalKey* end,
                                   std::vector<FileMetaData*>* inputs) {
  assert(level >= 0);
  assert(level < config::kNumLevels);
  inputs->clear();
  Slice user_begin, user_end;
  if (begin != nullptr) {
    user_begin = begin->user_key();
  }
  if (end != nullptr) {
    user_end = end->user_key();
  }
  const Comparator* user_cmp = vset_->icmp_.user_comparator();
  for (size_t i = 0; i < files_[level].size();) {
    FileMetaData* f = files_[level][i++];
    const Slice file_start = f->smallest.user_key();
    const Slice file_limit = f->largest.user_key();
    if (begin != nullptr && user_cmp->Compare(file_limit, user_begin) < 0) {
      // file 最大值 file_limit 小于 user_begin,则必然不存在交集
      // "f" is completely before specified range; skip it
    } else if (end != nullptr && user_cmp->Compare(file_start, user_end) > 0) {
      // file 最小值 file_start 小于 user_end,则必然不存在交集
      // "f" is completely after specified range; skip it
    } else {
      // 当前文件和区间有交集,则将其加入 Compaction 中
      inputs->push_back(f);
      if (level == 0) {
        // 0 层需要特殊考虑,因为 0 层的文件自身也会互相产生交集
        // 具体就是 0 层需要修改 user_begin 和 user_end
        // 如果遇到一个 file_limit > user_end ,更新 user_end = file_limit 
        // 如果遇到一个 file_start < user_begin ,更新 user_begin = file_start
        // 然后从第一个文件根重新开始遍历,以最新的 [user_begin, user_end]
        // 这样不断扩充,因为 0 层文件很小,所以我们考虑直接暴力即可。
        // Level-0 files may overlap each other.  So check if the newly
        // added file has expanded the range.  If so, restart search.
        if (begin != nullptr && user_cmp->Compare(file_start, user_begin) < 0) {
          user_begin = file_start;
          inputs->clear();
          i = 0;
        } else if (end != nullptr &&
                   user_cmp->Compare(file_limit, user_end) > 0) {
          user_end = file_limit;
          inputs->clear();
          i = 0;
        }
      }
    }
  }

  // 最后 inputs 中就是与 [user_begin, user_end] 直接重叠或者间接重叠的所有文件
}

AddBoundaryInputs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// l ==> low, u ==> up
// 从 compaction_files 中获取最大的文件 b1 ,然后在 level_files 中找到一个 b2 ,
// 满足 b1 中的最大值的 user_key 和 b2 中的最小值的 user_key 相等
// 如果找到这么一个 b2 ,那么就将其将入到 compaction_files 中,
// 然后以 compaction_files 中最大的文件来继续搜索
// 此时最大的文件已经更新了,更新为 b2 了
// 

// Extracts the largest file b1 from |compaction_files| and then searches for a
// b2 in |level_files| for which user_key(u1) = user_key(l2). If it finds such a
// file b2 (known as a boundary file) it adds it to |compaction_files| and then
// searches again using this new upper bound.
//
// If there are two blocks, b1=(l1, u1) and b2=(l2, u2) and
// user_key(u1) = user_key(l2), and if we compact b1 but not b2 then a
// subsequent get operation will yield an incorrect result because it will
// return the record from b2 in level i rather than from b1 because it searches
// level by level for records matching the supplied user key.
//
// parameters:
//   in     level_files:      List of files to search for boundary files.
//   in/out compaction_files: List of files to extend by adding boundary files.
// 注意,本函数是用来处理 level1-n 的,所以每个 SSTable 不存在交集
void AddBoundaryInputs(const InternalKeyComparator& icmp,
                       const std::vector<FileMetaData*>& level_files,
                       std::vector<FileMetaData*>* compaction_files) {
  InternalKey largest_key;

  // Quick return if compaction_files is empty.
  // 尝试找到最大的 key ,其实这里返回 false 当且仅当 compaction_files 为空
  if (!FindLargestKey(icmp, *compaction_files, &largest_key)) {
    return;
  }

  bool continue_searching = true;
  while (continue_searching) {
    // 找到当前的 level_files 中,一个文件中最小的 key 大于 larget_key ,而且其 user_key == largetst_key 的 user_key
    // 那么说明有重叠,如果不将这部分加入到合并区间内,那么之后这块的数据是过期数据
    //(具有相同的 user_key ,那么 internal_key 更大的 key 一定是 sequence_number 更小的,所以是国企数据)
    // 所以我们需要不停地将这部分找到,直到 smallest_boundary_file 为空,说明找不到了,那么就可以停止了
    FileMetaData* smallest_boundary_file =
        FindSmallestBoundaryFile(icmp, level_files, largest_key);

    // If a boundary file was found advance largest_key, otherwise we're done.
    if (smallest_boundary_file != NULL) {
      // 将这个文件加入 compaction_files
      compaction_files->push_back(smallest_boundary_file);
      // 然后这个文件的 largest 必然是新的 compaction_files 中最大的,将其设置为 largetst_key
      largest_key = smallest_boundary_file->largest;
    } else {
      // 否则没有,那么说明可以停止寻找了
      continue_searching = false;
    }
  }
}

SetupOtherInputs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void VersionSet::SetupOtherInputs(Compaction* c) {
  const int level = c->level(); 
  InternalKey smallest, largest;
  //level层边界文件文件查询
  AddBoundaryInputs(icmp_, current_->files_[level], &c->inputs_[0]);
  GetRange(c->inputs_[0], &smallest, &largest);
  //获取level+1层重叠文件
  current_->GetOverlappingInputs(level + 1, &smallest, &largest,
                                 &c->inputs_[1]); 

  // 获取文件集合边界[all_start, all_limit]
  InternalKey all_start, all_limit;
  GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);

  if (!c->inputs_[1].empty()) {
    std::vector<FileMetaData*> expanded0;
  //获取level层[all_start, all_limit]的重叠文件,若level为0会挑选出边界文件
    current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0); 
  //level非0时挑选边界文件
    AddBoundaryInputs(icmp_, current_->files_[level], &expanded0); 

    const int64_t inputs0_size = TotalFileSize(c->inputs_[0]);
    const int64_t inputs1_size = TotalFileSize(c->inputs_[1]);
    const int64_t expanded0_size = TotalFileSize(expanded0);

  //文件集合总数小于25
    if (expanded0.size() > c->inputs_[0].size() &&
        inputs1_size + expanded0_size <
            ExpandedCompactionByteSizeLimit(options_)) {
      //获取level的文件边界[new_start, new_limit]
      InternalKey new_start, new_limit;
      GetRange(expanded0, &new_start, &new_limit);
      std::vector<FileMetaData*> expanded1;
      //获取level+1层[new_start, new_limit]重叠文件
      current_->GetOverlappingInputs(level + 1, &new_start, &new_limit,
                                     &expanded1);
      //若level+1层文件数不变,更新input0
      if (expanded1.size() == c->inputs_[1].size()) {
        smallest = new_start;
        largest = new_limit;
        c->inputs_[0] = expanded0;
        c->inputs_[1] = expanded1;
        GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
      }
    }
  }

 //获取level+2层重叠文件
  if (level + 2 < config::kNumLevels) {
    current_->GetOverlappingInputs(level + 2, &all_start, &all_limit,
                                   &c->grandparents_);
  }

  //更新合并文件终止点
  compact_pointer_[level] = largest.Encode().ToString();
  c->edit_.SetCompactPointer(level, largest);
}

Compaction 的文件挑选流程

Compaction 分为 Auto 和 Manual,即自动和人工

Auto 选择的压缩文件范围的函数是 PickCompaction

Manual 选择的压缩文件范围的函数是 CompactRange

PickCompactionCompactRange 中都调用了 SetupOtherInputs

注意 SetupOtherInputs 的参数是 Compaction* c

因为 Compaction 涉及到第 i 层和第 i+1 层,所以 PickCompactionCompactRange 都预先选择了符合条件的第 i 层的文件,

这部分用 GetOverlappingInputs 实现的。

考虑一种情况,假设第 i 层的 SSTable 编号范围是 [0, n-1]

我们通过 PickCompactionCompactRange 预先选择的第 i 层 SSTable 编号范围是 [l,r] ,其中 r+1<n,

如果第 r+1 个 SSTable 的最小 InternalKey 大于第 r 个 SSTable 的最大 InternalKey ,而两者的 UserKey 又相同

则说明 r+1 中最小的 InternalKey 是过期数据,而我们只选择了 [l, r] 去压缩,接下来查询的时候会先到第 i 层,查到了这个 UserKey ,

但是已经是过期数据了,就会造成错误,所以我们需要将 r+1 也加入,这部分就是 AddBoundaryInputs 实现的了。

接下来,我们需要到第 i+1 层选择需要 Compaction 的文件,

选择的方式依然是先 GetOverlappingInputs ,再 AddBoundaryInputs

如此,我们就得到了 input_[0] 即第 i 层的文件,input_[1] 即第 i+1 层的文件。

但是,LevelDB 比较贪心。如果说 input_[1] 的文件与第 i 层不在 input_[0] 中的文件还有交集,

那么调用 GetOverlappingInputs 再去查询,产生最新的 new_input_[0]

然后看第 i+1 层与 new_input_[0]交集和边界的文件集合 new_input_[1]

如果 new_input_[1] == input_[1] ,那么就以 new_input_[1]new_input_[1] 作为最终的 Compaction 文件

如果new_input_[1] != input_[1] ,此时必然是 input_[1]new_input_[1] 的子集,此时我们就又要去第 i 层来更新 new_input_[0] 了,如此循环往复不是我们所希望的,所以在这种情况下,我们就拿 input_[0]input_[1] 作为最终的 Compaction 文件

此外,使用 new_input_[0]new_input_[1](new_input_[1] == input_[1]) 的另一个条件是,new_input_[0]input_[1] 的文件总大小小于限制(50M,每次文件不大于2M,即最多 25 个文件)

new_input_[0] 的数量大于 input_[0] 的数量,