LevelDB .ldb 文件 key 前綴壓縮方法

LevelDB 的 table 文件以 .ldb 作為文件擴(kuò)展名颜说,包括若干個 block,data block 存儲按照 key 的字母表順序排序的 KV 對數(shù)據(jù)俗孝,meta block 存儲 filter 數(shù)據(jù)晃危,metaindex block 存儲 每個 meta block 的 offset 信息,index block 存儲每個 data block 的 offset 信息奕锌。對于 data block,引入了前綴壓縮技術(shù)減少磁盤空間占用村生。本文結(jié)合 LevelDB 代碼,探討 .ldb 文件的 data block key 前綴壓縮方法饼丘。

<beginning_of_file>
[data block 1]
[data block 2]
...
[data block N]
[meta block 1]
...
[meta block K]
[metaindex block]
[index block]
[Footer]        (fixed size; starts at file_size - sizeof(Footer))
<end_of_file>

對于排序后的 KV 對趁桃,相鄰的 key 通常會具有相似的前綴。LevelDB 基于相鄰 key 的共享前綴對 key 進(jìn)行了壓縮肄鸽。更小的 data block 可以實現(xiàn)更快的讀取卫病,也對 page cache 更為友好。

Layout

[k1, v1] -- offset1
[k2', v2] 
[k3', v3]
...
[k14', v14]
[k15', v15]
[k16, v16] -- offset2
[k17', v17]
...
[offset1, offset2, offset3, ...]
[num of restart points]

key 壓縮后的 data block 如上所示典徘,由 KV 對和 restart point 構(gòu)成蟀苛。采用類似于稀疏索引的方式,部分 KV 對記錄完整的 key逮诲,作為 restart point帜平。restart point 之間的 key 則通過共同前綴進(jìn)行壓縮幽告。在 data block 的最后,保存了各個 restart point 的 offset 以及 restart point 的數(shù)量裆甩。

Writing Flow


void BlockBuilder::Add(const Slice& key, const Slice& value) {
  size_t shared = 0;
    // 每16個 key 為一個 restart point冗锁,restart point 保存完整的 key
  if (counter_ < options_->block_restart_interval) {
    // 計算共同前綴長度
    const size_t min_length = std::min(last_key_piece.size(), key.size());
    while ((shared < min_length) && (last_key_piece[shared] == key[shared])) {
      shared++;
    }
  } else {
    // 記錄 restart point
    restarts_.push_back(buffer_.size());
    counter_ = 0;
  }
  const size_t non_shared = key.size() - shared;

  // 寫入 KV 對
  buffer_.append(key.data() + shared, non_shared);
  buffer_.append(value.data(), value.size());

  // 記錄 last key
  last_key_.resize(shared);
  last_key_.append(key.data() + shared, non_shared);
  counter_++;
}

Slice BlockBuilder::Finish() {
  // Append restart array
  for (size_t i = 0; i < restarts_.size(); i++) {
    PutFixed32(&buffer_, restarts_[i]);
  }
  PutFixed32(&buffer_, restarts_.size());
  finished_ = true;
  return Slice(buffer_);
}

data block 的生成流程如上所示,以第每16個 key 作為 restart point嗤栓,記錄完整的 key冻河。restart point 間的 key 則只在前一個 key 的基礎(chǔ)之上,記錄不同的后綴茉帅。當(dāng) block 寫入完成時叨叙,在 block 的最后追加寫入 restart point。

Reading Flow

virtual void Seek(const Slice& target) {
    // 二分查找最大的 restart point堪澎,滿足 restart point key < target
    uint32_t left = 0;
    uint32_t right = num_restarts_ - 1;
    while (left < right) {
      uint32_t mid = (left + right + 1) / 2;
      uint32_t region_offset = GetRestartPoint(mid);
      uint32_t shared, non_shared, value_length;
      const char* key_ptr =
          DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
                      &non_shared, &value_length);
      if (key_ptr == nullptr || (shared != 0)) {
        CorruptionError();
        return;
      }
      Slice mid_key(key_ptr, non_shared);
      if (Compare(mid_key, target) < 0) {
        left = mid;
      } else {
        right = mid - 1;
      }
    }

    // 從 restart point 后的 key 開始線性搜索
    SeekToRestartPoint(left);
    while (true) {
      if (!ParseNextKey()) {
        return;
      }
      if (Compare(key_, target) >= 0) {
        return;
      }
    }
  }

數(shù)據(jù)的讀取摔敛,是一定需要完整的 key 進(jìn)行比較的。數(shù)據(jù)讀取流程為了避免不必要的 decode全封,先通過 restart point 進(jìn)行二分查找马昙,找到最大的 key,再進(jìn)行線性搜索刹悴。最差情況下的搜索次數(shù)為 Log(n) + 15行楞,其中 n 為 data block 中 key 的數(shù)量。

參考文獻(xiàn)

leveldb/table_format

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末土匀,一起剝皮案震驚了整個濱河市子房,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌就轧,老刑警劉巖证杭,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妒御,居然都是意外死亡解愤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門乎莉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來送讲,“玉大人,你說我怎么就攤上這事惋啃『喵蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵边灭,是天一觀的道長异希。 經(jīng)常有香客問我,道長绒瘦,這世上最難降的妖魔是什么称簿? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任扣癣,我火速辦了婚禮,結(jié)果婚禮上予跌,老公的妹妹穿的比我還像新娘搏色。我一直安慰自己,他們只是感情好券册,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布频轿。 她就那樣靜靜地躺著,像睡著了一般烁焙。 火紅的嫁衣襯著肌膚如雪航邢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天骄蝇,我揣著相機(jī)與錄音膳殷,去河邊找鬼。 笑死九火,一個胖子當(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
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拌喉,地道東北人速那。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像尿背,于是被迫代替她去往敵國和親端仰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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