有哪些類型的OLAP數(shù)倉? 按數(shù)據(jù)量劃分 對一件事物或一個東西基于不同角度,可以進行多種分類方式。對數(shù)倉產(chǎn)品也一樣。比如我們可以基于數(shù)據(jù)量來選擇不同類型的數(shù)量,如下圖所示: 本系列文章主要關(guān)注的是數(shù)據(jù)量處于百萬到百億級別的偏實時的分析型數(shù)倉,Cloudera的Impala、Facebook的Presto和Pivotal的GreenPlum均屬于這類系統(tǒng);如果超過百億級別數(shù)據(jù)量,那么一般選擇離線數(shù)倉,如使用Hive或Spark等(SparkSQL3.0看起來性能提升很明顯);對于數(shù)據(jù)量很小的情況,雖然是分析類應用,也可以直接選擇普通的關(guān)系型數(shù)據(jù)庫,比如MySQL等,“殺雞焉用牛刀”。 按建模類型劃分 下面我們主要關(guān)注數(shù)據(jù)量中等的分析型數(shù)倉,聚焦OLAP系統(tǒng)。根據(jù)維基百科對OLAP的介紹,一般來說OLAP根據(jù)建模方式可分為MOLAP、ROLAP和HOLAP 3種類型,下面分別進行介紹并分析優(yōu)缺點。 1、MOLAP 這應該算是最傳統(tǒng)的數(shù)倉了,1993年Edgar F. Codd提出OLAP概念時,指的就是MOLAP數(shù)倉,M即表示多維(Multidimensional)。大多數(shù)MOLAP產(chǎn)品均對原始數(shù)據(jù)進行預計算得到用戶可能需要的所有結(jié)果,將其存儲到優(yōu)化過的多維數(shù)組存儲中,可以認為這就是上一篇所提到的“數(shù)據(jù)立方體”。 由于所有可能結(jié)果均已計算出來并持久化存儲,查詢時無需進行復雜計算,且以數(shù)組形式可以進行高效的免索引數(shù)據(jù)訪問,因此用戶發(fā)起的查詢均能夠穩(wěn)定地快速響應。這些結(jié)果集是高度結(jié)構(gòu)化的,可以進行壓縮/編碼來減少存儲占用空間。 但高性能并不是沒有代價的。首先,MOLAP需要進行預計算,這會花去很多時間。如果每次寫入增量數(shù)據(jù)后均要進行全量預計算,顯然是低效率的,因此支持僅對增量數(shù)據(jù)進行迭代計算非常重要。其次,如果業(yè)務發(fā)生需求變更,需要進行預定模型之外新的查詢操作,現(xiàn)有的MOLAP實例就無能為力了,只能重新進行建模和預計算。 因此,MOLAP適合業(yè)務需求比較固定,數(shù)據(jù)量較大的場景。在開源軟件中,由eBay開發(fā)并貢獻給Apache基金會的Kylin即屬于這類OLAP引擎,支持在百億規(guī)模的數(shù)據(jù)集上進行亞秒級查詢。
其架構(gòu)圖較直觀得反映了基于cube的預計算模型(build),如下所示: 2、ROLAP 與MOLAP相反,ROLAP無需預計算,直接在構(gòu)成多維數(shù)據(jù)模型的事實表和維度表上進行計算。R即表示關(guān)系型(Relational)。顯然,這種方式相比MOLAP更具可擴展性,增量數(shù)據(jù)導入后,無需進行重新計算,用戶有新的查詢需求時只需寫好正確的SQL語句既能完成獲取所需的結(jié)果。 但ROLAP的不足也很明顯,尤其是在數(shù)據(jù)體量巨大的場景下,用戶提交SQL后,獲取查詢結(jié)果所需的時間無法準確預知,可能秒回,也可能需要花費數(shù)十分鐘甚至數(shù)小時。本質(zhì)上,ROLAP是把MOLAP預計算所需的時間分攤到了用戶的每次查詢上,肯定會影響用戶的查詢體驗。 當然ROLAP的性能是否能夠接受,取決于用戶查詢的SQL類型,數(shù)據(jù)規(guī)模以及用戶對性能的預期。對于相對簡單的SQL,比如TPCH中的Query響應時間較快。但如果是復雜SQL,比如TPC-DS中的數(shù)據(jù)分析和挖掘類的Query,可能需要數(shù)分鐘。 相比MOLAP,ROLAP的使用門檻更低,在完成星型或雪花型模型的構(gòu)建,創(chuàng)建對應schema的事實表和維度表并導入數(shù)據(jù)后,用戶只需會寫出符合需求的SQL,就可以得到想要的結(jié)果。相比創(chuàng)建“數(shù)據(jù)立方體”,顯然更加方便。 有分析表明,雖然ROLAP的性能比如MOLAP,但由于其靈活性、擴展性,ROLAP的使用者是MOLAP的數(shù)倍。 3、HOLAP MOLAP和ROLAP各有優(yōu)缺點,而且是互斥的。如果能夠?qū)烧叩膬?yōu)點進行互補,那么是個更好的選擇。而HOLAP的出現(xiàn)就是這個目的,H表示混合型(Hybrid),這個想法很樸素直接。對于查詢頻繁而穩(wěn)定但又耗時的那些SQL,通過預計算來提速;對于較快的查詢、發(fā)生次數(shù)較少或新的查詢需求,像ROLAP一樣直接通過SQL操作事實表和維度表。 目前似乎沒有開源的OLAP系統(tǒng)屬于這個類型,一些大數(shù)據(jù)服務公司或互聯(lián)網(wǎng)廠商,比如HULU有類似的產(chǎn)品。相信未來HOLAP可能會得到進一步發(fā)展,并獲得更大規(guī)模的使用。 4、HTAP 從另一個維度看,HTAP也算是一種OLAP類型的系統(tǒng),是ROLAP的一個擴展,具備了OLAP的能力。最新發(fā)展顯示,有云廠商在HTAP的基礎上做了某種妥協(xié),將T(transaction)弱化為S(Serving),朝HSAP方向演進。關(guān)于HTAP/HSAP,本文不做進一步展開,可自主查詢其他資料。 主流的OLAP數(shù)倉系統(tǒng)很多,包含上面所述的各種類型,下圖是Gartner 2019 年發(fā)布的數(shù)據(jù)分析市場排名: 可以發(fā)現(xiàn),傳統(tǒng)的商業(yè)廠商和閉源的云服務廠商占據(jù)了絕大部分市場。大部分系統(tǒng)筆者只聽過而沒有研究過。作為屁股在互聯(lián)網(wǎng)公司的數(shù)據(jù)庫/數(shù)據(jù)倉庫開發(fā)者,本文后續(xù)主要聚焦在基于Hadoop生態(tài)發(fā)展的開源OLAP系統(tǒng)(SQL on Hadoop)。 有哪些常用的開源ROLAP產(chǎn)品? 目前生產(chǎn)環(huán)境使用較多的開源ROLAP主要可以分為兩大類,一個是寬表模型,另一個是多表組合模型(就是前述的星型或雪花型)。 寬表模型 寬表模型能夠提供比多表組合模型更好的查詢性能,不足的是支持的SQL操作類型比較有限,比如對Join等復雜操作支持較弱或不支持。 目前該類OLAP系統(tǒng)包括Druid和ClickHouse等,兩者各有優(yōu)勢,Druid支持更大的數(shù)據(jù)規(guī)模,具備一定的預聚合能力,通過倒排索引和位圖索引進一步優(yōu)化查詢性能,在廣告分析場景、監(jiān)控報警等時序類應用均有廣泛使用;ClickHouse部署架構(gòu)簡單,易用,保存明細數(shù)據(jù),依托其向量化查詢、減枝等優(yōu)化能力,具備強勁的查詢性能。兩者均具備較高的數(shù)據(jù)實時性,在互聯(lián)網(wǎng)企業(yè)均有廣泛使用。 除了上面介紹的Druid和ClickHouse外,ElasticSearch和Solar也可以歸為寬表模型。但其系統(tǒng)設計架構(gòu)有較大不同,這兩個一般稱為搜索引擎,通過倒排索引,應用Scatter-Gather計算模型提高查詢性能。對于搜索類的查詢效果較好,但當數(shù)據(jù)量較大或進行掃描聚合類查詢時,查詢性能會有較大影響。 多表組合模型 采用星型或雪花型建模是最通用的一種ROLAP系統(tǒng),常見的包括GreenPlum、Presto和Impala等,他們均基于MPP架構(gòu),采用該模型和架構(gòu)的系統(tǒng)具有支持的數(shù)據(jù)量大、擴展性較好、靈活易用和支持的SQL類型多樣等優(yōu)點。 相比其他類型ROLAP和MOLAP,該類系統(tǒng)性能不具有優(yōu)勢,實時性較一般。通用系統(tǒng)往往比專用系統(tǒng)更難實現(xiàn)和進行優(yōu)化,這是因為通用系統(tǒng)需要考慮的場景更多,支持的查詢類型更豐富。而專用系統(tǒng)只需要針對所服務的某個特定場景進行優(yōu)化即可,相對復雜度會有所降低。 對于ROLAP系統(tǒng),尤其是星型或雪花型的系統(tǒng),如果能夠盡可能得縮短響應時間非常重要,這將是該系統(tǒng)的核心競爭力。這塊內(nèi)容,我們放在下一節(jié)著重進行介紹。 有哪些黑科技用于優(yōu)化ROLAP系統(tǒng)性能? 目前生產(chǎn)環(huán)境使用的ROLAP系統(tǒng),均實現(xiàn)了大部分的該領域性能優(yōu)化技術(shù),包括采用MPP架構(gòu)、支持基于代價的查詢優(yōu)化(CBO)、向量化執(zhí)行引擎、動態(tài)代碼生成機制、存儲空間和訪問效率優(yōu)化、其他cpu和內(nèi)存相關(guān)的計算層優(yōu)化等。下面逐一進行介紹。 什么是MPP架構(gòu)? 首先來聊聊系統(tǒng)架構(gòu),這是設計OLAP系統(tǒng)的第一次分野,目前生產(chǎn)環(huán)境中系統(tǒng)采用的架構(gòu)包括基于傳統(tǒng)的MapReduce架構(gòu)加上SQL層組裝的系統(tǒng);主流的基于MPP的系統(tǒng);其他非MPP系統(tǒng)等。 1、MR架構(gòu)及其局限 在Hadoop生態(tài)下,最早在Hive上提供了基于MapReduce框架的SQL查詢服務。 第一個問題導致無法進行跨MR操作間的優(yōu)化,第二個問題導致MR間數(shù)據(jù)交互需要大量的IO操作。兩個問題均對執(zhí)行效率產(chǎn)生很大影響,性能較差。 2、MPP優(yōu)缺點分析 MPP是massively parallel processing的簡稱,即大規(guī)模并行計算框架。相比MR等架構(gòu),MPP查詢速度快,通常在秒計甚至毫秒級以內(nèi)就可以返回查詢結(jié)果,這也是為何很多強調(diào)低延遲的系統(tǒng),比如OLAP系統(tǒng)大多采用MPP架構(gòu)的原因。 下面以Impala為例,簡單介紹下MPP系統(tǒng)架構(gòu)。 上圖即為Impala架構(gòu)圖,展示了Impala各個組件及一個查詢的執(zhí)行流程。 用戶通過Impala提供的impala-shell或beeline等客戶端/UI工具向Impala節(jié)點下發(fā)查詢SQL;接收該SQL的Impala節(jié)點即為Coordinator節(jié)點,該節(jié)點負責進行SQL解析;
首先產(chǎn)生基于單節(jié)點的執(zhí)行計劃;再對執(zhí)行計劃進行分布式處理,比如將Join、聚合(aggregation)等并行化到各Impala Executor節(jié)點上。執(zhí)行計劃被切分為多個Plan Fragment(PF),每個PF又由一到多個Operator組成;
接著,下發(fā)經(jīng)過優(yōu)化后的執(zhí)行計劃的PF到對應的Executor節(jié)點,多個執(zhí)行節(jié)點并行處理任務,縮短整個任務所需時間;
執(zhí)行節(jié)點掃描HDFS/Hbase等存儲上的數(shù)據(jù),并逐層進行處理,比如進行跨節(jié)點的數(shù)據(jù)shuffe,Join等操作;
執(zhí)行節(jié)點完成任務并將輸出結(jié)果統(tǒng)一發(fā)送到Coordinator節(jié)點;
Coordinator節(jié)點匯總各個執(zhí)行節(jié)點數(shù)據(jù),做最后處理,最終返回給用戶想要的結(jié)果集。
3、MPP架構(gòu)之所以性能比MR好,原因包括: 這樣可以充分利用CPU資源,減少IO資源消耗。但事情往往是兩面的,MPP并不完美,主要問題包括: 中間結(jié)果不落盤,在正常情況下是利好,但在異常情況下就是利空,這意味著出現(xiàn)節(jié)點宕機等場景下,需要重新計算產(chǎn)生中間結(jié)果,拖慢任務完成時間;
擴展性沒有MR等架構(gòu)好,或者說隨著MPP系統(tǒng)節(jié)點增多到一定規(guī)模,性能無法線性提升。有個原因是“木桶效應”,系統(tǒng)性能瓶頸取決于性能最差的那個節(jié)點。另一個原因是規(guī)模越大,出現(xiàn)節(jié)點宕機、壞盤等異常情況就會越頻繁,故障率提高會導致SQL重試概率提升;
基于上述分析,MPP比較適合執(zhí)行時間不會太久的業(yè)務場景,比如數(shù)小時。因為時間越久,故障概率越大。 4、其他非MPP架構(gòu) 基于MR系統(tǒng)局限性考慮,除了采用MPP架構(gòu)外,Hive和Spark均使用不同方式進行了優(yōu)化,包括Hive的Tez,SparkSQL基于DAG(Directed Acyclic Graph)等。 不同架構(gòu)有不同優(yōu)缺點,重要的是找到其適用的場景,并進行靠譜地優(yōu)化,充分發(fā)揮其優(yōu)勢。 什么是基于代價的查詢優(yōu)化? 有了適合的系統(tǒng)架構(gòu)并不一定能夠帶來正向收益,“好馬配好鞍”,執(zhí)行計劃的好壞對最終系統(tǒng)的性能也有著決定性作用。執(zhí)行計劃及其優(yōu)化,就筆者的理解來說,其來源于關(guān)系型數(shù)據(jù)庫領域。這又是一門大學問,這里僅簡單介紹。 分布式架構(gòu)使得執(zhí)行計劃能夠進行跨節(jié)點的并行優(yōu)化,通過任務粒度拆分、串行變并行等方式大大縮短執(zhí)行時間。除此之外,還有2個更重要的優(yōu)化方式,就是傳統(tǒng)的基于規(guī)則優(yōu)化以及更高級的基于代價優(yōu)化。 基于規(guī)則優(yōu)化 通俗來說,基于規(guī)則的優(yōu)化(rule based optimization,RBO)指的是不需要額外的信息,通過用戶下發(fā)的SQL語句進行的優(yōu)化,主要通過改下SQL,比如SQL子句的前后執(zhí)行順序等。比較常見的優(yōu)化包括謂語下推、字段過濾下推、常量折疊、索引選擇、Join優(yōu)化等等。 謂語下推,即PredicatePushDown,最常見的就是where條件等,舉MySQL為例,MySQL Server層在獲取InnoDB表數(shù)據(jù)時,將Where條件下推到InnoDB存儲引擎,InnoDB過濾where條件,僅返回符合條件的數(shù)據(jù)。在有數(shù)據(jù)分區(qū)場景下,謂語下推更有效; 字段過濾下推,即ProjectionPushDown,比如某個SQL僅需返回表記錄中某個列的值,那么在列存模式下,只需讀取對應列的數(shù)據(jù),在行存模式下,可以選擇某個索引進行索引覆蓋查詢,這也是索引選擇優(yōu)化的一種場景; 常量或函數(shù)折疊也是一種常見的優(yōu)化方式,將SQL語句中的某些常量計算(加減乘除、取整等)在執(zhí)行計劃優(yōu)化階段就做掉; Join優(yōu)化有很多方法,這里說的基于規(guī)則優(yōu)化,主要指的是Join的實現(xiàn)方式,比如最傻瓜式的Join實現(xiàn)就是老老實實得讀取參與Join的2張表的每條記錄進行Join條件比對。而最普遍的優(yōu)化方式就是Hash Join,顯然效率很高。不要認為這是想當然應該有的功能,其實MySQL直到8.0版本才具備。另外Join的順序及合并,有部分也可以直接通過SQL來進行判斷和選擇。 基于代價優(yōu)化 基于規(guī)則的優(yōu)化器簡單,易于實現(xiàn),通過內(nèi)置的一組規(guī)則來決定如何執(zhí)行查詢計劃。與之相對的是基于代價優(yōu)化(cost based optimization,CBO)。 CBO的實現(xiàn)依賴于詳細可靠的統(tǒng)計信息,比如每個列的最大值、最小值、平均值、區(qū)分度、記錄數(shù)、列總和,表大小分區(qū)信息,以及列的直方圖等元數(shù)據(jù)信息。 CBO的一大用途是在Join場景,決定Join的執(zhí)行方式和Join的順序。這里所說的Join我們主要是討論Hash Join。 根據(jù)參與Join的驅(qū)動表(Build Table)和被驅(qū)動表(Probe Table)的大小,Hash Join一般可分為broadcast和partition兩種。 廣播方式適用于大表與小表進行Join,在并行Join時,將小表廣播到大表分區(qū)數(shù)據(jù)所在的各個執(zhí)行節(jié)點,分別與大表分區(qū)數(shù)據(jù)進行Join,最后返回Join結(jié)果并匯總。 而分區(qū)方式是最為一般的模式,適用于大表間Join或表大小未知場景。分別將兩表進行分區(qū),每個分區(qū)分別進行Join。 顯然,判斷大小表的關(guān)鍵就看是否能夠通過某種方式獲取表的記錄數(shù),如果存儲層保存了記錄數(shù),那么可從元數(shù)據(jù)中直接獲取。 如果Join的兩表都是大表,但至少有個表是帶Where過濾條件的,那么在決定走分區(qū)方式前還可進一步看滿足條件的記錄數(shù),這時候,物理上進行分區(qū)的表存儲方式可發(fā)揮作用,可以看每個分區(qū)的最大值和最小值及其記錄數(shù)來估算過濾后的總記錄數(shù)。當然,還有種更精確的方式是列直方圖,能夠直接而直觀得獲取總記錄數(shù)。 如果上述的統(tǒng)計信息都沒有,要使用CBO還有另一種方式就是進行記錄的動態(tài)采樣來決定走那種Join方式。 如果一個查詢的SQL中存在多層Join操作,如何決定Join的順序?qū)π阅苡泻艽笥绊憽_@塊也已是被數(shù)據(jù)庫大佬們充分研究過的技術(shù)。 一個好的CBO應該能夠根據(jù)SQL 語句的特點,來自動選擇使用Left-deep tree(LDT,左圖)還是 bushy tree(BYT,右圖)執(zhí)行join。 兩種Join順序沒有好壞之分,關(guān)鍵看進行Join的表數(shù)據(jù)即Join的字段特點。 對于LDT,如果每次Join均能夠過濾掉大量數(shù)據(jù),那么從資源消耗來看,顯然是更優(yōu)的。對于給每個列都構(gòu)建了索引的某些系統(tǒng),使用LDT相比BYT更好。 一般來說,選擇BYT是效率更高的模式,通過串行多層Join改為并行的更少層次Join,可以發(fā)揮MPP架構(gòu)的優(yōu)勢,盡快得到結(jié)果,在多表模式ROLAP場景常采用。 為什么需要向量化執(zhí)行引擎?其與動態(tài)代碼生成有何關(guān)系? 查詢執(zhí)行引擎 (query execution engine) 是數(shù)據(jù)庫中的一個核心組件,用于將查詢計劃轉(zhuǎn)換為物理計劃,并對其求值返回結(jié)果。查詢執(zhí)行引擎對系統(tǒng)性能影響很大,在一項針對Impala和Hive的對比時發(fā)現(xiàn),Hive在某些簡單查詢上(TPC-H Query 1)也比Impala慢主要是因為Hive運行時完全處于CPU bound的狀態(tài)中,磁盤IO只有20%,而Impala的IO至少在85%。 什么原因?qū)е逻@么大的差別呢?首先得簡單說下火山模型的執(zhí)行引擎。 火山模型及其缺點 火山模型(Volcano-style execution)是最早的查詢執(zhí)行引擎,也叫做迭代模型 (iterator model),或 one-tuple-at-a-time。在這種模型中,查詢計劃是一個由operator組成的DAG,其中每一個operator 包含三個函數(shù):open,next,close。Open 用于申請資源,比如分配內(nèi)存,打開文件,close 用于釋放資源,next方法遞歸的調(diào)用子operator的 next方法生成一個元組(tuple,即行row在物理上的表示)。 下圖描述了“select sum(C1) from T1 where C2 > 15”的查詢計劃,該查詢計劃包含Project,HashAgg,Scan等operator,每個 operator的next方法遞歸調(diào)用子節(jié)點的 next,一直遞歸調(diào)用到葉子節(jié)點Scan operator,Scan operator的next 從文件中返回一個元組。 大量虛函數(shù)調(diào)用:火山模型的next方法通常實現(xiàn)為一個虛函數(shù),在編譯器中,虛函數(shù)調(diào)用需要查找虛函數(shù)表, 并且虛函數(shù)調(diào)用是一個非直接跳轉(zhuǎn) (indirect jump), 會導致一次錯誤的CPU分支預測 (brance misprediction), 一次錯誤的分支預測需要十幾個周期的開銷?;鹕侥P蜑榱朔祷匾粋€元組,需要調(diào)用多次next 方法,導致昂貴的函數(shù)調(diào)用開銷
類型裝箱:對于a + 2 * b之類表達式,由于需要對不同數(shù)據(jù)類型的變量做解釋,所以在Java中需要把這些本來是primitive(如int等類型)的變量包裝成Object,但執(zhí)行時又需要調(diào)用具體類型的實現(xiàn)函數(shù),這本質(zhì)上也是虛函數(shù)調(diào)用的效率問題;
CPU Cache利用效率低:next方法一次只返回一個元組,元組通常采用行存儲,如果僅需訪問第一列而每次均將一整行填入CPU Cache,將導致Cache Miss;
條件分支預測失?。含F(xiàn)在的CPU都是有并行流水線的,但是如果出現(xiàn)條件判斷會導致無法并行。比如判斷數(shù)據(jù)的類型(是string還是int),或判斷某一列是否因為其他字段的過濾條件導致本行不需要被讀取等場景;
CPU與IO性能不匹配:每次從磁盤讀取一個行數(shù)據(jù),經(jīng)過多次調(diào)用交給CPU進行處理,顯然,大部分時間都是CPU等待數(shù)據(jù)就緒,導致CPU空轉(zhuǎn)。
通過上述描述,可以得出解決問題的基本方法??梢詫栴}分為2大類,分別用下述的向量化引擎和動態(tài)代碼生成技術(shù)來解決。 向量化執(zhí)行引擎 向量化執(zhí)行以列存為前提,主要思想是每次從磁盤上讀取一批列,這些列以數(shù)組形式組織。每次next都通過for循環(huán)處理列數(shù)組。這么做可以大幅減少next的調(diào)用次數(shù)。相應的CPU的利用率得到了提高,另外數(shù)據(jù)被組織在一起。可以進一步利用CPU硬件的特性,如SIMD,將所有數(shù)據(jù)加載到CPU的緩存當中去,提高緩存命中率,提升效率。在列存儲與向量化執(zhí)行引擎的雙重優(yōu)化下,查詢執(zhí)行的速度會有一個非常巨大的飛躍。 動態(tài)代碼生成 向量化執(zhí)行減少CPU等待時間,提高CPU Cache命中率,通過減少next調(diào)用次數(shù)來緩解虛函數(shù)調(diào)用效率問題。而動態(tài)代碼生成,則是進一步解決了虛函數(shù)調(diào)用問題。 動態(tài)代碼生成技術(shù)不使用解釋性的統(tǒng)一代碼,而是直接生成對應的執(zhí)行語言的代碼并直接用primitive type。對于判斷數(shù)據(jù)類型造成的分支判斷,動態(tài)代碼的效果可以消除這些類型判斷,使用硬件指令來進一步提高循環(huán)處理效率。 具體實現(xiàn)來說,JVM系如Spark SQL,Presto可以用反射,C++系的Impala則使用了llvm生成中間碼。相對來說,C++的效率更高。 向量化和動態(tài)代碼生成技術(shù)往往是一起工作達到更好的效果。 都有哪些存儲空間和訪問效率優(yōu)化方法? 存儲和IO模塊的優(yōu)化方法很多,這里我們還是在Hadoop生態(tài)下來考慮,當然,很多優(yōu)化方法不是Hadoop特有的,而是通用的。OLAP場景下,數(shù)據(jù)存儲最基礎而有效的優(yōu)化是該行存儲為列存儲,下面討論的優(yōu)化措施均基于列存。 數(shù)據(jù)壓縮和編碼 數(shù)據(jù)壓縮是存儲領域常用的優(yōu)化手段,以可控的CPU開銷來大幅縮小數(shù)據(jù)在磁盤上的存儲空間,一來可以節(jié)省成本,二來可以減小IO和數(shù)據(jù)在內(nèi)存中跨線程和跨節(jié)點網(wǎng)絡傳輸?shù)拈_銷。目前在用的主流壓縮算法包括zlib、snappy和lz4等。壓縮算法并不是壓縮比越高越好,壓縮率越高的算法壓縮和解壓縮速度往往就越慢,需要根據(jù)硬件配置和使用場景在cpu 和io之間進行權(quán)衡。 數(shù)據(jù)編碼可以理解為輕量級壓縮,包括RLE和數(shù)據(jù)字典編碼等。 上圖截至Presto論文,展示了RLE編碼和數(shù)據(jù)字典編碼的使用方式。RLE用在各列都是重復字符的情況,比如page0中6行記錄的returnflag都是"F"。數(shù)據(jù)字典可高效使用在區(qū)分度較低的列上,比如列中只有幾種字符串的場景??紤]到同個表的列的值相關(guān)性,數(shù)據(jù)字典可以跨page使用。 與數(shù)據(jù)壓縮相比,數(shù)據(jù)編碼方式在某些聚合類查詢場景下,無需對數(shù)據(jù)進行解碼,直接返回所需結(jié)果。比如假設T1表的C1列為某個字符,RLE算法將16個C1列的值“aaaaaabbccccaaaa”編碼為6a2b4c4a,其中6a表示有連續(xù)6個字符a。當執(zhí)行 select count(*) from T1 where C1=’a’時,不需要解壓6a2b4c4a,就能夠知道這16行記錄對應列值為a有10行。 在列存模式下,數(shù)據(jù)壓縮和編碼的效率均遠高于行存。 數(shù)據(jù)精細化存儲 所謂數(shù)據(jù)精細化存儲,是通過盡可能多得提供元數(shù)據(jù)信息來減少不必要的數(shù)據(jù)掃描和計算,常用的方法包括但不限于如下幾種: 數(shù)據(jù)分區(qū):數(shù)據(jù)分區(qū)可用于將表中數(shù)據(jù)基于hash或range打散到多個存儲節(jié)點上,配合多副本存儲??梢蕴岣邤?shù)據(jù)容災和遷移效率。除此之外,在查詢時可以快速過濾掉不符合where條件要求的數(shù)據(jù)分區(qū),無需逐列讀取數(shù)據(jù)進行判斷。
行組:與數(shù)據(jù)分區(qū)類似,Hadoop中常用的parquet和orcfile還將表數(shù)據(jù)分為多個行組(row group),每個行組內(nèi)的記錄按列存儲。這樣即達到列存提高OLAP查詢效率,同時能夠兼顧查詢多行的需求;
局部索引:在數(shù)據(jù)分區(qū)或行組上創(chuàng)建索引,可以提高查詢效率。如下圖所示,orcfile在每個行組的頭部維護了Index Data來,保存最大值和最小值等元數(shù)據(jù),基于這些信息可以快速決定是否需掃描該行組。某些OLAP系統(tǒng)進一步豐富了元數(shù)據(jù)信息,比如建立該行組記錄的倒排索引或B+樹索引,進一步提高掃描和查詢效率。
數(shù)據(jù)本地化訪問 數(shù)據(jù)本地化讀寫是常見的優(yōu)化方法,在Hadoop下也提供了相應的方式。 一般來說,讀HDFS上的數(shù)據(jù)首先需要經(jīng)過NameNode獲取數(shù)據(jù)存放的DataNode信息,在去DataNode節(jié)點讀取所需數(shù)據(jù)。 對于Impala等OLAP系統(tǒng),可以通過HDFS本地訪問模式進行優(yōu)化,直接讀取磁盤上的HDFS文件數(shù)據(jù)。HDFS這個特性稱為"Short Circuit Local Reads",其相關(guān)的配置項(在hdfs-site.xml中)如下: <property > <name > dfs.client.read.shortcircuit</name > <value > true</value > </property > <property > <name > dfs.domain.socket.path</name > <value > /var/lib/hadoop-hdfs/dn_socket</value > </property > 其中:dfs.client.read.shortcircuit是打開這個功能的開關(guān),dfs.domain.socket.path是Datanode和DFSClient之間溝通的Socket的本地路徑。 運行時數(shù)據(jù)過濾 這是少部分OLAP系統(tǒng)才具有的高級功能,比如Impala的RunTime Filter(RF)運行時過濾,和SparkSQL 3.0的 Dynamic Partition Pruning動態(tài)分區(qū)裁剪,可以將驅(qū)動表的bloomfilter(BF)或過濾條件作用在被驅(qū)動表的數(shù)據(jù)掃描階段,從而極大減少需掃描/返回的數(shù)據(jù)量。下面分別用一個圖進行簡述,在后續(xù)分析具體OLAP系統(tǒng)時再詳述。 上圖直觀得展示了Impala runtime filter的實現(xiàn)。流程如下: 同時下發(fā)兩個表的SCAN操作。左邊是大表,右邊是小表(相對而言,也有可能是同等級別的),但是左表會等待一段時間(默認是1s),因此右表的SCAN會先執(zhí)行;
右表的掃描的結(jié)果根據(jù)join鍵哈希傳遞掃不同的Join節(jié)點,由Join節(jié)點執(zhí)行哈希表的構(gòu)建和RF的構(gòu)建;
Join節(jié)點讀取完全部的右表輸入之后也完成了RF的構(gòu)建,它會將RF交給Coordinator節(jié)點(如果是Broadcast Join則會直接交給左表的Scan節(jié)點);
Coordinator節(jié)點將不同的RF進行merge,也就是把Bloom Filter進行merge,merge之后的Bloom Filter就是一個GLOBAL RF,它將這個RF分發(fā)給每一個左表Scan;
左表會等待一段時間(默認1s)再開啟數(shù)據(jù)掃描,為了是盡可能的等待RF的到達,但是無論RF什么時候到達,RF都會在到達那一刻之后被應用;
左表使用RF完成掃描之后同樣以Hash方式交給Join節(jié)點,由Join節(jié)點進行apply操作,以完成整個Join過程。
sparksql圖1(官方這個圖有誤,右邊應該是Scan Date) 上面2幅圖是SparkSQL 3.0的動態(tài)分區(qū)裁剪示意圖。將右表的掃描結(jié)果(hashtable of table Date after filter)廣播給左表的Join節(jié)點,在進行左表掃描時即使用右表的hashtable進行條件數(shù)據(jù)過濾。 除了上面這些,還有其他優(yōu)化方法嗎? 還有個極為重要的技術(shù)是集群資源管理和調(diào)度。Hadoop使用YARN進行資源調(diào)度,雖然帶來了很大遍歷,但對性能要求較高的OLAP系統(tǒng)卻有些不適合。 如啟動AppMaster和申請container會占用不少時間,尤其是前者,而且container的供應如果時斷時續(xù),會極大的影響時效性。 目前的優(yōu)化方法主要包括讓AppMaster啟動后長期駐守,container復用等方式。讓資源在需要用時已經(jīng)就位,查詢無需等待即可馬上開始。 最后做個總結(jié) 本文主要是想闡述下自己對數(shù)倉和OLAP系統(tǒng)的理解,之所以采用問答形式,是因為筆者就是帶著這些問題去google網(wǎng)上或公司內(nèi)部的資料,或者直接請教在這個領域的大佬。 由于水平有限,難免有所錯誤,非常歡迎大家看后能夠指出。