加速leveldb查詢性能之Cache技術
目錄
1.兩種Cache
2.Table Cache
3.Block Cache
注:本節所有內容更新至星球。
學習本節之前最好提前需要學習前面兩篇文章,這樣便好理解本節內容。
多圖文講解leveldb之SST/LDB文件格式
【深入淺出leveldb】LRU與哈希表
1.兩種Cache
在leveldb中有兩種cache,分別table cache與block cache,這兩個都是cache但不一樣。
table cache是用來緩存sstable的文件描述符、index block、meta block等信息。
block cache是緩存解壓后的data block。
兩者相同點都是基于前面一節講的ShardedLRUCache實現。
維度 | Block Cache | Table Cache |
---|---|---|
緩存內容 | 數據塊(存儲實際鍵值對) | SSTable元數據(索引、文件句柄等) |
優化目標 | 減少磁盤I/O和重復解壓 | 減少文件打開和元數據解析開銷 |
生命周期 | 隨數據塊訪問頻率動態更新 | 隨SSTable文件的訪問頻率更新 |
配置參數 | Options.block_cache | Options.max_open_files |
通常來說一個block的大小在4K ~ 4MB
之間,block cache默認是一個8MB的LRU cache。
table cache默認的文件數是990(可以理解為990個sstable文件或索引(data block index)),還保留十個左右的文件用于其他用途,并將其余990個文件交給 TableCache。
假設查詢鍵K
:
Table Cache幫助快速定位
K
在SSTable-X
的Block 5
。Block Cache若已緩存
Block 5
,則直接讀取;否則從磁盤加載Block 5
并緩存。整個過程避免了重復打開
SSTable-X
文件和解壓Block 5
的開銷。
通過這種分層緩存設計,LevelDB在有限內存下顯著提升了讀取效率。
下面來講一下這兩塊的實現。
2.Table Cache
開始是在DBImpl構造函數時創建table cache:
new?TableCache(dbname_, options_, TableCacheSize(options_)
此時會通過NewLRUCache緩存990個文件。
lru cache中的kv分別是:
k = file name(壓縮之后)
v = TableAndFile
struct?TableAndFile?{RandomAccessFile* file; ?// 已打開的文件句柄Table* table; ? ? ? ? ? ?// 對應的 SSTable 解析結構
};char?buf[sizeof(file_number)];
EncodeFixed64(buf, file_number);
Slice?key(buf,?sizeof(buf));
TableAndFile* tf =?new?TableAndFile;
tf->file = file;
tf->table = table;
*handle = cache_->Insert(key, tf,?1, &DeleteEntry);
核心接口有4個,分別是:
1.Evict
刪除這個文件的緩存
2.FindTable
比如現在要打開某一個sstable,通過table cache可以快速拿到sstable的索引/filter,隨后快速查詢data block數據。
這里其實就是對lru的操作:
k = 壓縮的file_name不存在,打開sstable文件,并緩存下來,此時會在table cache的lru中寫入<k = 壓縮的 filename, v = TableAndFile>
如果k是直接在緩存中,直接從lru中拿到handle,隨后從handle拿到 value就是TableAndFile。
Status?TableCache::FindTable(uint64_t?file_number,?uint64_t?file_size,Cache::Handle** handle)?{}
3.Get
Get接口用來查詢某個key在不在sstable中,如果在回調處理函數;不在返回即可。
所以 Get函數第一步調用FindTable拿到緩存的handle,然后從TableAndFile中拿到table,調用其接口InternalGet,隨后釋放這個handle。
InternalGet的實現步驟如下:
先通過data block index 看看這個key在不在
在的話在打開拿到了對應data block的handle
然后通過filter來查,不在的話直接返回不存在
存在的話再通過BlockReader拿到這個block并構造出iter
然后iter定位到key,找到調用回調處理即可。
4.NewIterator
根據key=file_name查找cache,并返回這個cache與基于這個cache創建的iterator。
2.Block Cache
block cache是在讀取真正的data block的時候使用,函數是BlockReader。
根據輸入的索引數據(二進制),解析為對應data block的block handle,此時去看options有沒有設置block_cache,如果沒有直接ReadBlock,否則從block_cache中查詢key是一個16字節,具體來說:
key = 8字節id + block的offset
value = Block
所以這里查詢的時候,能查找到就拿到Block,否則ReadBlock之后寫入cache。
if?(cache_handle !=?nullptr) {block =?reinterpret_cast<Block*>(block_cache->Value(cache_handle));
}?else?{s = ReadBlock(table->rep_->file, options, handle, &contents);if?(s.ok()) {block =?new?Block(contents);if?(contents.cachable && options.fill_cache) {cache_handle = block_cache->Insert(key, block, block->size(),&DeleteCachedBlock);}}
}
本節完
學習更多干貨,歡迎關注轉發!