TerarkDB是一款高性能和高壓縮率的存儲引擎,,既可以單獨作為數(shù)據(jù)庫使用,也可以作為已有數(shù)據(jù)庫的存儲引擎使用(如MySQL/MongoDB)
TerarkDB的定位類似于WiredTiger,、RocksDB或LevelDB
1. 為什么使用 TerarkDB
- 高性能的同時具有高壓縮率
- 高性能并非來自于時間空間的互換
- 時間和空間同時獲得的縮減
- 延遲非常低并且很穩(wěn)定
- 基于Schema定義,,并具有豐富的數(shù)據(jù)類型
- 針對不同的數(shù)據(jù)類型進(jìn)行了獨特優(yōu)化
- 支持單表多索引
- 使用列存儲,支持列簇
2. 性能(不限內(nèi)存)
2.1. 壓縮率
2.2. 讀性能
2.3. 寫性能
2.4 讀寫混合
3. 使用TerarkDB
TerarkDB有兩種使用方式,,第一種是作為單獨的數(shù)據(jù)庫或存儲,第二種是作為其他數(shù)據(jù)庫的存儲引擎:
- 作為單獨的數(shù)據(jù)庫或存儲
- 原生C++支持(同時正在集成Java,、Python綁定)
- 這種方式能獲得完整的TerarkDB性能和特性
- 作為其他數(shù)據(jù)庫的存儲引擎(MongoDB, MySQL, FUSE等)
- 簡潔易用的上層接口
- 能夠獲得大部分的TerarkDB性能和特性
4. 索引壓縮簡介
TerarkDB的數(shù)據(jù)索引使用了 Succinct 技術(shù),、Nested Succinct Trie數(shù)據(jù)結(jié)構(gòu),壓縮率是B+樹的3到5倍,,并且針對不同類型的字段進(jìn)行了單獨的優(yōu)化處理,,同時用戶也可以指定字段使用的壓縮方式。
我們把自己 TerarkDB 的索引壓縮算法稱為可檢索索引壓縮(Searchable Index Compression) ,,TerarkDB 的索引經(jīng)過高度壓縮的同時,,可以被直接檢索,而且無需事先解壓,。
由于整個索引結(jié)構(gòu)會非常的小,,默認(rèn)情況下 TerarkDB 會將所有的索引都加載到內(nèi)存中,,在高度壓縮的索引上檢索速度可以達(dá)到極快的速度。
5. 數(shù)據(jù)壓縮簡介
TerarkDB的數(shù)據(jù)壓縮方式,,與傳統(tǒng)的數(shù)據(jù)庫也有很大的不同,,我們把自己的壓縮算法稱為 可定點訪問數(shù)據(jù)壓縮(Seekable Data Compression) :
- 傳統(tǒng)數(shù)據(jù)庫壓縮算法
- 將多條記錄壓縮到一個塊(block)或者頁(page)中
- 壓縮后的數(shù)據(jù)存儲于磁盤,解壓后進(jìn)入內(nèi)存由數(shù)據(jù)庫管理,,會有OS Cache和Memory兩份緩存(Double Cache)
- 壓縮塊越大,,壓縮率越高,但是讀性能就越低,,不可兼得
- TerarkDB 壓縮算法
- 直接通過RecordID查詢數(shù)據(jù),,不需要額外的數(shù)據(jù)解壓(即, 非塊壓縮)
- 可以處理更大的數(shù)據(jù)量,同時有更高的壓縮率
- 不需要緩存未壓縮的記錄
- 利用空閑內(nèi)存,,充分發(fā)揮文件系統(tǒng)緩存的優(yōu)勢
- 擁有更高的讀性能
同時,,數(shù)據(jù)的壓縮算法,還針對以下兩種字段情況,,進(jìn)行了特殊處理
- 較小的字符串
- 使用Nested Succinct Trie數(shù)據(jù)結(jié)構(gòu)進(jìn)行壓縮
- 具有非常高的壓縮率
- 相對較低的讀性能(依然比 塊壓縮 高很多)
- 較大的字符串
- 全局+局部字典壓縮(lz77變種)
- 較高的壓縮率,,比gzip高,某些情況下比bzip2更高
- 極快的讀性能(幾乎是內(nèi)存拷貝(
memcpy )的速度)
6. TerarkDB 架構(gòu)
TerarkDB是一系列領(lǐng)先技術(shù)的結(jié)晶, 具有模塊化,、易擴展等特性,,同時極大的降低了各種系統(tǒng)損耗。
6.1. 表結(jié)構(gòu)
TerarkDB中,,最上層的邏輯層就是一個表(Table),,從上圖可以看出,邏輯上一個 Table 就是一個二維的表格,,其中recordId 是邏輯連續(xù)的,,當(dāng)某一行的數(shù)據(jù)被刪除后,這個recordId 還會繼續(xù)存在,。
第二列是刪除標(biāo)記,,表示改行的刪除狀態(tài),共有兩個標(biāo)記,,分別是:(邏輯刪除,,物理刪除) , 如 (1, 0) 表示邏輯刪除,,物理上還沒有刪除,。
雖然從邏輯上來說,recordId 是連續(xù)的,,但是實際上物理上每一個表都是"分段(segment)"的,,每一段的結(jié)構(gòu)都與上圖一樣。
邏輯編號和物理編號的映射是通過 rank select 實現(xiàn)的,,這部分的性能損耗非常的?。ㄐ∮?%,,甚至小于0.1%)。
特殊情況下,,如果沒有做過刪除,,物理編號和邏輯編號是完全相同的,這種情況下映射帶來性能損耗也就沒有了,。
6.2. Record Id不變性
RecordId是標(biāo)示一個表中的一行數(shù)據(jù)用的,,當(dāng)RecordId的不變形被確保的情況下:
- 相同的RecordId永遠(yuǎn)取到同樣的值
- 相同的Key(字段名)檢索到的RecordId永遠(yuǎn)相同
- Segment的變動(合并、壓縮,、刪除)不會影響RecordId
當(dāng)然,,我們可以配置RecordId的不變性聲明周期:
- 永久性的:多次表重載后,依然不變
- 表周期:每次表被重新加載后,,被刪除的 RecordId 將失去原有意義
- 節(jié)省 RecordId 數(shù)組的下標(biāo)位置
- 輕微的提升性能
6.2. 段,索引和列組
未建立索引的列,,會被存儲在列組(Column Group)中,,列組是在 Table Schema 中提前定義好的。
常見的列組會使用我們的 可定位數(shù)據(jù)壓縮(Seekable Data Compression) 算法進(jìn)行壓縮,,同時定長的列組可以被定義為支持原地更新,。
索引操作的過程:
- 如用戶希望找到 name 為 AAA 的記錄中,age的值,,其中name字段已建立索引
- 根據(jù) name 所在的索引,,快速檢索到數(shù)據(jù) AAA,然后獲得當(dāng)前記錄的 RecordId
- 根據(jù)RecordId和目標(biāo)key(即age),,直接獲得最終這一個字段的數(shù)據(jù)
- 傳統(tǒng)數(shù)據(jù)庫此處需要提取完整的一行記錄,,然后再這條記錄中尋找目標(biāo)字段的值
- TerarkDB 只需要根據(jù) RecordId 獲得目標(biāo)字段的邏輯數(shù)組,直接以數(shù)組下標(biāo)即可獲得目標(biāo)字段的值
索引操作的這兩個操作過程,,能實現(xiàn)的功能如下:
- 根據(jù)KEY獲得邏輯的RecordId
- 精確搜索: 最快
- 范圍搜索: 根據(jù)不同索引的迭代器實現(xiàn)
- 正則查詢: 盡最大可能避免線性掃描
- 根據(jù)RecordId讀取目標(biāo)數(shù)據(jù)
- 讀取整條記錄
- 讀取指定的幾個字段
- 讀取指定的列組(最快)
6.3. 段(Segment)
6.3.1. 可寫(Writable) & 只讀(Readonly) 段
TerarkDB 的可寫段(Writable Segment):
- 可以是正在寫(Writing)或者凍結(jié)(Frozen)狀態(tài)
- 目前的實現(xiàn)基于一個傳統(tǒng)的數(shù)據(jù)庫
- 不做壓縮
- 比只讀的段讀取速度慢一些
TerarkDB的只讀段(Readonly)是我們的核心競爭力:
- 高性能同時具有高壓縮率
- 穩(wěn)定的延遲(沒有慢查詢,,更優(yōu)異的P99延遲)
- 不需要專有的緩存(自然解決double cache問題)
- 定長字段可以原地更新
6.3.2. 正在寫(Writing) 和 凍結(jié)(Frozen) 段
- 正在寫的段是由傳統(tǒng)數(shù)據(jù)庫實現(xiàn)的
- 當(dāng)正在寫的段,數(shù)據(jù)達(dá)到了預(yù)設(shè)的
segment size 后,,將會轉(zhuǎn)變成凍結(jié)狀態(tài)(Frozen)
- 然后新的可寫段會被創(chuàng)建,,并且立即變成正在寫的段
- 插入/更新/刪除是是實時的,不會阻塞用戶線程
- 凍結(jié)段
- 可以是一個只讀的段(只讀段總是凍結(jié)的)
- 可以是一個可寫段(當(dāng)?shù)却龎嚎s或者正在壓縮時)
- 刪除(或原地更新)是實時的
6.3.3. 正在寫的段 (Writing Segment)
- 正在寫的段是最新的可寫段
- 正在寫的段中的記錄,,可以被直接修改
- 熱數(shù)據(jù)(尤其是被頻繁更新的數(shù)據(jù))通常都在正在寫的段
- 寫數(shù)據(jù)時索引是同步的(插入/更新/刪除)
- 索引同步會降低插入速度
- 索引同步某些情況下可以禁用掉
- 如: 批量導(dǎo)入數(shù)據(jù), 此時沒有并發(fā)的讀取操作, 禁用可以顯著的提高性能
- 如果禁用了索引同步, 新插入的數(shù)據(jù)可能無法被任何索引搜索到,但是可以被 RecordId 訪問
6.3.4. 凍結(jié)段 (Frozen Segment)
- 凍結(jié)段可以是一個只讀段或者一個可寫段
- 所有的數(shù)據(jù)都是只讀(即便是被凍結(jié)的可寫段)
- 對于原地更新的字段是例外
- 刪除: 設(shè)置刪除標(biāo)記
- 更新: 設(shè)置邏輯刪除標(biāo)記,,并且在正在寫的段創(chuàng)建一個新的記錄(即便是在可寫段進(jìn)行的更新操作)
- 物理刪除
- 永久性的把邏輯刪除的數(shù)據(jù)情況
- 需要重新構(gòu)建只讀段
6.3.5. 只讀段 (Readonly Segment)
只讀段的概述:
- 絕大多數(shù)的數(shù)據(jù)都會被存儲于只讀段中(比如 99% 的數(shù)據(jù))
- 具有非??斓目蓹z索索引壓縮
- 具有非常快的可定位數(shù)據(jù)壓縮(在8倍的壓縮率的情形,,達(dá)到 7GB/s)
- 由后臺線程創(chuàng)建,,不阻塞用戶線程
- 更大的段具有更高的壓縮率和性能
- 針對列存儲進(jìn)行了優(yōu)化
如何會產(chǎn)生只讀的段:
- 構(gòu)建一個新的只讀段,,表示
- 將一個可寫段壓縮成一個只讀段
- 清除邏輯刪除了的記錄
- 將多個只讀段合并為同一個只讀段
- 當(dāng)一個正在寫的段被凍結(jié)了
- 它會被轉(zhuǎn)交給壓縮隊列
- 然后會被后臺線程壓縮成只讀段
- 壓縮通常會比插入更慢一些
- 當(dāng)壓縮(或合并、清除)結(jié)束
- 原始的段會被壓縮后的段代替,,原始段同時被刪除
- 將壓縮過程中產(chǎn)生的更新和刪除操作進(jìn)行同步
- RecordId 不變性繼續(xù)保持
6.3.6. 原地更新的列組 (In-place updatable column groups)
- 原則: Less pain, more gain
- 只適用于定長的列組
- 可以直接通過內(nèi)存地址訪問
- 可以以極低的損耗實現(xiàn)
- 對所有的段都生效(包括 只讀段)
- 當(dāng)一個段正在被壓縮,、合并或物理刪除
- 暫時記錄這個更新
- 當(dāng)壓縮或合并完成后,繼續(xù)這個更新
- 不會阻塞用戶線程
|