从托管到原生,MPP架构数据仓库的云原生实践
簡介:本文介紹了云原生數據倉庫產品AnalyticDB PostgreSQL從Cloud-Hosted到Cloud-Native的演進探索,探討為了實現真正的資源池化和靈活售賣的底層設計和思考,涵蓋內容包括產品的架構設計,關鍵技術,性能結果,效果實現和后續計劃幾方面。
作者 | 翰明
來源 | 阿里技術公眾號
一 前言
Garner預測,到2022年,所有數據庫中有75%將部署或遷移至云平臺。另外一家權威機構IDC也預測,在2025年,超過50%的數據庫將部署在公有云上,而中國則會達到驚人的70%以上。云數據庫經過多年發展,經歷從Cloud-Hosted (云托管)到 Cloud Native(云原生)模式的轉變。
Cloud-Hosted:基于市場和業界的云需求,大部分廠商選擇了云托管作為演進的第一步。這種模式將不再需要用戶線下自建IDC,而是依托于云提供商的標準化資源將數據倉庫進行移植并提供高度托管,從而解放了用戶對底層硬件的管理成本和靈計劃資源的約束。
Cloud-Native:然而隨著更多的業務向云上遷移,底層計算和存儲一體的資源綁定,導致用戶在使用的過程中依然需要考量不必要的資源浪費,如計算資源增加會要求存儲關聯增加,導致無效成本。用戶開始期望云資源能夠將數據倉庫進行更為細粒度的資源拆解,即對計算,存儲的能力進行解耦并拆分成可售賣單元,以滿足業務的資源編排。到這里,云原生的最大化價值才被真正凸顯,我們不在著重于打造存算平衡的數據倉庫,而是面向用戶業務,允許存在大規模的計算或存儲傾斜,將業務所需要的資源進行獨立部署,并按照最小單位進行售賣。這一刻我們真正的進入了數據倉庫云原生時代。
阿里云在2021云棲大會上,預告了全新云原生架構的數據倉庫【1】。本文介紹了云原生數據倉庫產品AnalyticDB PostgreSQL(以下簡稱ADB PG)從Cloud-Hosted到Cloud-Native的演進探索,探討為了實現真正的資源池化和靈活售賣的底層設計和思考,涵蓋內容包括產品的架構設計,關鍵技術,性能結果,效果實現和后續計劃幾方面。(全文閱讀時長約為10分鐘)
二 ADB PG云原生架構
為了讓用戶可以快速的適配到云數據倉庫,目前我們采用的是云上MPP架構的設計理念,將協調節點和計算節點進行獨立部署,但承載于單個ECS上,實現了計算節點存儲計算一體的部署設計,該設計由于設計架構和客戶側自建高度適配,可快速并無損的將數倉業務遷移至云上,對于早期的云適配非常友好且滿足了資源可平行擴展的主要訴求。
隨著云原生的進一步演進,我們提供了全新的存算分離架構,將產品進一步拆分為服務層、計算層和共享存儲層,架構圖如下:
Master協調節點:保存全局的schema信息,并實現了全局事務管理;
行存引擎:用來保存元數據信息,這里的元數據信息主要指共享存儲文件的可見性信息,包括兩個部分:
- 一個是文件與表的關系
- 另外一個是被刪除的數據的delete bitmap
基于行存我們可以繼承PG的本地事務能力,在增刪改查的同時,與PG的事務能力完全兼容;
本地緩存:通過引入存儲團隊的DADI來實現高性能的本地緩存,DADI全稱是Alibaba Cloud Data Accelerator for Disaggregated Infrastructure,相比開源產品,性能有數量級的提升;
共享存儲:我們借鑒了ClickHouse的一些關鍵設計,在存儲層實現了一個基于MergeTree的行列混存,此外我們對共享存儲基于文件接口做了一層統一訪問接口,同時高度適配了OSS和HDFS 兩種形態的分布式文件系統;
當我們在架構設計的時候,和同樣源自Greenplum的HAWQ對比,HAWQ把元數據都保存在master,在每次寫的時候,把修改的元數據帶到master來更新,讀的時候,先從master讀取需要的元數據,然后在執行計劃里面把元數據都帶上,這樣segment就能拿到對應的元數據, 同時segment可以完全無狀態。
但是這個設計會帶來2個核心問題:
而我們不這樣設計做的原因是我們希望在未來能夠支持高并發的任務,ADB PG大概花了2年多的時間,將Greenplum的單點master架構拓展為multi-master,核心是為了解決高并發實時寫入的問題,如果把元數據都保存在master上會帶來如問題:
所以我們改進了架構,將元數據分散到segment,規避并實現了:
共享存儲使用OSS的原因在于,隨著單個用戶業務數據不斷增長,需要一個可持續發展的存儲方案,而OSS的低存儲成本,高可用性和數據持久性是最好的選擇。
使用OSS的另外一個好處在于按需付費,用戶不需要預制存儲空間大小,存了多少數據,付多少錢,數據刪除后即不再收費;ESSD云盤通常需要根據數據計算存儲水位,無法做到將存儲資源真正的按需供應,而且無法自動縮容,這些都違背了我們云原生的設計理念。但同時OSS的劣勢在于RT:
為了解決OSS的RT問題,我們為計算節點配置了一定比例的本地盤,用來做訪問加速。此外,我們設計了一個高性能的行列混存,借鑒了ClickHouse mergetree存儲的核心思想,以有序為核心,文件內絕對有序,文件與文件大致相對有序,通過merge的異步操作,實現文件和并和排序,基于有序,我們在文件內設計了3層的統計信息,并做了大量的IO裁剪優化。
下面我們對每個技術點做進一步介紹。
三 關鍵技術
1 彈性伸縮
為了實現快速的彈性伸縮,我們的方式是數據在共享存儲上hash bucket來組織,擴縮容后通過一致性hash把計算節點和bucket做重新映射,為了解決bucket與segment分配的均勻性,并降低擴縮容后cache失效的影響,我們對傳統的一致性hash算法做了改進,支持擴縮容時的動態映射。
把數據根據hash bucket分成多個分片,按分片粒度在擴縮容的重新映射對象存儲上的數據。如果擴容計算節點超過分片個數的時候,只能重分布數據。為了解決這個問題,我們支持hash bucket可以后臺分裂和合并,避免重新分布數據。
上述是擴縮容時“數據”的重現映射,而描述數據文件可見性的元數據,由于保存在行表中,我們還是使用了Greenplum的數據重分布策略,不同的是,為了加速元數據的重分布,我們做了并行化分布等優化。
我們以擴容為例進一步細化擴容的流程:
結合ECS資源池化,網卡并行加載和docker鏡像預熱等技術,16節點內端到端的耗時接近1分鐘。
2 分層存儲
分層存儲的實現如下:
如上圖所示,我們把存儲的資源分成3層,包括內存、本地盤和共享存儲。
內存:主要負責行存訪問加速,并負責文件統計信息的緩存;
本地盤:作為行存的持久化存儲,并作為遠端共享存儲的本地加速器;
遠端的共享存儲:作為數據的持久化存儲。
3 讀寫流程
寫入流程如下:
- 用戶寫入數據通過數據攢批直接寫入OSS,同時會在本地磁盤上記錄一條元數據。這條元數據記錄了,文件和數據表的對應關系。元數據使用PG的行存表實現,我們通過file metadata表來保存這個信息。
- 更新或者刪除的時候,我們不需要直接修改OSS上面的數據,我們通過標記刪除來實現,標記刪除的信息也是保存在本地行存表中,我們通過visibility bitmap來存這個信息。標記刪除會導致讀的性能下降,我們通過后臺merge來應用刪除信息到文件,減少刪除帶來的讀性能影響。
我們在寫入的時候,是按照bucket對segment上的數據做了進一步劃分,這里會帶來小文件的問題。為了解決小文件問題,我們做了下面幾點優化:
因為遠端持久化存儲提供了12個9的持久性,所以只有保存元數據的行存才有WAL日志和雙副本來保證可靠性,數據本身寫穿到共享存儲,無需WAL日志和多副本,由于減少了WAL日志和WAL日志的主備同步,又通過異步并行和攢批,在批量寫入場景,我們寫入性能做到了基本與ECS彈性存儲版本性能持平。
讀取流程如下:
- 我們通過讀取file metadata表,得到需要掃描的OSS文件。
- 根據OSS文件去讀取對應文件。
- 讀到的文件通過元數據表visibility bitmap過濾掉已經被刪除的數據。
為了解決讀OSS帶來的延遲,我們也引入了DADI幫忙我們實現緩存管理和封裝了共享文件的訪問,讀文件的時候,首先會判斷是否本地有緩存,如果有則直接從本地磁盤讀,沒有才會去 OSS讀,讀到后會緩存在本地。寫的時候會直寫OSS,并回寫本地磁盤,回寫是一個異步操作。對于本地緩存數據的淘汰我們也通過DADI來管理,他會根據LRU/LFU策略來自動淘汰冷數據。
由于事務是使用PG的行存實現,所以與ADB PG的事務完全兼容,帶來的問題是,我們在擴縮容的時候需要重新分布這部分數據,我們重新設計了這塊數據的重分布機制,通過預分區,并行拷貝,點對點拷貝等技術,極大縮短了擴縮容時間。
總結一下性能優化點:
- 通過本地行存表實現事務ACID,支持數據塊級別的并發;
- 通過Batch和流水線并行化提高寫入吞吐;
- 基于DADI實現內存、本地SSD多級緩存加速訪問。
4 可見性表
我們在File Metadata中保存了共享存儲文件相關的信息,它的結構如下:
Hash bucket:是為了在擴縮容的時候搬遷數據的時候,能夠按照bucket來掃描,查詢的時候,也是一個bucket跟著一個bucket;
Level:是merge tree的層次,0層代表實時寫入的數據,這部分數據在合并的時候有更高的權重;
Physical file id:是文件對應的id,64字節是因為它不再與segment關聯,不再只需要保證segment內table的唯一性,需要全局唯一;
Stripe id:是因為一個oss文件可以包含多個bucket 的文件,以stripe為單位,方便在segment一次寫入的多個bucket合并到一個oss文件中。避免oss小文件,導致性能下降,和oss小文件爆炸;
Total count:是文件行數,這也是后臺合并的一個權重,越大合并的權重越低 。
Visibility bitmap記錄了被刪除的文件信息
Start_row對應32k對應一個delete bitmap。這個32000 4k,行存使用的32k的page可以保存7條記錄。
Delete count是被刪除的數量。
我們無需訪問oss,可以直接得到需要merge的文件,避免訪問oss帶來的延遲,另外oss對于訪問的吞吐也有限額,避免頻繁訪問導致觸發oss的限流。
5 行列混存
Mergetree的結構如上圖左側所示,核心是通過后臺merge的方式,把小文件merge成有序的大文件,并且在merge的時候,我們可以對數據重排,例如數據的有序特性做更多的優化,參考后續的有序感知優化。與leveldb的不同在于:
每個文件我們使用了行列混存的格式,右側為行列混存的具體的存儲格式,我們是在ORC的基礎上做了大量優化。
ORC文件:一個ORC文件中可以包含多個stripe,每一個stripe包含多個row group,每個row group包含固定條記錄,這些記錄按照列進行獨立存儲。
Postscript:包括文件的描述信息PostScript、文件meta信息(包括整個文件的統計信息,數據字典等)、所有stripe的信息和文件schema信息。
stripe:stripe是對行的切分,組行形成一個stripe,每次讀取文件是以行組為單位的,保存了每一列的索引和數據。它由index data,row data和stripe footer組成。
File footer:保存stripe的位置、每一個列的在該stripe的統計信息以及所有的stream類型和位置。
Index data:保存了row group級別的統計信息。
Data stream:一個stream表示文件中一段有效的數據,包括索引和數據兩類。
索引stream保存每一個row group的位置和統計信息,數據stream包括多種類型的數據,具體需要哪幾種是由該列類型和編碼方式決定,下面以integer和string 2種類型舉例說明:
對于一個Integer字段,會同時使用一個比特流和整形流。比特流用于標識某個值是否為null,整形流用于保存該整形字段非空記錄的整數值。
String類型字段,ORC writer在開始時會檢查該字段值中不同的內容數占非空記錄總數的百分比不超過0.8的話,就使用字典編碼,字段值會保存在一個比特流,一個字節流及兩個整形流中。比特流也是用于標識null值的,字節流用于存儲字典值,一個整形流用于存儲字典中每個詞條的長度,另一個整形流用于記錄字段值。如果不能用字典編碼,ORC writer會知道這個字段的重復值太少,用字典編碼效率不高,ORC writer會使用一個字節流保存String字段的值,然后用一個整形流來保存每個字段的字節長度。
在ORC文件中保存了三個層級的統計信息,分別為文件級別、stripe級別和row group級別。而提升存儲性能的核心是減少IO,我們基于ORC的統計信息和索引實現各種下推,幫助我們實現IO裁剪。例如Projection下推,我們只會掃描需要物化的列。Agg下推中,我們會直接把需要的min,max,sum,unique從統計信息或者索引中讀取即可返回,避免了對data stream的解壓。對于predicate,我們還支持把filter下推,通過統計信息直接做過濾,直接跳過不符合的條件的stripe,我們支持各種操作符,以及in/not in,以及表達式的等價轉換。
此外我們針對存儲格式對性能還做了下面的優化:
6 本地緩存
DADI幫助我們實現2個能力,一個是高效的緩存管理,另外一個是統一存儲訪問。在了解DADI之前,我們可以首先看一下,DADI與開源解決方案從RT與throughput 2個維度做了對比測試:
從中看到,DADI相比開源解決方案alluxio在內存命中的場景RT上有數量級的提升,在throughput上也有明顯的優勢。在命中磁盤的場景,也有明顯的性能優勢,在部分分析場景下,我們會頻繁但是少量讀取文件統計信息,這些統計信息我們會緩存在本地,這個優勢帶來整體性能的較大提升。
DADI在緩存命中場景下的性能優勢,可以參考下面的架構:
DADI SDK:通過標準讀寫接口訪問存儲,通過緩存是否命中,選擇短路讀(short circuit read),還是IPC進程通信訪問Local DADI Service,或者訪問遠端的DADI Service,對應分布式緩存服務,作為lib庫嵌入ADB PG的讀寫進程;
Cache Instance:管理本地緩存,緩存文件抽象成虛擬塊設備來訪問,數據在memory和本次磁盤的冷熱以block為單位管理。
這里的核心設計在于:
內存:DADI Service使用的內存在100~200M,原因在于基于共享內存的IPC實現,hash表等數據結構,避免多進程架構下內存膨脹, 精簡的編碼方式,1個內存頁16k 對應 4byte的管理結構;
CPU:Local DADI Service在磁盤打滿的時候單核CPU使用20%左右。CPU的使用在SDK這邊,SDK與Local DADI Service通信很少。
此外為了更好的發揮DADI在命中內存的優勢,我們結合行列混存做了以下優化:
7 向量化執行
ADB PG云原生版本也同樣支持向量化執行引擎,核心還是通過攢批的方式提高數據在CPU cache的命中率,通過codegen減少函數調用次數,減少復雜計算指令跳轉,通過SIMD指令加速計算,通過內存池管理,降低算子間的內存拷貝,更多信息可以參考【3】。
8 有序感知
數據的有序主要用在2個方面,基于有序的IO裁剪,另外一個是盡量減少計算過程中的排序,IO裁剪在行列混存以及有較多的討論,這里主要討論第二點,這里我們做的主要工作有:
我們通過下列方法來生成sort scan的算子,查詢SQL解析生成AST后,會根據一系列啟發式規則做變換生成物理執行計劃:
此外就是sort scan算子的實現,存儲層面只能保證文件內嚴格有序,文件的大致有序,我們通過多路歸并的算法來實現。
這里的問題在于sort scan的多路歸并需要一條條讀取數據,與向量化的batch scan與文件的批量讀沖突,我們通過CBO來選主最優的執行計劃。
9 細粒度并行
ADB PG是MPP架構,能夠充分發揮節點間并行計算能力,云原生版本由于對數據按bucket做了切分,能幫助我們在節點內實現更細粒度的并行,我們以join為例說明:
左邊是沒有節點內并行的join的執行計劃,會起2個進程,一個做hash join的build,另外一個做probe,右邊是節點內做了并行,我們會根據segment所分配的bucket來做并行,例如上圖每個bucket的數據都可以并行的去做計算,由于數據是按照bucket做的劃分,join key是分布健的時候,節點內并行也能完美命中local join的優化。
四 性能結果
1 擴縮容性能
2 讀寫性能
為了測試性能,我們使用了4*4C規格的實例,ADB PG的新版云原生與存儲彈性版本做了性能對比測試。
寫性能測試
測試表選用scale factor = 500的TPC-H lineitem表。通過同時執行不同并發數的copy命令,測得命令執行時間,用總數據量除以命令執行時間,得到吞吐量。
讀性能測試
為了全面的測試讀性能,我們針對3種場景做了測試:
全內存:使用的是TPCH sf為10的數據集,會生成10G的測試數據集。
全本地磁盤緩存:使用的是TPCH sf為500的數據集,會生成500GB的測試數據集。
一半緩存,一半OSS:使用的是TPCH sf為2000的數據集,會生成2000GB的測試數據集。(本地磁盤緩存960GB)
測試結果如下(縱軸為RT單位ms)
全內存
全本地磁盤緩存
一半本地緩存,一半OSS
從上述測試結果來看:
五 總結
AnalyticDB PostgreSQL新版云原生是充分的將物理資源進行了池化,將存儲和計算能力單元化進行分配,實現靈活的部署。這個特性為使用者提供極致的性價比,做到了算力的最優分配,同時降低用戶使用的門檻,讓用戶專注于業務而無需將大量精力放在算力和存儲的規劃上,實現體驗升級。
六 后續計劃
在上述存儲分離的架構上,我們后續主要有3個大的方向:
在云原生升級我們主要有2個重點方向:
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的从托管到原生,MPP架构数据仓库的云原生实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云成为首个通过“虚拟化云平台性能测试
- 下一篇: 技术干货| 阿里云基于Hudi构建Lak