LevelDB的一些簡單總結

lipeng08發表於2016-10-15

LevelDB特點

  1. key value記錄持久化儲存,記錄可壓縮
  2. 支援高寫入的場合
  3. 具有插入Put,刪除Delete,以及讀取Get記錄三種基本操作。不包含更新操作,可以直接插入新的value表示更新。支援批量寫入,利用WriteBatch將多個寫操作合併到一起
  4. 預設寫入為非同步寫入,速度快,先寫入日誌再寫入記憶體,同步寫入極慢。
  5. 支援快照操作。可通過快照保持讀取資料的一致性,不受到中間寫入過程的影響
  6. 單程式多執行緒操作一個庫是安全的,多程式是需要使用者自己管理併發的。
  7. 僅僅是一個庫而已,沒有自己的服務程式

Level儲存結構

1,記憶體的資料

記憶體中有兩個表MemTable和Immutable MemTable
MemTable代表當前活躍的表,它主要包含Log, Manifest,以及Current三個硬碟檔案,以及記憶體中的一個跳錶SkipList。
Log是用來記錄使用者的寫入或者刪除操作,先寫入log檔案(按操作的順序),再寫入MemTable的SkipList中(根據key有序插入到相應的跳錶位置)。

當MemTable的容量達到一定程式後,此Memtable被轉換為Immutable MemTable,仍然在記憶體中,但可讀不可寫。新的MemTable被建立,並用來服務新的寫入請求,至於Immutable MemTable什麼時候會被寫入到硬碟中,可參見資料的合併中simple compaction操作。

2,硬碟中的資料

硬碟中有多級Level的資料表,叫做SSTable
它從Level 0 到Level N,每一個級別都可能有多個sst資料表。
Level 1到Level N: 它們的每級內部的資料表之間的key是無交叉的(具體參見資料的合併)。
Level 0: Level 0比較特別,它的多個資料表的key有交叉。Level 0的每一個資料表都是直接來源於Immutable MemTable,當系統進行記錄的簡單合併操作時候,直接將Immutable MemTable中的跳錶轉換為一個sst,因此Level 0中的多個sst的key可能有交叉。

知識點

資料的合併Compaction

當需要刪除資料時候,系統直接插入刪除標記即可,那舊的資料什麼時候才會被清除呢?這就要用到Compaction操作。有兩種合併,一種是simple,一種是major。
simple compaction操作就是Immutable MemTable中的跳錶轉換為Level 0的一個sst(記憶體到硬碟)。
major compaction操作指的是Level L與Level L+1的合併操作,是層級之間的合併操作(硬碟到硬碟)。當L >=1的時候,首先選取Level L中的一個資料表,然後尋找Level L+1中的所有與Level L的key有交叉的資料表,進行多路合併。合併的一個原則就是,如果Level L中有一個key在Level L+1中存在,那麼將Level L+1中的所有這樣的key記錄都刪除即可。當L = 0,它和Level 1的合併有些特殊,我們需要選取Level 0的多個資料表(由於Level 0的key是有交叉的),與Level 1的多個資料表進行合併。

SSTable資料表的結構

資料表有多個資料塊,儲存有Block的索引,以及相應Block的資料。Block的大小為32KB。

快取

有兩個級別的快取,資料表的索引快取,資料Block的快取。索引的快取是預設,Block的快取可配置。

為什麼寫入效能較好?

跳錶是在記憶體中,利用了多級連結串列的結構,查詢和插入效率高,比平衡樹減少了很多節點移動的操作,因此插入速度極快;日誌的寫入雖然是磁碟寫入,但由於是順序寫入,因此效能也很好。

為什麼讀取比較慢?

首先查詢MemTable和Immutable MemTable,沒有找到的話進入cache中查詢,仍然沒有找到,那麼只能通過硬碟查詢了。
硬碟查詢首先查詢Level 0,如果沒有繼續Level 1,直到最後一層Level,還沒找到,那麼不存在,如果中間任何一個地方找到了,那麼直接返回。這個順序時根據資料的新舊順序而來的。 對於某一個Level的資料表具體查詢如何執行的呢? 首先載入這個資料表的索引到記憶體中,檢視key位於哪一個Block中,然後將相應的Block載入到記憶體中,逐個查詢記錄。 至少需要兩次硬碟讀取操作,很慢,順序讀因為有快取的緣故,效能相對較好。

官方的效能資料

這一個部分摘取的一個部落格的內容(感謝其整理翻譯):
作者:Jeff Dean, Sanjay Ghemawat
原文:http://leveldb.googlecode.com/svn/trunk/doc/index.html
譯者:phylips@bmy 2011-8-16
譯文:http://duanple.blog.163.com/blog/static/70971767201171705113636

測試環境

使用一個具有百萬條記錄的資料庫,每條記錄有一個16位元組的key及100位元組的value。對於values值壓縮後可能大概只有原始大小的一半。
LevelDB: version 1.1
Date: Sun May 1 12:11:26 2011
CPU: 4 x Intel(R) Core(TM)2 Quad CPU Q6600 @ 2.40GHz
CPUCache: 4096 KB
Keys: 16 bytes each
Values: 100 bytes each (50 bytes after compression)
Entries: 1000000
Raw Size: 110.6 MB (estimated)
File Size: 62.9 MB (estimated)

寫效能

“fill” benchmarks會以順序地或者隨機的方式建立一個全新的資料庫。”fillsync” benchmark 的每次操作都會將資料從作業系統flush到磁碟;其他的寫操作會允許資料在作業系統buffer中停留一段時間。”overwrite” benchmark 會進行隨機的寫入以更新資料庫中現有的key值。
fillseq : 1.765 micros/op; 62.7 MB/s
fillsync : 268.409 micros/op; 0.4 MB/s (10000 ops)
fillrandom : 2.460 micros/op; 45.0 MB/s
overwrite : 2.380 micros/op; 46.5 MB/s
上面的一次”op”意味著對於單個key/value對的一次寫人。比如隨機寫benchmark每秒大概可以近似達到400,000寫操作。
每個”fillsync”操作花費(0.3ms)遠小於一次磁碟seek操作(通常需要10ms)。我們懷疑這是因為硬碟本身會將這些更新快取到它的memory裡,在資料真正寫入到扇區之前就做出了響應。這種情況下的安全性取決於硬碟在電力供應出問題時是否有足夠的電力去儲存它的memory中的資料。

讀效能

我們列出了正向及反向順序讀的效能,以及隨機查詢的效能。需要注意的是,由benchmark建立的資料庫是很小的。因此這個報告只是刻畫了工作集可以載入到記憶體時的LevelDB的效能。對於那些未命中作業系統快取的單片資料讀取操作來說,開銷主要是由為從磁碟獲取資料所需進行的一次或兩次磁碟seek操作造成的。而寫操作效能幾乎不受工作集能否載入到記憶體的影響。
readrandom : 16.677 micros/op; (每秒大概60,000 reads)
readseq : 0.476 micros/op; 232.3 MB/s
readreverse : 0.724 micros/op; 152.9 MB/s
LevelDB為提高讀效能會在後臺壓縮它的底層儲存資料。上面的測試是在進行過大量的隨機寫操作之後立即進行的。在進行過compactions(通常是自動觸發的)再進行測試結果會更好些。
readrandom : 11.602 micros/op; (每秒大概85,000 reads)
readseq : 0.423 micros/op; 261.8 MB/s
readreverse : 0.663 micros/op; 166.9 MB/s
某些讀操作的高花費是由於從硬碟讀取出的blocks的重複解壓導致的。如果我們可以為LevelDB提供足夠的快取使得它可以將所有的未壓縮塊放入記憶體,那麼讀效能會有更大地改善:
readrandom : 9.775 micros/op; (每秒大概 100,000 reads before compaction)
readrandom : 5.215 micros/op; (每秒大概190,000 reads after compaction)

相關文章