《騰訊云:騰訊云數據庫技術實踐精選集(2022年版)(232頁).pdf》由會員分享,可在線閱讀,更多相關《騰訊云:騰訊云數據庫技術實踐精選集(2022年版)(232頁).pdf(232頁珍藏版)》請在三個皮匠報告上搜索。
1、版權聲明本冊精選集版權屬于騰訊云計算(北京)有限責任公司和 InfoQ 極客傳媒,并受法律保護。轉載、摘編或利用其它方式使用本報告文字或者觀點的,應注明“來源:騰訊云計算(北京)有限責任公司和 InfoQ 極客傳媒”。違反上述聲明者,將追究其相關法律責任。參與編寫單位及人員內容指導內容策劃專家顧問作者(按拼音首字母)卷首語過去數年間,在云計算發展和國產化趨勢的雙重驅動下,國產數據庫呈現出百花齊放、齊頭并進的新格局?;赝?2022 年,國產化替代進程加速,國產數據庫投產的廣度和深度持續增加。從廣度來看,一方面,給國產數據庫產品和服務能力帶來了更大的挑戰;另一方面,也給諸多數據庫廠商提供了廣闊的機
2、會;從深度來看,數據庫開始大規模落地于銀行、證券、保險等金融企業的核心業務系統。在數據庫競爭與變化的新格局中,騰訊云作為數據庫領域的前沿探索者,其自研數據庫的歷史可以追溯到 2006 年。歷經數十年的磨礪,騰訊云在數據庫核心層自研、生態工具打造,以及數據庫在不同場景下的落地解決方案等方面,積累了足夠多的經驗,并持續向業界輸送。在過去一年中,來自騰訊云的技術專家,通過 DBTalk 技術公開課、QCon 全球軟件開發大會、ArchSummit 全球架構師峰會以及 DIVE 全球基礎軟件創新大會,分享了騰訊云數據庫的技術探索與不同場景下的落地實踐。騰訊云數據庫技術實踐精選集2022 版,收錄了全年
3、 40 余場分享的內容,也涵蓋了優秀企業數據庫應用的成功案例,相信一定能給數據庫領域的各位同仁帶去一些思考和啟發。展望未來,國產數據庫的“進化”之路仍任重道遠,愿與君攜手,百尺竿頭,更進一步!騰訊云技術實踐精選集騰訊云技術實踐精選集張 倩王魯俊 潘怡飛 唐 彥 盧衛 劉家文 竇賢明孟慶鐘 陳育興 丁艷飛 孫勇福 楊亞洲 陳 昊王宇庭 田清波 賈瓅園 韓 碩 汪泗龍 韓 鋒孫 亮 彭 濤南云鵬 劉 暢 雷海林劉 迪 伍旭飛 孫 旭 劉生洲 顧雅各 余成真胡錦前 楊玨吉 王宏博 胡 翔 謝燦揚 朱閱岸王義成賈瓅園 唐 彥 劉少蓉 伍鑫 潘安群 劉亞瓊周璐璐 溫曉樺 扶婕徐櫻丹劉穎內容編輯任傳英第
4、1 章:數據庫架構設計CDW PG 大規模在線數倉技術構架分享騰訊云原生數據庫 TDSQL-C 架構探索和實踐騰訊云數據庫云上 SaaS 生態演進基于 LSM-Tree 的分布式組件化 KV 存儲系統探索分布式數據庫的多級一致性及構建技術演進之路敏態擴展,靈活應變!TDSQL 新引擎 TDStore 技術探索Redis 如何實現多可用區?新一代云原生數據庫暢想基于壓縮數據直接計算技術,定義新型數據庫處理第 2 章:數據庫性能優化數據庫事務一致性實現上的各種細節,你注意到了嗎?還在為數據庫事務一致性檢測而苦惱?讓 Elle 幫幫你向量化執行從理論到實現,僅需五步!節省 30%磁盤空間的同時如何保
5、障數據安全?一些有趣的 B+樹優化實驗基于 LSM-Tree 存儲的數據庫性能改進面向個性化需求的在線云數據庫混合調優系統第 3 章:數據庫運維管理云原生數據庫管控探索和實踐騰訊云 MongoDB 智能診斷及性能優化實踐數據庫納管平臺 DBhouse 的技術路線與實踐從 0 到 1 實現智能化運維目 錄2813193850708295101116131147159172195203208221229騰訊云技術實踐精選集騰訊云技術實踐精選集第 1 章:數據庫架構設計1第 1 章數據庫架構設計金融級分布式數據庫架構設計與對接實踐國產金融級分布式數據庫在金融核心場景的探索實踐金融級分布式數據庫 TD
6、SQL 升級版引擎架構和關鍵技術介紹深度解析金融級分布式數據庫一致性技術金融業數據庫選型之路金融應用對分布式數據庫的訴求銀行核心業務系統數據庫主機下移架構與實踐分布式數據庫在金融場景下的實戰TDSQL 破局敏態業務背后的技術演進TDSQL 在 TP 領域的技術探索和實踐新一代云原生數據庫 TDSQL-C 關鍵技術突破KeewiDB 輕 TP 技術實踐只有時代的 Oracle,沒有 Oracle 的時代-看國產數據庫如何突出重圍!得物數據庫中間件平臺“彩虹橋”架構演進及落地實踐云原生下緩存架構如何演變?在敏態業務場景中,微盟數據庫的應用實踐之路50W+小程序開發者背后的數據庫降本增效實踐TDSQ
7、L-PG 數據庫在微信支付的應用實踐如何像用自來水一樣使用數據庫?HTAP 數據庫存儲引擎技術演進如何利用資源管理技術,讓 HTAP 負載同時運行HTAP 數據庫最佳實踐HTAP 系統的問題與主義之爭238248256263285291298308318329339348355367377386392399406417429436445第 1 章:數據庫架構設計第 1 章:數據庫架構設計23CDW PG 大規模在線數倉技術構架分享張倩騰訊云數據庫專家工程師騰訊云數據庫專家工程師,中國人民大學博士,具備多年數據庫內核研發經驗,曾在 Teradata 天睿公司以及華為公司高斯實驗室工作多年。加入騰
8、訊后,負責 CDW PG 數據倉庫內核優化器、執行器等多項核心功能研發。騰訊云數倉產品 CDW PG 是一款面向超大規模集群海量數據分析的在線數倉。通過創新的數據轉發平面、全自研列式存儲、分布式延遲物化技術,以及向量化執行引擎等核心技術為用戶帶來極致的數據分析體驗,可以真正將海量數據所蘊藏的價值釋放出來。ArchSummit 2022 北京站上,騰訊云數據庫專家工程師張倩帶來題為 CDW PG 大規模在線數倉技術構架分享的分享,主要聚焦 CDW PG 在構架設計以及核心優化上面的一些經驗和思路。CDW PG 發展歷程介紹CDW PG 是騰訊基于 PostgreSQL 自主研發的分布式在線關系型
9、數據倉庫。PostgreSQL 是一個單機版數據庫,我們在這個基礎上開發了一個無共享 MPP 架構,支持行列混合存儲,同時支持超大規模集群,目前集群規??梢灾蔚缴锨Ч濣c,同時可以支撐超高速的計算能力。整體架構演進大規模集群面臨的一大挑戰是集群擴展性挑戰,分布式 JOIN 消耗大量網絡連接和對應資源。對于分布式 JOIN,數據是分布在不同節點上的,數據的分布鍵和分布式 JOIN 的鍵并不是CDW PG 的整體架構如上圖所示。完全一致,這時數據需要重分布,重分布之后再進行 JOIN 連接才能得到最后結果。數據的重分布通過 DN 節點,把本 DN 自身的數據通過模塊連接發送到對應的其他 DN 上。
10、如果 DN 節點非常多,每個 DN 都要和 JOIN 建連,則一個數據重分布就會有很多連接。假如有 200 個 DN 節點,有 100 個并發查詢,如果每個查詢 5 個重分布,這種情況下可以計算出 10 萬個連接。每個連接在每個節點上對應一個進程,對服務器的壓力非常大,也是限制分布式數據庫拓展性的問題之一。原來的集群架構顯然不適合大規模集群,所以我們在這個基礎上開發了異步執行框架,一個新型的分布式架構。在這個架構里,我們會在查詢優化階段分析物理查詢計劃,統一創建 DN 上的各層執行進程,各層執行進程之間不需要建立直接連接,不同層級進程可以異步啟動執行。假設我們有 N 個節點,就會產生 N 個進
11、程數,這是進程數上的優化。我們在連接數上會進一步引入 FN,FN 來負責節點間的數據交互。每臺物理機只需要布置一個 FN 節點,FN 節點會負責本臺物理機和其他集群內其他物理機的數據交換。在本機節點上,這個 FN 與 CN 和 DN 之間是通過共享來進行數據交互的,本機和其他的物理機進行數據交互的時候是通過網絡進行。在這樣的分布式架構下,假設有 N 個節點,不管 JOIN 有多么復雜,查詢可以有十幾個 JOIN,它們只有 N*(N-1)個連接數。在優化器階段會查詢計劃分片。優化器是根據代價來審核的最優計劃。在單機上的代價是一些算子,每個算子有不同的代價;但在分布式的情況下,數據重分布也有自己的
12、代價。優化器會生成代價最優的計劃,在分布式的計劃里可以看到,Remote Subplan 會有數據重分布節點,數據重分布節點會把下層執行的數據按照一定規則進行數據重分布,生成分布式的計劃之后會變成整個執行計劃,對這個計劃進行分片。分片的原理很簡單,我們對每個數據分布節點下面的 Log 節點作為一個組數,劃分成一個分片,把分片用 FID 編號。每個分片除了 Remote 第 1 章:數據庫架構設計第 1 章:數據庫架構設計45Subplan 節點,下面的計算都可以在本地完成。本地完成之后再通過 Remote Subplan 節點把數據發送出去,串聯起整個分布式的計算。生成這樣一個分布式計劃之后,
13、會下發這個計劃分片到對應的執行進程上,每個分片會在每個執行節點上創建一個進程來執行對應的計劃片段。在這個 JOIN 里會分成兩個 FN 片,會有 FID1 和 FID2,FID2 對數據重分布,然后再跟 FID1 做一個 JOIN,這樣兩個計劃分片在 DN 片會有 4 個進程。這 4 個進程,每個進程會負責執行自己收到的進程,然后把數據通過 Remote Subplan 節點,不會直接建連,而是把數據發送給本機的 FN,FN 負責把數據轉發。不同層級之間的進程是異步啟動執行的,下面的分片和下面的分片是同時啟動執行的,通過 FN 進行數字交互。自研列式存儲我們自研的列式存儲支持按照行存儲和列存儲
14、建表,列表和行表之間可以相互操作,行列表之間的混合查詢保證事務一致性。很多查詢都是點查詢或者是只會操作比較少量的數據,在這種情況下,我們點查每次只出一行數據,在這個行數里面可以獲取這一行數據進行處理。但是在 AP 場景中,對于寬表來說它的列會非常多,每次用戶操作的時候只操作其中幾列,比如說只查名字或者只查名字和部門,或者只查名字和年齡,后面的幾百上千列都不會管。如果還按行存儲就會浪費很多 IO。后面的列數都是我們需要的,每一列單獨存儲,多個列邏輯上組成一行,一次的磁盤 IO 只包含一列數據。這種情況下,因為這一個數據文件只存儲單列的數據,它的數字類型都是固定的,很方便做數字壓縮,也能夠提供比較
15、高的數字壓縮效果。在我們的數據庫里支持行列存儲,既可以在行存表和列存表,可以同時互相操作,也可以查詢復雜的操作,行列表之間的混合查詢能夠保證數據的一致性。分布式集群里一個比較難的問題是保證分布式數字一致性。我們是基于 Timestamp 的分布式提交協議。我們會有一個 GTS 集群,GTS 集群包括 GTS 組和 GTS 位。GTS 集群是提供給數據控制節點,它是從內部開始,內部單向并且唯一由 GTS 維護,由硬件來保證使用源的穩定度。在這個情況下,加上數據庫內部的 MVCC 的存儲能力(MVCC 是整個并發控制的基礎),這兩個結合起來提供了分布式事務的一致性。GTM 單點可靠性是由 GTM
16、主備來支持的,也就是說主節點可以通過對外提供服務來保證分布式事務的一致性,然后主備之間是通過日志簿時間戳的狀態來保證 GTS 的可靠性。對于單點可靠性,數據庫里面如果是非常繁忙的業務,TS85 服務器每秒能夠處理 1200 萬的 GTS,可以滿足絕大部分業務團隊的需求。上圖介紹了我們自研的列式存儲,主要包含兩部分。第一部分是左邊的表,會有一些信息,有 C1 文件和 C2 文件,每個文件里面的數據是按照 Silo 來倉儲的,對應的 Silo 信息會存儲在這個表里,同時 Silo 的輸出上,事務的信息也是通過這個表來保證的。在這樣的基礎上,可以看到這種數據結構比較適合大批量的數據導入,也就是說我們
17、在做大批量數據插入的時候很快會填滿,再寫入下一個 Silo,內存的效率最高。但是在很多業務場景中,這個場景并不是很單一,也有可能除了大批量的數據插入之外,還有一些小數據量的操作。這種情況下如果我們還用 Silo 來存儲就會顯得效率比較低。為此我們開發了一個 Stash 表,它是一個行情表,主要目的是在用戶進行小數據量操作的時候,這些數據都會進入到 Stash 表里,提供一個像行存一樣的性能和訪問能力。在后臺我們會有一個 Stash Merge 操作,會定期 Merge。數據積累到一定程度,我們有了整個 Silo 的數據,就會把它生成一個 Silo,從而獲得批量性能提升,這對用戶來說是完全無感知
18、的。Silo 頭部會定義一些數據特征,包括 CRC 校驗、壓縮信息、Bitmap 信息、數據壓縮文件,最后有一些頁面對齊。我們提供了列存文件格式之后,由于每一列的數據格式是一定的,我們就可以提供一個比較高效的數據壓縮算法。在這里我們提供了三層壓縮級別,分別是 Low、Middle 和 High。選擇了對應的壓縮級別之后,數據庫內部是根據這個列具體的數據類型來選擇不同的壓縮算法,這個壓縮算法是內部選擇,用戶不用自己操心。我們寫入的時候會有一個輕量級壓縮,再一個是透明壓縮,然后是寫入 Silo 文件中,然后讀取到內存中計算,這個過程對用戶是完全透明的。High 的壓縮比會達到 5.5,Low 是
19、3.5 左右。在列存的存儲格式中,我們進一步提供了延遲物化掃描優化。傳統的方式是把數據掃描上來做對應的計算,生成對應的結果。但在列存的數據格式下我們可以提供一個優化的方式,比如說有一個多列計算,可以先掃描其中一列,如果都不符合條件可以都不掃描,第二列直接跳過,或者我們掃描的這一列里面只有一個數據是符合條件的,在對第二列計算的時候,可以掃描對應的數據位置來計算,相比傳統的方式需要掃描所有數據,這種方式可以減少很多 IO,提升掃描和查詢計算的性能。同樣這個列存也是支持索引的,我們現在支持的是兩種索引,一個是 B-Tree 索引,一個是 Hash 索引。列存的 Stash 表后臺的自動合并能力是為了
20、小數據量插入和小數據量更新準備的。在這種情況下,數據會進入到 Stash Table 里,然后 Stash Table 會自動做一個 Stash Merge 到 Silo 表里。第 1 章:數據庫架構設計第 1 章:數據庫架構設計67我們的列存表是支持創建配套的 Stash 緩存表,如果是批量的插入,中心會直接寫到 Silo 表,如果是小數據量插入,中心會寫入到 Stash 表,但這對于用戶是完全透明的,是后臺自動判斷的。后臺也會有一個 Stash Merge 的進程,自動判斷這個 Stash 表里的數據大小,進行列存格式的整理。也就是說把 Stash 的表格數據 Merge 到 Silo 里
21、,通過列式加 Silo 表能夠達到性能統一。執行引擎能力優化我們執行引擎的能力優化分為幾個部分,首先是多級并行能力的提升。我們是分布式集群,天然就提供節點級的并行。在每個 DN 內部,我們又提供了進程級的并行。某個 DN 會有計劃分片,計劃分片在傳統的數據里會啟動一個進程完成它。在我們的集群里面,對于同樣的一個計劃分片,我們會生成多進程并行。一個簡單的 Stash 操作,如果有兩個進程并行 Stash,效率會提升 50%,每個進程只需要處理一半數據。上面做計算時也是兩個進程并行執行,最后得到的結果是兩個進程結果的疊加。在進程級的并行基礎上,我們還提供了 SIMD 指令級并行。在做計算時可以通過
22、 SIMD 進行指令級并行。我們的向量化執行引擎也是基于自研的列式存儲上開發的執行引擎。傳統的執行引擎是采用火山模型,每次處理一個原子,邏輯比較簡單,但效率比較低。數據量非常大的時候,比如我們有非常多的源組庫的時候,函數調用和指令級調用都非常多,CPU 大部分時間中數據和指令的命中率都比較低,內存的命中率也很低,沒辦法利用 SIMD 能力。向量化產品執行引擎每次不是處理一條數據,是處理一組數據。它會顯著減少函數調用的開銷,提高 CPU 的執行效率。我們這個列存天然可以將一組原子變成一組列存向量,從列存里面,我們可以讀取一個向量數據再發送給算子,算子就可以對這一組數據進行計算。這樣我們既可以減少
23、函數的調用,又可以實現 SIMD 指令來加速算子執行。我們提供的另外一個能力是 Plan Hint 自動調優。很多場景都是非常復雜的查詢,會有非常多層。在這種情況下,我們的順序和對應的每個表選擇什么,都會影響 Plan 的執行效率。傳統的優化器是通過我們收集的表統計信息來決定查詢的執行計劃。但統計信息的收集是通過數據采樣大概模擬出這個數據到底是什么樣的,顯然不太精確。而且由于統計信息收集不及時,會造成 Plan 的劣化,統計收集會有代價,在大數據量的情況下需要很長時間。在這種情況下,我們提供了 Plan Hint 的能力,會把這個計劃先執行一遍,收集一下計劃中執行的信息,生成 Row Set
24、Hint:每個表里選擇了什么樣的方式,順序是什么樣的,數據存儲方式是什么樣的,并行執行的并行度選擇什么樣的,每一步算子執行實際的數據量是什么樣的。生成后還會把信息反饋到 Plan Hint 里面,根據這個信息生成一個新的執行計劃,再對比新的執行計劃和之前的執行計劃,然后把新的執行計劃再次通過 Plan Hint 執行,循環往復,最后能夠生成比較優的執行計劃。這個生成過程是完全自動的,用戶可以在后臺比較空閑的時間來執行,或者用戶有一些調優需求的時候可以自動完成。第三是集群資源管理功能。在整個集群可能不會承載一個業務,有可能會有各種不同的各部門業務,都需要用這個集群的資源。在這種情況下,我們需要對
25、集群資源進行管理和劃分。比如說我們做一些并發的控制、內存的控制、CPU 的控制,我們通過 Leader CN 節點統一規劃資源組使用情況。資源組里會有三個定義,一個是我們的并發,指我可以同時發起多少個查詢;內存控制,決定我可以使用多少內存;還有 CPU 控制,決定我可以使用的 CPU。這樣一個資源組是在 Limit 節點上進行統一規劃控制,優化器會根據 MEMORY_LIMIT 設置 Query 中不同算子的 work_mem 內存占用。GPU 通過 cgroup 來配置占用百分比或者綁核,所有當前資源組啟動的后端進程會掛在對應的 cgroup 上。這是一個非常便利的操作。我們開發的 TDX
26、服務器負責外部數據源導入和導出的操作。CDW PG 引擎可以定義外部表與 TDX 服務器資源進行綁定。數據由 DN 節點并行導入與數據重分布,充分利用分布式系統資源。TDX 服務器支持并行多任務導入導出、管道、錯誤表等高級功能,提高用戶體驗,相比傳統 Copy 入庫出庫功能有數倍提升。我們還有多平面的能力,意思是說我們可以提供兩個平面,一個是讀寫平面,還有一個是只讀平面。通過這樣兩個平面可以做到讀寫分離,也就是說讀寫請求可以進入到讀寫平面,大量讀請求可以通過只讀平面來完成。內部是通過 DN 之間的內部復制來保證兩個平面之間的數據一致性。后續規劃上述內容已經在騰訊云上線,包括異步執行框架、FN
27、能力提升、自研列存儲,分布式延遲物化技術。我們后續在構架上會進一步優化,包括列存優化升級、向量化引擎深度優化、算子并行計算優化、SIMD 優化場景覆蓋。我們還會持續打造生態,持續融合 PG 社區能力,在兼容性上會有持續的功能提升,還有大數據生態對接和機器學習算法支持。第 1 章:數據庫架構設計第 1 章:數據庫架構設計89騰訊云原生數據庫 TDSQL-C 架構探索和實踐產品架構介紹騰訊云原生數據庫早期也曾采用傳統架構實現,但實踐中存在很多問題:傳統架構的實例存儲上限完全受限于本地磁盤容量,擴展成本高昂且操作復雜。用戶數據較多時需要分庫分表,帶來很多分布式事務問題。而用戶希望一個實例有 100T
28、 以上的容量,且存儲容量可以快速透明擴展。傳統架構基于 Block 復制,普通的異步或半同步復制方式可能丟失數據,同步復制方式的性能損失較大。用戶要求數據不丟失(RPO=0),且數據有多副本容災,可靠性滿足要求。傳統架構可用性不足,HA 或宕機重啟后一段時間(分鐘級)不可用,且主備副本延遲較高,有些達到小時級。用戶期望能夠快速切換 HA,實現秒級恢復、回檔,且副本時延在毫秒級。王魯俊騰訊數據庫內核開發工程師曾就職于螞蟻金服、阿里云等,在數據庫內核領域有多年開發經歷,對分布式系統、數據庫存儲引擎等方面有豐富經驗。TDSQL-C 是騰訊打造的云原生數據庫產品,其高可用、高可靠、高性能的特性已經被越
29、來越多的用戶認可。在 DIVE 2022 全球基礎軟件創新大會(北京站)上,騰訊數據庫內核開發工程師王魯俊帶來了主題為騰訊云原生數據庫 TDSQL-C 架構探索和實踐的分享,為大家介紹 TDSQL-C 總體架構和核心能力的持續演進現狀,并重點介紹 TDSQL-C 在可用性、可靠性和性能方面的工作,同時結合用戶場景分享 TDSQL-C 在實際應用中的實踐經驗。傳統架構水平擴展時需要復制一份原有數據庫實例數據,再建立同步關系。才能真正水平擴展;創建只讀副本時要把原始數據復制過來,然后搭建主備同步,只讀副本才能開始工作,過程可達小時級別。用戶則希望實現秒級副本擴展。騰訊云針對這四方面的用戶需求采用了
30、存儲計算分離的架構,就是 TDSQL-C 的核心架構。它的存儲是云存儲,可以水平擴展,理論容量無限,對每一份數據都有多副本來保證可靠性。數據現在分散在云存儲的各個節點上,可以做持續備份、運營回檔等功能。在可用性方面,數據的分片可以并行恢復、并行回檔,時延更低??蓴U展性方面,新建只讀副本時數據不需要復制,只需要對數據做共享操作,再構建增量的數據復制即可。TDSQL-C 架構整體分為上面的計算層和下面的存儲層。計算層有一個讀寫節點可以提供讀寫請求,還有多個只讀節點可以提供讀請求,也就是上圖中的 Master 和 Slave 節點。讀寫請求進入后,Master 節點產生數據修改,然后把修改產生的 I
31、nnoDB 的 Redo Log 下傳到存儲層,同時會把 Redo Log 分發到自己 RO 節點上。存儲層負責管理所有數據,包括數據頁面、InnoDB 層的 Segment。當 Redo 日志發送到存儲層之后,Segment 負責 Redo 日志的回放。整個存儲層架構在 COS 存儲服務上。TDSQL-C 的存儲可以自動擴容,最大提供超過 1PB 的容量,最大支持 96 CPU、768G 內存,只讀性能超過 100 萬 QPS,讀寫超過 45 萬 QPS。該架構實現了秒級故障切換、秒級快照備份和回檔,且存儲和計算層有足夠彈性,可以實現一定程度的 Serverless。需要擴展只讀性能時,該架
32、構很容易增加只讀節點。TDSQL-C 現在最多可以掛 15 個只讀節點,且在讀寫節點和只讀節點之間只有毫秒級延遲。整個工程是基于 MySQL 代碼庫演化來的,所以 100%兼容 MySQL。第 1 章:數據庫架構設計第 1 章:數據庫架構設計1011用戶場景實踐TDSQL-C 的典型客戶場景如下:1.Serverless有時業務端預測未來的需求持續上漲,所以會提前準備數據庫實例,但實際需求卻偏小,最終會造成浪費。但如果突發情況下實際需求暴漲,實例資源無法立刻跟上,又會對業務造成嚴重影響。理想情況下,資源容量應該與真實業務需求同步變化,隨時保持在比真實需求略高的水平。TDSQL-C 實現了智能化
33、的極致彈性,可以根據負載來快速啟停實例。TDSQL-C 還實現了按需計費,可以按秒計量、按小時結算,避免浪費。2.彈性擴容有些業務每天都能產生大量數據,且對單庫容量要求很高;有些業務如開發測試場景,某一次測試后數據庫實例直接刪除,生命周期非常短;有些歷史庫場景中歷史數據只存儲最近一段時間,老數據直接刪除來節省成本。針對上述場景,TDSQL-C 支持按需擴容,不需要預先進行存儲規劃;還支持自動回收,按實際使用容量計費等。3.備份回檔很多場景對備份回檔要求較高,如金融行業對備份速度和備份時效性都有很高要求,涉及到頻繁回檔的游戲業務對備份回檔速度要求較高。TDSQL-C 支持持續備份,存儲分片可以根
34、據備份點獨立備份,同時可以設定全局一致的備份點來備份。TDSQL-C 還支持每一個分片并行回檔,各自的數據全量、增量備份,并且可以并行回放自己的日志。另外,TDSQL-C 還支持 PITR,可以快速恢復到數據庫任意一個時間點的狀態。系統關鍵優化以下是 TDSQL-C 支撐相關場景的關鍵優化:1.極速啟??梢钥吹?TDSQL-C 對比傳統 RDS 數據庫,在停機時間、啟動時間、事務恢復時間和性能恢復時間上都有巨大優勢。2.二級緩存二級緩存是 TDSQL-C 在存儲計算分離架構下所做的創新優化,也是對這種架構的重要補充。例如存儲層水平擴展后數據量膨脹上百倍,但計算節點的 Buffer Pool 容
35、量沒有太大變化,意味著相同大小的 Buffer Pool 要服務更多數據。由于 InnoDB 的 Buffer Pool 一定程度上承擔了讀緩存的作用,意味著讀緩存的效率可能會下降,對性能影響較大。其次,傳統 RDS 的 Buffer Pool 和本地的磁盤空間存儲中間沒有其他層次的硬件存儲設備,但在存儲計算分離架構下數據存在遠端機器上,需要通過網絡訪問其他機器的 SSD。這之間有至少兩個層次的硬件存儲設備,一個是 SSD(本機硬盤),另一個是持久化內存。我們將這一類的存儲用作二級緩存,能夠有效減緩 IO bound 場景下命中率較低的問題。這種方式的運維成本也不高,因為 SSD 的成本遠低于
36、內存。3.極致伸縮存儲功能下放到存儲層后,存儲層有了存儲池的概念。這樣可以把存儲空間擴展到整個存儲層,所有存儲空間都池化。這樣頁面回收之后可以在物理上真正刪除,而不是只標記為刪除,這樣能降低客戶的存儲成本,實現按需計費。4.極速備份回檔現在數據分散到了分布式存儲上,分布式存儲包含很多存儲節點,本身還有一定計算能力。所以每個實例、存儲節點都可以獨自備份,也就是自治備份。有時候我們還需要全局一致的備份點,這時有計算節點負責協調,做統一備份?;貦n也是并行的,每個計算節點都可以獨立做自己的回放。根據測試數據來看,1TB 數據的備份時間用 RDS 實例需要 61 分鐘,用 TDSQL-C 只需 21 分
37、鐘;1TB 的回檔恢復時間,RDS 需要 168 分鐘,TDSQL-C 也只需 22 分鐘。5.Instant DDL 和并行構建索引Instant DDL 是 MySQL 8.0 的新增功能,是指用戶在新增列、修改列類型、刪除列時,實際上僅僅修改元數據就可以直接返回,這樣注冊時間非常短。原來重建表索引時都是單線程構建,先掃描所有主表數據,再根據每一行數據生成對應的第 1 章:數據庫架構設計第 1 章:數據庫架構設計1213索引行信息,生成臨時文件。之后對這個臨時文件按照索引行的索引鍵排序,再將數據導入,完成構建。我們針對這里做了并行優化,包括并行掃描、并行采樣和并行導入,很多場景提升到 2
38、倍以上的性能。產品未來演進我們探索的第一個演進叫 Global Database:上圖左邊有一個 Primary 實例,現在新增了一個 Log Store 模塊。它負責接收日志、分發日志,可以提升日志的響應速度和整體的 IO 吞吐量,進一步提升寫性能。另外 Primary 實例與圖右的 Standby 實例可以通過各自的 Log Store 來建立數據復制鏈路,這樣數據就擴展出來一個只讀節點,從而可以讀擴展,而且是跨 Region 讀。另外我們通過這種方式實現了跨 Region 容災,這對很多金融業務來講是剛需。第二個演進是計算下推。我們的存儲實際上擁有一定的計算能力,所謂下推就是利用它的計算
39、能力。例如讀下推(頁面內計算下推),不用將索引的葉子節點讀入 Buffer Pool,而是直接將下推條件發給存儲,返回符合條件的 Record。又如讀下推(undo 頁面間的計算),對于數據的歷史版本掃描下推到存儲層完成,內存不讀取頁面,僅獲取最終結果。還有寫下推,特定頁面的修改直接在存儲層進行??偨Y騰訊云原生數據庫 TDSQL-C 繼承了傳統架構下的云數據庫 1.0 的優勢和競爭力。我們面向云上用戶場景分析其業務特性,持續提升產品的可用性、可靠性、可擴展性和性能,進一步提升了 TDSQL-C 產品的競爭力。未來我們還會繼續面向客戶需求,跟進新硬件的發展,再去探索表現更好的新型架構。騰訊云數據
40、庫云上 SaaS 生態演進潘怡飛騰訊云數據庫高級工程師騰訊云數據庫解決方案架構師,擁有多年的數據庫服務應用優化實踐經驗。近年來,騰訊云數據庫產品在 SaaS 生態方向上積極探索與布局,在數據庫服務平臺化、自動化、智能化領域推出一系列優秀的 SaaS 產品和完善解決方案,助力互聯網、金融、傳統企業等客戶架構升級,數字化轉型,構建新一代的數據庫服務平臺。ArchSummit 2022 北京站上,騰訊云數據庫高級工程師潘怡飛發表題為騰訊云數據庫云上 SaaS 生態演進的演講,介紹騰訊云 SaaS 產品矩陣、能力、生態和演進方向。騰訊云數據庫 SaaS 生態矩陣騰訊云數據庫 SaaS 生態分為基礎能力
41、 SaaS 化、易用性 SaaS 化、智能化運維和模塊化 SaaS 產品四個方向??傮w來說,SaaS 產品的生態演進是基于 PaaS 產品的基礎能力和衍生能力,提煉出更靠近業務屬性的 SaaS 能力,幫助用戶和客戶更好更方便地使用數據庫,創造業務價值。騰訊云 SaaS 生態矩陣的幾個主要產品包括:數據傳輸服務 DTS,定位為數據遷移與數據流打通的服務,包含在線遷移同步、低時延和高可靠的特性,主要包含遷移、同步和訂閱模塊。數據智能管家 DBbrain,是定位于自動化、智能化運維統一管理平臺,從前期的數據庫巡檢、故障發現、故障定位到后期的故障解決與系統優化,形成一套數據庫全生命周期管理運維的工具。
42、第 1 章:數據庫架構設計第 1 章:數據庫架構設計1415數據庫安全審計,是一個內核級別的安全審計平臺。區別于一些需要旁路管控部署的方式,它對性能和收集完整度都支持得比較好。同時它跟 DBbrain 聯動,對收集到的審批日志進行匯總、分析,這樣可以看到具體的問題根因,發展出在運維或者臺賬過程中也可以使用審計日志的能力。騰訊云圖,是一個定位于定制化大屏 BI 展示的平臺。它可以快速通過可視化的圖標管理來降低建立專業大屏的門檻。它內置預設了多個行業模版,可以用自由拖拉拽的方式快速形成一套炫酷大屏,包括前端數據展示。騰訊云數據傳輸服務 DTS數據傳輸服務 DTS 包括以下幾個部分:數據遷移功能面向
43、單次的數據庫遷移,支持多種網絡鏈路,全量增量不停機遷移,最小化遷移過程中停機對業務造成的影響,全程自動化減少風險點,支持一致性校驗能力。適用于一次性遷移數據庫遷移,數據庫遷移上云或遷移下云場景。數據同步功能指兩個數據源之間的數據長期實時同步,具有多種高級特性,庫表重映射、DML/DDL 過濾,Where 條件過濾,雙向同步,豐富沖突策略。適用于云上云下多活、異地多活、跨境數據同步、實時數據倉庫場景。數據訂閱是指實時按需獲取數據庫中關鍵業務的數據變化信息,將這些信息包裝為消息對象推送到內置 Kafka 中,方便下游實時消費。適用于異構數據更新、業務異步解耦、緩存更新,ETL 實時同步等場景。上圖
44、是數據傳輸服務整體架構,主要有兩個關鍵措施。第一個是基于原生解耦,采用統一的數據中間格式,導出和導入完全流程化。第二是插件式設計,方便數據庫快速接入,解決歷史遺留問題。數據傳輸服務應用的場景有:云數據庫遷移,提供靈活的上云下云場景支持,同時支持下云到自建 IDC,有數據正確性保證。雙向數據同步,支持異地多活、異地災備,支持雙向、環形數據同步,解決了同步數據重復寫沖突問題,具備高性能、低延遲。多到一數據同步,包括多源數據聚合、匯總分析場景。支持多到一數據匯總、同步,支持分表聚合、一拆多、跨云賬號。異構數據同步,包括同源異構同步與非同源異構同步。數據傳輸服務中,數據訂閱適用于很多沒有固定下游的場景
45、,例如數據倉庫、自定義函數、云函數等。數據訂閱的架構和同步類似,基于 Binlog 解析,將這些 Binlog 解析為正確的消息對象,基于客戶配置的不同的表或庫,再推送到內置的 Kafka 里。用戶可以很方便地通過各種 Kafka 來消費不同的異構下游,實現了異構數據庫之間或者復雜鏈路之間的同構。同時 Kafka 也支持多分區,可以設置不同的策略來優化大數據量同時傳輸效率。Binlog 本身是無法自解析的,解析出來的日志只有數值,沒有對應的表結構字段。如果按這種方式推送到下游,其實數據是不可用的。所以我們需要自研的 Schema Tracker 組件來實時、無鎖,動態獲取維護表結構的變更,這樣
46、就完成了整個鏈路的打通。另外考慮到數據訂閱也支持到了更多數據格式,降低了用戶切換和使用的成本,可以無損替換使用,方便打通不同鏈路。通過遷移、同步和訂閱三種模塊,可以充分打通數據鏈路之間的流轉管理,構建雙向、環形、異構、多合一等場景。用戶不用自己開發工具或定時任務解決這些需求,可以更專注于業務。數據庫智能管家 DBbrain數據庫中統一管理時有許多難點和痛點,DBbrain 是應用于這個場景的一款 SaaS 工具。它有三大功能:智能優化。針對獲取信息、分析信息、優化手段痛點,利用完善的云平臺監控、智能算法與案例庫沉 淀,針對問題根因形成優化手段。數據庫安全。DBbrain 內置深度學習算法、海量
47、樣本,提供的功能包括安全預警、敏感第 1 章:數據庫架構設計第 1 章:數據庫架構設計1617數據發現、數據脫敏、治理建議等。數據庫管理。DBbrain 的功能包括云上云下混合云管理、無侵入安全鏈路、掌上運維?;谏鲜鋈蠊δ?,DBbrain 適用于數據庫日常運維、安全威脅識別、混合云管理數據庫、掌上數據庫運維等場景。上圖是 DBbrain 的整體架構圖與核心優勢展示?;?DBbrain,運維人員可以針對突發復雜告警問題做快速處理或提前規避。DBbrain 的全生命周期管理分為幾個模塊,一是監控、預防、分析、告警,主要用于事前分析。包括 70 余項監控項,監控大屏、秒級定期巡檢,做到全程問題
48、把控,把問題扼殺在搖籃中。這一部分還可以進行性能預測、容量預測,避免因為存儲問題影響性能。事中模塊包括快速定位和應急管理,如果不幸發生問題可以快速止損。比如一鍵分析異常能力會給出具體分析,應急處理里的靈活限流可以避免問題擴散,一鍵 Kill 提供兜底能力。事后需要復盤,復盤后需要一鍵優化能力,DBbrain 會給出全盤報告對之前的問題復盤,包括自動調優、專家級建議,還有優化效果對比。DBbrain 的一個性能優化場景是慢 SQL 分析。它會基于多維度統計,包括用戶和賬號等信息,根據耗時區間來統一匯總 SQL 模版并自動排序,這樣我們可以靈活判斷出是哪個 SQL 導致的問題。下一步是性能優化,我
49、們可以進行編譯器、優化器的改寫,比如計算代價和成本,同時可以判斷 SQL 是不是優質,是不是需要添加索引,可以針對性做優化,也減少了 DB 和研發的各種對接。甚至一些不必要麻煩可以在 DB 層面處理掉。它甚至可以做 API 的拉取,再通過開源組件進行存儲分析。這樣可以基于 DBbrain 的 SaaS 能力構建自己的運維平臺,免費滿足收集、分析、優化需求。第二個性能優化場景是異常診斷。日常診斷是根據秒級監控,對十幾個維度的信息做匯總展示。診斷提示展示診斷事件的概要信息,包括等級、開始時間、診斷項、持續時長。DBbrain 會定期進行健康巡檢。診斷事件詳情可查看事件概要、現象描述,給出智能分析以
50、及專家建議。騰訊云 BI 云圖騰訊云圖可以零門檻構建 2D 和 3D 的前端大屏,預設有多個模版,不需要有代碼門檻,可以快速構建出一個炫酷大屏,同時投屏到不同終端,后端數據源也支持多種。構建大屏只需要四步,首先創建一個大屏項目,再基于預設的模板形成及時預覽和所見即所得的頁面。第三步是配置數據源,然后預覽發布。不管是運維大屏還是業務大屏都可以快速交付。云圖的一些案例包括現在常見的疫情監控大屏,包括 3D 版的智慧校園、騰訊內部用的騰訊云大屏,還包括智慧城市、智慧零售等等。這些案例都可以用幾步很簡單地制作出來,大幅降低研發成本。第 1 章:數據庫架構設計第 1 章:數據庫架構設計1819總結和展望
51、SaaS 產品價值總結下來就一句話:可以降低用戶在工具或者不必要的研發上的投入,更專注聚焦于自己的業務,不需要再去建數據庫和前端,直接依賴 SaaS 就好,幫助業務更好地創造自己的價值,不需要分散精力。DTS 后續會在場景化和復雜拓撲場景深耕,可以一鍵創建用戶需要的復雜拓撲鏈路,比如環形、雙向等場景,而且不需要逐條配置沖突策略。DBbrian 后續會向智能化、AI 化方向發展,可以直接基于慢 SQL 自動創建索引,甚至數據庫負載自動擴縮容,同時可以利用云原生數據庫的快速縮擴容能力,充分結合更多產品進行場景聯動,幫助客戶創造業務價值?;?LSM-Tree 的分布式組件化 KV 存儲系統唐彥騰訊
52、云數據庫專家工程師、浙江大學博士。研究領域主要關注分布式存儲、大規模數據密集型系統相關的關鍵技術,曾以第一作者身份在領域 Top 類期刊和會議上發表多篇論文。博士畢業后來到騰訊從事基礎研究與技術工程化工作,目前主要負責分布式數據庫 TDSQL 的元數據管理與集群管控調度相關工作。隨著云服務基礎架構以及微服務技術的日益成熟,很多大型系統能夠分解為根據應用 workload 需求的多個子系統,再通過網絡交互組裝在一起協同工作。Nova-LSM,一個將基于 LSM-Tree 的分布式 KV 存儲系統分解為使用 RDMA 進行通信的組件的工作。這些組件將存儲與處理分開,使處理組件能夠共享存儲帶寬和空間
53、。處理組件將文件塊(SSTable)分散到任意數量的存儲組件中,并通過一定機制平衡它們之間的負載,在運行時動態構建范圍以并行化壓縮并提高性能。Nova-LSM 具有很好的可伸縮性,在一些場景下性能優于其單機版本(LevelDB 和 RocksDB)幾個數量級。本期 DB洞見將由騰訊云數據庫專家工程師唐彥,從前沿學術的角度深度解讀 Nova-LSM,重點介紹 Nova-LSM 的特性、重要設計及帶來的啟發。以下為分享實錄:LSM-Tree 基本概念1.1 LSM-Tree 存儲系統第 1 章:數據庫架構設計第 1 章:數據庫架構設計2021LSM-Tree 全稱為“Log Structured
54、Merge-Tree”,它是一種基于磁盤存儲的數據結構。在以前,數據庫的索引基本上采用 B+樹方式來作為索引結構,在此情況下,隨機寫性能常被別人詬病。LSM-Tree 則主打將離散的隨機寫請求轉換成批量的順序寫操作。而無論是在機械硬盤還是在固態硬盤,順序的讀寫性能永遠好于隨機讀寫。因此 LSM-Tree 作為一種高效的 KV 存儲結構,被許多業界的成熟系統所應用,比如騰訊云數據庫 TDSQL 新敏態引擎也是基于 LSM-Tree 進行開發。LSM-Tree 結構有較多優點:寫性能強大、空間利用率高、較高的可調參特性、并發控制簡單、有完備的修復方案等。1.2 LSM-Tree 的歷史在數據庫更新
55、方面,LSM-Tree 與 B+樹的區別可以理解為:一個是 in-place update,一個是 out-place update?;?B+樹的更新,我們稱之為 in-place update。如下圖所示,k1 本來的值是 v1,隨后我們想在 k1 寫下新值 v4,這時需要找到 k1,再把 v4 寫進去。因此,在 B+樹的索引結構里對數據進行更新或者插入,會引起實時的 I/O。在 B+樹的場景下,寫性能會受到一定影響,但由于 B+樹可以支持較好的檢索性能,因此讀性能會較好。相比之下,在 LSM-Tree 結構里,如果這時對 k1 進行 v4 的更新,我們不會馬上把 k1 改成 v4,而是將
56、它轉化成順序寫,把它寫到內存里,追加在(k3,v3)后面,因為順序寫的性能遠比隨機寫高,但這種方式則會犧牲讀性能及導致空間放大。下圖展示的是 1996 年 LSM-Tree 最原始的結構。C0 代表的是在內存里的狀態,每當有數據寫入,它就會逐漸往下 merge。當第 i 層滿時,它會把底下的葉子精簡,從 Ci 到 Ci+1 去往下 merge。層數越大則表明數據寫入越早。每一層最初的版本的頭部是 B+樹,C0 是在內存的節點,接受最新的數據更新,C1 層到 Ck 層都存在于磁盤。第 1 章:數據庫架構設計第 1 章:數據庫架構設計22231.3 LSM-Tree 基本結構目前主流的 LSM-T
57、ree 基本架構如圖所示。我們會在內存中保留 memtable 結構,用于接受最新的數據更新操作,memtable 結構里的數據查找則通過跳表 skiplist 或者 B+樹實現。當 memtable 達到一定大小時,我們會進行 flush 操作,停止寫入,再把 memtable 刷到磁盤上,變成靜態文件即 SSTable。SSTable L0 層與 memtable 保持一致,從 L0 層到 L1 層則會進行歸并排序。排序意味著 L1 層到 Lk 層都處于有順序的狀態,因此在每一層往下沉時,內部的數據會在物理上保持有序。每個數據再往下沉時,會進一步根據不同的 key range 來轉化成一個
58、個互相不重疊的 SSTable。1.4 LSM-Tree 在 RocksDB 中的實現下圖展示的是基于 LSM-Tree 數據結構進行二次開發的 RocksDB。當遇到寫請求時,RocksDB 會先寫一個 log,即 Write-Ahead Log(WAL)日志。當 memtable 還沒有刷到磁盤上時,如果機器發生故障,Write-Ahead Log(WAL)日志則可以用于故障恢復。這是非常重要的功能。對 TDSQL 等金融應用場景數據庫而言,可靠性永遠排在第一位,寫日志必須成功,才能把最新的數據插入到內存(memtable)里。memtable 還有閾值控制機制。在實際的生產環境中,一般將
59、閾值設置為 1G,到 1G 后則會凍結成 immutable memtable。當活躍的 memtable 被凍結成 immutable memtable 后,原來的 memtable 則可以清空內存,重新接受數據的寫入。immutable memtable 則不會再接受寫入,會準備被 flush 到磁盤上。隨著越來越多的 immutable memtable 被 flush 到 L0 層,L0 層的文件個數會逐漸達到一個閾值,這時會觸發系統的 compaction。由于 L0 層的 SST 文件之間呈現的是無序的狀態,它們包含的 key 范圍有可能重疊,這時需要把 L0 層的文件從磁盤上重新
60、讀取并進行歸類排序,進而往下生成 L1 層的文件。從 L1 層到 Ln 層(生產環境中一般配置成 7 層),所有的文件的 key range 已經互不重疊,且按照 key 的順序進行排放。當我們要讀取一個比較舊的數據,如果該數據不在內存也不在 L0 層時,我們會從 L1 層到 Ln 層去讀取該 SST 文件。因為 SST 文件數量較多,在實際中我們會采用 bloom filter 來加快讀取。1.5 LSM-Tree 查詢基于 LSM-Tree 的查詢可分為點查與范圍查詢兩大類,對應的執行方式如下:第 1 章:數據庫架構設計第 1 章:數據庫架構設計2425 點查:從上往下進行查詢,先查 me
61、mtable,再到 L0 層、L1 層。因為上層的數據永遠比下層版本新,所以在第一次發生匹配后就會停止查詢。范圍查詢:每一層都會找到一個匹配數據項的范圍,再將該范圍進行多路歸并,歸并過程中同一 key 只會保留最新版本。1.6 LSM-Tree 之空間/讀/寫放大LSM-Tree 性能的衡量主要考慮三個因素:空間放大、讀放大和寫放大。一是空間放大。LSM-Tree 的所有寫操作都是順序追加寫,對數據的更新并不會立即反映到數據既有的值里,而是通過分配新的空間來存儲新的值,即 out-place update。因此冗余的數據或數據的多版本,仍會在 LSM-Tree 系統里存在一定時間。這種實際的占
62、用空間大于數據本身的現象我們稱之為空間放大。因為空間有限,為了減少空間放大,LSM-Tree 會從 L1 往 L2、L3、L4 不斷做 compaction,以此來清理過期的數據以及不同數據的舊版本,從而將空間釋放出來。二是讀放大。假設數據本身的大小為 1k,由于存儲結構的設計,它所讀到的值會觸發多次 IO 操作,一次 IO 意味著一條讀請求,這時它所讀取到的則是在后端所需要做大的磁盤讀的實際量,已經遠大于目標數據本身的大小,從而影響到了讀性能。這種現象我們稱之為讀放大。為了減輕讀放大,LSM-Tree 采用布隆過濾器來避免讀取不包括查詢鍵值的 SST 文件。三是寫放大。在每層進行 compa
63、ction 時,我們會對多個 SST 文件進行反復讀取再進行歸并排序,在刪掉數據的舊版本后,再寫入新的 SST 文件。從效果上看,每條 key 在存儲系統里可能會被多次寫入,相當于一條 key 在每層都會寫入一次,由此帶來的 IO 性能損失即寫放大。LSM-Tree 最初的理念是用空間放大和讀放大來換取寫放大的降低,從而實現較好的寫性能,但也需要做好三者的平衡。以下是兩種假設的極端情況。第一種極端情況是:如果完全不做 compaction,LSM-Tree 基本等同于 log 文件,當 memtable 不斷刷下來時,由于不做 compaction,只做 L0 層的文件,這時如果要讀一條 ke
64、y,讀性能會非常差。因為如果在 memtable 里找不到該條 key,就要去掃描所有的 SST 文件,但與此同時寫放大現象也將不存在。第二種極端情況是:如果 compaction 操作做到極致,實現所有數據全局有序,此時讀性能最優。因為只需要讀一個文件且該文件處于有序狀態,在讀取時可以很快找到對應的 key。但要達到這種效果,需要做非常多的 compaction 操作,要不斷地把需要刪的 SST 文件讀取合并再來寫入,這會導致非常嚴重的寫放大。第 1 章:數據庫架構設計第 1 章:數據庫架構設計2627Nova-LSM 的簡介與特性2.1 Nova-LSM 架構設計一覽Nova-LSM 是基
65、于 LSM-Tree 結構的架構體系,其主要組件包括三部分:第一部分是寫日志的組件,將 WAL 寫成功后再往 LSM-Tree 的 memtable 中查詢新的數據。第二部分是本身處理 LSM-Tree 寫入的線程,其縮寫為 LTC(LSM-Tree Component),代表著將該線程單獨組件化。第三部分則是底層的存儲,負責把接收到的上層 LTC 組件下發下來,并提供標準的文件接口。Nova-LSM 是一個存算分離的架構。上面處理 LSM-Tree 的是計算節點,它們要寫磁盤時,需要用 flush 操作將 memtable 寫到磁盤,compaction 時要先從存儲節點讀取上來,接著進行歸
66、并排序并再次寫回,再寫下去時則由底下的分布式存儲節點來進行。它的架構借用了當下較好的數據庫產品理念。在計算節點和存儲里,存儲節點會按照彼此的功能去劃分獨立的線程池,每個線程池的線程數可以配置。這相當于在計算節點里將線程的功能分為四種:第一種線程與 client 相關,負責收發客戶請求;第二種線程負責 RDMA、網絡 IO 的相關操作;第三種線程與 compaction 相關,會不斷掃描當前的 SST 是否符合 compaction 及推動 compaction 的進行;第四種線程與 Drange 相關,負責不斷整理當前 Drange 的重排、重組織。該工作的主要亮點之一,是在于把原本 LSM-
67、Tree 這樣的單機系統明確地劃分出計算層、存儲層,通過一定方式解決了在計算層本來會發生的停寫、緩寫現象。2.2 Nova-LSM 所解決的核心問題Nova-LSM 所解決的核心問題主要有兩個。第一個核心問題是:基于 LSM-Tree 結構的存儲系統,例如 LevelDB、RocksDB 等,都會不可避免地遇到緩寫或者停寫的問題。比如內存里的 memtable,在配置時最多可以寫 8 個,因為寫入多,需要全部 flush 到磁盤上。與此同時,當前 L0 層的 SST 文件非常多,L0 層即將開始做 compaction。但 compaction 會涉及到磁盤 IO,在還沒做完時,就會阻塞內存中
68、的 memtable 對 L0 層 SST 進行 flush 的過程。當 flush 無法進行時,就會發生緩寫,隨著閾值的推進,在實在寫不進時甚至會停寫,這種現象體現在客戶端就是請求掉零。為了解決 LSM-Tree 結構存儲系統中的緩寫、停寫問題,該文章提出了兩個解決辦法:第一種方法是設計了存算分離的架構體系,具體如上圖所示。該架構的重要作用之一,是把處理寫入和處理磁盤 IO 的兩大主力模塊拆分,計算存儲分離,哪個部分慢就為哪個部分增加節點以此來提高該部分的能力,這是比較亮眼的突破。第二種方法是引入了動態分區,即 Drange 機制。該機制的目的是為了讓業務的寫入壓力,在 LTC 即計算層的
69、memtable 上進行區間劃分,每個 range 都有自己的 memtable,通過區間劃分,從而實現多個 range 之間進行并行 compaction。以 L0 層為例,我們可以把 L0 層變成沒有互相重疊的狀態,這時我們就可以對 L0 層進行并行的 compaction,可以加快 L0 層的文件的消化,從而減輕對 memtable flush 到磁盤上的過程的影響。第 1 章:數據庫架構設計第 1 章:數據庫架構設計2829第二個核心問題是:在這種方式下需要劃分很多不同的 Drange,每個 range 都會增加一定的 memtable 數量,memtable 數量的增加會影響 sca
70、n 和 get 的性能。假設有一個主請求,在原來所有數據都寫在一個 memtable 里的情況下,在讀取時,索引只需要面向這個 memtable,再根據跳表進行 get,如果 get 到則可以馬上返回?,F在劃分成不同的 Drange,memtable 數量增加,因此需要查找的 memtable 以及 L0 層的 SST 也會變多。解決辦法是:實現了一個索引,可以查詢到一個 key 在 memtable 或 L0 SST 中的最新值(若存在)。2.3 Nova-LSM 論文主要成果這篇文章將原本獨立的單節點存儲系統做成了一個存算分離、可以任意擴展的分布式架構。這種存算分離的架構體系,在擴展時對總
71、吞吐量、總的響應和請求的能力有顯著提升。下圖是對該效果的具體展示。左下角采用了最原始的配置,只有 1 個存儲節點和 1 個計算節點,計算節點只配置了 32M 的內存,這也意味著 memtable 相對較少,在這種情況下它的總吞吐量只有 9k,相對較低。然后我們從縱軸來看,把計算能力向上擴展,通過垂直擴容把內存從 32M 變成 4G,這時總吞吐量已經從 9k 提高到 50k。但從圖中也可以看到,這時性能曲線中間有空隙的地方越來越多,這些就是前面所提到的請求掉零。計算能力的加強意味著可以進行更多的寫入,內存變大意味著 memtable 的數量變多,L0 層的 SST 文件生成速度也會加快。當 L0
72、 層的生成文件速度加快后,就會對存儲層 compaction 的能力造成壓力,因為它在默認配置下只有 1 個節點。這時雖然它的峰值已經提高到了 5k,但請求掉零的情況也更多了,即發生了停寫。因為 L0 SST 已經來不及 compaction,這時只能等待,相當于計算節點在等存儲節點。為了解決這個問題,我們需要對存儲節點進行擴容,比如將 1 個存儲節點擴到 10 個。這時可以明顯看到總吞吐量從 5 萬提高到了約 250 萬。雖然某些地方請求也會驟降,穩定性還有待提高,但從整體上看,幾乎已經沒有請求掉零現象出現了。這也體現了傳統單機單節點 LSM-Tree 存儲系統與 Nova-LSM 之間的區
73、別。在傳統單機單節點 LSM-Tree 存儲系統中,如果計算能力非常好但是磁盤能力不夠,這時很難在單節點上進行擴展。但在 Nova-LSM 中,如果發現哪部分能力不夠就可以進行擴展,計算能力不夠就擴計算節點,存儲能力不夠則擴存儲節點。這也遵循了當前分布式數據庫里比較常見的存算分離、計算層和存儲層可以獨立擴容的理念。Nova-LSM 若干重要設計3.1 LTC 和 StoCs 之間的寫數據流程第一個比較重要的設計是 LTC 和 StoCs 之間的寫數據流程。該流程展示的是:當在客戶端發起寫請求時,計算節點和存儲節點是以怎樣的方式將數據寫進去的過程。首先是計算節點的客戶端發起一個新的寫請求操作。存
74、儲節點在接收到該請求后,基于 RDMA 交互,它會在 buffer 區域分配一個內存區域,并且為這塊內存和偏移量(當前哪塊內存可以寫)分配一個 id,告知 StoC??蛻舳私拥巾憫缶蜁_始寫數據,完成后會通知存儲節點。存儲節點接收到信號后,將數據持久化并且再告知客戶端。上述流程是寫一個數據文件即 SSTable。寫完后,我們要以同樣的流程將元數據文件更新。因為底層是分布式架構,需要知道哪些文件寫在哪里以及每個 SST 的范圍、版本號。第 1 章:數據庫架構設計第 1 章:數據庫架構設計30313.2 動態區間劃分第二個比較重要的設計是動態區間劃分。假設業務的請求范圍為 0-1 萬,當前有 1
75、0 個計算節點,將這 10 個計算節點的區間劃分為 10 等份,比如第一個 key 的空間范圍為 0-1000。在負責 0-1000 的計算節點里,它會再進行劃分,這一層劃分業務無感知。這就叫動態區間劃分,簡稱 Drange。其作用主要有以下幾點:首先,每個 range 都是一棵 LSM-Tree,按照數據區間,不同的 Drange 都有自己的 memtables。比如 0-1000 區間又可以劃分為 10 個 Drange,10 個 Drange 之間的 memtable 相互獨立。這樣做的好處是這些 Drange 之間的 key 互不重疊,例如 0-100、100-200、200-300。
76、其次,在 Dranges 下還有一層 Tranges。如果發現 Drange 里的部分 range 比如 890-895 存在熱點現象,而旁邊的 range 并非熱點,則可以用 Tranges 進行細粒度的復雜重均衡,實現動態均衡負載。最后,在此基礎上,因為 Drange 的 key 范圍互不相交,當 memtable 變成 immutable,不可再寫后,它們需要獨立地 flush 到磁盤上。這時,在 L0 層的 SSTable 來自不同的 Drange,它們之間的 key 完全不相交,我們就可以進行并行的 compaction。3.3 Compactions文章還將沒有 Drange 劃分
77、和有 Drange 劃分兩種情況進行了對比。在沒有 Drange 劃分的情況下,L0 的 compaction 無法很好并行。在這種情況下,如果遇到最壞的情況,L0 層的某一個 SST 有可能覆蓋了整個 key 空間,假設 key 范圍為 0-600,L0 層的 SST 文件的范圍是 0-1000,當發生 compaction 時,它必須要跟其他 4 個 SST 做歸并,這時不但要把 L0 層的其他 SST 全部讀取比較一遍,還要把 L1 層所有的 SST 都讀一遍再做歸并排序。這時寫放大會較為嚴重,意味著 L0 層到 L1 層的 compaction 會變慢,flush 也會變慢,甚至 fl
78、ush 不了時,前端就會出現緩寫、停寫現象。有 Drange 劃分后,相當于 compaction 可以分開區間,如下方的示意圖所示。在 0-100 區間,L0 到 L1 可以獨立去 compaction,100-200 區間也可以獨立去 compaction,可以較好地實現并行 compaction。而在原生的 RocksDB 里,只有從 L1 開始 compaction,才能進行并行 compaction 操作。如果協調者發現當前存儲層的節點資源非常充足,compaction 操作可以由存儲層主動發起,不需要計算層去發現當前有哪些可以做 compaction,這是這篇文章中提到的另一個想法
79、。至于考慮下沉的原因,因為文章并未深入展開,個人猜測主要是考慮到在這種架構體系里,存儲層比較容易擴展,而計算層較難擴展。因為計算層相當于分庫分表,如果擴展則會涉及到一定的路由重分布,需要告訴前端請求路由的變化。但存儲層則非常容易擴展,如果能將這些非常耗時的操作放到存儲層,可以極大地減少在計算節點跟存儲節點之間數據的開銷。存儲層做完后,可以直接把更新后的元數據告訴計算層。3.4 索引查找以及 Scan 操作因為劃分了很多不同的動態區間,memtable 的數量也會增加,意味著查詢操作的耗時也會增加。所以要如何在原來的基礎上維護好讀性能?這篇文章提出了以下解決思路:每個 LTC 維護了一個 loo
80、kup index。如果這些數據存在于 memtable 和 L0 層的 SST 上,通第 1 章:數據庫架構設計第 1 章:數據庫架構設計3233過 lookup index 我們就可以快速查找到想要的數據。當某一個 L0 層 SST 被 compaction 到 L1 層時,索引上就會移除掉對應的 key。LTC 同時還維護了一個范圍索引即 range index。因為知道每個 Drange 的范圍,所以當一個 scan 請求所涉及到的 key,都可以在 memtable 和 L0 層 SST 中找到時,該范圍索引就能快速響應 scan 操作。3.5 SSTable 的分布最后一個比較重要
81、的設計涉及到存儲層。當某個 SST 文件要寫到存儲節點時,分布式系統首先要保證負載均衡,要保證數據避免單點故障不可恢復的場景。該文章提出根據一定策略,將數據文件即 SST 打散寫入到多個存儲節點里??紤]到存儲成本,每個 SSTable 采用糾刪碼(Erasure Coding)的方式進行編碼然后分布式存放。默認情況下對每個 SSTable 采用“3+1”的 EC 配置,將一個 SSTable 切分為 3 個數據塊,根據一定算法,在這 3 個數據塊里去計算出一個校驗塊,變成了“3+1”的形式。這種方式比傳統的多副本可以節省更多空間。假設一個 SSTable 是 3M,這種“3+1”的方式最終所占
82、空間為 4M,并且能容忍一個節點的丟失,與占用 6M 空間的雙副本方案擁有同樣的故障容忍等級。而元數據文件因為體積比較小,所以直接采用多副本存儲的方式,比如 1 個元數據文件可以寫 3 個副本。Nova-LSM 性能效果展示 在本篇論文中,Nova-LSM 具有較好的性能數據表現。以自身調參測試為例,數據表明,Nova-LSM 可以通過調整不同的參數達到較好的擴展效果。文中分別使用 Uniform 均勻分布和 Zipf 分布來打散數據,存在熱點(比如 80%的訪問概率都集中在 20%的數據上)的情況下,實驗結果數據表明,在讀寫比例、數據訪問概率不一樣的各種場景下,Nova-LSM 都能取得較好
83、的性能結果。下圖所示為 Nova-LSM 在自身調參下幾組不同參數的比較:第 1 章:數據庫架構設計第 1 章:數據庫架構設計3435下圖展示了 Nova-LSM 自身擴展性的效果:下圖所示為 Nova-LSM 吞吐量擴展性測試:以上測試是 Nova-LSM 自身不同參數下的對比。除此之外,該文章還將 Nova-LSM 與 LevelDB 以及 RocksDB 進行性能對比。在小數據場景下,Nova-LSM 的性能表現比 RocksDB 要更優異,特別在 Zipf 分布且熱點數據存在、讀寫各占一半的情況下,測試出來的性能數據要比 RocksDB 高 4 倍。但隨著數據量的擴大,在某些 work
84、load 下 Nova-LSM 的優勢逐漸變得不明顯。比如在上圖中的(d)情況,一個 10 節點、2T 的數據庫,RocksDB 將其分為 10 份,在這種寫較多的場景下,Nova-LSM 與原生的 RocksDB 差距不明顯。另外,上圖中的藍色數據項 RocksDB-tuned,它是 RocksDB 進行調優后產生的數據項,紅色數據項則沒有經過 RocksDB 調優,而紅色項卻取得了比藍色項更好的性能數據。經過較多場景的驗證,像 Nova-LSM 這種基于 LSM-Tree 結構的存儲系統,實際上并不存在某一組參數能夠讓它在所有不同性質的 workload 下都取得較好性能。如上圖(d)組,
85、即中間 100%寫、均勻分布的測試組,RocksDB 經過調優后比沒經過調優、用原始參數的對照組的吞吐量更低。因為 Nova-LSM 本身需要有非常多的調優參數,因此很難存在一套參數在所有的場景里都為最優。Nova-LSM 帶來的啟發和討論一般情況下,基于 LSM-Tree 結構去進行優化的工作都面臨以下問題讀放大、寫放大及空間放大。如果完全不做 compaction,LSM-Tree 將會退化為 Log 文件,此時讀性能最差,需要掃描所有 SSTable 文件,但不存在寫放大。如果通過 compaction 操作將所有 SSTable 文件維持為一個 sorted run,即始終保持所有 k
86、v 數據的全局有序,則退化為 sorted array,此時讀性能最優,查第 1 章:數據庫架構設計第 1 章:數據庫架構設計3637詢時只需讀取一個 SSTable 中的一個數據塊,但頻繁的 compaction 操作會導致嚴重的寫放大。所以我們不能走極端,需要在兩者之間提出新的改進方法,在讀放大、寫放大及空間放大之間做好平衡。Nova-LSM 就是在這三個因素之間做取舍,它的設計原理之一是將原本單機單節點的系統用分布式組件化的方式,將原本一份代碼里面的不同模塊拆分出來,從而令每一個模塊具有可擴展性,打破原先單機資源的限制。此外,該文章還創新性地提出,將不定期的 compaction 對磁盤
87、 IO 造成的短期沖擊剝離出去。與此同時,該篇文章在實驗驗證及工程實踐上仍有許多地方需要完善和優化。第一,實驗使用的每個 KV 的默認大小是 1KB。根據原理判斷,Drange 這種設計在該場景下比較占優勢。在具體實現中,當一個 memtable 包含的 unique key 小于一定閾值時,不同的 Drange 之間會將 memtable 進行合并,其目的都是為了減少磁盤的寫入。因此使用 1KB 這種不算小的測試數據,對它而言是占據優勢的。而其他原生的 RocksDB 則需要不斷地寫磁盤,由于每一條 key 的體積都不小,1000 條可達到 1 兆,100 萬條就能達到 1G,這時 Dran
88、ge 機制所帶來的減少磁盤寫入的優勢就會被放大了。第二,Drange 和 Tranges 機制的設立,可以保證一個計算節點在不同的 memtable 寫入之間存在動態均衡。從工業界落地的角度出發,這會涉及到較多的數據一致性保證。但文章并沒有進一步論述數據的移動是否會造成沒寫或者雙寫。第三,工程實踐上仍存在不少流程細節有待深入推敲,比如在 LTC 和 StoC 的寫入流程交互中,文中提到先更新數據文件 block 再更新元數據文件 block 的流程,但如果在這兩次寫入中間發生了故障,如何處理?StoC 使用 erasure code 方式保證數據可靠性時,如何保證 n+1 個數據塊和校驗塊寫入
89、同時成功?故障時如何回滾?單個數據塊發生丟失時如何發現以及重新生成?這些問題都值得我們進行推敲。第四,N 個 LTC 會負責 N 個區域數據的寫入。比較傳統的基于中間件的分布式數據庫,會存在一個中間件,中間件知道其下的存儲節點以及負責寫入的節點分別負責哪一部分數據,此時路由變更的靈活性會存在一定限制。第五,所有的性能測試中基本只描述性能的最大值。最大值、最大吞吐量這些指標很重要,它代表著一個系統的能力上限。但在真實的業務場景中,除了能力的最大值,另一個非常重要的考察指標是穩定性。而 Nova-LSM 基于分布式架構,它所有的讀寫數據一直在進行網絡交互。compaction 本身因為磁盤的 IO
90、,給總體性能帶來了不穩定性,現在又加入了網絡之間的開銷,網絡抖動就更加頻繁,性能的抖動也是我們必須考慮的因素。Nova-LSM 并非只有理論,它在 LevelDB 的源碼基礎上新增了 2 萬多行代碼,實現了一套核心的設計,上圖所示為其源碼地址,感興趣的同學可以嘗試進行二次開發。第 1 章:數據庫架構設計第 1 章:數據庫架構設計3839探索分布式數據庫的多級一致性及構建技術演進之路盧衛中國人民大學教授,博士生導師,中國計算機學會數據庫專委委員。主要研究方向為大數據管理與分析,近五年來在 VLDB、ICDE、ATC、VLDB Journal、TKDE 等 CCF A 類會議或期刊上發表論文 10
91、 余篇,主持國家重點研發計劃課題、國家自然科學基金面上項目等。主講數據庫系統概論、數據庫系統實現等數據庫相關課程,曾獲首批國家級線上線下混合一流課程、北京市教學成果一等獎、中國計算機學會優秀博士學位論文獎,入選微軟亞洲研究院青年教師鑄星計劃。近年來,憑借高可擴展、高可用等技術特性,分布式數據庫正在成為金融行業數字化轉型的重要支撐。分布式數據庫如何在不同的金融級應用場景下,在確保數據一致性的前提下,同時保障系統的高性能和高可擴展性,是分布式數據庫的一個核心技術挑戰。針對以上分布式一致性的困境,中國人民大學-騰訊協同創新實驗室研究提出“多級一致性”的事務處理理念。該技術包含嚴格可串行化、順序可串行
92、化、可串行化三大隔離級別,可針對不同應用場景要求,極大地平衡性能與一致性要求,滿足金融及各類企業場景的分布式事務處理需求。該項技術已應用于騰訊分布式數據庫 TDSQL 產品中,確保 TDSQL 按需提供數據一致性,并確保數據無異常。TDSQL 是當前國內率先進入國有大型銀行核心系統正式投產的國產分布式數據庫,該項技術是其中的關鍵支撐。這次,中國人民大學教授、博士生導師盧衛老師為大家全面解鎖分布式數據庫的多級一致性及構建技術!背景從本質上看,數據庫是長期存儲在計算機內、有組織的、可共享的數據集合。當多個用戶并發操作數據庫時,事務調度的可串行化是并發控制的正確性理論。但該觀點在當前卻受到了挑戰。D
93、aniel J.Abadi 在 2019 年發布的一篇博客中提到,以往學界普遍認為可串行化是數據庫隔離級別的黃金標準,但經過研究,他發現實際上嚴格可串行化才是黃金標準。即在該理論中,可串行化仍存在一定的問題,只有嚴格可串行化才能做到沒有問題。在過去,為什么可串行化不存在問題?原因有兩方面:一是對集中式數據庫而言,可串行化其實就是嚴格可串行化,兩者之間并沒有區別。二是對于分布數據庫而言,如果數據庫里有唯一的事務調度器或協調器,這兩者之間也可等價。當來到去中心化的分布式數據庫時代,我們希望分布式數據庫產品可在全球部署。全球部署意味著范圍更大,如果仍然依賴集中式調度,性能和可擴展性都無法滿足應用的需
94、求,因此需要在系統當中安排多個事務協調者進行協調。第 1 章:數據庫架構設計第 1 章:數據庫架構設計4041回顧發展歷程,20 年前的數據庫的標注配置為業務系統+主庫+備庫。業務系統訪問主庫,主庫通過同步協議使數據在主庫和備庫之間保持一致性。在這一階段,集中式的 IBM 小型機、Oracle 數據庫、EMC 存儲(IOE)在處理小規模的數據場景時較為合適。但這種架構模式的問題在于,當數據量比較大或者業務場景比較密集時,集中式主庫就會成為整個系統的負擔。到了第二階段,典型的做法是分庫分表,將業務按照主庫進行拆分,因為業務系統建立在主庫之上,因此實現了業務的隔離,TDSQL 的早期版本也采用了這
95、種做法。這種做法的前提假設是數據/業務能夠很好地進行切分,從而解決前一階段業務不可擴展的問題。但當業務系統進行跨庫訪問時,就會帶來新的問題。為了解決上述問題,我們來到了第三階段,即去中心化的分布式數據庫階段。在該階段,數據庫中設置了更多的事務調度器,由調度器來對每個節點數據上的子事務進行事務提交,每個事務調度器都可以獨立地去處理事務。但這也會產生新的問題,即不同協調者之間如何協調。問題與挑戰我們以下圖中的例子來說明分布式數據庫中不同協調者之間如何協調的問題。假設有一個家庭賬戶,丈夫和妻子共用,都可以進行讀和寫。丈夫在 ATM 機上存了 100 塊,存完后通知妻子,但妻子有可能看不到丈夫存的這筆
96、錢。因為這是一個多協調器的架構,設備 1 交由協調者 1 來進行協調,妻子發起的這個事務可能由另外一個協調者去發起,這就會出現協調者之間 AMG 時鐘不統一的問題。第 1 章:數據庫架構設計第 1 章:數據庫架構設計4243該事務發起的時間雖然在 2:01 PM 后,但因為協調的時間偏慢,所以此時 1:59 PM 的這個時鐘去讀 2:01 PM 的時間戳提交的這個數據,就會出現讀不到的情況。形象地說,即有兩個協調器,其中一個協調器執行了事務 T0、T1,T1 事務已經提交成功。這時協調者 2 發起了事務 T2,當 T2 查詢余額時,我們發現時鐘比 T1 提交的時鐘來得小,所以讀不到 T1。但實
97、際上,是先執行 T0 再執行 T2 再執行 T1,屬于可串行化。但這又會跟前述提到的執行相違背,因為既然 T1 已經提交,T2 理應可以讀到,但結果沒有讀到。因此 Daniel J.Abadi 的可串行化存在一定的問題,讀不到最新數據。這個問題的本質是保序,而嚴格可串化的本質是線性一致性加上可串行化。從事務角度來看,根據線性一致性要求,如果 T0 事務已經結束,T1 才開始,則 T1 要讀到 T0 的寫;同理,T1 已經完成了 T2 才開始,T2 要讀到 T1 的寫。雖然這里的 T0、T2、T1 是可串行化,但違背線性一致性的要求,只有 T0 T1 T2 時才是正確的,這就是保序。因此,這里的
98、實時序就是 T0 結束后開始 T1 事務,T0 排在 T1 的前面;T0 完成后 T2 才開始,T0 排在 T2 的前面;T1 結束后 T2 才開始,T1 排在 T2 的前面。因此核心理念就是保序,即在原來可串行化全序的基礎上,對可串行化的序做約束,這個約束是線性一致性所造成的。嚴格串行化雖然能保證數據的準確性,但也帶來了較多的問題。以 Google Spanner 為例,Google Spanner 支持嚴格可串行化,但是嚴格可串行化要求有一個原子鐘,或者有一個中心授時器(本質上是因為協調器和協調器之間缺少一個協調),因而導致性能較低,難以被廣泛應用于實際業務場景中。第 1 章:數據庫架構設
99、計第 1 章:數據庫架構設計4445多級可串行化建?;谏鲜銮闆r,我們希望可以找到一個中間環節,在一致性上比可串行化級別高、比嚴格串行化級別低;在性能上接近可串行化、優于嚴格可串行化。針對這個需求,我們提出了多級可串行化建模,本質是在可串行化的基礎上加了序。線性一致性是并發系統中一致性最強的,比它弱一點的有順序一致性、因果一致性、寫讀一致性、最終一致性等。我們嘗試將可串行化與它們進行結合,最終發現只有可串行化和線性一致性以及可串行化和順序一致性可以實現結合。因為可串行化要求全序,但因果一致性不要求全序,因此無法結合。我們的做法是將可串行化+線性一致性,從而得到嚴格可串行化;可串行化+順序一致性
100、,從而得到順序可串行化。所以我們提出了嚴格可串行化、順序可串行化、可串行化這三個隔離級別。多級可串行化實現的核心理念就是保序。我們定義了五個序:實時序,即原來的線性一致性要求。程序序,比如代碼中的 session order,session 連接后,事務之間就變成了 T0,T0 提交后才能提交 T1,這就是程序序。寫讀序,即如果 T2 讀取了 T1 的寫,T1 必定排在 T2 之前。因果序,指寫讀序和程序序之間形成的閉包。寫合法,假設有一個 x 數據項,T1 寫了數據項 x1,T3 寫了數據項 x2,但如果 T2 讀了一個 x,就必須要求 T2 要緊跟 T1,因為它不能緊跟在 T3 后;如果它
101、排在 T3 后,則它讀的應該是 x2,因此這時 T1 和 T3 形成了一個序,要求 T2 要排在 T3 前。第 1 章:數據庫架構設計第 1 章:數據庫架構設計4647有了序后,我們重新定義了事務的可串行化理論,即可串行化等于寫讀序的傳遞閉包+寫合法;可串行化+順序一致性,即寫讀序+程序序的傳遞閉包,再加寫合法;嚴格可串行化就是寫讀序+實時序的傳遞閉包,加寫合法。因此可以理解為所有的一致性模型就是保序。該理論成果已經應用于騰訊分布式數據庫 TDSQL 產品當中,使得 TDSQL 成為全球范圍內首個能夠具備嚴格可串行化、順序可串行化、可串行化三大隔離級別的國產分布式數據庫,還可針對不同應用場景要
102、求,極大地平衡性能與一致性要求,滿足金融及各類企業場景的分布式事務處理需求。并發控制算法并發控制算法-雙向動態時間戳調整的實現如下圖所示。圖中有兩個協調者 P1 和 P2,協調者 P1 有兩個事務 T1 和 T3,協調者 P2 有 T2 和 T4。我們先定義順序,T1 和 T3 之間有一個 session order 或 program order,T2 和 T4 也存在一個 program order,我們將它 preserve 出來。其中還存在寫讀序,像 T1 和 T4 之間,T1 寫了 x1,T4 讀了 x1,此時就存在一個寫讀序,所以要把 T1 和 T4 的 order preserv
103、e 出來。同時還存在寫合法,因為 T3 讀了 y 數據項,然后 T2 寫了 y 數據項,但是基于可串行化理論,R3 讀取的是 y0,沒有讀取到 y2,如果讀到 y2,這時 T3 就必須排在 T2 后。因為此時讀不到 y2,要排在 T2 前面,因此 T3 和 T2 之間存在寫合法。在整個執行過程中,我們要保證必須存在保序。主要思想是每次事務提交時,都需要判斷能否違背事務的先后順序。比如 T1 開始提交,因為 T1 只包含自身,我們將它放到隊列中時不需要回滾。T2 提交時,T2 和 T1 之間沒有序,但 T1 和 T3 之間以及 T2 和 T3 之間都分別存在一個序,因為此時存在 y 數據項,所以
104、只有 T1、T3、T2 這樣的序才能保證可串行化,否則 T 必須進行回滾。最后為保證協調器間能進行協調,我們還需要引入混合邏輯時鐘,來保證因果序。第 1 章:數據庫架構設計第 1 章:數據庫架構設計4849實驗評價我們以下圖中的實驗為例,來說明可串行化、嚴格可串行化和順序可串行化之間的關系。如果在局域網情況下,在正確性方面,嚴格可串行化跟可串行化性能基本一致。但在廣域網情況下,嚴格可串行化、順序可串行化和可串行化之間的性能差異較大,所以導致在廣域網上很難實現嚴格可串行化。如圖所示,BDTA 算法比現有的算法如 MAT、SUNDIAL 等快了將近 1.8 倍,主要原因是 BDTA 中保序,但如果
105、用前面的方法實現并發控制,就會造成大量事務的回滾??偨Y與討論本文提出了提出了面向分布式數據庫的多級可串行化模型,將并發系統中的一致性要求結合到可串行化中,實現了多級可串行化原型系統,保證了去中心化的事務處理機制,并設計了雙向動態時間戳調整算法(BDTA),可以在統一系統架構下支持多個可串行化級別。該技術已應用于騰訊云數據庫 TDSQL 中,確保 TDSQL 無任何數據異常,且具備高性能的可擴展性,解決了分布式數據庫在金融級場景應用的最核心技術挑戰,使得國產分布式數據庫實現在金融核心系統場景的可用?;诖?,TDSQL 是當前國內唯一進入國有大型銀行核心系統正式投產的國產分布式數據庫。第 1 章:
106、數據庫架構設計第 1 章:數據庫架構設計5051敏態擴展,靈活應變!TDSQL 新引擎 TDStore 技術探索韓碩騰訊云數據庫高級工程師,北京大學博士研究方向包括分布式數據庫、圖數據庫、存儲引擎優化等,在 SIGMOD 等數據庫領域頂級會議上發表多篇論文。2019 年博士畢業后加入騰訊計費,主要負責 TDSQL 存儲相關的研發。隨著產業互聯網發展,傳統產業中業務爆發式增長與無限增長趨勢愈加明顯與普及。業務敏態發展對底層基礎技術提出了具備敏態能力的要求。TDSQL 是騰訊云企業級分布式數據庫,具有完全兼容 MySQL、分布式事務全局一致性、彈性擴縮容、智能調度管控、在線表結構變更等關鍵特性。金
107、融級分布式數據庫 TDSQL 新引擎 TDStore 針對產業技術趨勢需求,聚焦適配金融級敏態業務,在頻繁進行模式變更、數據流量陡增等敏態場景下,實現彈性伸縮變更、對業務透明無感知。今天,騰訊云數據庫高級工程師韓碩帶領大家了解金融級分布式數據庫 TDSQL 新引擎 TDStore 的特性與設計理念,揭秘金融級分布式數據庫 TDStore 引擎彈性伸縮特性在存儲層的實現原理。背景TDSQL 誕生自騰訊內部百億級賬戶規模的金融級場景,目前已在眾多金融、政務、電商、社交等客戶應用案例中奠定了金融級高可用、強一致、高性能的產品特性和口碑,國內前 20 的銀行近半都在使用 TDSQL,有力推動了國產數據
108、庫技術創新與發展。隨著業務場景的不斷增長和復雜化,業務形態、業務量的不可預知性增大,業務的敏態發展對數據庫底層技術提出了具備敏態變更能力的要求。目前已投產的 TDSQL 在應對業務的敏態變更時,有三個痛點需要改善:兼容性:在建表的過程中需要手工去執行 Shard key。運維:隨著業務的不斷增長,規模越來越大,面對擴容場景,目前還需要 DBA 來進行發起,導致部分輸入會被阻斷,使得升級過程不夠平滑。模式變更:目前在線表模式變更更多依賴于 PT 等外圍工具。因此,針對以上業務痛點,我們在 2021 年發布了 TDSQL 新引擎 TDStore,并于 2022 年 8 月開始投入公有云商業化運營。
109、TDStore 引擎針對不斷變化的敏態業務,實現 PB 級存儲的 Online DDL,可以大幅提升表結構變更過程中的數據庫吞吐量,有效應對業務變化;其獨有的數據形態自動感知特性,使數據能根據業務負載情況實現自動遷移,打散熱點,降低分布式事務比例,獲得極致的擴展性和性能。第 1 章:數據庫架構設計第 1 章:數據庫架構設計5253綜上所述,TDSQL 新引擎 TDStore 的愿景,是希望可以讓業務能夠像使用單機數據庫一樣去使用分布式數據庫。TDSQL 新引擎 TDStore 技術亮點 TDSQL 新引擎 TDStore 具備以下功能特性:一是 MySQL 兼容、分布式、低成本:兼容原生 My
110、SQL 語法,具有事務的全局一致性,無感知擴縮容。純分布式引擎,數據以 KeyRange 來做組織和路由,業務層不再需要做分庫分表。存儲引擎模塊采用 LSM-tree 結構,具備良好壓縮比,適合大規模數據量的業務。二是高可擴展、計算/存儲資源彈性擴縮容:計算層為多主模式,每個 SQLEngine 都是完全對等的節點,可讀可寫;無狀態化設計,可根據業務流量靈活增加或減少計算層節點,從而適應業務的峰值或低谷。存儲層可根據存儲量需求,做彈性、對業務無感知的擴縮容,通過數據的添加或者遷移自動打散數據分布熱點,進一步降低分布式事務比例,實現線性擴展比。三是全局讀一致性:通過管控模塊 TDMetaClus
111、ter 給計算節點、存儲節點分配全局統一的事務時間戳。存儲層基于 MVCC 和事務時間戳來做全局一致的可見性判定。四是 Online DDL:支持在線表模式變更的常見操作。支持在線加減索引。支持大部分 DDL 操作以 Online 方式執行TDSQL 新引擎 TDStore 架構設計TDSQL 新引擎 TDStore 架構分為三個核心模塊,分別為計算模塊 SQLEngine、存儲模塊 TDStore、管控模塊 TDMetaCluster。3.1 計算模塊 SQLEngine 第 1 章:數據庫架構設計第 1 章:數據庫架構設計5455 每個 SQLEngine 節點完全對等,都可讀可寫,且為無
112、狀態節點,SQLEngine 之間會通過一定的方式來同步在線表模式變更的信息。TDSQL 完全兼容 MySQL8.0。我們通過移除各自有狀態化的數據信息,從而實現無狀態化設計,并將多線程框架替換為協程框架,實現更好的并發性能。3.2 存儲模塊 TDStore存儲模塊是 share nothing 架構,底層實現則是基于 LSM-tree 結構,這意味著每個數據分片的基本數據單元都有自己獨一無二的 owner。我們以 Key Region 作為劃分標準,將數據劃分成一個個分片Region。同時為了滿足災備的需求,Region 的多個副本通過分布式一致性協議Raft 來提供一主多備方案。存儲模塊的
113、每個節點都會分布多個 Region,Region 有多個副本,在不同的 TDSQL 節點之間會隨著數據量的變化進行性能、數據量的自動均衡。在自動均衡的過程中,需要上層 TDMetaCluster 的管控模塊來配合完成。TDMetaCluster 模塊通過下發基本的基于 Region 的任務,比如分列、合并、遷移、切主等,來實現彈性擴縮容。3.3 管控模塊 TDMetaClusterTDSQL 新引擎 TDStore 的管控模塊主要負責三方面工作:高效地生成和下發全局唯一的事務時間戳;管理各模塊的元數據以及 Region 的路由信息;以 Region 為基本單位進行負載均衡和存儲均衡調度。調度需
114、要考慮 Region 的熱點,盡量將熱點 Region 打散在不同存儲節點上,還需要兼顧對性能的影響,通過對 Region 的合理劃分和調度,盡可能將跨 Region 的 2PC 事務轉化為 1PC,降低事務延遲。第 1 章:數據庫架構設計第 1 章:數據庫架構設計5657 TDSQL 新引擎 TDStore 分布式事務TDSQL 新引擎 TDStore 的分布式事務模型,遵從了 2PC(2 階段)提交協議,來保證分布式事務提交的原子性。對于涉及多個 Region 的分布式事務,所有參與者要么全部提交成功,要么全部回滾,不允許半提交的發生。與經典的 Percolator 模型相比,我們的實現方
115、案做了部分改動。Percolator 模型的沖突檢測、MVCC 機制等都是在上層維護,由計算層作為兩階段提交的協調者。在 TDSQL 新引擎中,我們將協調者下沉到存儲層,即在多個 Region 參與者中選取一個作為協調者,由 TDStore 保證事務提交的原子性,同時將未提交事務數據的緩存也下沉到存儲層 TDStore。這樣做的好處是降低了數據落盤的次數。在 prepare 階段,Percolator 方案需要將鎖數據落盤,在 commit 階段再把數據項落盤。但在我們的下沉方案中,數據項和鎖緩存在 TDStore 中,只有在 commit 階段將數據落盤一次,有效降低了 IO 開銷。在實現層
116、面,我們對存儲層 TDStore 的改動包括兩方面:添加了對數據項加 memory lock 的邏輯,實現 prepare 時 lock、commit 時 unlock 的效果,相當于在存儲節點上實現了 2PC 協議的協調者和參與者邏輯。歷史版本的清除需要計算全局最小活躍事務時間戳,不能只考慮單個 TDStore,我們借助 compaction 機制進行自動的過期版本數據清除,不需要額外的 gc 模塊。在分布式事務總體流程中,管控模塊會周期性地去推動全局最小時間戳,即全局快照點,從而保證讀的全局一致性以及過期數據的自動回收。在事務的開啟階段,首先 SQLEngine 會接收到來自 Client
117、 端的 SQL 語句,開啟一個事務。開啟事務后,我們會從管控模塊拿到事務的開啟時間戳 Start-ts,這時它會留意自身緩存中有沒有 SQL 語句對應要查詢數據范圍 Region 的路由信息。如果沒有,就會從 MC 重新拉入最新的路由。SQLEngine 會將事務的讀寫請求發送到 Region 的 Leader 所對應的 TDStore 上,再由 TDStore 的 Leader 節點作為兩階段事務的協調者來開啟兩階段事務。進入到準備階段后,我們會拿到一個 prepare-ts,再基于 prepare-ts 做并發事務的沖突檢測。當所有的參與者都 prepare 就緒后,會生成一個 commi
118、t-ts 作為事務提交的時間戳。如果事務提交過程中發生了切主、分裂等 Region 信息的變更,這時我們需要重新去更新一下最新的 Leader 消息以及 Region 參與者列表,從而保證事務提交過程中的完整性。當事務提交后,我們會把結果通過 SQLEngine 再返回到客戶端。TDSQL 新引擎 TDStore 無感知擴縮容無感知擴縮容是 TDSQL 新引擎 TDStore 最重要的特性,其通過存儲層 TDStore 和管控模塊 TDMetaCluster 的配合來完成。TDMetaCluster 會自動監測到每個 TDSQL 節點上數據分布的信息,包括查詢熱點的信息,這些都是通過 TDSt
119、ore 上報給管控模塊后再進行處理。以擴容為例,數據調度通過 Region 來進行,主要分為三個任務,即分裂、遷移和切主。5.1 無感知擴縮容-分裂下圖中有三個 TDStore 節點,屬于一主兩備三副本的 Region 的數據分布。假設 Region1 的數據量達到一定閾值,這時 MC 監測到 Region1 的數據量超過閾值,就會判定 Region1 需要進第 1 章:數據庫架構設計第 1 章:數據庫架構設計5859行分裂,將 Region1 分裂成 Region1 和 Region4。MC 會向 TDStore1 節點發送信息,讓它在 Region1 節點下發一個分裂的任務請求。TDSto
120、re1 會計算 Region1 的合適分裂點,但合適分裂點的選擇需要考量很多因素,比如希望 Region 分裂后的數據盡可能均勻或希望分裂后的新 Region 和老 Region 之間的事務熱點盡可能均勻,針對不同的場景,我們也會有不同的分裂策略。整個分裂流程也遵循兩階段提交,MC 作為協調者,Region 的所有副本作為參與者,保證全員成功或者失敗,避免部分副本分裂成功、部分副本分裂失敗的半提交狀態。分裂過程中,TDStore Region Leader 會計算出分裂點,將 Region 分裂地盡可能均勻。需要注意的是,分裂流程只是在元信息層面對 Region 一分為二,對于底層數據沒有變動
121、。我們以 Split-key 的節點,來重新計算老 Region1 的區間。分裂結束后,數據在 TDStore 節點之間并無明顯變化,只是多了一個 Region。這時并沒有實現存儲數據的擴容。5.2 無感知擴縮容-遷移遷移是讓數據擴展出去的最關鍵操作。如下圖所示,我們把數據分成更多的 Region 后,會將部分 Region 遷移到新增的存儲節點如 TDStore4 上。該例子是把 Region2 進行遷移,遷移的整體流程使用 Raft 協議增減副本的方式,即先增加一個副本、再減少一個副本。比如 Region2 最開始是 3 副本,在 TDStore2 上的節點是 Leader,另外兩個節點是
122、 Follower。在遷移時,我們先在 TDStore4 上創建一個 Region2 的 Follower 新副本,創建的新副本里的數據是空的,這時會觸發 Raft 協議中快照安裝的流程,會將快照的數據信息從 Leader 節點上傳到新創建的副本上。第 1 章:數據庫架構設計第 1 章:數據庫架構設計6061在增加一個副本后,即由 3 副本變成了 4 副本,后續還要恢復成 3 副本,因此需要再刪掉一個舊副本,即把 TDStore1 上的 Region2 Follower 節點刪掉,這樣就實現了 Region 副本的數據遷移,Region2 副本的數據也遷移到新的 TDStore4 節點上。在遷
123、移過程中,Region 分裂只是在元數據層面,最關鍵的是要把數據通過快照的方式傳輸且裝載到新的節點上,這就是性能瓶頸。為了突破這個瓶頸,我們在遷移的過程中結合 Raft 協議以及 LSM-tree 結構,具體的實現步驟如下:根據上文例子,我們需要把 Region2 遷移過去,MC 會不斷下發遷移任務,慢慢使三個節點之間的數據規模達到均衡,將其他的三個 Region 中的某個副本逐漸遷移過來。遷移結束后,這 4 個 Region 在新增的 TDStore4 以及之前的三個 TDStore 之間,Region 的數量都已經達到均衡。數據存儲在不同的 TDStore 節點上達到均衡,并不意味著負載就
124、真正均衡。因為 TDStore 是基于一主多備的形態,而 Raft 協議的原則是讀寫請求都會由 Leader 節點來承擔,TDStore1 上現有兩個 Leader,分別是 Region1 和 Region4 的 Leader,但 TDStore4 沒有 Leader。假設 4 個 Region 上的讀寫請求的流量都一致,這時 TDStore1 上承載的讀寫請求會多于 TDStore4,因為 TDStore4 是一個單純的 Follower 節點,它只需要從各個 Leader 節點上同步最新的 log 以及事務數據信息。值得一提的是,TDSQL 新引擎 TDStore 在遷移過程中是天然地對事
125、務透明,遷移只是 Follower Region 副本在不同 TDStore 節點上的變動,對事務完全不阻塞。第 1 章:數據庫架構設計第 1 章:數據庫架構設計62635.3 無感知擴縮容-切主出現負載不均衡的原因是 Region1 上的 Leader 較多。如果要達到近似的負載均衡,就依賴于下一個任務切主。切主的目的是均衡 Region 在不同 TDStore 存儲節點的 Leader 分布,從而達到調整讀寫事務的熱點,實現最終負載的均衡。在切主過程中,部分事務還在活躍階段,這時我們需要進行界定,因為事務的推進都是由 Leader 節點來進行,我們以 2PC 的準備階段作為分界點。如果進入
126、 prepare 階段的事務,我們就會將事務的上下文信息在切主過程中同時傳遞到它的新 Leader 上,由新 Leader 推進事務后續提交的執行流程;如果還沒有進入到 prepare 階段的兩階段事務,我們就會把這個信息做拷貝,傳遞過來。5.4 Region 調度與事務并發TDSQL 新引擎 TDStore 最重要的設計目標,是讓業務在敏態變化時無感知。因此在數據進行分裂遷移時,不影響業務事務的正常進行就顯得尤為重要。但數據分裂遷移涉及到 Region 元數據的變化以及底層數據的變動,要在工程上進行實現是一個較大的挑戰。第 1 章:數據庫架構設計第 1 章:數據庫架構設計6465遷移場景對于
127、事務執行透明,而分裂和切主任務都是在 Leader 上執行,因此存在事務的并發,對此我們需要保證事務的生命周期跨越分裂和切主流程。以分裂為例,圖中的 Region1 分裂前的數據范圍是 A 到 Z,這時它上面有活躍的事務 T,之前寫入兩條數據,分別是 put A=1、put H=5。分裂前事務 T 在事務緩存中保留 A=1、H=5 的數據。我們以 G 為分裂點進行分裂,把它分裂成 Region1、Region2,其中 Region1 的范圍變成了 A 到 G,Region2 的范圍變成了 G 到 Z。我們發現 H 數據項在分裂后屬于 Region2。在分裂過程中,如果事務變成一個跨 Regio
128、n 的事務,就會把它所屬的 Region 上的數據項 H=5 分裂傳輸到新的 Region 節點上。即 Region 上活躍的私有事務,在分裂過程中要做事務上下文數據的遷移。遷移后,除事務信息的分裂和傳輸,事務也從最開始只涉及到一個 Region,優化到一個一階段提交。分裂后,也由單一的一階段事務變成兩階段提交的事務。因為在分裂過程中事務沒有提交,因此在后續的提交過程中,就需要進行參與者列表的更新。這時在事務準備和提交階段,我們會檢查舊 Region 有沒有分裂。如果 Region 發生分裂,我們還需要更新事務參與者列表,從而使得事務在最終提交時,能夠讓新增的參與者 Region2 也能一同提
129、交,保證分布式事務的原子性。TDSQL 新版本數據存儲與遷移本部分主要介紹存儲節點中數據庫具體的存儲步驟以及突破遷移的瓶頸實現方式。下圖展示了 TDStore 中數據的持久化流程。持久化數據可分為 Raft log 和 kv 數據,分別對應左側的 Raft log 存儲和右側的 kv 數據存儲。假設我們開啟兩個 DB 實例,事務寫入的數據以 Raft log 的形式同步到 Region 的不同副本上,當一條 log entry 在多數副本上都落盤到 Raft log file 中,就認為這條 log entry 已提交,將它進行 apply 執行或者 replay 回放,其中的事務數據會暫存在
130、 write batch 中。當事務提交時,write batch 中的數據會寫入到 memtable 中,如果事務失敗 abort,write batch 中的數據就會直接丟棄。當 memtable 達到閾值時,就會變為一個不可變的 immutable memtable。當 immutable memtable 的數量滿了以后,就會觸發 flush 操作,將 memtable 中的數據落盤到 LSM-tree 的 L0 層,形成 SST 文件。隨著 L0 層的 SST 文件數量增加,會觸發后臺 compaction 操作,來維持 LSM-tree 的形狀??傮w而言,越新的數據通常處于 LSM
131、-tree 的上層,越老的數據,通過 compaction 不斷往 LSM-tree 的下層沉積。第 1 章:數據庫架構設計第 1 章:數據庫架構設計6667無論是 Raft log file 還是 SST 文件,都不應該允許數據的無限增長,需要對過期數據進行及時清理。對于 Raft log file 中持久化的 log entry 數據,通過 save snapshot 即生成快照的形式進行清理。一個 snapshot 由原數據和狀態機存儲數據構成。當一個 snapshot 生成以后,它所覆蓋到的 Raft log entry 都可以被安全地清理掉。當發生重啟時,如果發現已存在 snapsh
132、ot,會先加載該 snapshot,再去回放剩余的 Raft log,避免每次都從頭回放所有 Raft log。對于 Rocksdb 磁盤中的數據,會通過 compaction 操作來合并和清理過期版本數據。整個集群會維護一個全局最小 seqno,在 compaction 過程中,對于早于全局最小 seqno 的數據,僅需要保留最新的一條。下圖右邊的例子中,L0 的兩個 SST 合并到 L1,全局最小 seqno=s5,因此 SST_0_1 中的 put:k1-v2-s4 和 put:k1-v3-s1,只需要保留 s4 這條。我們可以發現,一個 Region 中的持久化數據由三部分組成,分別位
133、于 Raft log db、snapshot data 和 data_db 中的 SST 文件。其中 Raft log 和 snapshot data 兩者是互補的關系,因為 Raft log file 中的部分 log entry 被存到 snapshot data 中,所以這部分 log entry 可以從 Raft log db 刪除。SST 中則保存了 Region 所有通過 flush memtable 落盤的數據,這意味著 SST 與 snapshot data 中的數據存在重復。為此,我們想到了可以用 flush memtable 作為 save snapshot 的觸發點。當
134、memtable 中所包含的 Raft log 中的數據落盤到 SST 時,進行一次 save snapshot。這次 save snapshot 只寫一個 snapshot meta,即包含了 snapshot 位點元信息,但不再生成 snapshot data,因為該 snapshot data 中的數據已經隨著 flush memtable 存儲在 SST 中。這樣就可以節省掉生成 snapshot data 的開銷,讓 save snapshot 過程變得輕量。第 1 章:數據庫架構設計第 1 章:數據庫架構設計6869下面介紹數據是如何以 Region 為單位在不同的 TDStore
135、 之間進行遷移。下圖是一個基于快照傳輸和裝載的 Region 數據遷移的流程。第一步:Raft 協議觸發由 Leader 發向 Follower 的 Install Snapshot RPC,告知 Follower 所需同步的下一條 Raft log 在 Leader 上已被清理,因此需要開啟 install snapshot 流程。第二步:Follower 在接收到 install snapshot RPC 請求后,向 Leader 發送一條 download snasphot data 的 RPC,用于向 Leader 請求 Region 的 snapshot data。第三步:Leade
136、r 會在 LSM-tree 中找到該 Region 的范圍對應的 SST,每個涉及到的 SST 會生成一個 SSTIterator。我們在上層實現一個 MergeIterator,將這些來自不同 SST 但是屬于同一個 Region 中的 kv 數據進行合并排序,順序按照 key 從小到大的順序,對于相同的 user key,seqno 更大的即版本更新的排在前面。再通過流式 RPC 將 Region 的這些 kv 數據按順序發送到 Follower 上。Follower 接收到數據后,按照順序寫入到 external SST 中,每寫滿一個就會開啟一個新的 SST。第四步:Follower
137、將這些 extrenal SST 注入到 LSM-tree 中合適的位置。合適位置指的是在不與已有 SST 發生范圍重疊的前提下,盡量往下面的層去放。例如圖中的第二個 external SST,它的范圍是 151 到 176,與 L0 層、L1 層的 SST 都不重疊,但是與 L2 層的 0 到 175 這個 SST 發生重疊,我們就會把它放在 L1 層。原因是為了讓 isnstall snapshot 遷移過來的 kv 數據可以覆蓋掉其殘存的數據,從而保證被訪問到的數據版本是最新的。這就是數據層面遷移的全流程??偨Y與未來規劃下一階段,TDSQL 新引擎 TDStore 將會實現以下三方面特性
138、:數據地理位置感知,進一步降低分布式事務比例,從而使性能進一步提升。對等架構,將計算模塊和存儲模塊做對等架構的設計和一定程度上的重構,主要是為了適用于公有云上的小規模集群,達到更高的資源利用率。管控模塊實現更加智能化的負載均衡調度策略。作為領先的國產分布式數據庫,TDSQL 致力于將數據庫打造成一種服務,用戶隨取隨用,把簡單留給用戶,把復雜留給自己。未來,TDSQL 將持續推動技術創新,釋放領先技術紅利,繼續推動國產數據庫的技術創新與發展,幫助更多行業客戶實現數據庫國產化替換。第 1 章:數據庫架構設計第 1 章:數據庫架構設計7071Redis 如何實現多可用區?劉家文騰訊云數據庫高級工程師
139、先后負責 Linux 內核及 redis 相關研發工作,目前主要負責騰訊云數據庫 Redis 的開發和架構設計,對 Redis 高可用,內核開發有著豐富的經驗。在如今的業務場景下,高可用性要求越來越高,核心業務跨可用區已然成為標配。騰訊云數據庫高級工程師劉家文結合騰訊云數據庫的內核實戰經驗,給大家分享 Redis 是如何實現多可用區,內容包含 Redis 主從版、集群版原生架構,騰訊云 Redis 集群模式主從版、多 AZ 架構實現以及多 AZ 關鍵技術點,具體可分為以下四個部分:第一部分:介紹 Redis 的原生架構,包含主從版及集群版;第二部分:介紹騰訊云 Redis 架構,為了解決主從架
140、構存在的問題,騰訊云使用了集群模式的主從版。其次為了更好的適應云上的 Redis 架構,引入了 Proxy;第三部分:分析原生 Redis 為何不能實現多 AZ 架構的高可用以及騰訊云是如何實現多可用區;第四部分:分享實現多可用區的幾個關鍵技術點,包含節點部署、就近接入及節點的選主機制。Redis 原生架構Redis 的原生架構包含主從版及集群版。主從版是由一個 master,多個 slave 組成。主從的高可用一般采用哨兵模式。哨兵模式在節點故障后,能自動把相應的節點進行下線處理,但是哨兵模式無法補節點。為了配合補從,需要管控組件進行協助,因此一般會將監控組件和管控組件配合完成主從版的一個高
141、可用。無論是哪種方式,主從版的可用性依賴外部的仲裁組件,存在恢復時間長及組件本身的高可用問題。其次主從版還會導致雙寫的問題及提主有損的功能缺陷。第 1 章:數據庫架構設計第 1 章:數據庫架構設計7273Redis 的集群版中的每一個節點相互獨立,節點之間通過 Gossip 協議來進行通信,每一個節點都保存了集群中所有節點的一個信息。集群版的數據基于 slot 進行分片管理,slot 總共有 16384 個,節點的 slot 并不是固定的,可以通過搬遷 key 的方案來完成 slot 的遷移,有了 slot 的搬遷功能,集群版可以實現數據均衡及分片的動態調整。Redis 集群版內部集成了 Go
142、ssip 協議,通過 Gossip 協議能完成節點的自動發現和自動成災的功能。自動發現是指一個節點要加入到集群中,只需要加入集群中的任何一個節點,加入后,新節點的信息會通過 Gossip 推廣到集群中的所有的節點。同理,當一個節點故障后,所有節點都會把故障信息發送給集群其它節點,通過一定的判死邏輯,它會讓這個節點進行自動下線,這個也就是 Redis 集群版的自動容災功能。為了說明單可用區是如何部署的,我們需要進一步了解 Redis 集群版的自動容災。自動容災總共分為兩個步驟,第一個就是我們的判死邏輯,當超過一半的主節點認為該節點故障,集群就會認為這個節點已經故障。此時從節點會發起投票,超過一半
143、的主節點授權該節點為主節點時,它會將角色變為主節點,同時廣播角色信息。根據上面這兩點分析,不難發現 Redis 集群版有兩個部署要求,一個是主從不能同機,當主從同機的機器故障后,整個分片就相當于已經故障了,集群也就變為一個不可用的狀態。其次是我們的節點數不能超過分片數的一半,這里要注意的是節點數,而不是只限制主節點數。上圖的右邊部分是錯誤部署方式,在集群節點狀態沒有變化的情況下,是能夠滿足高可用的,但集群的主從發生切換后,一個機器上的主節點已經超過大多數,而這個大多數機器故障后,集群無法自動恢復。因此三分三從的集群版,要滿足高可用總共需要六臺機器。騰訊云 Redis 架構為了解決雙主的問題及支
144、持無損提主的操作,騰訊云上使用了集群模式的主從版。實現集群模式的主從版,先要解決三個問題:第一個是集群模式需要至少 3 個投票(仲裁)節點的問題,由于主從版本只有一個 Master,為了達到 3 個仲裁節點,我們引入了兩個 Arbiter 節點,Arbiter 只有投票權,不存儲數據,通過這個改造后,就能夠滿足了集群版的高可用。第二個是多 DB 問題,由于騰訊云上引入了 Proxy,減少了對多 DB 管理的復雜,因此可以放開單 DB 限制。最后一個是需要啟用跨 slot 訪問,在主從版中,所有的 slot 都在一個節點上面,不存在跨節點問題,因此可以取消跨 slot 限制。解決完這幾個主要問題
145、后,集群模式可以達到完全兼容主從版,同時擁有集群版的自動容災、無損提主及可以在業務支持的情況下,無縫升級為集群版。第 1 章:數據庫架構設計第 1 章:數據庫架構設計7475由于 Client 版本比較多,為了兼容不同的 Client,騰訊云引入了 Proxy。Proxy 除了屏蔽 Client 的差異外,也屏蔽的后端 Redis 的版本差異,業務可以使用主從版的 Client 去使用后端的集群版。Proxy 也補齊了 Redis 缺少的流量隔離及支持更豐富的指標監控,還能將多個連接的請求轉換為 pipeline 請求轉發到后端,提升 Redis 的性能。Redis 的多 AZ 架構部署高可用
146、的多可用區架構,需要至少滿足兩個條件:主從不能部署到同一個可用區;一個可用區的節點數不能超過分片數的一半。如果我們部署一個三分片的實例,那應該需要個 6 個可用區才能真正保證它的高可用。即使可用區充足,它也會有性能的抖動,訪問本可用區,性能和單可用區相同,但如果跨可用區訪問,至少出現 2ms 延遲,因此原生的 Redis 是不適合多可用區的部署,為了實現高可用的部署,我們需要更深入的分析它的問題所在。這種場景的高可用不滿足主要是由于主節點漂移,而投票權和主節點又是綁定關系。當投票權在不同可用區間切換后,導致超過大多數投票節點在該可用區,此時該可用區故障后就會出現集群無法恢復的情況。從上面分析可
147、以看出,高可用的問題是由于投票權發生漂移導致的。假如能把投票權固定在某些節點上面,這樣投票權就可以不再漂移。當然這里無法將投票權固定在從或者主節點上,對于多可用區,最好的方式就是引入了一個 ZoneArbiter 節點,它只做節點的判死及選主,不存儲任何數據。這樣投票權就從存儲節點中分離出來。在投票權分離后,即使數據節點的 Master 可以位于一個可用區,從位于不同的可用區也能滿足高可用。業務在主可用區中訪問和單可用區訪問性能是相同的。多 AZ 的關鍵技術保證高可用后,接下來介紹多可用區的三個關鍵的點:高可用如何部署、性能如何達到最優、可用區故障后保證集群自動恢復。第 1 章:數據庫架構設計
148、第 1 章:數據庫架構設計7677節點部署同樣需要滿足兩個點:第一是主從不能同可用區,這個比較容易滿足,只要有 2 個可用區即可,第二點是至少三個 ZoneArbiter 節點位于不同的可用區,第二個條件需要三個可用區,如果沒有三個可用區的地域也可以將 ZoneArbiter 部署于就近的地域,因為數據節點和仲裁節點是分離的,位于其它可用區的節點只會出現判死及提主有毫秒級延遲,對性能和高可用不會有任何影響。分析完部署后,再來看下數據的存儲鏈路,存儲鏈路分為讀和寫鏈路,寫鏈路是從 client 到 LB,再到 Proxy,最后將數據寫入到相應的 Master。在讀的時候,開啟就近讀的特性后,鏈路
149、從 client 到 LB,再到 Proxy,最后選擇一個就近的節點讀取數據。就近路徑選擇包含 LB 的就近選擇及 Proxy 的就近選擇,LB 要根據 Client 的地址選擇相對應的 Proxy。如果是讀,Proxy 要跟據自身所在可用區信息選擇同可用區的節點進行讀訪問。如果是寫,Proxy 需要訪問主可用區的 Master 節點。能實現就近訪問,最關鍵的一個點就是要 LB 及 Proxy 要存儲相關后端的可用區信息,有這些信息后,就能實現就近的路由選擇。單可用區和多可用區故障的最大區別是:首先多可用區的某一節點故障后,主節點有可能切到其它可用區會導致性能波動。其次對于多可用區的實例,整個
150、可用區故障后,需要投票的節點比單可用區的節點多。在多節點故障的場景測試中,128 分片,63 節點同時故障,99%以上都無法正?;謴图?。而無法恢復的關鍵就是 Redis 的選主機制導致。因此我們需要更深入的理解 Redis 的選主機制。第 1 章:數據庫架構設計第 1 章:數據庫架構設計7879首先看下選主機制的授權機制,當主節點收到一個 failover 信息后,核對自身節點為 Master,然后檢查投票的這個節點集群的 epoch 是不是最新的,并且在這個 epoch,并沒有投票任何的節點,為了防止一個節點的多個從節點重復發起投票,這里在 30s 內不允許重復發起。最后再核對這個 slo
151、t 的歸屬權是否屬于發起 failover 的這個節點,如果都沒有問題,那么就會投票給該節點。綜上,允許該主節點投票的條件是:發起投票的節點的集群信息是最新的;一個 epoch 只能授權一個節點;30s 內同一分片只能授權一次;slot 的歸屬權正確??赐曛鞴濣c的授權機制后,再看下從節點發起投票的機制。發起投票的流程是先核對 1 分鐘內沒有發起過投票,再核對該節點數據是否有效(不能和主斷開 160s)。從節點是有效的,就開始計算發起投票的時間,當投票時間到后,將集群的 epoch+1,然后再發起 failover,如果主節點的授權超過分片數的一半,則自身提為主節點,并廣播節點信息。這里從節點投
152、票有兩個關鍵的點,一分鐘只能重試一次投票,最大重試 160s,從節點最多可以投票 3 次。當超過一半的 master 授權提主,提主成功,否則超時。在某一主節點故障后,集群的選主盡量在同可用區中選擇。一個分片不同從節點之間的選主時間由節點的 offset 排名及的 500ms 的隨機時間決定。在寫少讀多情況下,offset 排名大多時間是相同的。在單可用區場景下,隨機選擇一個節點本身無任何影響,但多可用區就會出現性能的抖動。因此這個就需要在排名中引入同可用區的排名。而同可用區的排名就需要要每個節點都知道所有節點的可用區信息。在 Gossip 中剛好有一個預留字段,我們將可用區信息存儲在這個預留
153、字段中,然后將這個節點的可用區信息會廣播到所有節點中,這樣每個節點都有所有節點的可用區信息。在投票的時候我們按照 offset 和可用區信息排名綜合考慮來保證同可用區優先提主。第 1 章:數據庫架構設計第 1 章:數據庫架構設計8081一個節點的選主分析完后,我們來分析下多節點故障的投票時機。多個節點發起投票時,會隨機選擇 500 毫秒內的一個時間點,然后發起投票。假如集群有 100 個主節點,500 毫秒發起完投票,每個節點投票時間是 5 毫秒,5 毫秒肯定是不夠一個投票周期。在之前的多節點故障投票成功率測試結果也就證明了這種情況幾乎不能成功。投票不能成功的關機是集群不同節點投票是隨機發起導
154、致的,既然隨機存在沖突,最直接的解決辦法就是按順序來投票。按順序投票可以簡單分為兩種,一種是依賴外部的控制,引入外部依賴就需要保證它的高可用,一般情況下,存儲鏈路的高可用最好不要依賴外部組件,否則會導致整體的可用性受外部組件加存儲節點的高可用的影響。那再考慮下集群內部實現順序投票,集群內部實現順序投票也有兩種方式,一個是仲裁節點按順序來授權。但是這種方式很容易打破一個 epoch 投一個從節點的原則,打破后可能會導致投票結果不符合預期。還有一種解決辦法是由從節點來順序發起投票。從節點要保證順序發起投票,那就需要每個節點的排名是保證相同的,而節點 ID 在生命周期中是唯一的,且每個節點都有其它節
155、點的 ID 信息,因此這里選擇節點 ID 的排名是比較好的一種方案,每個從節點 ID 發起投票前,首先核對自己的節點 ID 是不是第一名,如果是就發起投票,如果不是就等待 500ms。這個 500ms 是為了防止隊頭投票失敗的場景。按這種方式優化后,投票都可以成功。由于本身是分布式的,這里還是存在著小概率失敗,在失敗后就需要外部監控,強行提主,保證集群的盡快恢復。專家答疑1.通過 sentinel 連接 redis 也會出現雙寫么?答:雙寫是對存量的連接來說的,如果存量的連接沒有斷開,它會寫入到之前的 master 節點,而新的連接會寫入到新的 master 節點,此時就是雙寫。而集群模式出現
156、雙寫最多 15s(判死時間),因為 15s 后發現自身已經脫離大多數,會將節點切換為集群 Fail,此時寫入及讀取出錯,而規避了雙寫的問題。2.固定節點投票,這幾個節點會不會成為單點答:單點是不可規避的,比如 1 主 1 從,主掛了,那么從提主后,這個節點就是單點,出現單點是需要我們進行補從操作。而這里仲裁節點出現故障,補充一個節點即可,只要保證大多數仲裁節點正常工作即可,由于仲裁和數據訪問是分離的,故障及補節點對數據訪問無任何影響。3.整個可用區故障和可用區內節點故障 failover 的處理策略是什么?答:不管是整個可用區故障還是單機故障導致的多節點故障,都應該采用順序投票來完成,減少沖突
157、,而如果同可用區有從節點,該節點應該優先提主。4.想問下寫請求,要同步等從復制完嗎?Redis 不是強同步,Redis 強同步需要使用 wait 命令來完成。5.最大支持多少 redis 分片呢,節點多了使用 gossip 有會不會有性能問題?答:最好不要超過 500 個,超過 500 個節點會出現 ping 導致的性能抖動,此時只能通過調大 cluster_node_timeout 來降低性能抖動。6.多區主節點,寫同步如何實現?答:可能想問的是全球多活實例,多活實例一般需要數據先落盤,然后再同步給其它節點,同步的時候要保證從節點先收到數據后,才能發送給多活的其它節點。還需要解決數據同步環路
158、,數據沖突等問題。7.當前選主邏輯和 raft 選主有大的區別嗎?答:相同的點都需要滿足大多數授權,都有一個隨機選擇時間,不同的點 Redis 是主節點有投票權(針對多分片情況),而 raft 可認為是所有節點(針對單分片情況)。Raft 數據寫入要超一半節點成功才返回成。Redis 使用弱同步機制(可以使用 wait 強勢主從同步完返回),寫完主節點立即返回,在主故障后,需要數據越新的節點優先提主(數據偏移值由 Gossip 通知給其它節點),但不保證它一定成功。第 1 章:數據庫架構設計第 1 章:數據庫架構設計8283新一代云原生數據庫暢想竇賢明騰訊云數據庫專家工程師從事云數據庫產品研發
159、多年,從零到一主導研發多款云數據庫、云原生數據庫。目前負責 TDSQL-C PostgreSQL、TencentDB For PostgreSQL 的全棧研發工作,和 TDSQL-C MySQL 的產品化研發工作。云與分布式是數據庫發展的兩大趨勢,那云時代下新一代數據庫會是什么樣的呢?騰訊云數據庫專家工程師竇賢明講師給大家分享了自己的暢想,基于冷熱分級存儲與 ServerlessDB 結合的新一代數據庫?;隍v訊云數據庫 TDSQL-C PostgreSQL 在云原生這條路上的一些探索,重點從 Serverless 數據庫和分級存儲(冷熱分離)的設計與實現兩個方面進行分享。內容主要分為四個部分
160、:第一部分:介紹云時代的數據庫的背景;第二部分:探討云上數據庫進化的邏輯是什么、方向是什么;第三部分:描述 Serverless 數據庫具體是什么樣子;第四部分:云原生數據庫在存儲方向上的進一步演進,分級存儲所達到的效果、在冷熱分級等場景下的一些用例。云時代的數據庫在過去一二十年間,整個關系型數據庫行業最大的變化只有兩個,一個是云,一個是分布式。云時代,對數據庫產生了一個非常大的一個沖擊,或者說變化。就 Gartner 近期數據,云上數據庫的份額也已經趕上并即將超過 on promise(傳統方式)的產值。那云時代的數據庫是一個什么樣子呢?以騰訊云的 TencentDB for Postgre
161、SQL 為例。云數據庫的第一個階段,其實是在將傳統數據庫搬到云上來。首先,最開始部分是基礎環境,比如說硬件資源、網絡、存儲之類;在此基礎上,進行一些虛擬化,這些是云本身具備的能力;在用戶側,資源準備好了之后,是一個資源的管理分配和運行環境的準備;然后是做云數據庫托管的一些事情,包括部署、HA、備份恢復等,還有監控這些;那整體上還是一個 PaaS 平臺這么一個事情。這個過程中,整體來講還是將傳統的主備搬到一個云環境這么一個過程,其實并沒有對數據庫的內核或者說數據庫的架構產生一些根本性的變化。這其實是在過去幾年云數據庫或云的一個演進的第一階段,把原來的數據庫運維,以云的方式托管起來、在云上使用起來
162、。那在這期間是怎樣一個實現方式呢?首先,會有一個云數據庫的一個部署,即實例進程運行在虛擬機里面、存儲則放在一個云盤存儲上;然后會建立一個備庫,主備之間通過 Replication 實現數據的復制,通過兩份完整的數據節點實現高可用。這一點,與當前物理環境并沒有什么本質上的變化,即一主一備的方式。即使如此(簡單的實現),云數據庫相比傳統數據庫仍然會有很多的優點。第一,成本更低,當前主要是云本身的特質帶來的;第二點就是擴容能力更好,比如說它第 1 章:數據庫架構設計第 1 章:數據庫架構設計8485擴容能力好的是在于說存儲這里可以動態擴容,計算這里可以更為輕量的擴縮容;第三點,是體驗上也有非常大的提
163、升。然而,仍然有些問題沒有解決,尤其是在站在云的視角來看。當有越來越多的數據庫實例跑在一起的時候,發現這個成本的增長是線性的,成本上是沒有去得到一些根本性的降低。就一主一備的情況來說,有兩份完全一樣的數據;當我們再新加一個 RO(Read Only 節點,僅用于讀請求)的時候,還會再加一份完整的數據。存儲成本沒有根本性的收斂,根據 RO 的數量線性增長。而另外一種情況,網絡存儲成為一個瓶頸。再拿這個例子來說,每次讀寫都會對網絡形成一個吞吐,且隨著 RO 數量增加也是一個線性增長,對網絡總流量造成較大的壓力。RO 節點(Replica)的建設時間成本也是一個問題。多添加一個 RO 節點,就是多一
164、份數據,是完整的一個復制(對應存儲上的三副本),復制時間較長。云原生進化云原生數據庫(Cloud Native Database)的提出,是為了解決原來傳統架構下無法解決的這些問題,基于云基礎設施重新設計與實現所提出的一個解決思路。那其中核心的解決思路是什么?答案就是存算分離??梢运伎家粋€問題:主節點和 RO 節點各自有一份完整的存儲,且各自都有三副本。為什么我們這些節點不共用同一份數據呢?這里很容易就想到一點,就是我們把所有節點的數據都放在遠端分布式存儲上,不再本地存放。這樣的話,存儲就可以實現僅有一份數據(三個副本),而計算仍然是多節點。把數據庫內部拆開來看,基本上來講分為兩部分:一個是存
165、儲層,一個是計算層。計算層一般包含詞法解析、語法解析、語義解析、查詢重寫、優化器等。下一層則是數據緩存,相對設計邏輯比較簡單一些,這部分一般不單獨列;中間這個事務層,保證數據存儲、讀取的正確性;最下層 storage 層負責跟實際的存儲硬件打交道。既然要把所有節點的數據放在同一個分布式存儲(租戶)上,就需要將數據庫內核的引擎和存儲部分拆開到兩個地方運行,存儲的部分放在分布式存儲系統內運行,把引擎的部分放在計算節點中運行。表現為所有的 RW、RO 都沒有實際的數據,只需要內存和 CPU 的資源拉起來即可,這個時候 RO 啟動的效率就非常高,本身就很輕。RO 則通過 Replication 從 R
166、W 拿到更新的修改,去更新它內存中的緩存。那前面也有講到網絡成為瓶頸的問題,需要分別針對 RW 和 RO 來說明。針對 RW,有一個 WAL 的概念,即 Write Ahead Log,所有數據的變更都會記錄在這里。第 1 章:數據庫架構設計第 1 章:數據庫架構設計8687這里做一個極端假設,假如從數據庫被初始化之后所有的 WAL 都被保留、且有一臺非常非常強大的計算機,在每次讀取數據時,都將最開始到最新的所有 WAL 全部重放到當前時刻,其實是可以得到與當前數據庫中完全一致的數據。這一點,可以描述為 Log is Database。如果 WAL 能夠對應一份完整的數據,那么在寫入存儲時,可
167、以不寫入數據部分、僅寫入 WAL。存儲層在收到 WAL 之后,基于存儲上已有的數據部分,對 WAL 進行不斷的重放,就可以實現數據不斷的更新,保證數據的一致性。這里具體的實現較為復雜,不再作展開。達到的效果就是,至少減少一半的網絡流量。RO 剛才已經簡單說過一些,在通過 Replication 獲取到 WAL 之后,會對緩存中數據進行更新。當需要從存儲上讀取數據時,根據該數據頁的時間戳,將其后的 WAL 更新上去,也因此,RO 需要在內存中緩存一份近期的 WAL,得到一個保證一致性的最新數據頁。整體來講,通過以上的機制,實現數據存儲在分布式存儲上主備節點采用同一份存儲,達到 RO 節點增加存儲
168、成本不變的效果。也因此,RO 的數量支持最大到 15 個。存儲計算分離之后,剛開始優勢未必特別明顯,在使用上和前面的云數據庫沒有本質的區別,用戶體驗基本一致。效果會隨著使用的加深而逐步體現出來。第一,擴縮容的效率完全不一樣,原本在擴容的時候,有可能出現空間不夠需要遷移,導致時間成本非常高。計算變得特別輕,可以快速拉起、關閉,遷移效率極高,不用擔心擴容時需要遷移的情況??捎眯?、可靠性等方面,因為所有的狀態都存儲在分布式存儲中,計算節點的啟停(可用性),不會影響存儲上數據的可靠性,因此在計算節點出現故障時,可以隨意關閉老節點、啟動新節點,不用擔心會丟失數據,可用性方面達到了較高的保證;存儲上數據有
169、三副本,保證了數據的可靠性;且多個節點共用同一份存儲,整體成本也更低。針對計算來說,計算節點被做得輕量、可以便捷地啟停,當這份便捷性達到一定水平的時候,就會引起質變。極致彈性 正常來講,業務有高峰低谷,比如訂餐系統。一家餐飲公司的訂單系統,肯定是有高峰和低谷,基本上是三餐的時候比較高,夜里幾乎為零。那沒有業務在運行的時候,為什么還要這個計算節點付錢?在傳統方式下,需要準備一臺物理機;在云數據庫形勢下,則以實例為單位,計費的粒度仍然較粗。那怎么去做呢?第 1 章:數據庫架構設計第 1 章:數據庫架構設計8889這里可以考慮這樣的產品形態,在用戶端呢,計算節點在不用時不計費,在真正使用時再拉起并開
170、始計費;且擴容也不再需要用戶操作,由后端自動根據負載來做調整,在不用時自動將資源占用降為 0。存儲空間也類似。原本客戶在購買了一個 100GB 的存儲空間,會一直按 100GB 來計算費用,但實際可能并沒有真正用到那么多。這里可以實現為僅僅為實際占用的空間付費。正常情況下,數據庫實例處于休眠狀態;當 SQL 到達時,將計算節點拉起來,然后執行 SQL。同時,RO 也可以加進去一起參與,就是可以一次拉起一個或者多個計算節點,在這些節點上做負載均衡。當沒有 SQL 在運行超過一定時間,比如超過五秒鐘、十秒鐘沒有 SQL 在運行,計算節點可以直接關閉,也因為業務沒有在運行,也感知不到負面影響。而計費
171、成本上,則獲得較大的降低。舉例來說,比如餐廳運營的高峰時間段(如中午時間),SQL 會一直進來一直使用,后端計算節點則持續存在。當資源用滿達到一定閾值,將自動升配;那中午過后,不再有請求,計算節點則會被關閉、也不再計費,計算成本降為 0。演進到當前,計算節點最細粒度可以做到一秒級別的計費。據內部數據來看,有不少客戶的業務一天只花可能不到幾塊錢、甚至幾毛錢都有,一個月可能就花幾十塊錢。而像傳統物理機的方式起碼十萬起,云數據庫也要每月幾百、幾千塊。這是一個巨大的飛越!分級存儲花分兩朵,各表一支。前面是計算節點的演進,存儲節點又該如何演進?存儲的演進持續在進行,比如性能、壓縮等。此處僅針對數據的存儲
172、成本。數據大致可以分為幾層:熱數據、溫數據、冷數據。熱數據放在內存緩存里,價格最貴、性能最好。然后,大量的數據放在分布式存儲上,那冷數據該如何處理?熱、溫、冷的劃分,主要基于的是訪問頻率、量和性能要求。冷數據,一般定義不經常訪問的數據,比如歷史訂單、歷史日志等。當前 TDSQL-C PostgreSQL 的存儲規模支持最高 第 1 章:數據庫架構設計第 1 章:數據庫架構設計9091128TB,絕大部分情況下足夠業務使用。冷數據比較多、不常訪問,放在 TDSQL-C PostgreSQL 的存儲中又較貴,該如何處理?答案是 COS。COS 是騰訊云的對象存儲服務,數據存儲成本要低于 TDSQL
173、-C PostgreSQL 的高性能存儲盤。溫熱的數據,或者說時刻需要用到的數據,還是要放在分布式存儲上正常使用,但針對時間較老、不大訪問的冷數據,可以放在 COS 中,并通過同樣的一套 SQL 語言、表結構來訪問。為實現這一點,TDSQL-C PostgreSQL 封裝了 COS 的訪問接口,通過與常規數據表完全一樣的使用方式,達到對 COS 上數據文件進行訪問的目的。這里舉一個具體的例子:第一步,創建一個 Server,指定 COS 的信息,包括 id、key、endpoint、文件等;第二步,在該 Server 上創建表;然后就可以通過讀這張表實現對 COS 上文件的讀取。異構存儲的訪問
174、,基本上分為三部分,計算、元數據(表結構等)、存儲格式。Server/表定義這里,即實現了元數據的維護,存儲格式上當前支持 CSV。具體的執行邏輯可以通過執行計劃查看。另外一個例子,是更復雜的一些用法,比如:Table a 和 Table b 都是本地表,數據存放在存儲上,再建一張 COS 表,就可以直接對這三張表做關聯運算。這比較適合報表類的場景,比如以賬戶信息為維度統計歷史訂單類的報表。第 1 章:數據庫架構設計第 1 章:數據庫架構設計9293還有一個例子,就是分區表。還是 Table a 和 Table b,放在分布式存儲上,一張 COS 表,三張表放在一起作為一張分區表的三張子表。P
175、ostgreSQL 有非常強大的分析能力,當基于分區字段做過濾的時候,會根據分區字段做裁剪;再配合分區表的 Time to live 的功能,比如 Table a 和 Table b 是正常的按時間分區,Table c 則是 COS 表,可以實現同樣一張(分區)表,部分放在存儲、部分放在 COS 上的效果。專家答疑1.存儲計算分離之前 PostgreSQL 用的什么高可用方案?答:原先一主一備形態下,高可用方案是內部自研的一套,基本分為探測、策略和執行三個步驟。2.主庫和備庫要做到同步,那資源豈不是很浪費?答:主庫和備庫的數據同步,主要是為了解決 RO、備庫上內存中熱數據的持續更新,一定程度上
176、是為了提升備庫的性能和數據一致性。如果不在 RO 上保持緩存,性能會較為糟糕;若保留了緩存,則需要對這些緩存持續更新,以保證數據一致。3.更新操作多的情況呢?答:主備間復制采用的是物理復制的方式,只是將數據頁上的變更部分發送到備庫上,在數據頁上重做,這種方式較為高效、延遲較低。4.RO 先讀取數據頁,再應用 WAL,會不會出現讀延遲?答:會有一定延遲,但這不會成為一個問題。主備方式在當前硬件、網絡情況下,必然會存在一定延遲,收斂在一定范圍內即可。若要保證完全無延遲,也不是做不到,代價過于高昂、不值得;WAL 的重放改為以頁為單位,相比原來的方式更為高效,延遲反而更低;主備間應該通過 Proxy
177、 實現全局一致性;通過以上措施綜合解決主備延遲帶來的影響。5.目前是一個 RW,多個 RO,怎么才能擴展寫能力呢?答:當前寫能力很多時候不會成為一個瓶頸。在僅寫入 WAL 之后,網絡 IO 是制約寫能力的核心因素之一,當前最新可以到達 100Gb/s 網絡,在存儲側又被分發到多臺機器上,少有多業務能將這兩個地方都打到瓶頸上。當趨勢出現的時候,我們也可以考慮采用多寫的方式來承接,這就是另外一個故事了。6.既然是 share 存儲了,為什么還要 master 到 replica 的復制?答:參考問題二,主要是為了解決 RO 上的查詢性能問題,需要在緩存中保持數據。7.存算分離后怎么解決謂詞下沉技術
178、和存儲節點計算瓶頸問題?所有數據都從存儲拉到計算節點計算?答:這里其實不用過于擔優性能的問題。比如,現在有一個 RW15 個 RO,一臺機器機器大約 96vCore,16 臺加起來一共 1536,很難有業務能吃掉這么多 CPU,計算資源在很多時候是足夠的。但謂詞下推確實也適合這種架構,減少網絡開銷,實現的方式是將 where 條件的描述下發到存儲層,存儲層在返回數據之前對數據進行過濾。其中比較難的課題是存儲上計算資源的調度問題,這時候要看存儲機的硬件條件。這也是存儲計算分離一個價值點,存儲層可以針對存儲層定制相關的機型,可能就要去配合一些新硬件,比如 FPGA、GPU 之類的8.沒明白存算分離
179、的意思,存儲單獨存儲,計算的話指的是每次查詢都需要新拉起一個實例去存儲中拉取數據么?答:是也不是。這里可以保持一個實例一直運行,這樣有部分數據是在內存中,沒有用到存儲上的數據時不用從存儲上讀取,沒有的時候從存儲上讀。缺點是,計算節點會持續計費。核心還是成本。這里比較好的玩法,是在每次執行 SQL 的時候才拉起實例,在超過閾值時間不再使用后,將其關閉,以節省資源。Serverless 形態解決的是計費和調度的問題,存儲和計算分離之后,存儲層可以單獨計費、計算成本按實際用量計算,可以極大節省成本。對于很多業務來講,尤其是有高峰低谷的業務,有非常大的成本降低的效果。并且,也解決了云數據庫架構上沒有解
180、決的問題,從而實現更高可用性、更好的用戶體驗。9.分級存儲,如果數據放到對象存儲上面,這種存儲方式只適合存儲歸檔數據,不適合第 1 章:數據庫架構設計第 1 章:數據庫架構設計9495有 update,delete 的數據。答:這個問題比較專業。分級存儲只解決一類的場景,數據量比較大、不改不刪、偶爾讀取,比如歸檔類的數據,放在數據庫內這種高性能存儲上成本又高,說到底還是成本問題。比如有很多行業有安全性的要求,要求至少保留兩年以上的歷史數據;而這些數據都不會訪問,但時不時的還會查一下。這個時候,如果用另外一套存儲、另外一套引擎、另外一套機器,維護成本是相當高的。我們提供這種方式,一部分相對冷的數
181、據放到 COS 來,但數據的管理和維護還是在數據庫中。因此,不適合刪改較多的場景。10.計算節點下的存儲是不是有優化的方向,比如 WAL 放在計算節點上,用高速設備進行優化?答:存算分離方案實現的一個核心思想,就是 Log is Database。通過將 WAL 寫到存儲、并在存儲中存放,達到數據寫入存儲的目的。我理解這里的問題是,是否可以先把 WAL 寫入本地、然后異步化寫入存儲中?這是一個有意思的思路,但這假設了一個前提,就是本地盤比分布式存儲的吞吐能力更強。在當前的網絡、硬件條件下,這個假設已經不一定成立了。畢竟現在 100Gb/s 的網絡已經可以作為標配,再有 RDMA 的加持,網絡吞
182、吐已經高于本地盤;且存儲側的寫入會分布在多個節點上,也提升了吞吐。騰訊云數據庫 TDSQL 與中國人民大學最新聯合研究成果被 SIGMOD 2022 接收并將通過長文形式發表。SIGMOD 是國際數據管理與數據庫領域頂尖的學術會議之一,騰訊云數據庫 TDSQL 論文已連續多年入選 VLDB、SIGMOD、ICDE 等國際頂級會議。本次入選論文題目為:CompressDB:Enabling Efficient Compressed Data Direct Processing for Various Databases。論文針對壓縮數據的直接操作與處理,提出一項新型數據庫處理技術Compress
183、DB。本研究提出并實現了新型數據庫技術,利用上下文無關文法來壓縮數據,通過新的數據結構和算法設計實現對語法規則進行解析,CompressDB 支持直接對壓縮后的數據進行數據查詢和操作,并且支持各種數據庫系統。SIGMOD 評委對 CompressDB 的創新性給予了高度評價:在本文中,作者提出了一個支持直接對壓縮數據進行更新和計算的系統 CompressDB,是一個優秀的系統工作。其作為文件系統層實現,可以被現有的數據庫系統直接使用,作者通過在其上運行一系列關系數據庫和 NoSQL 數據庫來證明了這一點。同時作者在實現中表明,啟用 CompressDB 可以讓數據庫系統實現更高的吞吐量和更低的
184、延遲,同時減少存儲空間。論文概述在如今的數據管理系統中,處理大數據時直接在壓縮數據上進行操作被證明是一種很成功的方式。這類系統展示了較大的壓縮優勢和性能提升。但是,當前此類系統只關注數據查詢,而一個完整的大數據管理系統必須支持數據查詢和數據操作。我們開發了一個新型存儲引擎 CompressDB。CompressDB 支持壓縮數據上的直接數據處理,有以下優點:基于壓縮數據直接計算技術,定義新型數據庫處理第 1 章:數據庫架構設計第 1 章:數據庫架構設計9697第一,CompressDB 利用 context-free 語法來壓縮數據,并且同時支持數據查詢和數據操作。第二,我們將 Compres
185、sDB 集成到文件系統中,使很多數據庫系統可以在不做任何改變的情況下使用 CompressDB。第三,我們將算子操作下推到存儲層,使得可以直接在存儲系統中執行數據查詢和數據操作,而不需要把大數據轉移到內存中,這提高了系統效率。我們驗證了 CompressDB 可以支持多種類型的數據庫系統,包括 SQLite、LevelDB、MongoDB 和 ClickHouse。我們用真實應用中的數據集測試了 CompressDB 在單機和分布式環境下的性能,這些數據集有不同的數據量大小、結構和內容。實驗表明 CompressDB 平均帶來 40%的吞吐量提升和 44%的延遲縮短,并實現 1.81 倍的壓縮
186、比。研究動機現代大數據系統面臨指數級增長的數據量,并使用數據壓縮來減少存儲空間。為了避免頻繁的壓縮和解壓縮操作的開銷,現有的研究探索了直接對壓縮數據執行大數據操作。這些系統在數據分析應用程序中表現出優秀的壓縮效率和性能提升。由于大數據通常存儲在磁盤中,我們的想法是基于規則在存儲層對壓縮數據進行隨機更新?,F有技術的局限性現有的壓縮技術在只讀的查詢處理方面顯示出巨大潛力,但功能完整的大數據系統必須同時支持數據的讀和寫。這就需要系統支持隨機更新以及數據的插入和刪除?,F有的壓縮技術并不支持在壓縮數據上直接修改數據,因此每次修改時都必須解壓縮和重新壓縮相對較大的數據塊,從而導致巨大的開銷。重要發現本研究
187、希望開發一種高效的技術來填補現有技術的不足,以支持直接對壓縮數據進行更新、插入和刪除,從而實現一個支持數據查詢和數據操作的高效的大數據系統。這是一項具有挑戰性的任務,因為現有的壓縮技術大多僅針對壓縮效率或讀數據操作進行了優化。此外,現有技術的壓縮數據結構不能修改。例如,一些壓縮技術基于索引和后綴數組,其中壓縮元素相互依賴,如果一小部分數據需要更新,則效率極低。系統設計本研究開發了一個新的存儲引擎 CompressDB,支持直接對壓縮數據進行數據查詢和數據操作,并且支持各種數據庫系統。我們發現,如果基于規則的壓縮方法的 DAG(directed acyclic graph,有向無環圖)深度被限制
188、在較小的數值,那么這種壓縮方法開銷較小,并適用于數據操作?;诖?,CompressDB 采用基于規則的壓縮技術并限制其規則生成深度。同時,CompressDB 可以通過操作語法規則對數據進行實時壓縮和操作。與之前基于規則的壓縮方法相比,我們開發了一系列新的設計:在元素級別,我們提出了一種新的數據結構數據洞(block hole)。在規則級別,我們為隨機更新啟用了有效的規則定位和規則拆分方案。在 DAG 級別,我們降低了規則的深度以提高效率。通過利用新的數據結構和算法設計,CompressDB 無需解壓即可高效處理數據。系統設計圖 1 展示了 CompressDB 的系統結構。CompressD
189、B 由三大模塊組成:1)數據結構模塊,2)壓縮模塊,3)運算模塊。這三個模塊支持基于 CompressDB 的數據庫系統。數據結構模塊為壓縮模塊和運算模塊提供必要的數據結構,包括三種:blockHashTable 表示數據內容到塊位置的映射關系,blockRefCount 記錄塊被引用次數,blockHole 是更新操作引起的存儲空洞。壓縮模塊支持文件系統中的分層壓縮,可應用于各種基于塊的文件系統。操作模塊可以將用戶操作下推到文件系統。圖:CompressDB 系統結構第 1 章:數據庫架構設計第 1 章:數據庫架構設計9899操作下推為了降低數據傳輸成本,本研究將算子操作下推到存儲層。算子下
190、推是指數據處理直接發生在文件系統層(較低的軟件層),使處理操作發生在更接近數據的地方?;谶@種技術,CompressDB 可以顯著減少對磁盤的數據訪問量,并加速所有上層數據庫應用程序。與數據庫系統的交互為了使 CompressDB 能夠支持各種數據庫,本研究在文件系統中開發 CompressDB。在文件系統層,CompressDB 可以處理讀取和寫入等系統調用,因為它們可以通過“提取”“替換”“添加”等操作實現。因此,CompressDB 可以支持在文件系統上運行的不同類型的數據庫系統(例如,SQLite、MySQL、MongoDB 等)。這些數據庫系統依賴于 CompressDB 提供的系統
191、調用。因此,CompressDB 可以支持數據庫系統的各種數據類型(例如,整數、浮點數、字符串等)和操作(例如,連接、選擇、插入等)。此外,我們為 CompressDB 開發了一些文件系統不支持的操作,例如“插入”和“刪除”。因為這些操作沒有對應的 POSIX 接口,我們提供了一組單獨的 API,可以有效地使用。適用性CompressDB 是一個存儲引擎,主要應用是支持各種數據庫系統,而無需修改代碼。用戶唯一需要做的就是將系統存儲目錄設置為 CompressDB 的存儲目錄。通常來說,CompressDB 適用于有大量冗余數據,并需要進行數據分析和操作的數據庫系統。對于其他應用場景,它可能仍然
192、有效,但尚未驗證。主要成果和貢獻為了驗證 CompressDB 的性能,本研究使用 CompressDB 支持多個數據庫系統,包括 SQLite、LevelDB、MongoDB 和 ClickHouse。本研究分別在單節點和集群環境中,使用多個具有不同尺寸、結構和內容的真實數據集來評估性能。集群環境中的實驗使用五節點集群和一種高性能網絡分布式文件系統 MooseFS。MooseFS 在集群中傳輸數據并提供對數據的高吞吐量訪問。與 MooseFS 的原始版本相比,CompressDB 平均帶來了 40%的吞吐量提升、44%的延遲減少和 1.81 的壓縮比,這證明了本研究的有效性。本研究做出以下主
193、要貢獻:本研究直接在壓縮數據上開發高效的數據操作,例如插入、刪除和更新。除了隨機訪問,本研究還支持數據查詢和數據操作。本研究開發了 CompressDB,這是一種集成到文件系統中的存儲引擎。CompressDB 可以無縫支持各種數據庫系統,而無需修改數據庫。本研究將數據算子操作下推到存儲系統,避免了內存和磁盤之間不必要的數據移動,從而提高了壓縮數據的處理效率。本次研究成果面向的領域本研究成果面向同時支持數據查詢和數據操作的大數據管理系統。CompressDB 提供的壓縮能力使數據管理系統能夠存儲較大的數據量,同時支持壓縮數據上高效的數據查詢和操作。存儲效率和數據查詢、操作效率在當今大數據時代是
194、至關重要的性能指標,CompressDB 可以幫助現有的數據庫系統同時提升這些指標的性能。第 2 章:數據庫性能優化101第 1 章:數據庫架構設計100第 2 章數據庫性能優化數據庫事務一致性實現上的各種細節,你注意到了嗎?數據庫的事務包含原子性、一致性、隔離性、持久性四個特性。隔離性與一致性緊密相連,它們也容易讓人迷惑。SQL 標準定義了 4 個隔離級別,但由于定義使用的是自然語言,而非形式化語言,導致人們對隔離級別的理解有所差異,各個數據庫系統的實現方式也有所不同。然而在分布式的場景下,又面臨新的問題。探索前沿研究,聚焦技術創新。本期由騰訊云數據庫高級工程師孟慶鐘為大家介紹數據庫事務一致
195、性的實現,內容包括事務的基本概念以及特性、主要的隔離級別及實現、TDSQL 事務一致性的實現。事務的基本概念及特性1.1 事務的基本概念及特性事務是用戶定義的一個數據庫操作序列,這些操作要么全做,要么全不做,是一個不可分割的工作單位。事務具有四個特性即 ACID:孟慶鐘騰訊云數據庫高級工程師,中國人民大學博士深耕存儲和優化器設計研發領域多年,熟悉 PostgreSQL 源代碼、Linux 操作系統內核代碼,目前在騰訊從事 TDSQL 存儲引擎的開發。研究領域包含數據庫存儲引擎、查詢優化等。第 2 章:數據庫性能優化第 2 章:數據庫性能優化102103 原子性(A):事務中包括的操作要么都做,
196、要么都不做;一致性(C):事務執行的結果必須使數據庫從一個一致性狀態轉移到另一個一致性狀態;隔離性(I):并發執行的事務之間不能互相干擾;持久性(D):事務一旦提交,它對數據庫的改變應該是永久性的。原子性和持久性比較直觀易懂,但是一致性和隔離性則較為復雜,不同人有不同的理解。1.2 一致性的理解一致性是偏應用角度的特性,每個應用程序需要自己保證現實意義上一致。數據庫在一致性方面對應用程序能作出的保證是:只要事務執行成功,都不會違反用戶定義的完整性約束。在執行事務的過程中,只要沒有違反約束,那么數據庫內核就認為是一致的。常見的完整性約束有主鍵約束、外鍵約束、唯一約束、Not-NULL 約束、Ch
197、eck 約束。只要定義了這些約束,數據庫系統在運行時就不會違反;只要沒有違反,數據庫內核就認為數據庫是一致的。至于現實意義上是否一致,需要由應用程序自行判斷。1.3 導致不一致的原因為什么數據庫可能會不一致呢?其實是由沖突所導致的。應用程序對數據的讀寫操作,最終體現為數據庫內核中的事務對數據庫對象的讀寫操作。如果不同事務對相同的數據進行操作,并且其中一個操作是寫操作,則這兩個操作就會出現沖突。如果不能正確處理這些沖突,就會出現某些異常。常見的異常主要有臟寫、臟讀、不可重復讀、幻讀等。并發執行的事務產生沖突,其實可以理解為科幻小說里兩個不相容的物體進入了同一時空。因為是在時空上產生沖突,所以我們
198、可以從時間和空間兩個維度解決:時間維度:把兩個操作從時間維度隔開,禁止同時訪問。這其實是基于鎖實現的并發控制思想??臻g維度:把這兩個操作從空間維度隔開,禁止訪問同一份數據。這其實是基于多版本實現的并發控制思想。1.4 隔離性的理解有些應用程序的執行邏輯,永遠不會導致某些異常的產生。在這種前提下,即使數據庫允許某些異常,實際上永遠也不會產生這些異常,數據庫仍然是一致的。我們用一個簡單的比喻來進行理解。底層模塊看作是數據庫,上層模塊看作是應用軟件,當上層軟件模塊調用底層模塊時,即使底層模塊有 BUG,但如果不踩這個坑就永遠不會觸發 BUG,則應用軟件和數據庫組成的成體看起來并沒有 BUG,數據庫則
199、會一致。隔離性是指并發執行的事務之間不能互相干擾。為了提高系統運行效率,SQL 標準允許數據庫在隔離性上進行妥協,即允許數據庫產生某些異常。那到底需要隔離到什么程度呢?這需要由隔離級別來確定。根據需求的不同,我們可以選擇不同的隔離級別。第 2 章:數據庫性能優化第 2 章:數據庫性能優化104105主要的隔離級別及實現2.1 SQL 標準定義的隔離級別我們所理解的隔離級別是指并發執行的事務能看到對方的多少。下圖是 SQL 標準給出的定義,根據數據庫禁止哪些異常來進行劃分。SQL 標準定義了四種隔離級別:Read Uncommitted、Read Committed、Repeatable Rea
200、d、Serializable。Read Uncommitted 為讀未提交,所以三種異常都有可能發生。比 Read Uncommitted 更嚴格一級的是 Read Committed,即不能讀未提交的事務,但可以出現不可重復讀和幻讀。更嚴格的是 Repeatable Read,只允許出現一種異常。最嚴格的是 Serializable,這三種異常都不允許發生。但該定義也有不足。一方面,上述定義來源于鎖實現的并發控制,但是又想擺脫對鎖實現的依賴,所以根據數據庫不允許哪些異常來定義數據庫的隔離級別?;阪i實現的并發控制可以完美匹配上面的定義,但是其它實現方式不一定匹配這個定義。比如常見的快照隔離(
201、Serializable Isolation),不會出現這三種異常,按定義屬 Serializable,但它可能出現其它異常(寫傾斜),所以快照隔離并非真正的 Serializable。另一方面,該定義使用的是自然語言,而非形式化的語言,導致人們理解有差異,有些系統因此直接把快照隔離稱為可串行化。2.2 基于鎖實現的并發控制鎖可以分為多種類型,包括讀鎖、寫鎖和謂詞鎖。讀鎖、寫鎖鎖單個數據對象,謂詞鎖鎖一個范圍。持鎖的時長也不相同,可以操作完數據直接放鎖,也可以等事務結束才放鎖,比如讀數據或寫數據前先把鎖拿到手,一直持著不放直到事務結束。下圖中的 Well-formed Reads/Writes
202、 是指讀/寫數據之前都要加鎖,非 Well-formed Reads/Writes 就是不加鎖而直接對數據進行讀/寫?;谏鲜鋈齻€前提,我們可以看到視圖中的隔離級別,關于寫的操作基本相同,都是寫數據前要先拿到寫鎖,寫鎖等事務結束后再放,主要區別在于讀鎖。Read Uncommitted 的讀不需要加鎖。事務寫數據要加寫鎖,事務結束后才放鎖。雖然讀鎖和寫鎖互斥,寫加寫鎖,但讀時不加鎖就可以直接讀到。比 Read Uncommitted 更嚴格一級的是 Read Committed,讀時需要加讀鎖。只要能加讀鎖就代表沒有其它事務在寫,寫該數據的事務一定已經提交。因為未提交的寫事務其寫鎖還沒有放,讀
203、鎖和寫鎖互斥,讀事務無法加讀鎖,因此用該隔離級別讀到的都已提交事務的數據。讀事務的讀鎖在讀完后就放鎖,下次再讀該數據時要重新加讀鎖。放鎖后再加鎖,該數據可能在讀事務未持讀鎖的期間被其它事務修改,因此讀到的數據可能有變化。第 2 章:數據庫性能優化第 2 章:數據庫性能優化106107在 Serializable 下,讀要加讀鎖,到事務提交時才放。這就保證了數據不會在讀事務執行期間被修改。因為如果其它事務要改就需要加寫鎖,寫鎖讀鎖互斥,因此其它事務的寫鎖加不上。括號中的 Both 指的是謂詞鎖和一般的讀鎖都到最后才放。Repeatable Read(RR)比 Read Committed(RC)
204、強,比 Serializable 弱。RR 比 RC 強在讀鎖,RR 比 RC 多一個讀鎖最后放。RR 比 Serializable 弱在謂詞鎖,RR 比 Serializable 少一個謂詞鎖最后放,所以 RR 可能出現幻讀。2.3 基于多版本實現的并發控制一個數據庫對象有多個不同的版本,每個版本關聯一個時間戳。事務在訪問數據時,會使用一個快照選出合適的版本。這就是多版本并發控制(MVCC),好處是讀寫互不堵塞,讀時可在多版本中讀合適的版本,寫時追加一個版本。時間戳的選擇有兩種主流的方式:使用事務的開始時間:PostgreSQL 屬于這類系統。大多數情況下,事務開始的時間越晚,則產生的版本越
205、新,但是存在特例。為了排除這些特例,PostgreSQL 的快照中有一個活躍事務列表,列表中的事務對快照不可見。使用事務的結束時間:TDSQL 屬于這類系統,事務結束的早,就可以對后續事務可見,所以這類系統的快照只需要一個時間戳,通常只是一個整數。典型的隔離級別有三種,Read Committed(RC)、Snapshot lsolation(SI)以及 Serializable Snapshot Isolation(SSI)。2.3.1 Read committed 的實現理論上,Read Committed 有三種實現方式:對每行數據來說,隨意讀取一個已經提交的版本;對每行數據來說,讀取數
206、據最后提交的版本;每條 SQL 語句開始時,獲取一個最新的快照,使用這個快照選擇數據合適的版本,修改數據時修改最新版本。第一種實現方式滿足定義,但可能因為讀取的數據太老,導致現實中無意義,因此實際系統里基本不用這種實現方式。第二種實現方式也滿足 RC 定義,但會存在讀偏序問題。讀偏序是指只讀取到某個事務的部分結果,比如 T1 更新了兩行數據,但是 T2 只讀到其中一行的更新。如果對每行數據都只讀最新提交的版本,就會存在讀偏序問題,實際系統中也較少使用這種實現方式。第三種則是實際系統里較為常用的實現方式。具體實現方式為:每條 SQL 語句開始時都會獲取一個最新的快照,用該快照選擇合適的版本,用同
207、一快照選擇就不存在讀偏序問題。2.3.2 Snapshot lsolation 的實現第二種比較典型的隔離級別是 Snapshot lsolation(SI),它并不在 SQL 標準定義的四種隔離級別中。其實現方式是:事務開始時獲取一個最新的快照,事務的整個執行過程中使用同一個快照,保證可重復讀。修改數據時,如果發現數據已被其它事務修改,則 abort。在 SI 中,上述提及的三個異常即臟讀、不可重復讀、幻讀都不存在,但存在寫偏序問題。如果兩個事務讀取了相同的數據,但是修改了這些數據中的不同部分,就可能導致異常,這種異常叫寫偏序。2.3.3 Serializable Snapshot Isol
208、ation 的實現第三種比較典型的隔離級別是 Serializable Snapshot Isolation(SSI),其實現方式為:事務開始時,獲取一個快照(通常是最新的。為了降低事務 abort 的概率,某些只讀事務可能拿到非最新快照)。修改數據時,如果發現數據已經被其它事務修改,則 abort。SSI 跟 SI 的不同在于:在讀數據時,SSI 記錄事務讀取數據的集合,再使用算法進行檢測,如果檢測到可能會有不可串行化的發生,則 abort。這種算法可能會有誤判,但不會有遺漏,因此 SSI 不存在寫偏序問題。SSI 是真正的 Serializable 隔離級別。2.3.4 寫寫沖突的處理對于
209、寫寫沖突的處理,基于多版本實現有兩種實現方式:第 2 章:數據庫性能優化第 2 章:數據庫性能優化108109 First commit win,誰先提交誰贏。誰最先把數據提交到數據庫里誰就勝出。First write win,誰先寫誰贏。誰先往數據庫里寫(不一定提交),就會阻塞后面的寫事務,從而更有可能贏。2.4 MySQL 的隔離級別在分析 MySQL 的隔離級別之前,我們需要先了解兩個概念:當前讀和快照讀。當前讀是指讀取數據的最新版本,讀寫時都需要加鎖??煺兆x是指使用快照讀取合適的版本,快照讀不加鎖。MySQL 支持 SQL 標準定義的四種隔離級別,具體實現方式如下:MySQL 在 Re
210、ad Uncommitted 直接讀,不加鎖,可能出現臟讀。MySQL 在 Read Committed 下,對于 select(非 for update、非 in share mode)使用快照讀,每個 SQL 語句獲取一個快照;對于 insert、update、delete、select for update、select in share mode 則使用當前讀,讀寫都加鎖,但不使用 gap 鎖,讀鎖用完就釋放,寫鎖等到事務提交再釋放。MySQL 的 Serializable 屬于純兩階段鎖實現,所有 DML 都使用當前讀,都讀最新版本,讀寫都加鎖,使用 gap 鎖,鎖都到最后再放。My
211、SQL 的 Repeatable Read 在 Serializable 的基礎上,放松了對 select 的限制,select(非 for update、非 in share mode)使用快照讀,其它場景則與 Serializable 相同。MySQL 在 Repeatable Read 下,混用了當前讀和快照讀,可能會導致看起來比較奇怪的現象,但只要理解它的實現方式,就可以對這些行為做出分析,這些都是可解釋的。2.5 PostgreSQL 的隔離級別MySQL 更像是基于鎖和多版本的結合。而 PostgreSQL 則是基于多版本的實現,寫時有行鎖。PostgreSQL 支持三種隔離級別,
212、即 Read Committed、SI、SSI,PostgreSQL 里沒有當前讀,都是快照讀,三種隔離級別的實現方式如下:在 Read Committed 中,每條 SQL 語句都會使用一個最新的快照。對于 update,如果發現本事務將要修改的行已經被其它事務修改了,則使用數據最新的版本重新跑一遍 SQL 語句,重新計算過濾條件、計算投影結果等,再嘗試更新最新的行,如果不滿足過濾條件則直接放棄更新。這個過程在 PostgreSQL 中被稱為 EPQ(EvalPlanQual)。在 SI 中,整個事務使用同一個快照,更新時如果發現數據已經被其他事務修改,則直接 abort。這在 Postgr
213、eSQL 代碼里有較為直觀的呈現,發現數據被改后,判斷當前隔離級別是否大于等于 SI,如果是則直接 abort,如果小于則會跑 EPQ。SSI 在 SI 的基礎上,使用 SIREAD 鎖(PG 內部也稱為 Predicate Locking),記錄本事務的讀集合。如果檢測到可能導致非可串行化,則將事務 abort。實際加鎖的數據范圍,與執行計劃相關,比如 seqscan 對整個表加鎖,index scan 只需要對部分行加鎖。需要注意的是,SIREAD 鎖是一套獨立于 PG 讀寫鎖之外的機制,與 PG 中讀寫鎖均不沖突,它只是起到記錄作用,用于寫傾斜的檢測。PostgreSQL 里面關于寫寫沖
214、突的處理方式是誰先寫誰勝出,具體實現機制為給行加上行鎖,這時其它事務就無法修改。PG 的行鎖幾乎不占內存,本文不詳細展開。第 2 章:數據庫性能優化第 2 章:數據庫性能優化110111下面介紹下 PostgreSQL 中 SSI 的檢測原理。SSI 檢測是為了消除 SI 里的寫偏序異常,主要檢測系統里是否存在下圖中的危險結構。PostgreSQL 把下圖中的結構稱為危險結構,具體如圖所示。T1 是第一個事務,T1 讀取數據后,T2 對讀取的數據進行修改,這就形成了一個讀寫依賴。同理,T2 和 T3 也形成了讀寫依賴關系。在所有可能導致不可串行化的調度里,必定會存在該結構,PostgreSQL
215、 通過檢測這種危險結構從而避免寫偏序。TDSQL 事務一致性的實現3.1 TDSQL 的架構TDSQL 的目標是讓業務能夠像使用單機數據庫一樣使用分布式數據庫。其功能特性主要有 MySQL 完全兼容、全局一致性、擴縮容業務無感知、完全原生的在線表結構變更,其存儲引擎為分布式的 KV 系統,提供事務和自動擴縮容能力。TDSQL 是存儲計算分離架構,有三個組件,分別是計算層 SQLEngine,分布式存儲層 TDStore、元數據管理層 TDMetaCluster。計算層完全無狀態,所有數據都存儲在存儲層。業務通過 MySQL 協議接到 SQLEngine,SQLEngine 在計算時如果需要數據
216、就從存儲層里讀取。存儲層是分布式的 KV 系統,用 Region 進行劃分,一個 Region 代表一個 Key 的范圍。以下圖為例,圖中有四個 Region,分別是 Region1、Region2、Region3、Region4,每個 Region 都有三個副本,副本之間用 Raft 協議保證一致性。3.2 多副本的一致性TDSQL 使用 Raft 協議保證高可用以及多副本間的一致性。在 Raft 協議中,比較重要的內容主要是選主、日志復制、安全和配置變更。選主。Raft 協議是一個強主的協議,集群中必須要有一個 leader,系統才能對外提供服務,要保證選出來的 leader 唯一。日志復
217、制。日志只會從 leader 到 follower 單向復制,日志沒有其它流動方向。安全。保證選出來的 leader 包含最多的日志,避免 leader 因為日志不全而需要到其它節點上拉取日志。如果日志不夠多,就不可能成為 leader。配置變更。Raft 協議中,系統只能有唯一的 leader,不能產生雙主。為了避免產生雙主,增減節點時使用兩階段完成。整體流程為:先是舊配置生效,再到新配置和舊配置同時生效,最后新配置生效。第 2 章:數據庫性能優化第 2 章:數據庫性能優化1121133.3 TDSQL 的并發控制TDSQL 的并發控制是基于時間戳的多版本變化控制。通過提供全局時間戳服務的
218、TDMetaCluster,保證時間戳全局單調遞增。每個事務開始時會拿一個 start-ts,即快照。讀數據時,因為數據項上有關聯時間戳,我們就讀取數據所有版本中關聯時間戳小于等于 start-ts 且最大的那個版本。start-ts 是事務開始時拿的時間戳,只要數據版本關聯的時間戳比 start-ts 大,就代表該版本是后面事務產生的,不應該可見。TDSQL 使用 write on commit。事務對數據的修改,會先緩存到本地的事務空間,在事務運行過程中只要不提交,就不會往存儲上面寫。事務的 commit-ts 寫到數據項上,這是關聯數據項的時間戳。我們以 update A=A+5 為例。
219、事務開始后先拿時間戳為 4,再選擇應該讀取哪一行。這個例子中有兩個 key 但有三個版本,A 有兩個版本,時間戳分別為 1 和 3。我們用 start-ts=4 的時間戳去取,因為要讀最新版本的值,1 為舊版本,所以讀取到的是時間戳為 3 的版本即 A=10。再進行計算 10+5=15,所以 A=15。update A=A+5 事務對數據的修改,會先緩存到本地的事務空間而非存到公共存儲,等到提交后拿到時間戳,關聯新產生的版本后再進行存儲。我們以下圖中的例子來說明 TDSQL 的并發控制。左邊事務讀到時間戳為 3 的版本即 A=10,通過計算 A=A+5 得到 15,緩存到本地事務空間再開始提交
220、。右邊事務在完成后準備提交,會先到存儲里檢查是否有其它事務先于自己往里面插入時間戳大于 4 的版本,讀取后發現最新版本關聯的時間戳為 3,因為 3T2,不允許出現 T2-T1。第 2 章:數據庫性能優化第 2 章:數據庫性能優化130131上述情況只涉及部分級別,我們還可以根據實際情況細分出更多的級別和一致性模型,用 Elle 進行更多的驗證。最后,我們還可以從以下四方面對 Jepsen/Elle 事務一致性測試框架進行優化:框架本身并不簡單,需要很多參數調節才能達到效果。比如有些情況壓測不充分,小概率異常難于發現,有時不易復現。不支持 Delete,不支持謂詞范圍查詢。無法檢驗幻讀(Phan
221、tom),無法判別 Repeatable Read 和 Serializable 之間的區別。采用新數據格式,而非傳統回歸測試的 SQL 語句,分析和 debug 都比較困難。Jepsen 有額外錯誤注入功能,但作者在 Elle 文章中并沒有細說。比如很多異常是因為某些節點重啟斷聯導致的,正常情況下允許并無異常。參考文獻1 Atul Adya,Barbara Liskov,Patrick E.ONeil:Generalized Isolation Level Definitions.ICDE 2000:67-782 Peter Alvaro,Kyle Kingsbury:Elle:Inferr
222、ing Isolation Anomalies from Experimental Observations.Proc.VLDB Endow.14(3):268-280(2020)向量化執行從理論到實現,僅需五步!胡翔騰訊云數據庫高級工程師博士畢業于中國科學院軟件研究所,加入華為高斯實驗室工作多年,加入騰訊后主要負責 TDSQL PG 版數據庫向量化執行引擎等相關特性的設計開發工作。隨著硬件技術的不斷發展,數據庫系統也需要進行相應的優化,以便可以充分發揮出底層硬件提供的能力。以查詢計劃執行為例。原有的數據庫執行一個查詢計劃,往往采用火山模型的方式。這種上層算子遞歸調用下層算子獲取并處理元組的方
223、式,存在虛函數調用次數較多、指令或數據 cache miss 率高的缺陷,并且這種一次處理一個元組的方式無法使用 CPU 的 SIMD 指令進行優化,從而造成查詢執行效率低下的問題。向量化執行就是解決上述問題的一種有效手段。探索前沿研究,聚焦技術創新。本期 DB洞見由騰訊云數據庫高級工程師胡翔為大家介紹向量化執行的基本原理、技術創新以及向量化引擎的相關實現。論文解讀1.1 論文簡介經研究,論文作者發現數據庫系統的 CPU 使用率(IPC 0.7)較低。原因在于,火山模型的一次處理一個元組的執行方式,導致了較高的解釋執行代價,阻止了可以允許 CPU 并行執行的編譯優化,這對性能的影響至關重要。第
224、 2 章:數據庫性能優化第 2 章:數據庫性能優化132133而 MonetDB/MIL 使用一次處理一個列的執行方式,避免了上述問題,但是數據的全部物化導致內存帶寬受限,進而影響 CPU 效率。最終作者在兩個模型之間找到了一個折中點,為 MonetDB 設計實現一個新的執行引擎 MonetDB/X100,使用向量化執行的方法,提高 CPU 使用率,在實際驗證中性能提升較為明顯。下圖為該論文的目錄:1.2 CPU 是怎么樣工作的?本論文發表于 2005 年,它列舉了 10 年內 CPU 的發展狀態,具體如下圖右上角的折線圖所示。我們可以看見 CPU 的發展速度越來越快,CPU 晶體管數目每隔兩
225、年會增加一倍,在當時是符合摩爾定律的。下方的點線相當于晶體管的長度,每隔兩年有 1.4 倍的減少,相當于整個容積提升了兩倍。中間的虛線表示的是使用了 pipeline 技術的 CPU,它具體指的是把一個指令劃分成多個階段,多個階段之間可以并行,帶來了較為可觀的性能提升。下圖中的左下圖描述的是一個簡單的 pipeline,它劃分成了 5 個階段,中間指令執行的時候,不同指令的不同 stage 可以并行執行。Pipeline 的運行會受某些條件約束,最主要的兩個影響因素是依賴關系和分支預測。依賴關系是指如果一個指令依賴前一個指令,就必須等待前一個指令執行結束之后才能放入 pipeline。相當于兩
226、個指令并不是獨立的,必須有一個多余的等待動作,因此并發粒度會降低。分支預測指的是 CPU 會預測程序將要執行的分支,并將其放入到 pipeline 中,但是如果預測失敗,之前執行的 pipeline 都會廢棄,因此會對 pipeline 的效率有較大影響。另外,超標量 pipeline 提供多個 pipeline,允許多個指令并行,從而使 IPC 大于一。具體示例如下方右下圖所示。我們編程時不需要特別關注哪些指令是獨立的或是相關的,實際上這些工作是通過編譯優化自動實現的。編譯優化中有一個比較重要的技術,即 loop pipeline,可以把一個循環里的多次迭代進行 pipeline,從而允許并
227、發執行,可以極大提高循環的執行效率。下圖左下角是執行一個循環的示例,該循環有 3 次迭代,順序執行則需要消耗 9 個 circle(假設 1 個 stage 執行時間為 1 個 circle)。通過編譯優化將其組成 pipeline 形式后(假設指令之間相互獨立),就可以在 5 個 circle 內完成整個三次迭代,提升了將近一半的效率。有些特定的 CPU 也會做一些特定的優化,如下圖所示的這款 CPU Itanium2,它會主動將指令進行劃分,再將相互依賴的指令拆解到不同的 pipeline 里,這樣就可以分別進行并發執行。而其他 CPU 使用較多的是亂序執行,即 CPU 自己需要承擔部分調
228、度任務,需要將某些獨立的指令拆分出來,再放到 pipeline 里。但 Itanium2 使用了指令劃分,這樣可以減輕自身負擔,因此可以把更多精力投入到 pipeline 中,提高了 pipeline 的運行能力。同時 Itanium2 對分支預測也做了對應優化,把 if then else 中的 then 和 else 這兩個分支都執行一遍。在后續執行時,會根據 if 的結果來確定拋棄對應分支獲得的結果。這是較早的 CPU 處理方式,但應用較為廣泛,目前某些硬件 CPU 上也會有類似的應用。第 2 章:數據庫性能優化第 2 章:數據庫性能優化134135上圖右上表研究了分支預測對性能的影響。
229、一個帶 Filter 條件查詢的兩種不同實現在兩種不同 CPU 的執行時間對比,其中,數據列均勻分布在 0100 區間內,故可以根據 X 來表示查詢篩選率。帶分支的實現將滿足條件的數據放到結果數組里面,而不帶分支的實現先把條件賦給一個布爾值,然后將數據放到結果數組里面,但是結果數組序號由自增變成對布爾值做加法,從而把條件去除,但指令數會增加??梢钥闯?,針對于 CPU Athlon,使用帶分支的實現,在選擇率較低或篩選率較高時,執行時間較短,表明分支預測誤判率較低時執行效率較高,而在中間位置篩選率中等時耗時較長,表明分支預測誤判率較高時執行效率較低。使用不帶分支的實現,執行時間比較穩定,因為沒有
230、分支,不受分支預測的影響,所以是一條比較直的線。而針對于 CPU Itanium,無論使用帶分支或者不帶分支的實現,執行時間都比較穩定,表明其 CPU 獨有的優化生效,但是帶分支的實現性能更好,而不帶分支的實現由于指令數更多性能變差。此外,CPU 芯片上的 cache 對 CPU 性能影響較大。部分研究表明 cache miss 對數據庫系統影響非常大。一些 cache 對齊的 btree,或者列式內存數據結構,以及限定 cache 上的算法也有助于提高性能。從上圖右下角可以看出,距離 CPU 越近計算速度越快,主存延遲是 cache 的 20 倍到 200 倍。如果將計算都集中在 cache
231、,則有利于發揮 CPU 的實際算力??偟膩砜?,對 CPU 性能影響較大的主要是 cache 命中率、分支數目、分支預測是否成功,還有指令之間是否獨立。數據庫系統的 CPU 使用率接近 0.7,低于其他系統,比如科學計算相關的系統的 CPU 使用率可以達到 2。數據庫系統不應該有如此的表現,特別是針對數據量和計算量都特別大的分析型任務。作者指出,需要充分利用好 CPU 的 pipeline 并行能力來提高 CPU 的利用率,這也是論文的主要優化目標。1.3 TPC-H Query 1 Benchmark論文選取 TPC-H 作為測試集,將 Query 1 作為研究對象,進行 profile 性能
232、測試,深入研究定位性能瓶頸。Query 1 掃描了幾乎所有的數據,同時包含多種計算,分別為列與常量減法 2 個、列與常量加法 1 個、列與列乘法 3 個、聚集函數 8 個(sum 4 個、avg 3 個、count 1 個)。此外,比較特殊的是分組鍵為兩個單字節的字符。論文逐個分析了在傳統關系型數據庫、MonetDB/MIL 以及手寫程序上 Query 1 的性能。第 2 章:數據庫性能優化第 2 章:數據庫性能優化136137作為傳統的關系型數據庫,查詢執行嚴格按照關系代數來實現,具體的執行過程,則按照火山模型 pipeline 的處理方式。因為其關系代數自由度較高,帶的參數比較多,需要實現
233、一個可以處理表達式所有情形的解釋執行器,這個會更加復雜。論文對 MySQL 進行了性能 profile,第二列表示當前函數占用的百分比(除去調用的部分),第一列是第二列累積的百分比,第三列是調用次數,第四列是每次函數調用執行的指令數,第五列是 IPC。論文發現,所有的實際工作只占了整個語句執行時間的一小部分。實際的計算只占了 10%,而 28%用來創建探測哈希表,其他 62%消耗于元組解析、數據拷貝等,鎖和 buffer 管理等其他因素的影響很小。另外,實際計算的效率與 CPU 能力差異大,主要原因是一次處理一個 tuple,無法進行 loop pipeline 優化,從而增加函數調用次數,進
234、而降低了 CPU 執行效率。其他數據庫也出現了類似的問題。論文接著對 MonetDB/MIL 執行引擎進行了 benchmark。MonetDB 是一個列存數據庫,相當于將數據進行垂直劃分再逐列存儲,每列存儲形式為 BAT 形式。其使用的代數查詢語言叫做 MIL,可以列式地處理輸入的多個 BAT,并輸出一個 BAT。另外,MonetDB 的自由度較低,針對特定的數據類型進行區分實現,而不是通用的實現,從而減少部分解釋代價。Benchmark 結果如右圖所示。前四列是不同數據量的對比,包括執行時間和帶寬。第五列是內存占用情況,以 1M 為單位。第六列是結果集大小??梢钥吹?,MonetDB/MIL
235、 解決了執行率低的問題,20 個調用執行占比達到 99%,比其他數據庫都要高。但與此同時,每個調用也都變成了內存密集型。前述提到在 MonetDB/MIL 中會將數據全部物化,物化的數據量太大,導致內存帶寬受限,進而影響 CPU 效率。作者還使用 MonetDB 的 UDF 獲取性能的基準?;鶞食绦虬焉婕暗牧卸甲鳛閰?,以 BAT 形式的數組表示,添加 restrict 關鍵字,用來告訴編譯器這個數組里的元素都是獨立不相關的,以便進行編譯優化。作者還利用了 group 列的特征,即單字節字符,直接按照 bit 位組合來獲取數組的序號,避免創建一個復雜的哈希表。另外,還有部分子表達式的優化。第
236、2 章:數據庫性能優化第 2 章:數據庫性能優化1381391.4 X100:A Vectorized Query Processor作者在前述優化的基礎上再進行優化,提出了 MonetDB/X100。測試顯示,其性能更強,超過基準性能。本部分將詳細介紹 MonetDB/X100 的有關細節。其設計目標是:能夠在執行大量的查詢時達到較高的 CPU 使用率;可以擴展到其他應用領域,如數據挖掘和多媒體檢索,并實現同樣的高效率可擴展性代碼;還能根據底層存儲規模大小進行伸縮。為了達到性能目標,作者梳理了計算機架構中可能出現的瓶頸點。磁盤:ColumnBM I/O 子系統面向高效的順序數據訪問來進行設計
237、,同時采用列式存儲,解決多余的數據存儲代價,減少帶寬壓力。另外,基于列式存儲還可以做一些輕量級壓縮,進一步減少帶寬壓力。內存:設計跟磁盤類似,也采取了列式存儲的組織形式,目的也是為了減少內存占用和帶寬壓力。Cache:把數據組織成 vector 形式,再把 vector 完全放入 cache 中,使得計算都在 cache 內進行,這樣可以減少數據到內存的換入換出,從而提高計算效率,而不必考慮內存帶寬問題。CPU:操作或算子都使用向量化原語,目的是便于編譯器優化成 loop pipeline 的高效代碼。右下圖為架構示意圖,上半部分是 MonetDB/X100 與原先的 MonetDB、Mone
238、tDB/MIL 之間的依賴關系,下半部分是更直觀的整體結構??梢钥吹?,執行引擎部分,處理單元都是一個方塊,即代表一個向量,按向量力度來進行處理。這些向量能夠直接放到 cache 里進行計算。在查詢語言方面,MonetDB/X100 與 MonetDB/MIL 不同,可以生成多個列向量(仍然是 BAT 形式),以作為其他操作或上層算子的輸入。具體語法如右上角圖所示,上面是標準的 SQL 語句,這實際上是 Q1 的簡化版本,而下面是 MonetDB/X100 代數的形式。執行流程仍然按照火山模型 pipeline 的方式,但是以 vector 為粒度。右下角的圖是簡化版本的 TPC-H Q1 在
239、MonetDB/X100 的執行流程。圖中包含了 4 個算子,即 Scan、Select、Project 和 Aggregate。Scan 每次從 MonetDB BATs 中獲取多個列對應的 vector,圖中有三列。Select 創建一個 selection-vector,在滿足謂詞條件的元組位置進行標記。這個數組在后面的計算過程中也同樣會用到。引入 selection-vector 的好處在于,不必將篩選出來的數據進行拷貝和重新編排,允許在原來的輸入向量上直接計算,效率會更高。Project 主要是完成表達式計算并為最終聚集計算做準備。Project 時會使用 selection-vec
240、tor 跳過之前被篩選掉的元組,避免不必要的計算。Aggregate 計算主要包含兩部分:計算每個元組在 HashTable 中的位置,計算聚集函數并將結果更新到對應的位置。新的位置需要在 HashTable 中創建。所有下層算子執行結束,不再生產新的 vector 后,遍歷 HashTable 獲取最終結果。Aggregate 時也會使用 selection-vector。以上就是 MonetDB/X100 的查詢執行流程,整體流程類似于原來的火山模型,主要區別在于執行粒度從原來的一個元組變成一個 vector,函數調用次數大幅減少,同時對一個 vector 進行循環計算時可以用編譯優化來提
241、高 CPU 運行效率。另外,具體實現時需要增加一些輔助數組,以及對原有的執行邏輯的改造等。第 2 章:數據庫性能優化第 2 章:數據庫性能優化140141與標準的 SQL 語言相比,MonetDB/X100 的代數形式是比較特殊的。它包含兩種數據輸入,Dataflow 表示在 pipeline 中流轉的元組,Table 是特殊的 Dataflow,表示物化的表。MonetDB/X100 的向量化原語部分,主要用來進行批量快速計算。以右圖為例,這是一個 double 類型的數據在做加法。函數的參數包括一些輸入和輸出的向量化,還包括一個類似于 section-vector 的輔助數組。如果 sel
242、ection-vector 不為空,就需要參考數組得到有效元組的位置,然后進行計算,而如果為空,就直接計算即可。每個輸入都是向量,可以完全放入到 cache 里面進行計算。批量處理的循環可以進行 loop pipeline 的優化,提高 CPU 的使用效率。另外,MonetDB/X100 針對每個數據類型都有單獨的實現,可以避免元組解析的代價。按照原語模板自動生成的方式,MonetDB/X100 包含了上百個向量化原語。MonetDB/X100 還運用到組合向量化原語,它也可以進一步提高執行效率。以做加法為例,以往可能需要兩個操作數先讀取數據,最后寫入數據,中間才是一條加法的指令,數據的讀寫代
243、價太高,就導致了實際計算工作占比較小。MonetDB/X100 對向量化原語進行組合后,一次執行中實際工作做的更多,這樣就均攤了數據讀寫的代價。在數據存儲方面,MonetDB/X100 采用列式存儲。但每列單獨存儲的方式一般會有更新刪除等代價,比如更新一行可能會涉及修改多個列文件。MonetDB/X100 通過經典的 delta 結構來解決列存更新/刪除代價增加的問題。比如 Delete 直接使用 deletion list 來記錄 tupleid;Insert 并沒有在原來的數據上增加,而是直接放在另一個 delta 結構區域,用行存方式進行存儲;Update 采用 delete+inser
244、t 的執行方式;Delta Merge 則是將 delta 結構區域合并到原列存上。另外,還有一些索引信息用于匯總局部的最大值和最小值,從而可以用于數據篩選。這些都是比較通用的列存實現方式。1.5 TPC-H 實驗作者在論文中將 MonetDB/X100 和 MonetDB/MIL 進行對比,在不同的處理器、不同的數據量上,MonetDB/X100 的性能都明顯更優。作者還在 Q1 上做了性能 profile 測試。在內存帶寬上,MonetDB/X100 的帶寬比較高,內存占用較少,另外有些列也采用 enum 類型進行了優化。第 2 章:數據庫性能優化第 2 章:數據庫性能優化142143作者
245、還對向量大小進行測試,即不同向量大小對性能的影響,從上圖中可知,大小適中時性能最優。過大過小都不是最優結果。過大無法放入 cache,會有額外的從內存讀寫數據的代價。過小則類似于原來的火山模型,無法做編譯優化,無法使用 CPU 并發能力的優化,而且函數調用次數增加,實際的工作占比則會變小。1.6 論文總結這篇論文基于經典火山模型和 MonetDB/MIL 的列式查詢執行模型做了進一步優化,從而得到性能極大提升。以往的火山模型一次處理一個 tuple 的方式造成過大的解釋執行代價,阻止了對性能影響極大的編譯優化。MonetDB/MIL 不存在上述問題,它可以讓 CPU 高效運行,但是全部物化的執
246、行方式會造成內存密集型的問題,內存帶寬會受限。通過向量化執行方式,使用較小數量的可放入 cache 的列式數據,即 vector,進行批量計算,則可解決上述兩個問題。驗證結果顯示,性能與其他相比有兩個數量級的提升。向量化執行引擎實現詳解2.1 如何實現向量化執行引擎我們結合 TDSQL 的具體實現,來詳細介紹向量化的實現過程。如何實現向量化執行引擎,其核心工作主要包括四個部分:向量化執行框架:向量化執行計劃的生成和執行以及與非向量化執行計劃的兼容。向量化數據結構:合理設計向量的內存組織形式,盡可能使用 cache 資源,減少內存拷貝。向量化算子實現:批量計算改造,拆分成小的循環來執行簡單的操作
247、,便于編譯優化成高效程序。向量化函數實現:與算子實現類似,還需要對表達式計算框架進行調整,簡單的計算函數可以通過 SIMD 顯式向量化。2.2 向量化執行框架向量化計劃生成的方式,采用貪婪的方式,盡可能將計劃路徑中涉及的算子轉換成向量化執行的方式。不支持向量化的計劃節點,可以通過在其上添加一個行轉向量的算子,相當于把輸出從行元組變成了向量,從而支持上層算子的向量化執行。我們以一個簡單的 SQL 為例,原有的火山模型執行流程和向量化模型執行流程如右圖所示。2.3 向量化執行數據結構向量化執行數據結構的原則有兩個:一個是盡可能將數據連續存儲在更靠近 CPU 的位置,如 cache;另一個則是列式組
248、織形式,方便對單個列進行快速計算。在以往,一個元組用一個 TupleTableSlot 來表示。為了便于向量化計算,我們把它改造成一個包含多個元組的結構,通過 VectorTableSlot 來表示。它實際上不是簡單地把元組合并起來,第 2 章:數據庫性能優化第 2 章:數據庫性能優化144145而是對數據進行垂直劃分,每列數據放在一起。相同列的數據組織在一起就稱為列向量,通過 ColumnVector 來表示,為實際計算的操作數。具體包括:元數據,如行數、列表示信息等;標記數組 flags,如標記 null 信息等;值數組 vals;內存掛載位置 bufs。數據則區分為定長和非定長存儲。定長
249、數據直接存儲在 vals 數組中。變長數據因為不能直接存在上面,需要分配非固定大小的內存,掛載在 bufs 上,并把地址存在 vals 數組中,內存可以快速復用。2.4 向量化算子實現向量化算子實現也有類似的原則:一個是盡可能地將復雜的循環處理過程拆解成多個簡單的小循環,以便批量地對同種類型的數據進行快速循環處理;另一個是減少分支以及數據依賴等。我們以 HashAgg 算子、HashJoin 算子為例,來介紹如何實現向量化改造。下圖實際上是一個簡化版的 Query 1,兩列做分組,再分別進行 HashAgg 操作。具體改造過程分為五步:1.對輸入的元組向量在分組列上批量計算 hash 值;根據
250、計算的 hash 值批量計算 hash bucket 值。2.構建 hash table,采用 Open-addressing 的沖突處理方式,記錄元組對應的 hash entry;如果內存占用超過內存限制,元組則需要下盤。3.計算聚合結果,并將其更新到對應的 hash entry 上。4.遍歷 hash table,拼接成向量輸出。5.如果存在下盤的數據,重新構建 hash table 并執行上述步驟。我們以下圖為例來介紹 HashJoin 算子向量化的過程。具體改造過程分為五步:1.對 Scan 內表得到的元組向量進行批量計算,得到 hash 值和 hash bucket 值。2.對內表構
251、建 hash table。3.對 Scan 外表得到的元組向量批量計算 hash 值和 hash bucket 值。4.使用外表元組向量探測內表構建的 hash table,再進行批量的匹配操作,如果匹配則進行標記,如果不匹配就去找下一個位置進行匹配。5.根據標記數組將匹配成功的行進行對應的 Proj 列輸出。第 2 章:數據庫性能優化第 2 章:數據庫性能優化146147節省 30%磁盤空間的同時如何保障數據安全?對于數據庫系統而言,存儲容量和數據安全是影響用戶系統的成本及信息安全的重要因素。TDSQL PG 版通過支持壓縮來減少磁盤空間使用以此來節省成本,同時支持對表內容加密來保證用戶數據
252、的安全性。探索前沿研究,聚焦技術創新。本期 DB洞見由騰訊云數據庫高級工程師丁艷飛從工程創新的角度,為大家介紹 TDSQL PG 版透明壓縮和透明加密的相關實現,主要包括整體架構介紹、壓縮方法簡介、透明壓縮實現、加密脫敏介紹四個部分。TDSQL PG 版整體架構 TDSQL PG 版是一款分布式數據庫,在內核層面由 GTM、CN、DN 節點組成。其中 GTM 是事務管理器,負責協調整個集群的事務,并管理全局的對象。CN 節點是協調節點,是業務訪問入口,每個節點之間相互對等,都能對外提供一致性視圖。DN 節點是數據節點,業務運行過程中產生的數據都會在 DN 節點上進行存儲。數據庫內核配合 OSS
253、 系統可以對外提供指標監控、運維管理、實時告警、安全審計、數據治理等運維管控能力。隨著業務的不斷進行,所產生的數據會越來越多,對 DN 節點的磁盤丁艷飛騰訊云數據庫高級工程師目前主要負責騰訊分布式數據庫 TDSQL-PG 內核以及 Oracle 兼容開發方面的工作,包括 Oracle 兼容性、執行引擎等內容。2.5 向量化函數實現我們具體實現時沒有增加新的類型,只是對原有版本的函數進行改造,支持各種通用的數據類型。在具體執行時需要進行函數的替換。以右圖為例,這是一個 intel4 類型的判斷,左邊是比較簡單的判斷,右邊的輸入則是列向量。需要注意的是,左邊的判空邏輯實際上是在函數外面,右邊因為要
254、對每行進行判空,所以這里涉及的函數比較多??偨Y和展望本期分享中,我們通過一篇有關向量化執行的經典論文介紹了向量化的基本原理,并結合 TDSQL 的實現詳細闡述了向量化的實現過程。為了進一步提升查詢執行效率,我們正在對算子的向量化算法進行更加深入的優化,盡可能方便編譯器對程序自動地進行向量化編譯。另外還通過 SIMD 顯式指令進一步提高向量化的粒度。編譯執行也是解決類似問題的有效手段,特別是對于表達式計算、元組解析等通用模塊尤為有效,該部分工作也正在進行中。未來我們會帶來更多的優化,以輕松應對各種不同復雜業務的需求。第 2 章:數據庫性能優化第 2 章:數據庫性能優化148149存儲也造成越來越
255、大的壓力。數據節點的存儲容量也越來越成為制約整個 IT 系統部署密度、影響系統成本的主要因素。所以目前大多數客戶都會要求數據庫系統具備數據壓縮存儲的能力。壓縮方法簡介2.1 存儲介質壓縮針對壓縮需求,在數據庫領域出現了一些特定的壓縮方法,其中一種就是在存儲介質中進行壓縮。這種方式會在原有的 SSD 內部引入一個計算引擎。數據按照常規方式來寫入 SSD,盤內的計算引擎可以對寫入的數據進行壓縮處理,再把壓縮后的數據寫入到存儲物理介質中。整個壓縮過程對業務透明,上層業務基本無感知,應用方面也可以不進行修改。此外,這種方式相當于零拷貝技術,不需要做額外的數據傳輸,當數據量增長較多時,也可以很方便地增加
256、多塊盤來實現壓縮能力的線性擴展。傳統的關系型數據庫(使用 B-Tree 數據模型,如 Oracle/MySQL/SQL Server/PostgreSQL)可以直接部署在這種智能存儲介質上使用,且不存在占用業務 cpu 的問題,使用方便,對性能影響小。但目前而言,很少有客戶使用,主要原因是這些存儲介質的價格昂貴,整體部署成本大,基本上很難實現大批量使用。2.2 數據編碼另一種常用的壓縮方法是使用數據編碼。數據庫關系表中存放的一般是結構化數據,同一列中字段具有相同的數據類型且有明確的數據邊界。對于不同的列,如果它們之間有相互關系,在這些列之間可能也會存在較多相似的數據。所以我們可以利用數據明確的
257、字段邊界和豐富的類型信息,采用一定的編碼技術來實現數據庫的壓縮。下表中羅列了部分常見的編碼方式,包括字典編碼、RLE 編碼、常量編碼、差值編碼、前綴編碼等。不同的編碼有不同的適配場景,字典編碼是較為通用的編碼方式,RLE 編碼則主要用于連續相等的數據,常量編碼主要針對近似性常量化的數據,差值編碼適用于在小值域范圍內分布均勻的整數型數據,前綴編碼則適用于前綴相同的字符型數據。第 2 章:數據庫性能優化第 2 章:數據庫性能優化150151右邊是一個數據庫使用數據編碼的簡單例子,是用字典編碼對頁面進行壓縮的一個實例。當頁面滿或者頁內行的數量達到閾值后,通過數據編碼進行壓縮,較為常用的做法是把重復性
258、較高的數據進行去重,將去重后的數據建立字典存儲在頁面固定區域,而把原來存放數據的地方存成指向特定字典下標的引用。但這種方案最大的問題是頻繁更新對性能影響較大。頻繁更新會導致頁級字典的頻繁重建,與此對應的頁面數據的引用也需要頻繁變動。除此之外,在實現上還需要在頁內增加字典區域,對現有頁面沖擊大,實現復雜。2.3 通用壓縮第三種方法是使用通用的壓縮算法對數據庫中的數據進行壓縮。常見的壓縮算法會把輸入的數據當作連續的字節流進行處理,再基于一些經典的編碼算法比如串編碼、嫡商編碼來實現對信息的壓縮。下表中列舉了部分常見的壓縮算法,不同的壓縮算法追求的目標不同,比如 lz4 算法追求快速的壓縮和解壓的速度
259、,而 zstd 算法則追求更高的壓縮比。在數據庫中,buffer 里存儲的原始頁面一般具有固定大小。這種方式的優點是,在將頁面寫入到磁盤中時能較好地計算每一個頁面在文件中的偏移位置,以方便對這個頁面讀或者寫。但對頁面引入壓縮算法后就會出現另一種情況,因為不同的頁面存儲的文件內容不同,所以采用壓縮算法后,實際壓縮后的大小是不固定的。即使對于同一個頁面,隨著增刪改的進行,壓縮之后的數據大小也有可能變化。所以當引入壓縮后,再重新去磁盤上讀取相同的頁面時,頁面的偏移位置就會發生變化,這樣會對整個數據庫層面的讀寫產生巨大的沖擊。為了解決這個問題,通常在數據庫領域會新增一個新的壓縮頁面類型。這個新的壓縮文
260、件里每個頁面的大小都是固定不變的,一般會配置成原始頁面的二分之一、四分之一或者八分之一,以此來存儲壓縮之后的數據。但這種實現方式也存在問題,它的壓縮主要受限于壓縮頁面大小的配置。如果壓縮頁面配置較大,當前的數據量比較小,壓縮之后所占用的空間也比較小,但由于壓縮頁面比較大,所以寫入到磁盤文件中時還是要占一個壓縮頁面大小,這就達不到節省磁盤空間的目的。但如果把壓縮頁面設置的比較小,則會存在另外一個問題。原有的頁面有可能需要多個壓縮頁面進行存儲,所以一個原始的頁面會分裂成多個壓縮頁面,需要額外的原數據信息進行額外的存儲。在讀取時,需要在原有的壓縮頁面解壓后,再把多個頁面重新拼裝成為原始頁面,因此又增
261、加了實現上的復雜度。透明壓縮實現我們的目標是實現數據文件的壓縮以此來節省存儲空間,且方案實現要簡單且適合 OLTP 場景。通過前文的介紹,我們先把智能存儲介質排除,因為它屬于磁盤層面的壓縮方案,不屬于數據庫本身,暫時不作考慮。同時我們也可以排除數據編碼,它不適合于頻繁更新的場景,且會對現有頁面的結構造成較大的沖擊,不滿足代碼低侵入性的要求,因此我們把實現基調定在通用壓縮方法上。通用壓縮方法存在前文提到的問題,即頁面壓縮后大小不固定,因此偏移也不固定。如果存在一種方法能使頁面壓縮后,還能按照原頁面固定大小進行偏移的查找,又能把壓縮后節省的空間釋放出來,整個方案的實現會簡單很多。這時我們可以采用文
262、件系統本身提供的打洞能力。文件系統的打洞能力是保證文件其他屬性不變的情況下,告知文件系統在指定偏移的位置上有一段空間不再需要,可以主動釋放該文件所占用的物理空間。第 2 章:數據庫性能優化第 2 章:數據庫性能優化152153找到這種方式后,我們把最終的壓縮方案定為使用通用壓縮加文件打洞來實現。整個方案以頁為壓縮粒度,在頁面落盤時先使用壓縮算法對頁面進行壓縮,壓縮完后按照原有的頁面偏移的計算方式找到頁面應該寫入的位置,將壓縮后的數據寫到固定的位置中。對于固定頁面的大小,除去壓縮數據后剩余部分所占用的空間,我們可以利用打洞工具將剩余磁盤空間釋放。在讀取頁面時,從磁盤上讀取后先進行數據的解壓,再將
263、解壓后的頁面放入 buffer 中。因此整個壓縮解壓在 buffer 層向上完全透明,在代碼實現層面滿足低入侵要求。為了提高壓縮速度,在刷臟頁時我們采用多進程并行的方式進行壓縮。在 syncbuffer 時,輪詢壓縮進程,將臟頁 id 加入待壓縮隊列,壓縮進程的主循環發現隊列中有數據,就會對相應頁面進行壓縮,再將壓縮后的頁面存儲在 slot 中。Syncbuffer 將臟頁 id 加入待壓縮隊列后不會等待壓縮完成,而是去訪問已壓縮完成隊列,將隊列里對應的頁面加入 buffer id list 中,再對 list 中的頁面進行刷盤。因此頁面壓縮采用多線程并行,而壓縮和落盤可以看作是一個 pipe
264、line 的過程,以此來加快速度。除此之外,我們也對 Vacuum 整理頁面時的壓縮進行了優化。因為元組更新時不是原位更新,會在原有的元組上做刪除標記,再插入一個新的元組。這樣在頁面里就會存在 dead tuple,因此 auto Vacuum 的進程會定期對頁面進行整理,將頁面中有效的元組按照原有的組織方式從頁面尾部進行排列和插入。對其他的 dead tuple,則會在 line pointer 上做標志,表示它們可以重用。在頁面整理后,沒有用的空閑空間可以進行置 0 操作,以此提高整個頁面中的數據重復度。右圖是一個簡單的測試。我們對表進行不同程度的更新,從 20%逐漸到 100%。我們發現
265、在全表更新的情況下,優化后比優化前可以節省 30%的磁盤空間。為了讓備機也支持壓縮,我們也做了相應適配。備機的信息來源渠道主要是主機的日志,因此備機獲取壓縮信息的場景之一就是 FULL PAGE IMAGE。在頁面第一次做修改前,在 FULL PAGE WRITE 的機制下,我們會把頁面記錄到日志里。備機在應用到這種類型的日志時,可以獲取到頁面上記錄的壓縮信息,在備機頁面落盤前根據壓縮信息進行頁面的壓縮,再進行打洞處理。另一種場景是備機在回放其他日志時可能產生一些新的頁面。假如在回放一個插入操作時,當前頁面空間不足,需要新增一個頁面來進行插入,這時備機會初始化一個新的頁面?;蛘弋攤錂C在回放新建
266、索引日志、索引分裂、元數據初始化的操作時,都會涉及到頁面初始化動作。備機本身沒有壓縮信息,因此要想得到壓縮信息,就需要在主機里對相應日志類型增加壓縮信息,給備機進行應用。這樣備機在初始化頁面后,就可以把壓縮信息設置在頁面的頁面頭上,后續就可以按照正常流程進行操作,在實際的落盤前再根據頁面頭上的壓縮信息進行壓縮處理。第 2 章:數據庫性能優化第 2 章:數據庫性能優化154155除了備機外,我們也需要對數據庫相對應的配套做壓縮的支持。一是備機重建。備機重建是使用 pg_basebackup 工具,讀取對應的主節點上的數據文件,再把該數據文件備份到指定目錄下。因為數據文件經過打洞處理,在讀取這個數
267、據文件時,它會把每個頁面對應的壓縮信息讀取上來,對空洞部分進行零的填充,這樣一來,備份好的文件每個頁面的大小就等于沒有壓縮前的大小。為了應對這種重建的壓縮情況,在獲取每個表文件后,我們會把該表文件的每個頁面讀取上來,根據頁面上記錄的壓縮后的大小,找到空洞對應的偏移位置,在指定的偏移位置上重新進行打洞處理。為了提高處理的速度,我們對多個文件采取了并行處理。二是壓縮信息查看。為了方便查看壓縮信息,我們對 pageinspect 進行了相應的適配。它能夠讀取指定頁面上的信息。在讀取時先將文件讀取,再讀到 buffer 里進行解析和顯示。對壓縮而言,我們不需要 buffer 里的信息,我們真正需要的是
268、磁盤中實際存儲壓縮的信息,因此我們在插件里增加了一些函數,可以支持從磁盤中直接把相應頁面讀上來,再去解析頁面頭,再顯示對應的壓縮信息。三是壓縮率獲取。因為我們通過文件系統的打洞功能來實現壓縮,而文件經過打洞后,使用普通的 stat 或者 ll 命令讀取的大小是包含空洞大小的,而真正存儲所占用的大小則不包括空洞大小,因此可以利用這兩個的實際比值來獲取壓縮比。四是參數設置。我們可以對表和索引分別指定不同的壓縮算法,同時也可以查看整個表的信息。在信息顯示里,我們的 option 選項能列出壓縮的情況。如果想要查看每個頁面的情況,我們也提供了函數支持,可以列出對應頁面里壓縮算法和壓縮后頁面的大小。如果
269、想要查看整個表的壓縮概述,我們也提供了函數支持,可以看出當前表有多少頁面是壓縮的,有多少頁面沒有壓縮。我們對壓縮和非壓縮情況做了 tpcc、pgbench 測試,可以對比壓縮前、壓縮開啟后產生的影響。我們在測試過程中把 shared buffer 設置得比較小,構造出頁面頻繁換入換出的場景,在這種情況下對頁面做頻繁的壓縮和解壓操作。從測試結果可以看到,在 tpcc 模型下壓縮比可以達到 2.3,ptmC 有 15%的新的下降。在 pgbench 模型下,壓縮比能達到 6.8,tpcB 有約 17%的下降。以我們目前采取的壓縮策略來看,采用通用壓縮算法,本質上是以時間換空間,因此性能劣化在我們預
270、期之內。第 2 章:數據庫性能優化第 2 章:數據庫性能優化156157加密脫敏介紹TDSQL PG 版整體的安全體系是以數據庫三權分立為基礎的。我們把傳統的 DBA 角色分解成安全管理員、審計管理員、數據管理員。在安全規則方面,我們針對安全管理員增加了安全規則和數據透明加密、脫敏的規則;在審計方面,我們結合業界的審計標準和業務的場景需要,增加對象審計、用戶審計、SQL 審計;數據管理員則履行之前 DBA 數據權限管理和數據庫運維的職能。通過三個角色的劃分,我們從根本上杜絕了系統的安全死角。安全管理員負責整體安全規則的制定,通過這種方式來約束系統中所有的角色。審計管理員則負責制定審計規則、設計
271、系統中包括審計管理員在內的所有角色,做到系統的所有操作都具有可追溯性。數據管理員負責數據庫的日常運維。這三個管理員之間相互約束,相互監督。在這部分我們主要分享數據透明加密、脫敏的規則。加密主要針對數據文件本身的泄露問題。目前常見的數據庫的存儲格式基本屬于半公開狀態,只要拿到了數據文件,再配合相應的數據程序,他人就可以解出里面對應的數據。在這種情況下,我們需要通過數據中心對一些有保密要求的文件進行加密,防止泄漏,因此需要把存儲的文件進行加密。TDSQL PG 版支持表級加密和列級加密兩種方式。通過下圖,我們可以看到表級加密的流程和壓縮的流程非常相似。一般是在頁面進行落盤前,先進行加密處理,再進行
272、落盤,在磁盤里存儲的都是經過加密后的文件。當需要讀取時,我們會從磁盤上讀取后先進行解密,再把解密后的數據放在 buffer 里,所以用戶在讀取時可以直接從 buffer 里獲取明文數據。列加密的不同之處在于,列加密會在一行數據進行插入時將這一列值先進行加密,加密完后再插入到 buffer 的頁面中,所以 buffer 里是加密的狀態,磁盤里存的也是加密的文件。當用戶需要使用這一列或這個頁面時,我們就會對相應的這列先進行解密,再給用戶進行明文顯示。脫敏對應的使用場景是,在訪問數據時,部分用戶沒有權限看到這些數據的明文,但為了進行運維操作或其他工作需要訪問這些數據,但他們不需要知道這些數據真正的存
273、儲結果。針對這種場景,我們可以在磁盤里存儲正常的明文數據,在用戶進行讀寫時進行處理。如果在用戶讀取時,發現是授權用戶,就可以直接從 buffer 里拿到正常的數據進行顯示;但如果發現是非授權用戶,就需要先對數據進行脫敏處理,把脫敏后的結果展示給非授權用戶。加密和脫敏之間互不沖突,可以靈活地搭配使用,既可以對存儲內容進行加密,也可以針對不同的用戶,采用用戶自己設置的脫敏規則來保證數據訪問的隔離性。下圖是加密和脫敏的簡單示例。在這個例子中,非授權用戶是分公司經理和人力資源經理,授權用戶是董事長。對同樣的表格而言,董事長能看到所有信息的原始狀態,薪酬和家庭信息也能正常顯示。但對非授權的分公司經理而言
274、,在薪酬和家庭信息方面,他查看到的都是脫敏后的結果,例如他只能看到薪酬為 0,家庭信息為空。通過這樣的方式,系統里不同等級的用戶對同樣的數據會看到不同的視圖,以此達到隔離的效果。第 2 章:數據庫性能優化第 2 章:數據庫性能優化158159加密和壓縮流程相似,因此我們也支持加密和壓縮的同時使用。在這種情況下,我們整體處理邏輯不變,在原始頁面進行落盤前先進行壓縮和加密處理,在讀取時進行解密和解壓的處理。但需要注意的是,我們要保證先進行壓縮再進行加密。這主要考慮到壓縮對于結構化的數據能夠達到較好的效果,而數據頁面本身存儲的就是相對結構化的數據,如果把原頁面作為輸入源,壓縮效果會相對較好。但如果把
275、原頁面先進行加密,加密后得到的數據是相對雜亂的數據,再把相對雜亂的結果作為壓縮的輸入源,壓縮效果就會大打折扣。因此為了保證壓縮和加密的正常使用,我們在處理時需要先進行壓縮再進行加密。與此相對應,讀取時也是從磁盤讀取后先對頁面進行解密,再把解密后的數據進行解壓,再放入到 buffer 里,以此保證數據正常的讀和寫。TDSQL PG 版起源于技術成熟、功能強大的 PostgreSQL,在此基礎上騰訊云數據庫構造和發行了功能更豐富、穩定性更好、兼容性更廣、安全性更高、性能更強、擴展性極好的分布式數據庫 TDSQL PG 版產品。騰訊公司對 TDSQL PG 版具有完全自主知識產權,實現安全可控,具備
276、在中高端市場規?;娲鷩鈹祿斓哪芰?,在數據庫基礎軟件層面有力支撐了國家安全可控戰略發展。當前 TDSQL PG 版已經在金融、保險、通信、稅務、公安、消防、政務等多個行業的核心交易系統上線運行,為眾多行業客戶提供優質服務。一些有趣的 B+樹優化實驗作為目前數據庫引擎的兩種主要數據結構,LSM-tree 和 B+-tree 在業界已經有非常廣泛的研究。相比 B+-tree,LSM-tree 犧牲一定的讀性能以換取更小的寫放大以及更低的存儲成本,但這必須建立在已有的 HDD 和 SSD 的基礎上。探索前沿研究,聚焦技術創新,本期 DB洞見由騰訊云數據庫高級工程師王宏博進行分享,主要介紹一篇 2
277、022 年 FAST 的論文,主題為“基于硬件透明壓縮的 B+樹優化”。本次分享的論文針對可計算存儲 SSD(支持硬件透明壓縮)提出了三種有趣的設計方法,從而極大地減少了 B+-tree 的寫放大(10X)以使其接近甚至超越 LSM-tree。以下為分享實錄:論文簡介本期分享的是 2022 年 FAST 的一篇論文,主題是基于硬件透明壓縮的 B+樹優化,該論文在內容上主要分為三個部分:第一部分是背景介紹,作者分別介紹了現有的 B+樹及其軟件壓縮、存儲硬件透明壓縮、B+樹和 LSM-tree 的簡要對比以及 B+樹寫放大的構成分析。王宏博騰訊云數據庫高級工程師研究領域包括分布式事務、分布式存儲、
278、副本一致性及高可用等方面,目前主要負責騰訊分布式數據庫 TDSQL-PG 內核開發以及性能優化方面的工作。第 2 章:數據庫性能優化第 2 章:數據庫性能優化160161第二部分是算法部分,作者針對性地提出了三種設計來優化 B+樹的寫放大:確定性的 page shadowing 來解決頁面原子寫引入的寫放大。通過頁面本地增量日志來減少刷臟頁引入的寫放大。通過稀疏日志來減少 redo 日志引入的寫放大。第三部分是實驗部分,作者通過前面所述的三個方法實現了一個新的 B+樹(我們稱之為 B-tree 為以示區別),并詳細比較了多種條件下的測試結果。背景部分2.1 現有的 B+樹及其軟件壓縮我們熟悉的
279、開源數據庫有很多都使用 B+樹作為存儲引擎,比如 MySQL、MongoDB、PostgreSQL 等,騰訊云數據庫 TDSQL-PG 也是基于 B+樹來實現的。B+樹的結構如下方的左圖所示,它通過一個個的數據頁面構成一個樹狀結構以提供索引加速數據查找。常見的數據頁面大小有 8k 和 16k,通過數據頁面壓縮可以減少 B+樹的空間占用以降低成本,此外還可以減少 B+樹的寫放大。在前面所提到的基于 B+樹實現的數據庫中,MySQL、MongoDB 都實現了 B+樹的軟件壓縮特性,TDSQL-PG 的商業化版本也有實現。軟件壓縮具有不依賴于特定硬件、靈活性高的優點,但美中不足的是,受限于現有的 I
280、O 4K 對齊的約束會產生一些額外的空間浪費。比如下方右圖,一個 16K 的數據庫頁面經過壓縮后產生了 5K 的數據。受限于 IO 4K 對齊的要求,當落盤時這 5K 的數據實際上要占用 8K 的空間,產生了額外的 3K 的空間浪費。常 見 的 B+樹 壓 縮 算 法 有 Lz4、zlib、ZSTD,后 面 所 提 的 LBA 則 是 指 logical block adressing,即邏輯上的磁盤塊大小。2.2 存儲硬件透明壓縮我們先簡單回顧下閃存的特性。如下圖所示,閃存支持三種基本操作:讀、寫、擦除。閃存的讀、寫都以 page 為單位,一般是 4K,擦除則是以 block 為單位,一個
281、block 一般包含多個 page,比如包含 64 個 page。之所以閃存需要支持擦除,是由于閃存的寫比較特殊,一個 page 只能在擦除之后一次寫入,而不能更新。但從用戶的角度來看,SSD 需要支持更新,因此我們在 SSD 里會有一個 FTL 層來實現該邏輯,即下圖中間藍色部分所示。CSD,顧名思義是一種帶有計算能力的存儲。如下圖所示,一個通用的 CSD 在存儲層可以支持 API 調用來進行計算下推。硬件透明壓縮存儲作為 CSD 的一種,支持在其內部對數據進行壓縮和解壓,后續我們提到的 CSD 主要是指帶有硬件透明壓縮的 CSD。對比最下方的兩張圖,左邊是普通的 SSD,右邊是 CSD???/p>
282、以看到普通 SSD 的 LBA 和閃存都是以頁面為基礎,且都是 4K 大小,可以由 FTL 直接進行映射。CSD 和 SSD 類似,都對外提供了 4K 的 LBA 接口,但 4K 的數據經過壓縮后長度不一定,所以在 CSD 內部,FTL 的閃存管理需要支持對變長數據的管理,這也使得 CSD 的 FTL 變得更加復雜。如此一來 CSD 能帶來什么好處呢?第 2 章:數據庫性能優化第 2 章:數據庫性能優化162163總體來看,CSD 的好處體現在它有更優的成本、更好的性能,但在此不做詳細討論,我們主要關注 CSD 的技術特點。CSD 可以實現邏輯地址和物理地址的解耦。如下方左圖所示,對一個實際閃
283、存大小只有 4T 的 CSD 來說,它可以對外提供遠大于閃存大小的 LBA。另外,如下方右圖所示,對于稀疏數據的寫入可以進行透明壓縮而不產生空間浪費?;?CSD 的這兩個特性,我們在上層應用如數據庫中,可以做一些針對性的設計,以進一步利用 CSD 的特性。2.3 B+樹和 LSM-tree 的簡要對比我們先回顧 LSM-tree 的寫入流程。如右上圖所示,一條數據記錄的寫入,會先寫 log,再進入 memtbale,memtable 寫滿之后會變成 immutable memtable,再與 L0 的 SSTable 進行合并,上層的 SSTbale 也會在合適的時機與下層 SSTable
284、進行合并。我們再簡要回顧 B+樹的寫入流程。一條記錄的寫入也是先寫入 log,再寫入 B+樹的數據頁面,當數據頁面寫滿時就需要進行頁面分裂,所以大部分時間 B+樹的頁面其實是不滿的。另外,由于 B+樹是直接操作目標頁面,當內存不夠時就會涉及到頁面的淘汰問題,這時就需要刷臟頁。極端場景是每寫一條記錄就需要刷一次臟頁,如果一條記錄是 128 字節,頁面是 8k,那么對應的寫放大就是 64 倍。B+樹和 LSM-tree 在寫方面的主要區別是:LSM-tree 是一個緊密排布的結構,可以占用更小的寫空間以及產生更小的寫放大。B+樹的寫放大與數據集的大小成正相關,與單條記錄的大小負相關。對于數據完全可
285、以放入內存的場景,B+樹可以產生比 LSM-tree 更小的寫放大。因此這篇論文聚焦在大數據集以及小數據記錄的場景,也就是說,B+樹在該場景下寫放大明顯劣于 LSM-tree。作者通過在 CSD 上執行一個隨機寫入的測試,初步展示了 B+樹和 LSM-tree 的差異。結果如右下圖所示,可以看到 LSM-tree 占用的邏輯空間更小,這與 LSM-tree 的數據緊密排列有關,經過 CSD 壓縮之后,B+樹的物理空間占用也大幅度減小。此外,通過對比發現,LSM-tree 的寫放大顯著小于 B+樹,因此本論文的目標就是需要通過改進 B+樹的實現來大幅度減小 B+樹的寫放大。2.4 B+樹寫放大的
286、構成分析B+樹的寫放大構成主要包含三個部分:第 2 章:數據庫性能優化第 2 章:數據庫性能優化164165 為了保證事務原子性采用 redo log 引入的寫放大;由于 lru buffer 淘汰或者 checkpoint 刷臟頁而引入的寫放大;為了保證頁面寫的原子性而引入的寫放大,如 MySQL 的 double write。當考慮 CSD 的壓縮特性時,公式 1 所列的寫放大會引入表示壓縮比的系數 ,其含義為 壓縮后的數據大小比壓縮前的數據大小,其位于 0-1 之間,也就是說,CSD 會降低寫放大。算法部分3.1 Deterministic Page Shadowing算法 1 是通過確
287、定性的 page Shadowing 來減少 page 原子寫引入的寫放大,其本質上是 copy on write,一次頁面刷盤寫只需要寫一次即可。具體實現如右圖所示,1 個數據頁面在磁盤上會有 2 倍的連續 LBA 空間與之對應,這個 LBA 被劃分為兩個 slot,兩個 slot 輪流寫入以實現 copy on write,從而保證每次寫入不管是否成功,頁面的前鏡像總是保持穩定。在頁面寫入成功后,可以通過 trim 操作清理前一個 slot 的物理空間占用。在頁面的寫失敗時,我們可以通過 checksum 進行檢測,并通過頁面前鏡像+redo 恢復出最新的頁面。對于寫成功后 crash 保
288、留兩個完整頁面的場景,則可以通過比較頁面的 LSN 找到最新的頁面,并 trim 掉舊的頁面。本方法帶來的一個負面影響就是每次讀需要產生額外的數據傳輸,但是作者認為 pcie 的傳輸能力遠大于 flash 的速度,因此不成問題。此外,對于支持計算下推的 CSD,我們可以將該邏輯下推,也可以避免掉額外的數據傳輸。3.2 Localized Page Modification Logging算法 2 是通過本地修改日志來減少刷臟頁引起的寫放大。如右圖所示,一個數據頁面在存儲層對應一個數據頁面+一個 4K 增量 log 頁面,兩者的邏輯地址連續。在內存中數據頁面被分為 k 個 segment,每個
289、segment 大小是 Ds,每次頁面修改所涉及到的 segment 會在 bitmap f 里面記錄。當需要刷臟頁時,我們可以通過 bitmap f 找到所有的修改增量,并把它記為,再把、bitmap f、補零后的數據一起寫入盤中。以一個 16K 的數據頁面為例,對應的寫放大會減少 75%。經過 CSD 透明壓縮后,寫放大可進一步減小。在該方法中,由于額外引入了增量日志,會造成一定的物理空間的放大,我們可以通過參數 T 來控制物理空間的放大。當增量日志大于 T 時直接刷臟頁并重置增量日志,T 越大寫放大越小,但是對應的物理空間放大就越大。讀取頁面時我們需要讀原始數據頁面+增量日志部分,再構造
290、最新的數據頁面。由于讀頁面需要額外讀所以會造成一定的讀放大,但由于數據頁面的地址和增量日志 LBA 是連續的,因此一次讀請求即可解決。另外,增量日志相對較小,所以可以認為這部分的讀放大影響較小。第 2 章:數據庫性能優化第 2 章:數據庫性能優化1661673.3 Sparse Redo Logging算法 3 是稀疏日志,用于減少 Redo log 造成的寫放大。目前常見的 Redo log 的實現如左圖所示,日志采用連續緊密排列,事務 1 提交時日志 L1 落盤,事務 2 提交時由于日志 buffer 未滿,因此會在 L1 的 buffer 中追加 L2,L1 和 L2 一起落盤,事務 3
291、 提交時,L1、L2 需要連同 L3 再落盤一次,直到當前日志 buffer 頁面被寫滿,才會開啟新的頁面。在該例子中,從 flash 的角度看,L1 被寫了 3 次,后兩次都是額外的寫放大。當采用右圖所示的稀疏日志時,每次日志寫入都是用一個新的頁面,頁面經過透明壓縮后落盤,則完全可以避免寫放大的問題,另外由于透明壓縮的存在也不會產生額外的空間占用。此外稀疏日志也是可以用于 LSM-tree 的。實驗部分在實驗部分,作者通過前述的三個優化方法實現了一個新的 B+樹(命名為 B-樹以示區別),同時還實現了一個 Baseline 的 B+樹,并且與 Rocksdb、WiredTiger 做了比較全
292、面的實驗對比。4.1 Experiments with Log-Flush-Per-Minute 在實驗 1 中,Redolog 的刷盤頻率被設置為每分鐘 1 次,以屏蔽掉 Redo log 寫放大的影響。通過不同的數據集大小、單條記錄的大小以及 B+樹頁面大小設置,作者比較全面地評估了 B-樹的特性,得出初步的結論:B-樹的寫放大已經比較接近于 LSM-tree,即右圖中所有的橙色、綠色和紫色的部分比較接近。而 Baseline B+tree 和 WiredTiger 這兩種標準的 B+Tree,它們的寫放大都比較大。通過實驗數據的對比,我們還可以發現:B+樹的寫放大與 Record siz
293、e 負相關,即 Record size 越小其寫放大越大。以前面提到的極端例子為例,每條 Record 都會引起一次刷臟頁,這時的寫放大是 Page size 比 Record size。如果 Page size 固定,當 Record size 越小,其寫放大會越大。B+樹的寫放大與并發數負相關,即并發數越大其寫放大越小。我們可以理解為,當并發數比較高時,一個頁面里可能會產生更多的更新,一次刷臟頁就會有更多的 Record 進來。LSM-Tree 的寫放大與數據集正相關。當數據集比較大時,做不同層之間的 merge,它的寫放大會變大。4.2 Experiments with Log-Flus
294、h-Per-Commit 在實驗 2 中,Redo log 的刷盤頻率被設置為每次事務提交,這也是我們在實際生產中為保證不丟數據而常用的方法。作者用一個單獨的圖來展示 log 部分的寫放大,我們可以看到 LSM-tree 和 B+樹的 Redo log 的寫放大都跟 Record size 相關,Record size 越小,寫放大越大,同時與并發數負相關,隨著并發數的上升寫放大會減小。這是由于并發數的上升實際上會產生更多日志的合并,即每次寫盤時會有更多的事務的提交日志一起寫入,因此寫放大減少了。對 B-樹而言,由于采用了稀疏日志,所以其在不同 Record size 大小和不同并發數下都保持
295、很小的日志寫放大。圖 12 展示了一個考慮日志寫放大后的整體寫放大。我們可以看到,B-樹相對 LSM-tree 而言,它的寫放大進一步降低,除了圖中紅框部分外,其他場景都比 LSM-tree 更優。第 2 章:數據庫性能優化第 2 章:數據庫性能優化168169如果把兩個不同的日志刷盤方式放在一起對比,我們可以發現,Redo log 的寫放大對 B-樹而言總體保持穩定,因為它使用了稀疏 Redo log。當其他 B+樹和 LSM-tree 采用每事務提交時,兩者的寫放大顯著提高,尤其是在并發數比較小的情況下,Redo log 日志所引起的寫放大占比較高。4.3 Impact of Thresh
296、old T在實驗 3 中,作者對算法 2 中提到的本地頁面修改增量日志的算法做了驗證。參數 T 表示當增量日志達到多少量時直接刷數據頁面并重置增量日志,參數 Ds 表示頁面分割的 segment 的大小。如圖 14 和表格 2,我們可以看到當 T 越大時,B-樹的寫放大會越小,但物理空間占用則會越大,這與我們之前的理論分析一致,所以這兩個參數在實際中需要 trade off。圖 13 展示了壓縮對于物理空間占用的影響。由于 B-樹需要額外的 4k 頁面占用,所以 B-樹的邏輯空間占用顯著高于其他 B+樹,但在經過 CSD 壓縮后,實際物理空間的占用放大則縮小了,相比 B+樹已經不明顯了。4.4
297、 Speed Performance Evaluation在實驗 4 中,作者對比了三種場景下的 B-樹相對于其他幾種樹的性能。在隨機點查場景中,B-tree 和 LSM-tree 相當,但兩者都小于 B+樹。這與 B-tree 采用本地頁面的增量日志刷臟頁有關,因為每次讀取需要重新通過增量日志來構造最新的數據頁面,這會引入額外的開銷。對隨機范圍查詢而言,B-樹和 B+tree 的性能相當,優于 LSM-tree。作者認為,由于一次查詢多條記錄,B-樹引入的讀開銷占比變小了,所以其性能比較高。在隨機寫場景中,B-樹稍微優于 LSM-tree,兩者都顯著優于 B+樹,這是由于前兩者都擁有更小的寫
298、放大。但需要強調的是,由于 B-樹和 B+樹都涉及到 buffer 淘汰問題,所以雖然是只寫負載但實際的 IO 卻是讀寫混合的,因為 B-樹需要讀取額外的 4k 增量日志頁面,因此讀 IO 比 B+tree 有所放大,其相對 B+樹的性能提升也就沒有像寫放大減少得那么顯著。第 2 章:數據庫性能優化第 2 章:數據庫性能優化170171拓展與總結最后分享一個 CSD 和 PG 結合起來的有趣實驗。PG 中有個名為 heap only tuple update 的機制,用于優化非索引列更新。如下圖所示,當更新一個元組時,如果當前元組所在的 heap 表頁面有空閑空間,就可以直接在當前頁面插入一條
299、新的元組,再把老元組的 ctid 指向新元組即可,這樣可以避免去找新的數據頁面寫入以及在索引頁面中寫入新的記錄。PG 中還有一個 fillfactor 的參數來控制 heap 表的填充率,我們可以利用較小填充率來換取非索引列更新的性能。當引入 CSD 之后,還可以進一步避免造成物理空間占用的放大,比較完美地解決了以往需要進行物理空間占用和性能 trade off 的問題。綜上所述,這篇論文提供了一個不同的視角去比較 B+樹和 LSM-tree,讓我們更深入地理解 B+樹和 LSM-tree。作者通過三種實現比較簡單但卻非常巧妙的方法來改進 B+樹,在 CSD 上也取得了很好的效果,為我們帶來了
300、很好的啟發。參考文獻1 Closing the B+-tree vs.LSM-tree Write Amplification Gap on Modern Storage Hardware with Built-in Transparent Compression2 The True Value of Storage Drives with Built-in Transparent Compression:Far Beyond Lower Storage Cost3 Understanding Flash:Blocks,Pages and Program/Erases4 B+Tree inde
301、x structures in InnoDB5 PostgreSQL 與透明壓縮6 The Internals of PostgreSQL7 WiscKey:Separating Keys from Values in SSD-Conscious Storage第 2 章:數據庫性能優化第 2 章:數據庫性能優化172173基于 LSM-Tree 存儲的數據庫性能改進韓碩騰訊云數據庫高級工程師,北京大學博士研究方向包括分布式數據庫、圖數據庫、存儲引擎優化等,在 SIGMOD 等數據庫領域頂級會議上發表多篇論文。2019 年博士畢業后加入騰訊計費,主要負責 TDSQL 存儲相關的研發。LSM-T
302、ree(Log Structured Merge Tree)是數據庫領域內較高效的 key-value 存儲結構,被廣泛應用于工業界數據庫系統,如經典的單機 kv 數據庫 LevelDB、RocksDB,以及被諸多分布式 NewSQL 作為底層存儲引擎。本期將由騰訊云數據庫高級工程師韓碩來為大家分享基于 LSM-Tree 存儲的數據庫性能改進,重點介紹近年來學術界對 LSM-Tree 的性能改進工作,并探討這些改進措施在工業界數據庫產品中的應用情況以及落地的可能性。以下是分享實錄:LSM-Tree 基本結構LSM-Tree 全稱為“Log Structured Merge Tree”,是一種基
303、于磁盤存儲的數據結構。1996 年 Patrick O Neil 等人在信息系統期刊上發表了一篇題名為“Log Structured Merge Tree”的論文,首次提出 LSM-Tree 結構。相比于傳統的 B+樹,LSM-Tree 具有更好的寫性能,可以將離散的隨機寫請求轉換成批量的順序寫操作,無論是在 RAM、HDD 還是在 SSD 中,LSM-Tree 的寫性能都更加優秀。作為高效的 key-value 存儲結構,LSM-Tree 已被廣泛應用到工業界數據庫系統中,如經典的單機 kv 數據庫 LevelDB、RocksDB,以及被諸多分布式 NewSQL 作為底層存儲引擎,近日發布的
304、 TDSQL 新敏態引擎存儲模塊也運用了 LSM-Tree 結構。LSM-Tree 結構有較多優點:寫性能強大、空間利用率高、較高的可調參特性、并發控制簡單、有完備的修復方案等。LSM-Tree 的本質是基于 out-place update 即不在原地更新。下圖展示了 in-place update 即原地更新與 out-place update 即不在原地更新的區別。在 in-place update 中,數據更新操作是將數據的新值直接覆蓋舊值,但會帶來隨機讀寫問題。在 out-place update 中,數據更新操作則是將新值按時間順序追加到文件末尾,通常會帶有版本號用于標記新值或舊值
305、,一般為順序寫,因此寫性能更好,但缺點是會產生冗余數據,在讀性能和空間利用率上也無法與 in-place update 相比。當前主流的 LSM-Tree 基本結構如下圖,分為內存和磁盤兩部分。內存采用 MemTable 數據結構,可以通過 B+樹或跳表實現。MemTable 用于接受最新的數據更新操作,維護內部數據 in-tree 邏輯上的有序。磁盤中的部分數據存儲物理有序。數據按照層進行堆放,層次越往下,數據寫入的時間就越早。每層內部按是否有序可劃分為一個或多個 sorted run。一個 sorted run 內部的數據必定有序(通常指物理上有序)。sorted run 數據可進一步劃分
306、為不同的 SSTable。當 MemTable 中的數據寫滿時,其會向 L0 進行落盤操作即 Flush 操作。如果 LSM-Tree 的某一層也達到容量閾值,就會向下合并,將同樣的 key 的過期版本清除,即 compaction 操作。第 2 章:數據庫性能優化第 2 章:數據庫性能優化174175RocksDB 是基于 LSM-Tree 的存儲引擎,其基本結構如下圖。它與 LSM-Tree 基本結構在主體上保持一致,不同點在于 RocksDB 的內存中增加了 Immutable MemTable 部分,這是用于 Flush 操作的寫緩存機制。當 MemTable 中的數據寫滿時,會先暫存
307、到 Immutable MemTable 中,再通過后臺的異步線程將 Immutable MemTable 慢慢落盤到磁盤中的 SST 基本件。RocksDB 中還增加了 WAL 日志,用于 crash 時的數據恢復。LSM-Tree 支持的查詢可分為點查與范圍查詢兩大類,對應的執行方式如下:點查:先查 MemTable,再從 SST 中的 Level0、Level1.一層層向下探查,找到數據就返回。由于上層數據必定比下層數據的版本新,因此返回的都是最新數據。范圍查詢:每一層都會找到一個匹配數據項的范圍,再將該范圍進行多路歸并,歸并過程中同一 key 只會保留最新版本。LSM-Tree 性能的
308、衡量主要考慮三類因素:空間放大、讀放大和寫放大。第一類因素是空間放大。在 LSM-Tree 中所有寫操作都是順序追加寫,數據的更新操作則是通過創建一個新的空間來存儲新值,即 out-place update。與此同時,因為舊值不會立即被刪除,因此會占用部分空間。實際上這部分冗余數據占用空間的大小要遠大于有效數據本身,這種現象被稱為“空間放大”。LSM-Tree 會利用 compaction 操作來清理舊數據從而降低空間放大。第二類因素是讀放大。在 LSM-Tree、B+樹等外存索引結構中,進行讀操作時需要按從上到下的順序一層層去讀取存儲節點,如果想讀一條數據,就會觸發多次操作,即一次讀操作所讀
309、到的數據量實際上要遠大于讀目標數據本身,從而影響讀性能,這種現象被稱為“讀放大”。第三類因素是寫放大。在 LSM-Tree 中,compaction 操作會將多個 SST 文件反復讀取,合并為新的 SSTable 文件后再次寫入磁盤,因此導致一條 kv 數據的多次反復寫入操作,由此帶來的 IO 性能損失即寫放大。LSM-Tree 的初衷是想通過空間放大和讀放大來換取寫放大的降低,從而達到極致的寫性能,但也需要做好三方面因素的權衡。EDBT 2016 的一篇論文首先提出 RUM 猜想(R 為 read,U 為 update,M 為 memory usage,RUM 為三者縮寫)。該論文認為,三者
310、之間存在權衡關系,無法使得三個方面的性能都達到最優,因此必須在三者之間進行有效權衡。以 compaction 操作為例,其目的是保證數據的局部有序和清理數據舊值,即一個 sorted run 內部的多個 SST 文件中的數據是有序的,從而降低讀放大。對一個查詢而言,在一個 sorted run 中至多只需要讀取一個 SST 中的一個數據塊。如果完全不做 compaction 操作,即一直順序寫,LSM-Tree 就會退化為 log 文件,這時寫性能達到最佳。因為只需要順序寫 log 即可,不需要做任何操作。但讀性能將會處于最差狀態,因為在沒有任何索引、無法保證有序性的情況下,每次想讀到固定的數
311、據項,就需要掃描所有的 SST 件。如果 compaction 操作做到極致,實現所有數據全局有序,此時讀性能最優。查詢時只需要通過索引和二分法即可迅速找到要讀的鍵值的位置,一次 IO 操作即可完成,但代價是需要頻繁進行 compaction 操作來維持全局有序狀態,從而造成嚴重的寫放大,即寫性能變差。這就延伸出兩種 compaction 策略:Tiering compaction:較少做 compaction 操作,有序性較弱,每一層允許有多個 sorted run。Leveling compaction:更頻繁的 compaction 操作,盡可能增強有序性,限制每一層最多只有 1 個 s
312、orted run(L0 層除外)。第 2 章:數據庫性能優化第 2 章:數據庫性能優化176177Compaction 優化策略與分析Leveling compaction 策略中,每層只有一個 sorted run,sorted run 內部的數據保持物理有序。具體實現上我們以 RocksDB 為例。一個 sorted run 可以由多個 key 不重疊且有序的 SSTable files 組成。當第 L 層滿時,L 層會選取部分數據即部分 SSTable,與 L+1 層有重疊的 SSTable 進行合并,該合并操作即 compaction 操作。Tiering compaction 策略
313、中,每層可以有至多 T 個 sorted run,sorted run 內部有序但每層不完全有序。當第 L 層滿時,L 層的 T 個 sorted run 會合并為 L+1 層的 1 個 sorted run。因為每層允許有多個 sorted run,因此 SST 文件間可能會存在數據范圍的重疊,compaction 操作的頻率會更低,寫性能也會更強。兩種 compaction 策略各有優劣。Tiering compaction 因為 compation 操作頻率低,過期版本的數據未能得到及時清除,因此空間利用率低,由此帶來的查詢操作的代價比較高。在極端情況 log file 即完全不做 co
314、mpaction 操作時,寫入性能最優。Leveling compaction 則會更頻繁地做 compaction 操作,因此數據趨向更有序。極端情況 sorted array 即數據達到全局有序時,此時查詢性能和空間利用率最優。SIGMOD 2018 的一篇論文對上述兩種 compaction 策略進行了詳細的理論分析,分析結果歸納為下表,其分析結果與前述一致。理論分析過程如下:第 2 章:數據庫性能優化第 2 章:數據庫性能優化178179先從寫入操作復雜度進行分析。I/O 以 block 為基本單位,一次 I/O 平均讀寫 B 條 kv,因此一條 kv 寫一次磁盤的代價可以表示為 1/
315、B。在 leveling compaction 策略中,第 i 層向第 i+1 層最多合并 T 次,在第 j 層合并到第 i 層時,其可被后續的數據項反復進行 t-j 次合并。由此可得,每條 KV 在第 i 層即任意一層被平均 IO 的讀寫次數為 t/2 次,即 O(T)。從 MemTable 一直向下合并到最后一層即第 L 層,會得到平均寫磁盤數即“TL/B”。以下圖為例,每合并一次,第 i 層的增量不會超過 1/T。第 i 層通道從空到滿,其最多向下合并 t 次,因此每條 kv 在每層平均被合并 2/T 次。在 Tiering compaction 策略中,每條 KV 在第 i 層時只會被
316、合并一次,因此從 MemTable 一直向下合并到最后一層即第 L 層,會得到平均寫磁盤數即 L/B。以下圖為例,在第 i-1 層向第 i 層合并時,會在第 i 層產生一個新的 sorted run,平均每條 kv 在每層只會被合并一次。第 2 章:數據庫性能優化第 2 章:數據庫性能優化180181再從空間放大率進行分析。space-amplification 定義為:過期版本的 kv 數與有效 kv 總數之比。在 Leveling compaction 策略中,最壞的情況為前 L-1 層的每條數據在最后一層即第 L 層中都各有一條未被清除的過期版本數據。每層容量為比值為 T 的等比數列,根
317、據等比數列求和公式,第 L-1 層的 kv 數量占總 kv 數的 1/T,最后一層則占(T-1)/T,因此空間放大為 1/T,空間放大率較低。在 Tiering compaction 策略中,允許每層最多有 T 個 sorted run,最壞情況下最后一層的 T 個 sorted run 之間都是每個 kv 的不同版本,即一條 kv 在最后一層的每個 sorted run 都出現一次,共 T 個不同版本,因此空間放大為 O(T)。下圖將兩者進行對比。Leveling compaction 的空間放大系數為 1/T,因此空間利用率較高。Tiering compaction 在最壞情況下,最后一層
318、的每個 sorted run 都是冗余數據,空間利用率較差。再從查詢角度進行分析。點查使用 Bloom filter 進行過濾,將其分成兩類:讀穿透,即查詢數據不存在。假設知道每次發生假陽性的概率,如果判定結果為假陽性,則需要讀一次磁盤。在 Leveling compaction 策略中,每層只有一個 sorted run,實現完全有序,因此每層只需要查詢一次 Bloom 過濾器。根據二項分布期望公式,推導出的期望讀盤次數為 Le 的-m/n 次方。第 2 章:數據庫性能優化第 2 章:數據庫性能優化182183在 Tiering compaction 策略中,每層最多有 T 個 sorted
319、 run,最多可能查詢 T 次 Bloom filter,在前述結構基礎上乘以 T 的系數,根據推導出的期望讀磁盤次數,其查詢性能落后于 Leveling compaction。如果查詢數據存在,因為發生假陽性的概率遠小于 1,因此大部分情況下,無論是 Tiering compaction 還是 Leveling compaction,都只需要讀一次盤。再從范圍查詢角度進行分析。該論文將范圍查詢分為短范圍查詢和長范圍查詢。短范圍查詢指返回 block 數量的大小不超過最大層數范圍的兩倍;反之則為長范圍查詢。通過統計公式發現,短范圍查詢在歸并前的各版本數據趨向于均勻分布在各層,因此 Leveli
320、ng compaction 和 Tiering compaction 的查詢復雜度分別為 O(L)和 O(TL)。長范圍查詢在歸并前各版本數據大部分來自最后一層,因此上述兩種策略的代價分別為返回 block 數的大小、返回 block 數的大小再乘以 T,因此 Leveling compaction 比 Tiering compaction 的讀性能更好。第 2 章:數據庫性能優化第 2 章:數據庫性能優化184185 通過上述理論分析,可以得到如下結論:Tiering compaction 的寫放大低,compaction 頻率低,其缺陷為空間放大高、查詢效率低,更利于 update 頻繁的
321、 workload;Leveling compaction 的寫放大高,compaction 操作更頻繁,但空間放大低,查詢效率高。盡管 Tiering compaction 和 Leveling compaction 的空間放大不同,但導致空間放大的主要原因相同,即受最下層的過期版本數據影響。越往下的層,做一次 compaction 的 I/O 代價越高,但發生的頻率也更低,不同層之間做 compaction 的期望代價大致相同。點查、空間放大、長范圍查詢的性能瓶頸在 LST-tree 的最下層,而更新操作則更加均勻地分布在每一層。因此,減少非最后一層的 compaction 頻率可以有效降
322、低更新操作的代價,且對點查、空間放大、長范圍查詢的性能影響較小。降低寫放大基于上述理論分析,該論文提出混合 compaction 策略即 Lazy Leveling。它將 Leveling 與 Tiering 進行結合,在最后一層使用 Leveling 策略,其他層使用 Tiering 策略,即最后一層只能存在唯一的 sorted run,其他層允許存在多個 sorted run,從而有效降低非最后一層做 compaction 的頻率。下表是采取 Lazy Leveling 策略后的性能匯總表,其中,綠色部分表示效果較好,紅色部分表示較差,黃色部分代表適中。從下表可以看出,Lazy Level
323、ing 的更新操作(update)性能優于 Leveling,接近于 Tiering。這是由于在前 L-1 層維持 Tiering 策略,做 compaction 的頻率更低,寫放大低。但 Lazy Leveling 的空間放大接近于 Leveling,遠好于 Tiering。這相當于結合了兩種策略的優勢。對于點查(point lookup),論文中分別分析了查找不存在 kv 和 kv 在最后一層兩種情況,并基于論文 Monkey 的思路對每層的 bloom filter bit 進行了優化,可以達到與 Leveling+Monkey 策略相匹配的點查性能。對于長范圍查詢,Lazy Level
324、ing 可以做到與 Leveling 一致的性能,而短范圍查詢則退化至接近 Tiering 的水平。論文對此進行總結:使用一種單一 compaction 策略,不可能在上述所有操作中做到性能最優。Lazy Leveling 本質上是 Tiering 與 Leveling 策略的折衷加調優,在側重于更新操作、點查和長范圍查詢的 workload 上性能較好;Leveling 適用于查詢為主的 workload;Tiering 則適用于更新操作為主的 workload。論文通過建立代價模型將 Lazy Leveling 進行推廣,將其稱為 Fluid LSM-Tree。假設有兩個參數,分別為 Z
325、和 K。Z 表示最后一層允許有最多 Z 個 sorted run;其他層則允許有多個 sorted run。K 為限制參數,表示每層最多有 K 個 sorted run。當每個 sorted run 的值達到 t/k 時會向下做 compaction。通過這樣的方式,將 Lazy Leveling 進行泛化推廣,通過不同的 workloads 來調節參數 Z 和 K。下表是將參數 Z 和 K 添加進去后的 Fluid LSM-Tree 的代價利用分析結果。但這在實際操作中會遇到問題,即如何根據 workloads 來選取參數 Z 和 K。論文采取搜索方式去找到針對某個 workload 代價模
326、型最小的參數 K 和 Z。在真正實現時,我們對 workload 的認知相對有限,想要找到最優的參數 K 和 Z 會較為困難,且該模型總體比較復雜,實踐性有限。RocksDB 在某種意義上也采取了混合 compaction 策略。RocksDB 的 L0 層的 SST 之間,K 允許互相重疊,即允許多個 sorted run。這實際上是 Tiering 策略,因為其落盤的 Flush 的性能第 2 章:數據庫性能優化第 2 章:數據庫性能優化186187更優。在 L1-Ln 層中采取 Leveling 策略,能夠更及時地清理過期數據,提高空間利用率。RocksDB 將每個 sorted run
327、 切割為多個 SST 文件,每個 SST 文件都有大小閾值。其優點在于每次發生 compaction 時,只需要選取一個或者少量的 SSTable 與下層有 KV 重疊的 SSTable 進行合并,涉及到合并的有效數據量處于可控范圍。SOSP 2017 中有一篇名為 PebblesDB 的論文,提出了 compaction Guard 概念。每層分割為多個分片,分片間的分割稱之為 compaction Guard,即一次 compaction 操作不會跨越該分界線,每個分界線內部 SST 之間允許重疊,可理解為 Tiering compaction。這種做法最大的好處是可以保證數據項從第 i
328、層向第 L+1 層進行 compaction 時,寫入只有一次。因為它將原生的 LSM-Tree 進行分隔,形成 FLSM 結構。其壞處是讀性能會變差。因為找到對應分片后,分片內部如果存在多個 SST,我們就不知道數據的真正存放位置,這時需要借助 Bloom 過濾器來對每個 SST 進行探查,且即使使用 Bloom 過濾器,其發生假陽性的期望次數也會增加。在 RocksDB 實踐過程中,我們發現它實際上提供了一個 SST Partitioner 的類,通過類可以在代碼實現上保證分片,但與 PebblesDB 不同的是,其在分片內部保持 SST 不重疊,仍然采取 Leveling 策略。其好處是
329、讀性能不變。雖然寫性能沒有得到提升,但更加適配于擴容場景,便于遷移及遷移后數據的物理刪除操作。以 TDSQL 新敏態引擎架構為例。每層的存儲節點稱為 TDstore,一個 TDstore 真實的數據存儲相當于維護一個 LSM-Tree 結構來存儲 KV 數據,再將 KV 數據按照區間劃分成不同 Region。需要擴容時,我們會增加若干個存儲節點,再將原來節點上的部分 Region 遷移過去。Region 遷移涉及到 Region 數據的遷移。如果采用前述的分割策略,將 LSM-Tree 的每一層基于 Region 邊界進行分割,將 Region 從相對完整的 SST 文件中撈取出來,并插入到新
330、增的 TDstore 存儲節點中。因為 SST 根據邊界進行分割,我們可以相對完整地將 Region 內部的數據遷移或刪除,因此 Region 數據遷移的性能會得到提升。第 2 章:數據庫性能優化第 2 章:數據庫性能優化188189降低讀放大降低讀放大必須結合布隆過濾器。具體實現為:每層設置一個布隆過濾器,通過布隆過濾器進行過濾,減少無效讀磁盤 block 的次數。下圖為前述的結論表。當數據查詢不存在即發生讀穿透時,發生假陽性的概率為 e 的-m/n 次方。只要發生假陽性,就必須去讀數據塊,進行一次讀磁盤操作。在 Leveling 中,每層只有一個 sorted run,查一次 Bloom
331、filter 的概率為 e 的-m/n 次方,根據期望公式推導出的期望讀盤次數為 L 乘以 e 的-m/n 次方。Tiering 中則是 T 乘以 L 再乘以 e 的-m/n 次方。假設點查時使用布隆過濾器進行過濾,每層都建立一個布隆過濾器即固定的 bits 團,讀性能就可得到提升。2017 年一篇名為 Monkey 的論文對布隆過濾器進行優化。在 LSM-Tree 不同層設置不同的布隆過濾器的 bits,可以有效降低 IO 開銷,從而減少讀穿透的期望次數。其結論分析如下:第 i 層設置(a+b)(L-i)的 bits。由于最后一層數據量最大,只用一個 bit 來 hash 到布隆過濾器中。相
332、比于前述公式,這可以減少一個 L 系數。因為 Bloom filter 要在內存中持有,而每層的 bits 不同,因此總體上降低了布隆過濾器的內存開銷。其點查的讀性能和整體 Bloom filter 的空間利用率都會得到提升。SIGMOD 2021 一篇名為 Cuckoo 的論文,采用布谷鳥過濾器代替布隆過濾器,從而優化讀性能。布隆過濾器的優化方式為:LSM-Tree 的每層甚至每個 SST 文件都會維護一個 Bloom filter,查詢時需要從 MemTable L0 到 Ln 一層層向下探查,每次探查時先走一遍相應的布隆過濾器。該論文提出替代方案,不需要每層都維護一個布隆過濾器,而是選擇
333、維護一個全局的布谷鳥過濾器。先查全局的布谷鳥過濾器,如果反饋不存在則表示數據不存在,如果反饋存在,考慮到可能發生假陽性,我們再去查數據文件。實現原理為:全局的布谷鳥過濾器里記錄了 Level ID。如果在布谷鳥過濾器中有 mash,則不需要繼續向下探查,可以直接找到其對應的 Level ID,找到對應層、對應的 sorted run 去讀磁盤,看數據是否存在。布谷鳥過濾器對接兩部分:其 hash map 或簽名,再加上位置 ID 即 level ID。布谷鳥過濾器的工作原理如下圖,相當于維護下圖右上角中的兩個桶。我們通過兩次哈希算出 key 所要插入的具體位置,所對應的兩個桶稱為 b1 和 b2。b1 直接拿 key 進行擦洗,b2 不需要用 key 的原值,在算出簽名指紋后再做哈希,因此每個 key 會有兩個可以存放的位置。布谷鳥過濾器哈希的數據插入過程如下:假設要插入 item X,通