【背景介紹】
數據壓縮與關系數據庫的結合,早已不是一個新鮮的話題,當前我們已經看到了各種各樣數據庫壓縮的產品和解決方案。對于 GaussDB 來說,在今天引入數據壓縮,究竟能夠給客戶帶來什么不一樣的價值,是過去一段時間我們一直在思考的問題。 為了回答這個問題,我們首先對各種通用壓縮算法進行了廣泛的測試,從性能最好的 LZ4/Snappy,到性能與壓縮率均衡的 Zstd/Zlib,再到強調壓縮率的 LZMA/BZip。我們發現:即使是性能最好的壓縮算法,仍然無法做到對一個在線數據庫的性能不產生顯著影響。我們也調研了數據庫領域的各種編碼方法,包括近幾年學術界發布的一些基于預測和線性擬合的編碼方法,從研究發布的測試結果及實測來看,數據庫編碼用于解決特定數值分布的可壓縮性問題,與壓縮算法的成熟度相比,當前并沒有一種通用的數據庫編碼方法,能夠在大多數真實數據集中的場景下提供穩定的壓縮率。
這是我們對于數據庫壓縮這個領域的一個基本技術判斷。過去的產品實踐也驗證了這一點,我們看到很多商業數據庫和開源數據庫都提供了對于壓縮的支持,絕大多數時候,留給客戶的選擇就是決定要不要在特定的表上開啟壓縮。開啟壓縮意味著空間節省,但同時意味著性能下降,這個看似簡單的選擇恰恰是客戶最難做的。這也是為什么有了這么多數據庫壓縮的產品,我們卻很少能看到數據壓縮真正廣泛應用在數據庫在線業務中的根本原因。 這給了我們更多的啟示。我們相信,真正可被應用的數據庫壓縮技術,能夠去兼顧壓縮率與業務影響的平衡,應該是選擇性的。即我們能夠基于技術去判定數據的溫度,并基于這樣的判定,去選擇性地壓縮業務中相對較冷的數據,而不去碰那些相對較熱的數據。 這樣的技術選擇意味著我們無法去滿足所有業務場景,我們要求業務的數據溫度分布,必須滿足 80-20 分布規則。即我們去壓縮那些占用 80% 存儲需求、但只占用 20% 計算需求的冷數據,而不去碰那些只占用 20% 存儲需求、但卻占用 80% 計算需求的熱數據。幸運的是,我們發現絕大多數對于容量控制有需求的業務,都具備這樣的特征。
【場景及目標選擇】
通過對大量業務場景的分析,我們發現業務對于數據庫壓縮技術的需求是多元化的,有在線交易業務(OLTP)存儲壓縮的場景,有分析業務(OLAP)存儲壓縮的場景,有歷史業務存儲壓縮的場景,也有容災業務傳輸壓縮的場景。不同的場景,對于壓縮技術的訴求,如果從壓縮性能、壓縮率、解壓性能的三維指標去看,從對業務侵入的容忍度去看,是完全不同的。 這意味著如果我們想要打造一個全場景的 GaussDB 高級壓縮特性,它應該是多個技術的組合,包括不同的壓縮算法、不同的冷熱判定模型及方法、不同的數據存儲組織等,通過不同的技術組合及應用去滿足不同的場景需求。 這同時意味著我們在不同壓縮適用場景的支持上需要有個優先級的取舍。我們的答案是選擇去優先支持 OLTP 存儲壓縮場景,這是我們認為數據庫壓縮技術最有價值的業務領域,當然也是技術挑戰最大的領域。
確定場景之后,接下來是確定技術目標,我們面向這個場景,究竟要打造什么樣的核心競爭力,這取決于我們對于典型客戶場景的分析。我們識別了兩類典型客戶場景: 場景 A:客戶業務來自于 IBM 小機,單庫容量 50TB,遷移到開放平臺后,面臨容量過大和運維窗口過長問題。選擇拆庫意味著分布式改造,對于一個已經穩定運行許多年的存量關鍵業務來說,這種技術選擇風險過高。選擇壓縮可以顯著降低容量風險,但業務最初的設計并沒有考慮冷熱分離(比如基于時間維度建立分區),需要一種零侵入的壓縮技術支持,同時對業務性能影響足夠低。 場景 B:客戶業務基于分布式集群部署,單集群容量已經超過 1PB,并且仍在快速增長,需要定期擴容。選擇壓縮可以降低擴容頻率,顯著降低業務的軟硬件成本,并減少變更風險。但業務的數據分布設計是面向擴展性的(比如基于用戶維度建立分區),沒有考慮冷熱分離,因此同樣的,業務需要一種零侵入的壓縮技術支持,同時對性能影響足夠低。 通過對客戶典型場景的需求梳理,我們確定了 GaussDB OLTP 存儲壓縮的基本設計目標:1)冷熱判定對業務應該是零侵入的,不應對業務的已有數據分布、邏輯模型有任何依賴;2)對業務影響必須足夠低,我們定義目標低于 10%,并挑戰 5%;3)提供合理的壓縮率,我們定義目標不低于 2:1?;驹O計目標的定義,使得我們能夠將后續每個具體場景中的技術選擇都變成一個確定性問題。
【冷熱判定】
確定設計目標后,我們開始進行工程落地。有三個問題需要解決:1)如何實現對數據的冷熱判定;2)如何實現壓縮后數據的存儲組織;3)如何實現有競爭力的壓縮算法。 對于冷熱判定,首先要確定判定的粒度。數據的冷熱判定可以基于不同粒度實現,行級、塊級或表 / 分區級,粒度越粗,實現的復雜度越低,但對業務的侵入也越大?;谠O計目標,很自然的,我們選擇行級的冷熱判定,這是對業務數據分布依賴最小的方案,我們需要解決的,是如何控制引入冷熱判定的代價。 我們利用 GaussDB 存儲引擎已有的機制巧妙地解決了這一問題。具體來說,GaussDB 存儲引擎在每行數據的元數據 Meta 中記錄了最近一次修改該行的事務 ID(XID),該信息被用來支持事務的可見性判定,從而實現多版本并發控制(MVCC)。對于特定行來說,如果其 XID 足夠 “老”,老到它對所有當前已經活躍的事務都可見,那么這時候我們實際上已經不關注 XID 的具體值,我們可以通過引入一個特定的標志位(FLG)來記錄這一點,而原來 XID 中填充的值可以被一個物理時間來代替,這個物理時間就表征了其所屬行最后一次修改時間的上限(LMT,Last Modified Time)。很顯然,LMT 可以用來支持冷熱判定(具體見圖 1):
圖 1:行級冷熱判定 上述方案的好處是引入 LMT 并沒有增加額外開銷,對業務的邏輯模型也沒有任何依賴,在大多數時候,如果不是特別嚴格要求,業務可以定義一個簡單的規則來實現冷熱判定,比如:
AFTER 3 MONTHS OF NO MODIFICATION此時系統會掃描目標表,對于所有滿足當前時間減去 LMT 超過 3 個月的行進行壓縮。 注意在上述方案中,我們實際上只識別了行的寫熱點,但并沒有識別行的讀熱點,我們只知道滿足條件的行 3 個月內未發生任何更新,但我們無法確認這些行在 3 個月內是否被頻繁讀取。維護行的讀熱點,目前從技術上沒有低成本的解決方案。對于像訂單明細這樣的流水類業務,這個方案可以很好地工作,因為數據的讀和寫呈現出相同的溫度特征,其訪問頻率隨著未修改時間的增加不斷衰減。但對于像手機相冊這樣的收藏類業務,僅識別寫可能是不夠的,因為一個很早建立的收藏關系仍然可能被頻繁訪問。 這意味著,即使系統進行了冷熱判定,我們仍然需要去優化業務可能訪問壓縮數據的場景,我們把這個問題留給了存儲組織和壓縮算法,對于壓縮算法來說,我們更關注其解壓性能。 另一個問題是在某些場景下,使用默認的冷熱判定可能是不夠的,比如對于某些類型的交易而言,其產生的訂單明細可能在 3 個月內確實不會被修改,但會在達到一個特定的觸發條件后被更新(比如解凍擔保交易)。這種場景在實際業務中并不常見,但如果業務確實關注性能,那么我們支持在默認的冷熱判定規則以外,允許業務自定義規則,比如:
AFTER 3 MONTHS OF NO MODIFICATION ON (order_status = "finished")此時系統會僅壓縮 3 個月未修改、且訂單狀態已經完結的數據。 當前我們支持的自定義規則,是任意合法的行表達式,業務可以寫任意復雜的表達式來表征數據的冷熱判定規則,但表達式中所引用的任何字段,只能是目標表上的合法字段。通過這種默認和自定義規則的組合使用,我們提供了業務足夠低的使用門檻和更好的靈活性。
【存儲組織】
當滿足冷熱判定條件的行被壓縮時,我們需要決定如何存儲這些壓縮后的數據,基于設計目標,我們選擇了對業務侵入最小的存儲組織實現 —— 塊內壓縮。 我們知道關系數據庫的存儲組織都是基于固定長度的分塊的,在 GaussDB 數據庫中,典型的數據塊大小為 8KB,選擇更大的數據塊顯然有利于壓縮,但對業務性能會造成更大的影響。所謂塊內壓縮,是指:1)單個塊內所有滿足冷熱判定條件的行,會作為一個整體進行壓縮;2)壓縮后形成的數據就存放在當前的數據塊中,存放區域稱為 BCA(Block Compressed Area),它通常位于塊的尾部。 塊內壓縮的設計意味著解壓任何數據只依賴于當前塊,而不需要訪問其它的數據塊,從壓縮率的視角看,這樣的設計并不是最友好的,但它非常有利于控制業務影響。注意在我們前面的討論中,即使業務定義了冷熱判定條件,仍然存在一定的概率會訪問壓縮數據,我們希望這個訪問代價能夠有一個確定性的上限。 圖 2 給出了塊內壓縮的詳細流程:首先,當壓縮被觸發時,系統掃描數據塊中的所有行,根據指定的冷熱判定條件,識別出 R1 和 R3 是冷數據(圖 2 (a));接著,系統將 R1 和 R3 作為一個整體進行壓縮,將壓縮后的數據就存放在該數據塊的 BCA 中(圖 2 (b));如果業務后續需要更新 R1,那么系統會為更新后的數據生成一個新的拷貝 R4,并標識 BCA 中的 R1 已經被刪除(如圖 2 (c));最后,當系統在該數據塊上需要更多空間時,可以回收 BCA 中屬于 R1 的空間(圖 2 (d))。
圖 2:塊內壓縮 在整個設計中有兩點需要注意:1)我們實際上只壓縮了用戶數據 Data,并沒有壓縮相應的元數據 Meta,后者通常用來支持事務的可見性;2)我們支持將冷數據重新變為熱數據,以消除因為冷熱誤判而帶來的影響。同樣地,從壓縮率的視角,這樣的設計并不是最友好的,但它極大地減少了對業務的侵入。簡單來說,業務對于壓縮數據的訪問,與正常數據完全相同,在功能上沒有任何限制,在事務語義上也沒有任何差別。這是非常重要的原則:我們的 OLTP 存儲壓縮對于業務是完全透明的,這是當前這個特性,以及后續 GaussDB 高級壓縮系列所有特性都將遵循的基本原則。
【壓縮算法】
基于設計目標,如果從壓縮率、壓縮性能、解壓性能的三維指標來看,我們實際上需要的是一個能夠提供合理的壓縮率、合理的壓縮性能、但是極致的解壓性能的壓縮算法,這是我們壓縮算法設計的基礎。 我們首先測試了直接使用 LZ4 進行壓縮,LZ4 是目前已知的壓縮性能和解壓性能最好的開源三方庫,從實測結果看,LZ4 的壓縮率是偏低的。我們仔細分析了其算法原理,LZ4 是基于 LZ77 算法的一種實現,LZ77 算法的思想非常簡單,就是把要壓縮的數據看成一個字節流,算法從字節流的當前位置開始,前向尋找和當前位置相同的匹配字符串,然后用匹配到的字符串的長度以及與當前位置的偏移,用來表示被匹配的字符串,從而達到壓縮的效果。從算法原理上看,LZ77 算法對于長文本會有比較好的壓縮效果,但是對于結構化數據中大量的短文本以及數值類型,效果就有限,我們實際的測試也驗證了這一點。
接下來,我們將壓縮算法分為了兩層:第一層,我們按列對一些數值類型進行了編碼,我們選擇了簡單的差值編碼,這種編碼足夠輕量級,解壓特定字段不需要依賴其它字段的值;第二層,我們將編碼后的數據再調用 LZ4 進行壓縮。注意在第一層中,我們實際上是按列編碼、按行存儲,這和業界的一般實現(按列編碼并存儲)有很大不同,按列存儲對壓縮率會更加友好,但是按列存儲意味著同一行的數據會被分散到 BCA 的不同區域,這種傳統的設計無法支持我們后續希望實現的部分解壓,我們將在結束語中更詳細地說明這一問題。 通過實測,我們發現這種列編碼 + 通用壓縮的實現方式有效地提升了壓縮率,同時控制了業務影響的明顯增加,但兩層實現之間是松耦合的,這引入了許多額外的開銷。因此我們在仔細權衡之后,決定放棄 LZ4,而是完全基于 LZ77 算法,重新實現一個緊耦合的壓縮算法。
這在當時看來是一個非常冒險的嘗試,事實上,在我們之前,還沒有任何數據庫內核團隊,會選擇自己去實現一個通用壓縮算法。但從最后取得的收益來看,我們實際上是打開了一扇全新的大門。當列編碼與 LZ77 算法之間的邊界被打破時,我們引入了一系列的優化創新,考慮篇幅原因,我們無法展現全部技術細節,在這里,我們只介紹兩個小的優化: 第一個優化是內置行邊界。我們發現,當系統采用兩層壓縮算法后,我們需要額外地保存每一行數據在編碼后的長度,因為我們需要在 LZ77 算法解壓后找到每一行的邊界,這是一個不小的開銷。為了消除這個開銷,我們選擇在 LZ77 的編碼格式中嵌入一個行邊界的標記,這個標記只占用了 1 個位,其開銷較現有方案大幅降低。當然,這個標記位被占用后,LZ77 前向搜索的最大窗口長度減少了一半,但在我們這個場景中,這并不是什么問題,因為我們的典型頁面長度只有 8KB。 第二個優化是 2 字節短編碼。原有 LZ4 的實現中,為了提高壓縮性能,系統使用 3 字節編碼來描述一個匹配,這意味著系統能夠識別的最短匹配為 4 字節。
但是在結構化數據中,3 字節的匹配是非常普遍的,參考下面一個例子: A = 1 … B = 2 其中,A 和 B 是同一行數據中的兩個整數型字段,它們的值分別為 1 和 2,基于當前的字節序,該行數據實際在內存中存放的形式如下所示: 01 00 00 00 … 02 00 00 00 注意上面標紅的部分,很明顯,這里面有一個 3 字節的匹配,但是它無法被 LZ4 識別。 我們通過在 LZ77 算法中額外引入 2 字節短編碼來解決這一問題,2 字節短編碼可以識別最小 3 字節的匹配,從而相對 LZ4 能夠提升壓縮率。當然,引入短編碼會有額外的開銷:1)壓縮性能會有一定程度的下降,因為我們需要建立兩個獨立的 HASH 表,幸運的是,在我們這個場景中,極致壓縮性能并不是我們追求的目標;2)2 字節編碼減少了表達匹配串與被匹配串之間距離的位寬,這意味著 3 字節的匹配必須離得更近才能被識別,在我們這個場景中,這并不是什么問題,因為相對于這個限制,一個典型數據行的長度已經是足夠小的。
【效果評估】
我們使用標準的 TPCC 測試來評估啟用 OLTP 存儲壓縮特性對業務的影響。TPCC 模型共包含 9 張表,其中空間會動態增長的流水表共有 3 張,在這 3 張表中,訂單明細表(Orderline 表)的空間增長比其它表多一個數量級,因此我們選擇在這張表上開啟壓縮?;?TPCC 的業務語義,每筆訂單一旦完成配送,其訂單狀態就進入完結狀態,完結的訂單不會再被修改,但仍有一定的概率被查詢?;谶@個語義,我們選擇冷熱判定原則為只壓縮已經完結的訂單。 我們分別測試了在不開啟壓縮和開啟壓縮狀態下系統的性能值,結果如圖 3 所示:
圖 3:業務影響評估 測試結果表明:在 TPCC 測試場景下,開啟壓縮與不開啟壓縮相比,系統性能大概降低了 1.5%。這是一個非常不錯的結果,這意味著即使在超過百萬 tpmC 的業務峰值場景中,系統也可以開啟壓縮。我們不知道在此之前,業內是否有其它數據庫產品也能夠達到這一水平。 我們測試了 Orderline 表的壓縮率,作為更豐富的數據集,我們同時選擇了 TPCH 模型中的 4 張表(Lineitem、Orders、Customer、Part 表)進行測試。為了便于比較,對于每個數據集,我們同時測試了 LZ4、ZLIB 和我們的壓縮算法的壓縮率表現,其中 ZLIB 是強調壓縮解壓性能和壓縮率均衡的算法,其壓縮解壓性能較 LZ4 低了 5-10 倍。最終結果如圖 4 所示:
圖 4:壓縮率評估 測試結果與我們預期的相符,在數值型字段較多時,我們的壓縮算法的壓縮率要高于所有通用壓縮算法,但在文本型字段較多時,我們的壓縮算法的壓縮率會介于 LZ 類和 LZ + Huffman 組合類的壓縮算法之間。
【運維 TIPS】
注意我們的壓縮方案實際上是離線的,也就是數據剛生成時必然是熱數據,它們不會觸發壓縮,業務訪問這些數據的性能也不會受任何影響;隨著時間的推移,這些數據的溫度會逐漸降低,最終被獨立的壓縮任務識別為冷數據并進行壓縮。 選擇在業務低峰期運行這些壓縮任務、并控制其資源消耗是運維端需要關注的問題。在這塊我們提供了豐富的運維手段,包括指定運維窗口、壓縮任務的并行度、每個壓縮任務的壓縮數據量等。對于絕大多數業務來說,單位時間內新增的數據量實際是比較有限的,因此業務也可以選擇一個特定的時間段集中完成壓縮任務,比如每個月第一天的凌晨兩點到四點,完成 3 個月前新增冷數據的壓縮。
業務在決定開啟壓縮之前,可能希望先了解開啟壓縮后的收益,并根據收益大小做出決策。為此我們提供了一個壓縮率評估工具,能夠對目標表的數據進行采樣,并使用和實際壓縮過程完全相同的算法對采樣數據進行壓縮,計算壓縮率,但不會實際生成 BCA,不會修改任何數據。 如果業務將壓縮數據遷移到另一個表,可能會導致所有數據從壓縮狀態變為非壓縮狀態,從而導致空間膨脹,這并非我們的方案引入的,而是所有壓縮方案都需要解決的問題。如果冷熱判定規則非常確定,那么業務可以手動執行壓縮任務使壓縮立即生效;對于耗時較長的大容量壓縮表的遷移,業務仍然可以選擇定期地開啟自動壓縮任務來完成。 最后,對于壓縮的開啟和關閉,我們提供最細粒度的控制,無論是普通表、普通分區表中的單個分區,還是二級分區中的任意單個分區、子分區,業務都可以單獨開啟或關閉壓縮。這使得對于業務本身已經對數據區分了冷熱(比如基于時間分區)的場景,仍然可以和我們的壓縮特性很好地配合。
【結束語】
在 OLTP 表壓縮這個特性中,我們引入了一系列的技術創新,包括全新的壓縮算法、細粒度的自動冷熱判定和塊內壓縮支持等,可以在提供合理壓縮率的同時,大幅度降低對業務的影響,我們希望這個特性能夠在支持關鍵在線業務的容量控制中發揮重要價值。 接下來我們還將在降低引入壓縮對業務的影響、部分解壓特性、OLTP 索引壓縮等方面持續創新迭代,我們希望能夠有開創性的技術突破來解決相關的問題,為業務創造更大價值。
審核編輯:劉清
-
編碼器
+關注
關注
45文章
3646瀏覽量
134648 -
存儲器
+關注
關注
38文章
7494瀏覽量
163915 -
壓縮機
+關注
關注
11文章
675瀏覽量
79344 -
LMT
+關注
關注
0文章
7瀏覽量
5982 -
MVCC
+關注
關注
0文章
13瀏覽量
1479
原文標題:GaussDB技術解讀丨高級壓縮
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論