?美團(tuán)關(guān)于 Apache Doris 存儲(chǔ)層向量化改造的設(shè)計(jì)與實(shí)現(xiàn)
導(dǎo)讀:本文將為大家介紹 Apache Doris 存儲(chǔ)層向量化的改造,最初的改造目的是提升 Doris 的查詢性能,這個(gè)改造就是利用向量化的一些特性去做查詢加速。
全文將圍繞以下五個(gè)部分展開(kāi):
- Apache Doris 引擎介紹
- 向量化編程介紹
- Apache Doris 存儲(chǔ)層概覽
- Apache Doris 存儲(chǔ)層向量化改造
- 總結(jié)
分享嘉賓|王博 美團(tuán) OLAP開(kāi)發(fā)工程師
編輯整理|張建闖 BOSS直聘
出品平臺(tái)|DataFunSummit
01
Apache Doris 引擎介紹
首先介紹一下 Apache Doris 在目前數(shù)據(jù)分析產(chǎn)品中的定位,主要是兩個(gè)路徑。
① 數(shù)據(jù)導(dǎo)入路徑
比如上面的 Spark、Flink 以及一些實(shí)時(shí)的場(chǎng)景,可以支持實(shí)時(shí)導(dǎo)入,也可以支持左側(cè)的離線導(dǎo)入,還有一些是下方的關(guān)系型數(shù)據(jù)庫(kù)或二進(jìn)制數(shù)據(jù)導(dǎo)入。慢的可能是分鐘級(jí),快的可能是秒級(jí)的數(shù)據(jù)導(dǎo)入,以及實(shí)時(shí)的端到端的數(shù)據(jù)同步,還有傳統(tǒng)的 T 1 的批量生產(chǎn)方式,目前用戶更青睞于數(shù)據(jù)的實(shí)時(shí)導(dǎo)入以及關(guān)系型數(shù)據(jù)庫(kù)的二進(jìn)制導(dǎo)入。
② 查詢路徑
主要就是報(bào)表分析,Doris 目前定位于 OLAP 數(shù)據(jù)庫(kù),主要針對(duì)分析場(chǎng)景,延遲一般在 3~5 秒以內(nèi),時(shí)間再長(zhǎng)可能穩(wěn)定性就難以保證了。
綜上,目前 Apache Doris 的定位還是一個(gè) MPP 架構(gòu),提供亞秒級(jí)查詢的 OLAP 數(shù)據(jù)庫(kù),同時(shí)支持離線和實(shí)時(shí)數(shù)據(jù)導(dǎo)入,支持 SQL,相對(duì) Flink 和 Spark 需要去配置一些定時(shí)的任務(wù)而言,開(kāi)發(fā)成本還是非常低的。
—
02
向量化編程介紹
第二部分來(lái)講一下向量化編程是什么。
這張圖來(lái)源于 CMU 大學(xué),向量化編程是數(shù)據(jù)庫(kù)里常見(jiàn)的一個(gè)操作,比如列的加減、比較、讀寫(xiě)操作,可以把它們抽象成兩個(gè)向量。例如圖中 x 列和 y 列,進(jìn)行累加,得出 z 的結(jié)果,這種計(jì)算方式就叫做單指令單數(shù)據(jù)。如果想算一個(gè)結(jié)果集,只能根據(jù)行去算,每一行都需要發(fā)送一次指令,這個(gè)就是原來(lái) Doris 處理數(shù)據(jù)的方式,需要發(fā)送的指令會(huì)更多。
新版的特點(diǎn)是可以批量算,之前是一行一行地去計(jì)算,現(xiàn)在新的算法可以對(duì)數(shù)據(jù)進(jìn)行一批一批地去計(jì)算。
CPU 有一個(gè) SIMD 寄存器,SIMD 就是單指令多數(shù)據(jù),也就是一條指令可以算一批的數(shù)據(jù),可以一次把這一個(gè)向量的數(shù)據(jù)批量加一,這個(gè)性能是比較快的,尤其對(duì)于數(shù)據(jù)分析的系統(tǒng)來(lái)說(shuō),對(duì)求 sum,min,max 非常合適,OLAP 數(shù)據(jù)庫(kù)是偏吞吐和數(shù)據(jù)密集型的,對(duì) SIMD 依賴是比較重的。
概括一下,向量化編程主要有兩個(gè)要點(diǎn):
① 計(jì)算機(jī)能夠提供 SIMD 指令和寄存器,硬件首先能支持;
② 對(duì)現(xiàn)有算法做些改造,代碼邏輯可以利用 SIMD 指令做批量計(jì)算。
目前比較新的硬件都是可以支持比較全量的 SIMD 指令集。
—
03
Apache Doris 存儲(chǔ)層概覽
存儲(chǔ)層主要是把數(shù)據(jù)讀上來(lái),發(fā)送給執(zhí)行節(jié)點(diǎn),在這個(gè)過(guò)程中還做了一些更細(xì)粒度的拆分,比如磁盤(pán)上的數(shù)據(jù)加載到內(nèi)存需要做的一些反序列化的操作,轉(zhuǎn)成執(zhí)行層的數(shù)據(jù)結(jié)構(gòu),有一個(gè)解碼的過(guò)程。另外,這個(gè)數(shù)據(jù)要可以被算子執(zhí)行。歸并的意思是對(duì)數(shù)據(jù)做一定的排序,對(duì)數(shù)據(jù)做進(jìn)一步的邏輯上的處理。物理算子主要就是對(duì)數(shù)據(jù)進(jìn)行最后的計(jì)算,生成最終計(jì)算結(jié)果。
寫(xiě)入對(duì)數(shù)據(jù)結(jié)構(gòu)是有影響的,Doris 的寫(xiě)入會(huì)分多個(gè)批次,一次導(dǎo)入會(huì)寫(xiě)一個(gè)文件,持續(xù)的寫(xiě)入會(huì)生成多個(gè)文件,比如文件 1,2,3,4…n, 文件內(nèi)容可能第一個(gè)文件 key=1,value=3,第二個(gè)文件 key=1,value=2,文件太多則需要壓縮,文件 1 和文件 2 在 Compaction 壓縮過(guò)程中是有邏輯的,比如表是主鍵更新的表,需要根據(jù)主鍵對(duì)值進(jìn)行更新,這是數(shù)據(jù)寫(xiě)入的邏輯。因?yàn)?Compaction 不一定是很及時(shí)的,所以在查詢的時(shí)候需要做一些排序和歸并,這就是存儲(chǔ)層除了做數(shù)據(jù)的讀取、解壓之外,還要做些歸并的原因,歸并就是因?yàn)椴糠直砟P托枰判虻倪壿嫛?/span>
舉一個(gè)查詢的例子,比如一個(gè)非常簡(jiǎn)單的查詢 Select key,value from table,如果在磁盤(pán)上有兩個(gè)文件,每個(gè)文件數(shù)據(jù)結(jié)構(gòu)是 key 和 value 兩列,做歸并就是把 value 列的這兩個(gè)值進(jìn)行覆蓋,這就是查詢邏輯,Compaction 的邏輯非常類(lèi)似,在數(shù)據(jù)歸并完之后就交給物理算子進(jìn)行計(jì)算了。
總的來(lái)說(shuō),數(shù)據(jù)的查詢流程就是先去讀盤(pán),然后解碼,再按照表模型去做歸并,有些明細(xì)表是不需要?dú)w并的,主要就是這三個(gè)關(guān)鍵點(diǎn)。
—
04
Apache Doris 存儲(chǔ)層向量化改造
Doris 存儲(chǔ)層主要做了下面這兩個(gè)工作:
① 數(shù)據(jù)的讀取,比如謂詞的下推優(yōu)化
② 數(shù)據(jù)的輸出,比如根據(jù)表模型做數(shù)據(jù)的歸并
Doris 存儲(chǔ)層向量化改造具體需要做下面這些事情:
① 對(duì)代碼進(jìn)行梳理,找出可向量化的代碼邏輯,因?yàn)?SIMD 指令只能執(zhí)行部分的邏輯,比如累加、比較、批量的讀和寫(xiě),它不是所有的邏輯都可以用 SIMD 的,只適用于數(shù)據(jù)密集的場(chǎng)景。
② 找出來(lái)之后,需要對(duì)這些模塊做改造,比如對(duì)這些代碼使用 SIMD 指令做替換。
③ 做 SIMD 改造的核心目的是希望提升性能,所以對(duì)于沒(méi)有辦法做向量化改造的邏輯需要去思考是否有其他方法做優(yōu)化。
上圖中黃色的部分是待調(diào)研的,綠色的是已經(jīng)完成的,我們這里主要看一下讀取數(shù)據(jù)是如何優(yōu)化的。
(1)有索引
Doris 支持一些索引結(jié)構(gòu),比如前綴索引,所以這里可以進(jìn)行一些數(shù)據(jù)的裁剪,具體實(shí)現(xiàn)是采用 Bitmap,對(duì)數(shù)據(jù)裁剪完成之后剩下一批行號(hào),有了行號(hào)就可以減少數(shù)據(jù)的 seek,讀取的數(shù)據(jù)少了,就可以提升性能。這里考慮到以下幾個(gè)問(wèn)題:
① 索引本身是否有代價(jià),比如索引本身的數(shù)據(jù)結(jié)構(gòu)是否有優(yōu)化空間。
② 讀取數(shù)據(jù)的時(shí)候又可以進(jìn)行一個(gè)分類(lèi),讀取定長(zhǎng)類(lèi)型和變長(zhǎng)類(lèi)型的數(shù)據(jù),這兩個(gè)處理邏輯是不一樣的。
- 定長(zhǎng)類(lèi)型包括 int、float、double,可以使用 SIMD 指令做批量讀
- 變長(zhǎng)類(lèi)型一般是字符串類(lèi)型,無(wú)法做 SIMD 優(yōu)化,這種優(yōu)化是嘗試消除一些不必要的拷貝,另一個(gè)是把字符串映射成數(shù)值類(lèi)型,這樣就能使用 SIMD 優(yōu)化了
③ 讀取數(shù)據(jù)的另一個(gè)優(yōu)化方向是,讀取位置的優(yōu)化。
- 從 Cache 讀,因?yàn)?Cache 也是一個(gè)數(shù)據(jù)結(jié)構(gòu),所以這里也會(huì)有可以優(yōu)化的地方
- 從磁盤(pán)讀,磁盤(pán)讀取是否能夠做一些優(yōu)化
(2)無(wú)索引
沒(méi)有索引就需要讀取所有的數(shù)據(jù),所以這里就和有索引讀取數(shù)據(jù)的問(wèn)題一樣了,這里可以考慮一個(gè)問(wèn)題,就是是否也可以生成一個(gè) Bitmap,事實(shí)上這里是沒(méi)必要,因?yàn)樽x取 Bitmap 也是有一定的開(kāi)銷(xiāo)的。
這是一個(gè)思考的邏輯,從一個(gè)點(diǎn)開(kāi)始一步步向下,逐步分析哪里可以做優(yōu)化。
對(duì)剛才的內(nèi)容做一些細(xì)化,比如索引的代價(jià),索引是可以提高性能的,可以減少數(shù)據(jù)的讀取量,但是考慮具體實(shí)現(xiàn)的話,我們使用 roaringBitmap 實(shí)現(xiàn),讀取索引也是有一些開(kāi)銷(xiāo)的。
另一個(gè)比較重要的點(diǎn)就是在處理數(shù)據(jù)的時(shí)候需要更關(guān)注定長(zhǎng)和變長(zhǎng)的數(shù)據(jù)類(lèi)型,變長(zhǎng)類(lèi)型的讀寫(xiě)開(kāi)銷(xiāo)是要大于定長(zhǎng)類(lèi)型的,就是字符串要大于 int、float 這些定長(zhǎng)類(lèi)型,不光是讀,比較操作也是一樣的,所以我們針對(duì)變長(zhǎng)類(lèi)型盡量去轉(zhuǎn)成整數(shù)來(lái)做。
這就是剛才說(shuō)的字典,Doris 存儲(chǔ)層目前只有文件的字典,還沒(méi)有全局的,文件的字典,我們暫時(shí)可以利用文件的字典做一些程度的優(yōu)化,這個(gè)目前已經(jīng)提到社區(qū)了。
這里主要看一下讀完數(shù)據(jù)之后,還是要做一些額外的優(yōu)化,就是謂詞下推,比如把一些謂詞下推到存儲(chǔ)層來(lái)做,可以減少訪問(wèn)的數(shù)據(jù)量,這里主要是用延遲物化來(lái)做的,原來(lái)的邏輯是可以只讀一次的,有了延遲物化之后,其實(shí)數(shù)據(jù)需要讀取兩次,這里有個(gè)問(wèn)題就是延遲物化的代價(jià),只讀一次其實(shí)還是要做謂詞計(jì)算,就又回到這兩個(gè)變長(zhǎng)類(lèi)型和定長(zhǎng)類(lèi)型的問(wèn)題了。
這里來(lái)解釋一下延遲物化到底是什么,這里沒(méi)有列出官方的定義,而是從 Doris 已經(jīng)實(shí)現(xiàn)的存儲(chǔ)層優(yōu)化來(lái)說(shuō)的,以圖片中的 SQL 為例:
- 什么不是延遲物化:也就是原方式,就是只讀一次,比如 SQL 中 a 列和 b 列的數(shù)據(jù)一次性全部讀取出來(lái)。
- 什么是延遲物化:就是先讀 b 列,做謂詞計(jì)算,獲得行號(hào),再讀 a 列,邏輯上看來(lái)好像讀取數(shù)據(jù)的量更少了,但事實(shí)上并不一定,目前 Doris 中是寫(xiě)死的,就是先讀謂詞,再讀非謂詞,實(shí)測(cè)并不是所有場(chǎng)景都很快。
這里舉幾個(gè) case,兩個(gè)很極端的例子,但是能說(shuō)明問(wèn)題,就是假如這個(gè)表有 10 億行數(shù)據(jù),假設(shè)這個(gè) b=1 只有一行,那么毫無(wú)疑問(wèn),第二次讀取數(shù)據(jù)只需要讀取一次,只需要 seek 一次,這個(gè)延遲物化一定是很快的;另一個(gè)情況就是如果 b=1 這個(gè)數(shù)據(jù)如果有 9 億行,這就不一定了,因?yàn)檠舆t物化需要多 seek 一次,是有額外開(kāi)銷(xiāo)的,過(guò)濾可以抵消這個(gè)開(kāi)銷(xiāo),所以延遲物化可以應(yīng)用的核心假設(shè)如下:
- 讀數(shù)據(jù)的開(kāi)銷(xiāo)要大于 seek 數(shù)據(jù)的開(kāi)銷(xiāo)
- 讀數(shù)據(jù)開(kāi)銷(xiāo)如何衡量,變長(zhǎng)類(lèi)型要遠(yuǎn)大于定長(zhǎng)類(lèi)型的
- seek 開(kāi)銷(xiāo)如何衡量,讀數(shù)據(jù)會(huì)遍歷行號(hào),取出連續(xù)的部分,而延遲物化要讀取兩次,會(huì)多 seek
- 謂詞選擇開(kāi)銷(xiāo)如何衡量,比如只seek一行就會(huì)很快
以這個(gè) SQL 為例:
select a from table where b=x
- 如果條件 x 的選擇性比較差,且很離散,也就是 seek 次數(shù)會(huì)比較多
- 如果 a 和 b 列都是定長(zhǎng)類(lèi)型,seek 的開(kāi)銷(xiāo)會(huì)大于讀開(kāi)銷(xiāo),那么不做延遲物化會(huì)更優(yōu),即減少 seek 次數(shù)
- 如果 a 和 b 列都是變長(zhǎng)類(lèi)型,讀取數(shù)據(jù)的開(kāi)銷(xiāo)>seek開(kāi)銷(xiāo),那么延遲物化可能更優(yōu)
核心的關(guān)鍵點(diǎn)在于,我們需要基于謂詞的選擇性,以及運(yùn)行時(shí) SQL 的寫(xiě)法,算出一個(gè)代價(jià),決定是否選擇延遲物化。
最后一部分就是數(shù)據(jù)輸出如何優(yōu)化,并不是所有的模型都需要做歸并,比如明細(xì)模型,數(shù)據(jù)讀出來(lái)之后就直接發(fā)到物理算子層了,不需要做歸并,也不需要做聚合,另外就是主鍵和聚合模型,它們都要有歸并的過(guò)程,這里有個(gè)問(wèn)題就是使用的是標(biāo)準(zhǔn)庫(kù),是否有優(yōu)化空間并不確定,需要后續(xù)研究。另外,聚合模型是否真的需要?dú)w并,因?yàn)槟壳笆窍扰判颍缶酆?,再輸出,那么是否可以不排序,直接聚合,然后再輸出,是否有差異。還有,所有模型計(jì)算,會(huì)先攢一批的數(shù)據(jù),再做批量的聚合,之前是來(lái)一行算一行,那樣計(jì)算開(kāi)銷(xiāo)是更大的,相對(duì)而言 SIMD 是可以一次算一批的,這個(gè)是數(shù)據(jù)輸出部分。
目前由于是測(cè)試開(kāi)發(fā)階段,查詢流程向量化基本上已經(jīng)完成,目前主要在做穩(wěn)定性的測(cè)試,所以目前能拿出來(lái)的測(cè)試主要是 SSB 的寬表性能測(cè)試,模型是明細(xì)表,不涉及歸并的邏輯,這個(gè)測(cè)試可以分成兩層看:
① 存儲(chǔ)層性能提升是比較大的。
② SQL 也有所提升,不過(guò)這里還是有優(yōu)化空間的,因?yàn)榇鎯?chǔ)層性能提升了,而應(yīng)用層反而好像還有一些性能下降,所以目前還是處于測(cè)試階段,需要更多的線上環(huán)境打磨。
小結(jié)一下向量化改造的工作:
① 我們期望和執(zhí)行層程序做一個(gè)統(tǒng)一的數(shù)據(jù)結(jié)構(gòu),因?yàn)橄蛄炕脑焓?Apache Doris對(duì)整體流程查詢鏈路的改造,也就是會(huì)統(tǒng)一使用一個(gè)數(shù)據(jù)結(jié)構(gòu),所以要保證存儲(chǔ)層和查詢層的數(shù)據(jù)結(jié)構(gòu)是對(duì)齊的,否則數(shù)據(jù)結(jié)構(gòu)不一樣會(huì)有一個(gè)轉(zhuǎn)換的開(kāi)銷(xiāo)。
② 向量化改造其實(shí)是比較單純的,比較容易想到哪些地方能夠做向量化改造,比如謂詞計(jì)算和數(shù)據(jù)拷貝,但是我們的第一版改造完成之后,發(fā)現(xiàn)性能并不是很符合預(yù)期,也就是向量化確實(shí)可以提升一部分性能,但是主要還是代碼邏輯相關(guān)的優(yōu)化。
③ 字典、延遲物化等其他的地方也需要考慮。
—
05
總結(jié)
如何做性能優(yōu)化?
1.了解你的代碼,就是要了解你的代碼在做什么事情,代碼邏輯是根本,決定了執(zhí)行邏輯和技術(shù)選型。
2.了解計(jì)算機(jī)的行為,要具體知道它到底在干什么,SIMD 本身也是計(jì)算機(jī)在發(fā)展過(guò)程中,提供的一種技術(shù)手段,這個(gè)技術(shù)是否能用于你的代碼,還是取決于代碼是否有這種邏輯,SIMD 也有自己的適用場(chǎng)景。
3.了解性能工具,可以幫助你做一些驗(yàn)證。
這塊功能目前正在做一些發(fā)板測(cè)試,歡迎有興趣的同學(xué)來(lái)參與到我們社區(qū),圖中是GitHub 的地址和 Doris 公眾號(hào),以及開(kāi)發(fā)者郵箱,歡迎大家來(lái)咨詢和討論。
今天的分享就到這里,謝謝大家。
|分享嘉賓|
王博|美團(tuán) OLAP開(kāi)發(fā)工程師
本科畢業(yè)后在百度外賣(mài)做數(shù)據(jù)報(bào)表開(kāi)發(fā);
現(xiàn)在在美團(tuán)做OLAP引擎開(kāi)發(fā),主要參與過(guò)Apache Doris的Spark Load開(kāi)發(fā)以及向量化改造。
|DataFun新媒體矩陣|
|關(guān)于DataFun|
專注于大數(shù)據(jù)、人工智能技術(shù)應(yīng)用的分享與交流。發(fā)起于2017年,在北京、上海、深圳、杭州等城市舉辦超過(guò)100 線下和100 線上沙龍、論壇及峰會(huì),已邀請(qǐng)超過(guò)2000位專家和學(xué)者參與分享。其公眾號(hào) DataFunTalk 累計(jì)生產(chǎn)原創(chuàng)文章800 ,百萬(wàn) 閱讀,15萬(wàn) 精準(zhǔn)粉絲。