leveldb深度剖析-壓縮流程(3)

本篇是壓縮流程中最後一篇,介紹DoCompactionWork方法。壓縮主要工作是什麼呢?剔除無效數據。什麼是無效數據呢?就是被標記爲刪除的數據,我們要在這個流程中從磁盤中刪除掉。下面來看一下具體是如何進行刪除的。

一、迭代器

該函數比較長,這裏分段介紹該函數。

/**
 * 執行壓縮
 * @param compact 壓縮信息
 */
Status DBImpl::DoCompactionWork(CompactionState* compact) {
  const uint64_t start_micros = env_->NowMicros();
  int64_t imm_micros = 0;  // Micros spent doing imm_ compactions

  Log(options_.info_log,  "Compacting %d@%d + %d@%d files",
      compact->compaction->num_input_files(0),
      compact->compaction->level(),
      compact->compaction->num_input_files(1),
      compact->compaction->level() + 1);

  assert(versions_->NumLevelFiles(compact->compaction->level()) > 0);
  assert(compact->builder == NULL);
  assert(compact->outfile == NULL);

  if (snapshots_.empty()) {//如果沒有快照,則重複的舊k/v數據都可以刪掉
    compact->smallest_snapshot = versions_->LastSequence();
  } else {//如果有快照,則只有sequenceNumber小於最老的快照的sequenceNumber的舊k/v數據纔可以刪掉
    compact->smallest_snapshot = snapshots_.oldest()->number_;
  }

  // Release mutex while we're actually doing the compaction work
  mutex_.Unlock();

  //創建迭代器 這個創建迭代器的過程非常重要  爲下面判斷數據是否有效 起到至關重要的作用
  Iterator* input = versions_->MakeInputIterator(compact->compaction);
  input->SeekToFirst();
  Status status;
  ParsedInternalKey ikey;
  std::string current_user_key;//當前記錄user key
  bool has_current_user_key = false;//是否有當前user key 和current_user_key成對出現
  SequenceNumber last_sequence_for_key = kMaxSequenceNumber;

說明:

1)這部分最重要的內容就是創建了一個迭代器Iterator,這個迭代器非常的關鍵,是將所有的key按照從小到大的順序進行排序。切記:創建完迭代器後就已經按照key的順序進行排序了。 

2)上面幾個變了是處理記錄是否真正刪除主要控制邏輯。

二、循環遍歷key並刪除數據

for (; input->Valid() && !shutting_down_.Acquire_Load(); ) {//循環遍歷
    // Prioritize immutable compaction work
    if (has_imm_.NoBarrier_Load() != NULL) {
      const uint64_t imm_start = env_->NowMicros();
      mutex_.Lock();
      if (imm_ != NULL) {
        CompactMemTable();//優先壓縮immutable memtable
        bg_cv_.SignalAll();  // Wakeup MakeRoomForWrite() if necessary
      }
      mutex_.Unlock();
      imm_micros += (env_->NowMicros() - imm_start);
    }

    Slice key = input->key();//InternalKey
    if (compact->compaction->ShouldStopBefore(key) &&
        compact->builder != NULL) {
      status = FinishCompactionOutputFile(compact, input);
      if (!status.ok()) {
        break;
      }
    }
    ...
  }//end for

這部分邏輯比較簡單,就是從迭代器中獲取一條數據,這裏的input->key()爲InternalKey對象,並非是user key。下面這部分是關鍵


    // Handle key/value, add to state, etc.
    bool drop = false;//代表記錄是否要被刪除 true 要從數據庫中刪除
    if (!ParseInternalKey(key, &ikey)) {//解析key 失敗
      // Do not hide error keys
      current_user_key.clear();
      has_current_user_key = false;
      last_sequence_for_key = kMaxSequenceNumber;
    } else {//解析key 成功
      /**
       * 經過排序之後 相同的user key被放到迭代器 組織到一起了(相同user key放到一起)
       * current_user_key 保存上次user key
       * ikey.user_key是本次循環中 處理的user key
       * 如果compare返回非0 表示這兩個user key不一樣,返回爲0則說明兩個key相同,
       * 那麼相同說明了什麼呢? 說明上一條是標記爲Delete的數據,而這條記錄是原始添加數據,所以
       * 這條記錄是需要刪除的。
       */
      if (!has_current_user_key ||
          user_comparator()->Compare(ikey.user_key,
                                     Slice(current_user_key)) != 0) {
        // First occurrence of this user key
        current_user_key.assign(ikey.user_key.data(), ikey.user_key.size());
        has_current_user_key = true;//表示第一次出現 設置爲current user key
        last_sequence_for_key = kMaxSequenceNumber; //設置爲最大序列號
      }

      if (last_sequence_for_key <= compact->smallest_snapshot) {
        // Hidden by an newer entry for same user key
        drop = true;    // (A)
      } else if (ikey.type == kTypeDeletion &&
                 ikey.sequence <= compact->smallest_snapshot &&
                 compact->compaction->IsBaseLevelForKey(ikey.user_key)) {
        // IsBaseLevelForKey函數返回true表示user_key不存在於高層中   false 表示存在於高層中
        // For this user key 對於當前user key來說:
        // (1) there is no data in higher levels 它不存在高層ldb文件中
        // (2) data in lower levels will have larger sequence numbers
        // (3) data in layers that are being compacted here and have
        //     smaller sequence numbers will be dropped in the next
        //     few iterations of this loop (by rule (A) above).
        // Therefore this deletion marker is obsolete and can be dropped.
        //要刪除該記錄 && 序列號比之前數據庫中還要小 && 當前記錄不存在於高層次文件中 所以標記爲刪除
        drop = true;
      }

      last_sequence_for_key = ikey.sequence;
    }
#if 0
    Log(options_.info_log,
        "  Compact: %s, seq %d, type: %d %d, drop: %d, is_base: %d, "
        "%d smallest_snapshot: %d",
        ikey.user_key.ToString().c_str(),
        (int)ikey.sequence, ikey.type, kTypeValue, drop,
        compact->compaction->IsBaseLevelForKey(ikey.user_key),
        (int)last_sequence_for_key, (int)compact->smallest_snapshot);
#endif

    if (!drop) {//不是刪除操作 寫入文件
      // Open output file if necessary
      if (compact->builder == NULL) {
        status = OpenCompactionOutputFile(compact);
        if (!status.ok()) {
          break;
        }
      }
      if (compact->builder->NumEntries() == 0) {
        compact->current_output()->smallest.DecodeFrom(key);
      }
      compact->current_output()->largest.DecodeFrom(key);
      compact->builder->Add(key, input->value());

      // Close output file if it is big enough
      if (compact->builder->FileSize() >=
          compact->compaction->MaxOutputFileSize()) {
        status = FinishCompactionOutputFile(compact, input);
        if (!status.ok()) {
          break;
        }
      }
    }

    input->Next(); //獲取下一條記錄

說明:

1)局部變量drop爲true表示記錄從物理文件中刪除,反之不需要刪除掉。

2) ParseInternalKey方法是用於解析InternalKey對象,正常場景會進入else分支。

3)上面提到了創建的迭代器會對InternalKey進行排序,那麼相同的user-key就會相鄰,那麼帶有刪除標記的一定在前面(刪除標記爲0,添加標記爲1)。所以在處理是刪除標記時直接刪除掉了,但是原始數據應該怎麼刪除呢?

4)代碼註釋中有這樣的內容:

      /**
       * 經過排序之後 相同的user key被放到迭代器 組織到一起了(相同user key放到一起)
       * current_user_key 保存上次user key
       * ikey.user_key是本次循環中 處理的user key
       * 如果compare返回非0 表示這兩個user key不一樣,返回爲0則說明兩個key相同,
       * 那麼相同說明了什麼呢? 說明上一條是標記爲Delete的數據,而這條記錄是原始添加數據,所以
       * 這條記錄是需要刪除的。
       */
      if (!has_current_user_key || user_comparator()->Compare(ikey.user_key, Slice(current_user_key)) != 0)

5)寫入文件這裏不在介紹了。

三、記錄統計信息


  if (status.ok() && shutting_down_.Acquire_Load()) {
    status = Status::IOError("Deleting DB during compaction");
  }
  if (status.ok() && compact->builder != NULL) {
    status = FinishCompactionOutputFile(compact, input);
  }
  if (status.ok()) {
    status = input->status();
  }
  delete input;
  input = NULL;

  // 統計信息
  CompactionStats stats;
  stats.micros = env_->NowMicros() - start_micros - imm_micros;
  for (int which = 0; which < 2; which++) {
    for (int i = 0; i < compact->compaction->num_input_files(which); i++) {
      stats.bytes_read += compact->compaction->input(which, i)->file_size;
    }
  }
  for (size_t i = 0; i < compact->outputs.size(); i++) {
    stats.bytes_written += compact->outputs[i].file_size;
  }

  mutex_.Lock();
  stats_[compact->compaction->level() + 1].Add(stats);

  if (status.ok()) {
    status = InstallCompactionResults(compact);
  }
  if (!status.ok()) {
    RecordBackgroundError(status);
  }
  VersionSet::LevelSummaryStorage tmp;
  Log(options_.info_log,
      "compacted to: %s", versions_->LevelSummary(&tmp));
  return status;
}// end function

這裏部分就是統計信息,這裏不在深入展開了,大家可自行閱讀相關源碼 

四、總結

至此壓縮整體流程介紹完畢了。由此可見,leveldb對於壓縮流程做了最大化的性能提升,裏面涉及比較複雜的邏輯,需要我們後續慢慢體味。下一篇介紹查詢接口。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章