從托管到原生,MPP架構(gòu)數(shù)據(jù)倉庫的云原生實踐
發(fā)布時間:2022-01-21 點擊數(shù):797
一 前言
Cloud-Hosted:基于市場和業(yè)界的云需求,大部分廠商選擇了云托管作為演進的第一步。這種模式將不再需要用戶線下自建IDC,而是依托于云提供商的標準化資源將數(shù)據(jù)倉庫進行移植并提供高度托管,從而解放了用戶對底層硬件的管理成本和靈計劃資源的約束。
Cloud-Native:然而隨著更多的業(yè)務(wù)向云上遷移,底層計算和存儲一體的資源綁定,導致用戶在使用的過程中依然需要考量不必要的資源浪費,如計算資源增加會要求存儲關(guān)聯(lián)增加,導致無效成本。用戶開始期望云資源能夠?qū)?shù)據(jù)倉庫進行更為細粒度的資源拆解,即對計算,存儲的能力進行解耦并拆分成可售賣單元,以滿足業(yè)務(wù)的資源編排。到這里,云原生的最大化價值才被真正凸顯,我們不在著重于打造存算平衡的數(shù)據(jù)倉庫,而是面向用戶業(yè)務(wù),允許存在大規(guī)模的計算或存儲傾斜,將業(yè)務(wù)所需要的資源進行獨立部署,并按照最小單位進行售賣。這一刻我們真正的進入了數(shù)據(jù)倉庫云原生時代。
二 ADB PG云原生架構(gòu)
為了讓用戶可以快速的適配到云數(shù)據(jù)倉庫,目前我們采用的是云上MPP架構(gòu)的設(shè)計理念,將協(xié)調(diào)節(jié)點和計算節(jié)點進行獨立部署,但承載于單個ECS上,實現(xiàn)了計算節(jié)點存儲計算一體的部署設(shè)計,該設(shè)計由于設(shè)計架構(gòu)和客戶側(cè)自建高度適配,可快速并無損的將數(shù)倉業(yè)務(wù)遷移至云上,對于早期的云適配非常友好且滿足了資源可平行擴展的主要訴求。
內(nèi)存:主要負責行存訪問加速,并負責文件統(tǒng)計信息的緩存;
本地盤:作為行存的持久化存儲,并作為遠端共享存儲的本地加速器;
遠端的共享存儲:作為數(shù)據(jù)的持久化存儲。
3 讀寫流程
- 用戶寫入數(shù)據(jù)通過數(shù)據(jù)攢批直接寫入OSS,同時會在本地磁盤上記錄一條元數(shù)據(jù)。這條元數(shù)據(jù)記錄了,文件和數(shù)據(jù)表的對應(yīng)關(guān)系。元數(shù)據(jù)使用PG的行存表實現(xiàn),我們通過file metadata表來保存這個信息。
- 更新或者刪除的時候,我們不需要直接修改OSS上面的數(shù)據(jù),我們通過標記刪除來實現(xiàn),標記刪除的信息也是保存在本地行存表中,我們通過visibility bitmap來存這個信息。標記刪除會導致讀的性能下降,我們通過后臺merge來應(yīng)用刪除信息到文件,減少刪除帶來的讀性能影響。
1. Group flush:一批寫入的數(shù)據(jù),可以通過group flush寫到同一個OSS文件,我們的OSS文件采用了ORC格式,不同bucket寫入到對應(yīng)strip;
2. 流水線異步并行:編碼攢批,排序是典型的cpu密集型任務(wù),上傳到oss是典型的網(wǎng)絡(luò)IO密集型任務(wù),我們會把這2種任務(wù)類型并行起來,在上傳oss的任務(wù)作為異步任務(wù)執(zhí)行,同時對下一批數(shù)據(jù)編碼排序,加快寫入性能。
- 我們通過讀取file metadata表,得到需要掃描的OSS文件。
- 根據(jù)OSS文件去讀取對應(yīng)文件。
- 讀到的文件通過元數(shù)據(jù)表visibility bitmap過濾掉已經(jīng)被刪除的數(shù)據(jù)。
? 通過本地行存表實現(xiàn)事務(wù)ACID,支持數(shù)據(jù)塊級別的并發(fā);
? 通過Batch和流水線并行化提高寫入吞吐;
? 基于DADI實現(xiàn)內(nèi)存、本地SSD多級緩存加速訪問。
4 可見性表
字段 | 類型 | 說明 |
table_oid | Int32 | 表的oid |
hash_bucket_id | Int16 | hash_bucket的id |
level | Int16 | 邏輯文件所處的merge級別,0表示delta文件 |
physical_file_id | Int64 | 邏輯文件對應(yīng)的oss物理文件id |
stripe_id | Int64 | 邏輯文件對應(yīng)的oss物理文件中的stripe id |
Total_count | int32 | 邏輯文件總共具有的行數(shù),包括被刪除行數(shù) |
Hash bucket:是為了在擴縮容的時候搬遷數(shù)據(jù)的時候,能夠按照bucket來掃描,查詢的時候,也是一個bucket跟著一個bucket;
Level:是merge tree的層次,0層代表實時寫入的數(shù)據(jù),這部分數(shù)據(jù)在合并的時候有更高的權(quán)重;
Physical file id:是文件對應(yīng)的id,64字節(jié)是因為它不再與segment關(guān)聯(lián),不再只需要保證segment內(nèi)table的唯一性,需要全局唯一;
Stripe id:是因為一個oss文件可以包含多個bucket 的文件,以stripe為單位,方便在segment一次寫入的多個bucket合并到一個oss文件中。避免oss小文件,導致性能下降,和oss小文件爆炸;
Total count:是文件行數(shù),這也是后臺合并的一個權(quán)重,越大合并的權(quán)重越低 。
字段 | 類型 | 說明 |
physical_file_id | Int64 | 邏輯文件對應(yīng)的oss物理文件id |
stripe_id | Int32 | 邏輯文件對應(yīng)的oss物理文件中的stripe id |
start_row | Int32 | delete_bitmap對應(yīng)的起始行號,每32k行對應(yīng)一個delete_bitmap |
hash_bucket_id | Int16 | hash_bucket的id |
delete_count | Int32 | 該delete_bitmap總共記錄刪除了多少行 |
bitmap | bytea | delete_bitmap的具體數(shù)值,壓縮存儲 |
Start_row對應(yīng)32k對應(yīng)一個delete bitmap。這個32000 4k,行存使用的32k的page可以保存7條記錄。
Delete count是被刪除的數(shù)量。
我們無需訪問oss,可以直接得到需要merge的文件,避免訪問oss帶來的延遲,另外oss對于訪問的吞吐也有限額,避免頻繁訪問導致觸發(fā)oss的限流。
5 行列混存
1. 0層實時寫入的會做合并,不同bucket的文件會合并成大文件,不同bucket會落到對應(yīng)的stripe;
2. Merge會跨層把符合merge的文件做多路歸并,文件內(nèi)嚴格有序,但是文件間大致有序,層數(shù)越高,文件越大,文件間的overlap越小。
ORC文件:一個ORC文件中可以包含多個stripe,每一個stripe包含多個row group,每個row group包含固定條記錄,這些記錄按照列進行獨立存儲。
Postscript:包括文件的描述信息PostScript、文件meta信息(包括整個文件的統(tǒng)計信息,數(shù)據(jù)字典等)、所有stripe的信息和文件schema信息。
stripe:stripe是對行的切分,組行形成一個stripe,每次讀取文件是以行組為單位的,保存了每一列的索引和數(shù)據(jù)。它由index data,row data和stripe footer組成。
File footer:保存stripe的位置、每一個列的在該stripe的統(tǒng)計信息以及所有的stream類型和位置。
Index data:保存了row group級別的統(tǒng)計信息。
Data stream:一個stream表示文件中一段有效的數(shù)據(jù),包括索引和數(shù)據(jù)兩類。
索引stream保存每一個row group的位置和統(tǒng)計信息,數(shù)據(jù)stream包括多種類型的數(shù)據(jù),具體需要哪幾種是由該列類型和編碼方式?jīng)Q定,下面以integer和string 2種類型舉例說明:
1. 零拷貝:為了把ORC的數(shù)據(jù)類型轉(zhuǎn)換成PG數(shù)據(jù)類型,我們對于定長類型的做值拷貝,變長類型直接轉(zhuǎn)換成PG的datum做指針引用。
2. Batch Scan:面向column采用batch scan,替代逐行訪問而是先掃完一列,再掃下一列,這樣對CPU cache更加友好。
3. 支持Seek read:方便過濾命中情況下的跳轉(zhuǎn)。
6 本地緩存
維度 | RT | Throughput | ||
產(chǎn)品 | DADI | Alluxio-Fuse | DADI | Alluxio-Fuse |
命中內(nèi)存 | 6~7 us | 408 us | 單線程: 4.0 GB/s四線程: 16.2 GB/s | 2.5 GB/s |
命中磁盤 | 127 us | 435 us | 四線程: 541 MB/s | 0.63 GB/s |
從中看到,DADI相比開源解決方案alluxio在內(nèi)存命中的場景RT上有數(shù)量級的提升,在throughput上也有明顯的優(yōu)勢。在命中磁盤的場景,也有明顯的性能優(yōu)勢,在部分分析場景下,我們會頻繁但是少量讀取文件統(tǒng)計信息,這些統(tǒng)計信息我們會緩存在本地,這個優(yōu)勢帶來整體性能的較大提升。
Cache Instance:管理本地緩存,緩存文件抽象成虛擬塊設(shè)備來訪問,數(shù)據(jù)在memory和本次磁盤的冷熱以block為單位管理。
1. 短路讀,直接讀共享內(nèi)存,避免通過IPC讀;
2. 緩存是否命中的數(shù)據(jù)結(jié)構(gòu),也是在共享內(nèi)存里面。通過reference count,結(jié)合robust mutex來保證共享內(nèi)存數(shù)據(jù)的多線程安全;
3. 磁盤讀,100us,+ 27us約等于磁盤讀本身rt,IPC走shm通信,沒有使用本地socket通信。
4. 極低的資源使用。
內(nèi)存:DADI Service使用的內(nèi)存在100~200M,原因在于基于共享內(nèi)存的IPC實現(xiàn),hash表等數(shù)據(jù)結(jié)構(gòu),避免多進程架構(gòu)下內(nèi)存膨脹, 精簡的編碼方式,1個內(nèi)存頁16k 對應(yīng) 4byte的管理結(jié)構(gòu);
CPU:Local DADI Service在磁盤打滿的時候單核CPU使用20%左右。CPU的使用在SDK這邊,SDK與Local DADI Service通信很少。
1. 緩存優(yōu)先級
支持統(tǒng)計信息高優(yōu)先級,常駐內(nèi)存,索引信息常駐本地磁盤。支持維度表數(shù)據(jù)高優(yōu)先級緩存在本地。
2. 細粒度緩存策略
為了避免大表冷數(shù)據(jù)訪問,導致本地熱數(shù)據(jù)被全部替換,大表使用專有緩存區(qū)。
3. 文件異步預(yù)取
根據(jù)查詢情況,把解析的數(shù)據(jù)文件,預(yù)先讀取到本地,這個過程不影響當前文件的讀寫,并且是異步的。
7 向量化執(zhí)行
8 有序感知
1. 消除多余sorting操作。如果data本身有序,且滿足排序要求,則不需要加sort操作。
2. 最小化需要排序的列。例如希望對{c1,c2,..cn}排序,如果有謂詞c1=5,則order簡化成{c2,..cn},避免排序多一個字段。
3. order下推。在初始化階段,降意向排序操作盡量下推。
1. 首先針對不同算子的有序性需求,例如(join/group by/distinct/order by),建立算子的interesting order(即這個算子期望的有序輸入)。
2. 其次在sort scan的過程中所生成的interesting order,會盡可能下推到下層算子中(sort-ahead),以盡早滿足order屬性要求。
3. 如果一個算子具有多個interesting order,會嘗試將他們合并,這樣一個排序就可以滿足多個order屬性的需求。
這里的問題在于sort scan的多路歸并需要一條條讀取數(shù)據(jù),與向量化的batch scan與文件的批量讀沖突,我們通過CBO來選主最優(yōu)的執(zhí)行計劃。
9 細粒度并行
四 性能結(jié)果
1 擴縮容性能
計算資源擴容(節(jié)點數(shù)) | 2->4 | 4->8 | 8->16 | 16->128 |
用時 | <1min | <1min | <1min | <7min |
2 讀寫性能
寫性能測試
|
ADB PG 彈性存儲 | ADB PG新版云原生 | ||||
并發(fā)數(shù) | 1 | 4 | 8 | 1 | 4 | 8 |
COPY | 48MB/s | 77MB/s | 99MB/s | 45MB/s | 156MB/s | 141MB/s |
-
在單并發(fā)下新版本與存儲彈性版本的性能差不多,主要在于資源都沒有滿;
-
在4并發(fā)下新版本的吞吐是存儲彈性的2倍,原因在于使用lineitem表都定義了sort key,新版本在寫入數(shù)據(jù)無需寫WAL日志,另外攢批加上流水線并行相比彈性存儲版本先寫入,再merge,merge的時候也需要寫額外的WAL有一定優(yōu)勢;
- 在8并發(fā)下新版本與4并發(fā)差不多,主要由于4C 4并發(fā)已經(jīng)把CPU用滿,所以再提升并發(fā)也沒有提升。
讀性能測試
全內(nèi)存:使用的是TPCH sf為10的數(shù)據(jù)集,會生成10G的測試數(shù)據(jù)集。
全本地磁盤緩存:使用的是TPCH sf為500的數(shù)據(jù)集,會生成500GB的測試數(shù)據(jù)集。
一半緩存,一半OSS:使用的是TPCH sf為2000的數(shù)據(jù)集,會生成2000GB的測試數(shù)據(jù)集。(本地磁盤緩存960GB)
測試結(jié)果如下(縱軸為RT單位ms)
全內(nèi)存
-
云原生版本對比老的彈性存儲版本均有1倍多的性能提升,原因在于細粒度并行帶來的加速效果;
- 對于TPCH這種計算密集型的作業(yè),即使數(shù)據(jù)一半緩存,一半OSS性能也不錯,sf 2000數(shù)據(jù)量是sf 500的4倍,rt增加到原來的2.8倍,主要原因在于4*4C規(guī)格的實例沒有到OSS的帶寬瓶頸,另外由于本身讀取的預(yù)取等優(yōu)化。
五 總結(jié)
-
通過存儲計算分離,用戶可以根據(jù)業(yè)務(wù)負載模型,輕松適配計算密集型或存儲密集型,存儲并按使用計費,避免存儲計算一體僵化而造成的資源浪費;
-
動態(tài)的適配業(yè)務(wù)負載波峰和波谷,云原生MPP架構(gòu)計算側(cè)使用了shared-nothing架構(gòu),支持秒級的彈性伸縮能力,而共享存儲使得底層存儲獨立不受計算的影響。這降低了用戶早期的規(guī)格選型的門檻,預(yù)留了后期根據(jù)業(yè)務(wù)的動態(tài)調(diào)整靈活性;
- 在存儲計算分離基礎(chǔ)上,提供了數(shù)據(jù)共享能力,這真正打破了物理機的邊界,讓云上的數(shù)據(jù)真正的流動了起來。例如數(shù)據(jù)的跨實例實時共享,可支持一存多讀的使用模式,打破了傳統(tǒng)數(shù)倉實例之間數(shù)據(jù)訪問需要先導入,再訪問的孤島,簡化操作,提高效率,降低成本。
六 后續(xù)計劃
1. 能力補齊,這塊主要是補齊當前版本的一些限制,例如Primary key,索引,物化視圖,補齊寫入的能力;
2. 性能持續(xù)優(yōu)化,主要優(yōu)化緩存沒有命中場景;
3. 云原生架構(gòu)持續(xù)升級,這塊主要是在當前存儲計算分離架構(gòu)下,進一步提升用戶體驗;
1. 存算分離往Serverless再進一步,擴縮容無感。會進一步把元數(shù)據(jù)和狀態(tài)也從計算節(jié)點剝離到服務(wù)層,把segment做成無狀態(tài)的,這樣的好處在于擴縮容能做到用戶無感,另外一個好處在于segment無狀態(tài)有利于提高系統(tǒng)高可用能力,當前我們還是通過主備模式提供高可用,當有節(jié)點故障的時候,主備切換緩存失效性能會急劇下降,segment無狀態(tài)后我們會直接將它提出集群,通過“縮容”的方式繼續(xù)提高服務(wù)。
2. 應(yīng)用跨實例的數(shù)據(jù)共享。此外對于分析型業(yè)務(wù),數(shù)據(jù)規(guī)模大,以TB起步,傳統(tǒng)數(shù)倉采用煙囪式架構(gòu),數(shù)據(jù)冗余,數(shù)據(jù)同步代價高的問題,我們希望提供跨實例的數(shù)據(jù)共享能力,重構(gòu)數(shù)倉架構(gòu)。