Leveldb解析之四:Compaction

1 簡述

這一篇我們來解析leveldb的Compaction機制,要把這個講清楚赊时,需要回答下面的問題:

  • 什么是Compaction?
  • 什么時候會觸發(fā)Compaction?
  • Compact到底怎么做? 選擇哪一個Layer和哪些Table文件做Compact? 具體怎么壓縮?

什么是Compaction?

隨著Put/Delete操作增多镊辕,sstable文件會越來越多着倾,占用的磁盤空間也越來越大慎璧。刪除無效的key(被標(biāo)記刪除的key卓嫂,被新版本覆蓋的老版本的key)瓮具,然后重新生成sstable文件槽袄,可以有效減少sstable文件的數(shù)量给僵,這個過程就是Compaction毫捣。

什么時候會觸發(fā)Compaction?

Compaction的入口函數(shù)是MaybeScheduleCompaction(),觸發(fā)的時機:

  • Open()
    打開數(shù)據(jù)庫時帝际,會發(fā)生Recover蔓同,重做log文件中的寫操作,這可能會引發(fā)Compaction蹲诀。
  • Write()
    mem寫滿時斑粱,會把mem改成imm,然后刷入sstable脯爪,這可能會觸發(fā)Compaction则北。
  • Get()
    leveldb對Get()進行統(tǒng)計,當(dāng)一個文件多次未命中key時痕慢,我們可以認(rèn)為這是一個冷數(shù)據(jù)文件尚揣,對這個文件進行Compact,把它推到更高的level掖举,減少它被查找的機會快骗,從而提高Get()效率。
  • Iterator
    與Get()類似塔次,使用Iterator訪問數(shù)據(jù)時方篮,也會進行key的命中統(tǒng)計,觸發(fā)對冷數(shù)據(jù)sstable的Compaction励负。
  • Compact()
    主動調(diào)用Compact(begin, Slice)藕溅,所有Layer當(dāng)中key值與[begin,end]有重疊的sstable都會進行Compact。
  • BackgroundCompaction()
    壓縮過程會在level+1產(chǎn)生新的sstable文件熄守,可能再次觸發(fā)Compaction

Compact到底怎么做? 選擇哪一個Layer和哪些Table文件做Compact? 具體怎么壓縮?

leveldb是在后臺線程執(zhí)行Compact蜈垮,目前只允許單線程耗跛,也就是說Compact只能串行執(zhí)行。

入口函數(shù)是DBImpl::BackgroundCompaction()攒发,流程如下:

1) if (imm != nullptr)调塌,調(diào)用CompactMemTable(),把imm生成sstable文件落盤惠猿。
2) 選擇進行Compact的layer和文件:
   if (is_manual) {  // 優(yōu)先考慮主動調(diào)用Compact()操作
     選定所有l(wèi)ayer當(dāng)中與[begin,end]區(qū)間有重疊的sstable文件羔砾;
   } else {
     調(diào)用VersionSet::PickCompaction()選擇最需要壓縮的layer和文件,具體算法下面代碼有解釋偶妖;
   }
3) 進行壓縮
   if (IsTrivialMove()) { // 修改文件layer就可以
      修改文件layer姜凄;
      把version修改歷史生成record寫入MANIFEST文件;
   } else {  // 真正需要壓縮
      生成InputIterator{inpus}趾访,遍歷key态秧,刪除無效數(shù)據(jù)(已經(jīng)被刪除的key,被新版本覆蓋的key)扼鞋,生成sstable文件申鱼;
      Compact過程中,除了正常的sstable文件大小限制之外云头,還要判斷生成的level+1文件與level+2層文件的重疊情況捐友;
      如果重疊的文件過多,則結(jié)束當(dāng)前文件溃槐,開啟新的文件匣砖,避免將來level+1向level+2壓縮時涉及的文件太多;
    }

2 源碼解析

VersionSet::LogAndApply()觸發(fā)Compaction

leveldb通過VersionSet::PickCompaction()來選擇要壓縮的Layer和Table昏滴,怎么做到的呢猴鲫?

原來,在每個Version生成的時候谣殊,會計算哪個Layer需要進行Compaction变隔,方法是計算每個layer的compaction_score,選擇得分最高的layer蟹倾,這是由LogAndApply()函數(shù)完成的:

LogAndApply() 生成Version的最后一步是調(diào)用VersionSet::Finalize()匣缘,計算compaction_score:
  1)對于level-0,因為每次read都要對所有文件進行merge鲜棠,文件太多影響讀效率肌厨,因此主要考慮文件數(shù)據(jù)量,
     compaction_score = file_num/kL0_CompactionTrigger
  2)對于其他level豁陆,因為文件之間key不重疊柑爸,主要考慮文件大小,
     compaction_score = file_size/MaxBytesForLevel()
最后選出compaction_score最高的layer作為compaction_level盒音。

注:當(dāng)max_compaction_score>1時表鳍,需要Compaction馅而,否則不需要。

調(diào)用LogAndApply()生成Version的時機:

1) Open數(shù)據(jù)庫時譬圣,會合并歷史VersionEdit瓮恭,生成新的Version。
2) Compaction過程厘熟,會生成新的Version屯蹦。
   具體是BackgroundCompaction()、CompactMemTable()绳姨、InstallCompactionResults()這幾個函數(shù)登澜。
DBImpl::Get()觸發(fā)Compaction,把冷數(shù)據(jù)推導(dǎo)更高的Level:
  // Get()入口
  Status DBImpl::Get(const ReadOptions& options, const Slice& key, std::string* value)  {
    ....
    // 調(diào)用VersionSet::Get()查找key
    // VersionSet::Get()在查找key之余飘庄,會通過GetStat返回查找過程中第一個未命中key的文件
    s = current->Get(options, lkey, value, &stats);
    ......
    // 根據(jù)GetStat判斷是否觸發(fā)Compaction脑蠕,判斷標(biāo)準(zhǔn) f->allowed_seeks<0
    if (have_stat_update && current->UpdateStats(stats)) {
      MaybeScheduleCompaction();
  }
   
  // f->allowed_seeks這個值是怎么來的呢?
  // 原來是在sstable文件加入到Version時,會根據(jù)文件大小跪削,設(shè)置查找次數(shù) allowed_seeks空郊,參考:
  void VersionSet::Builder::Apply(VersionEdit* edit) {
    ......
    
    // We arrange to automatically compact this file after
    // a certain number of seeks.  Let's assume:
    //   (1) One seek costs 10ms
    //   (2) Writing or reading 1MB costs 10ms (100MB/s)
    //   (3) A compaction of 1MB does 25MB of IO:
    //         1MB read from this level
    //         10-12MB read from next level (boundaries may be misaligned)
    //         10-12MB written to next level
    // This implies that 25 seeks cost the same as the compaction
    // of 1MB of data.  I.e., one seek costs approximately the
    // same as the compaction of 40KB of data.  We are a little
    // conservative and allow approximately one seek for every 16KB
    // of data before triggering a compaction.
    f->allowed_seeks = static_cast<int>((f->file_size / 16384U));
    if (f->allowed_seeks < 100) f->allowed_seeks = 100;

    ......
  }

Iterator觸發(fā)Compaction,刪除過期key(被新版本覆蓋):
// 調(diào)用DBImpl::NewIterator()生成一個Iterator切揭,而后遍歷key,每隔一段會讀取一些樣本锁摔,判斷是否過期數(shù)據(jù):
bool DBIter::ParseKey(ParsedInternalKey* ikey) {
  Slice k = iter_->key();

  size_t bytes_read = k.size() + iter_->value().size();
  while (bytes_until_read_sampling_ < bytes_read) {
    bytes_until_read_sampling_ += RandomCompactionPeriod();  // [0,2*kReadBytesPeriod]之間的隨機數(shù)
    db_->RecordReadSample(k);  // 讀取數(shù)據(jù)樣本廓旬,判斷是否需要Compact
  }
  assert(bytes_until_read_sampling_ >= bytes_read);
  bytes_until_read_sampling_ -= bytes_read;

  ......
}

// DBImpl::RecordReadSample() 實質(zhì)上是調(diào)用Version::RecordReadSample()調(diào)用統(tǒng)計樣本
void DBImpl::RecordReadSample(Slice key) {
  MutexLock l(&mutex_);
  if (versions_->current()->RecordReadSample(key)) {
    MaybeScheduleCompaction();
  }
}

// 真正干活的地方
bool Version::RecordReadSample(Slice internal_key) {
  ParsedInternalKey ikey;
  if (!ParseInternalKey(internal_key, &ikey)) {
    return false;
  }

  struct State {
    GetStats stats;  // Holds first matching file
    int matches;

    static bool Match(void* arg, int level, FileMetaData* f) {
      State* state = reinterpret_cast<State*>(arg);
      state->matches++;
      if (state->matches == 1) {
        // 當(dāng)一個key存在多個版本時,最先讀到的也最老的版本
        // Remember first match.
        state->stats.seek_file = f;
        state->stats.seek_file_level = level;
      }
      // We can stop iterating once we have a second match.
      return state->matches < 2;
    }
  };

  State state;
  state.matches = 0;
  ForEachOverlapping(ikey.user_key, internal_key, &state, &State::Match);

  // Must have at least two matches since we want to merge across
  // files. But what if we have a single file that contains many
  // overwrites and deletions?  Should we have another mechanism for
  // finding such files?
  if (state.matches >= 2) {  // key存在多個版本
    // 1MB cost is about 1 seek (see comment in Builder::Apply).
    return UpdateStats(state.stats);  // 更新Compaction統(tǒng)計谐腰,認(rèn)為key的第一個(最老)版本所在的文件未命中
  }
  return false;
}

選擇Layer和Table的過程頭緒比較多孕豹,具體實施Compaction的過程相對比較好理解,用下面這張圖表述Compaction的整個過程:


leveldb_compaction
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末十气,一起剝皮案震驚了整個濱河市励背,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌砸西,老刑警劉巖叶眉,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芹枷,居然都是意外死亡衅疙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門鸳慈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饱溢,“玉大人,你說我怎么就攤上這事走芋〖ɡ桑” “怎么了潘鲫?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肋杖。 經(jīng)常有香客問我溉仑,道長,這世上最難降的妖魔是什么兽愤? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任彼念,我火速辦了婚禮,結(jié)果婚禮上浅萧,老公的妹妹穿的比我還像新娘逐沙。我一直安慰自己,他們只是感情好洼畅,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布吩案。 她就那樣靜靜地躺著,像睡著了一般帝簇。 火紅的嫁衣襯著肌膚如雪徘郭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天丧肴,我揣著相機與錄音残揉,去河邊找鬼。 笑死芋浮,一個胖子當(dāng)著我的面吹牛抱环,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纸巷,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼镇草,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘤旨?” 一聲冷哼從身側(cè)響起梯啤,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎存哲,沒想到半個月后因宇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡祟偷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年羽嫡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肩袍。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡杭棵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魂爪,我是刑警寧澤先舷,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站滓侍,受9級特大地震影響蒋川,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撩笆,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一捺球、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夕冲,春花似錦氮兵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弥姻,卻和暖如春南片,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庭敦。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工疼进, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秧廉。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓伞广,卻偏偏與公主長得像,于是被迫代替她去往敵國和親定血。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.摘要 本文介紹LevelDB的介紹诞外,性能澜沟,框架,核心構(gòu)件原理峡谊,基本操作接口樣例茫虽。 2. LevelDB概述 L...
    筆名輝哥閱讀 6,078評論 0 6
  • 之前零零散散寫過幾篇和 LSM-Tree、LevelDB 有關(guān)的文章既们。之后也看了一些代碼和論文濒析,筆記也做了一些,但...
    linjinhe閱讀 1,171評論 2 3
  • 版本控制或元信息管理啥纸,是LevelDB中比較重要的內(nèi)容号杏。本文首先介紹其在整個LevelDB中不可替代的作用;之后從...
    CatKang閱讀 5,451評論 12 12
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭盾致,有人歡樂有人憂愁主经,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53
  • 信任包括信任自己和信任他人 很多時候庭惜,很多事情罩驻,失敗、遺憾护赊、錯過惠遏,源于不自信,不信任他人 覺得自己做不成骏啰,別人做不...
    吳氵晃閱讀 6,181評論 4 8