leveldb官方手冊摘錄
本文內容摘自leveldb官方手冊,版權歸其所有
CHAPTER 1 基本概念
leveldb是一個寫效能十分優秀的儲存引擎,是典型的LSM樹(Log Structured-Merge Tree
)實現。LSM樹的核心
思想就是放棄部分讀的效能,換取最大的寫入能力。
LSM樹寫效能極高的原理,簡單地來說就是儘量減少隨機寫的次數。
對於每次寫入操作,並不是直接將最新的資料駐留在磁碟中,而是將其拆分成
(1)一次日誌檔案的順序寫
(2)一次記憶體中的資料插入
leveldb正是實踐了這種思想,將資料首先更新在記憶體中,當記憶體中的資料達到一定的閾值,將這部分資料真正重新整理到磁碟檔案中,因而獲得了極高的寫效能(順序寫60MB/s, 隨機寫45MB/s)。
1.1 整體架構
leveldb中主要由以下幾個重要的部件構成:
- memtable
- immutable memtable
- log(journal)
- sstable
- manifest
- current
1.1.1 memtable
memtable就是一個在記憶體中進行資料組織與維護的結構。
memtable中,所有的資料按使用者定義的排序
方法排序之後按序儲存,等到其儲存內容的容量達到閾值時(預設為4MB
),便將其轉換成一個不可修改
的memtable,與此同時建立一個新的memtable,供使用者繼續進行讀寫操作。
memtable
據結構,這種資料結構效率可以比擬二叉查詢樹,絕大多數操作的時間複雜度為
O(log n)
。
1.1.2 immutable memtable
memtable的容量到達閾值時,便會轉換成一個不可修改的memtable,也稱為immutable memtable。
這兩者的結構定義完全一樣,區別只是immutable memtable是隻讀的。
當一個immutable memtable被建立時,leveldb的
後臺壓縮排程便會將利用其中的內容,建立一個sstable,持久化到磁碟檔案中。
1.1.3 log
leveldb在寫記憶體之前會首先將所有的寫操作寫到日誌檔案中,也就是log檔案。
當以下異常情況發生時,均可以通過日誌檔案進行恢復:
- 寫log期間程序異常;
- 寫log完成,寫記憶體未完成;
- write動作完成(即log、記憶體寫入都完成)後,程序異常;
- Immutable memtable持久化過程中程序異常;
- 其他壓縮異常(較為複雜,首先不在這裡介紹);
當第一類情況發生時,資料庫重啟讀取log時,發現異常日誌資料,拋棄該條日誌資料,即視作這次使用者寫入失敗,保障資料庫的一致性;
當第二類,第三類,第四類情況發生了,均可以通過redo日誌檔案中記錄的寫入操作完成資料庫的恢復。
每次日誌的寫操作都是一次順序寫,因此寫效率高,整體寫入效能較好。
此外,leveldb的使用者寫操作的原子性同樣通過日誌來實現。
1.1.4 sstable
除了某些元資料檔案,leveldb的資料主要都是通過sstable來進行儲存
雖然在記憶體中,所有的資料都是按序排列的,但是當多個memetable資料持久化到磁碟後,對應的不同的sstable之間是存在交集的,在讀操作時,需要對所有的sstable檔案進行遍歷,嚴重影響了讀取效率。
因此leveldb後臺會**“定期“整合這些sstable檔案,該過程也稱為compaction**。
隨著compaction的進行,sstable檔案在邏輯上被分成若干層,由記憶體資料直接dump出來的檔案稱為level 0層檔案,後期整合而成的檔案為level i層檔案,這也是leveldb這個名字的由來
1.1.5 manifest
leveldb中有個版本的概念,一個版本中主要記錄了每一層中所有檔案的元資料
元資料包括
(1)檔案大小
(2)最大key值
(3)最小key值
該版本資訊十分關鍵,除了在查詢資料時,利用維護的每個檔案的最大/小key值來加快查詢,還在其中維護了一些進行compaction的統計值,來控制compaction的進行
以goleveldb為例,一個檔案的元資料主要包括了最大最小key,檔案大小等資訊;
// tFile holds basic information about a table.
type tFile struct
{
fd storage.FileDesc
seekLeft int32
size int64
imin, imax internalKey
}
一個版本資訊主要維護了每一層所有檔案的元資料
type version struct
{
s *session // session - version
levels []tFiles // file meta
// Level that should be compacted next and its compaction score.
// Score < 1 means compaction is not strictly needed. These fields
// are initialized by computeCompaction()
cLevel int // next level
cScore float64 // current score
cSeek unsafe.Pointer
closing bool
ref int
released bool
}
當每次compaction完成(或者換一種更容易理解的說法,當每次sstable檔案有新增或者減少),leveldb都會建立一個新的version,
建立的規則是:
versionNew = versionOld + versionEdit
versionEdit指代的是基於舊版本的基礎上,變化的內容(例如新增或刪除了某些sstable檔案)
manifest檔案就是用來記錄這些versionEdit資訊的。
一個versionEdit資料,會被編碼成一條記錄,寫入manifest檔案中。
例如下圖便是一個manifest檔案的示意圖,其中包含了3條versionEdit記錄,
每條記錄包括
(1)新增哪些sst檔案
(2)刪除哪些sst檔案
(3)當前compaction的下標
(4)日誌檔案編號
(5)操作seqNumber等資訊
通過這些資訊,leveldb便可以在啟動時,基於一個空的version,不斷apply這些記錄,最終得到一個上次執行結束時的版本資訊
1.1.6 current
這個檔案的內容只有一個資訊,就是記載當前的manifest檔名。
因為每次leveldb啟動時,都會建立一個新的Manifest檔案。因此資料目錄可能會存在多個Manifest檔案。
Current則用來指出哪個Manifest檔案才是我們關心的那個Manifest檔案