基于Impala的高性能數(shù)倉實(shí)踐之執(zhí)行引擎模塊
導(dǎo)讀:
本系列文章將結(jié)合實(shí)際開發(fā)和使用經(jīng)驗(yàn),聊聊可以從哪些方面對數(shù)倉查詢引擎進(jìn)行優(yōu)化。
Impala是Cloudera開發(fā)和開源的數(shù)倉查詢引擎,以性能優(yōu)秀著稱。除了Apache Impala開源項(xiàng)目,業(yè)界知名的Apache Doris和StarRocks、SelectDB項(xiàng)目也跟Impala有千絲萬縷的聯(lián)系。筆者所在的網(wǎng)易數(shù)帆大數(shù)據(jù)團(tuán)隊(duì),是最早一批將其作為分析型數(shù)倉查詢引擎的團(tuán)隊(duì),目前正基于Impala打造有數(shù)高性能數(shù)倉引擎。
文章大致可以分為這幾個(gè)部分:首先會(huì)對簡單介紹下Impala的架構(gòu)和元數(shù)據(jù)管理,以便后續(xù)內(nèi)容展開;接著從執(zhí)行引擎,存儲(chǔ)優(yōu)化,物化視圖,數(shù)據(jù)緩存和虛擬數(shù)倉等維度進(jìn)行探討。本文為執(zhí)行引擎篇。
Impala簡介
Impala集群包含一個(gè)Catalog Server (Catalogd)、一個(gè)Statestore Server (Statestored) 和若干個(gè)Impala Daemon (Impalad)。Catalogd主要負(fù)責(zé)元數(shù)據(jù)的獲取和DDL的執(zhí)行,Statestored主要負(fù)責(zé)消息/元數(shù)據(jù)的廣播,Impalad主要負(fù)責(zé)查詢的接收和執(zhí)行。

Impalad又可配置為coordinator only、 executor only 或coordinator and executor(默認(rèn))三種模式。Coordinator角色的Impalad負(fù)責(zé)查詢的接收、計(jì)劃生成、查詢的調(diào)度等,Executor角色的Impalad負(fù)責(zé)數(shù)據(jù)的讀取和計(jì)算。默認(rèn)配置下每個(gè)Impalad既是Coordinator又是Executor。生產(chǎn)環(huán)境建議做好角色分離,即每個(gè)Impalad要么是Coordinator要么是Executor。
1.1 元數(shù)據(jù)管理
Impala的元數(shù)據(jù)緩存在catalogd和各個(gè)Coordinator角色的Impalad中。Catalogd中的緩存是最新的,各個(gè)Coordinator都緩存的是Catalogd內(nèi)元數(shù)據(jù)的一個(gè)復(fù)本。元數(shù)據(jù)由Catalogd向外部系統(tǒng)獲取,并通過Statestored 傳播給各個(gè)Coordinator。
以Hive表為例,Catalogd中的元數(shù)據(jù)分別從Hive Metastore(HMS)和HDFS NameNode(NN)獲取。從HMS獲取的信息包括元數(shù)據(jù)信息和統(tǒng)計(jì)信息兩部分,元數(shù)據(jù)信息指有哪些庫和表,表定義,列類型等,對應(yīng)“show databases,show tables,show create table xxx,show ”等操作。統(tǒng)計(jì)信息包括表的大小,行數(shù),分區(qū)和各列的信息等,對應(yīng)“show table stats xx,show column stats xx”等操作。從NN獲取的是文件粒度的信息,包括文件存儲(chǔ)位置,副本和文件塊信息等。
1.2 管理服務(wù)器
管理服務(wù)器是有數(shù)高性能數(shù)倉增加的Impala模塊,提供集群粒度的SQL查看界面,持久化保存歷史查詢信息并展示,SQL審計(jì),查詢錯(cuò)誤和查詢性能分析,自動(dòng)進(jìn)行統(tǒng)計(jì)信息計(jì)算等。

執(zhí)行引擎(Execute Engine)
2.1 執(zhí)行模型
在執(zhí)行模型這塊,目前主要有動(dòng)態(tài)代碼生成(code generation或just in time/JIT)和向量化計(jì)算兩個(gè)流派,Impala主要是基于JIT進(jìn)行性能優(yōu)化,對于向量化引擎,Impala社區(qū)版目前并沒有相關(guān)規(guī)劃,有數(shù)高性能數(shù)倉團(tuán)隊(duì)也有計(jì)劃對其進(jìn)行向量化改造。
在具體實(shí)現(xiàn)上,Impala屬于改進(jìn)版的火山模型,官方論文描述為
The execution model is the traditional Volcano?style with Exchange operators. Processing is performed batch?at?a?time: each GetNext() call operates over batches of rows, similar to
即在傳統(tǒng)的火山模型的基礎(chǔ)上加入Exchange操作符,用于進(jìn)行不同執(zhí)行節(jié)點(diǎn)的數(shù)據(jù)交換。每次會(huì)獲取一批記錄而不是一條記錄。
不管是JIT還是矢量化,其目的都是盡可能地減少執(zhí)行引擎核心代碼流程的調(diào)用次數(shù)并提高函數(shù)執(zhí)行效率,這對于需要處理海量記錄時(shí)非常重要。Impala通過每次獲取一批記錄來減少調(diào)用次數(shù),再利用JIT技術(shù)來生成針對特定類型數(shù)據(jù)的執(zhí)行流程函數(shù),提高每次調(diào)用的效率。
更進(jìn)一步,Impala采用數(shù)據(jù)流水線(streaming pipelined)執(zhí)行機(jī)制,充分利用計(jì)算資源進(jìn)行并發(fā)執(zhí)行。在Impala 4.0版本,完整支持了executor節(jié)點(diǎn)的多線程執(zhí)行模型,進(jìn)一步提高并發(fā)能力,壓榨計(jì)算資源。
動(dòng)態(tài)代碼生成原理及優(yōu)化
JIT技術(shù)與靜態(tài)編譯技術(shù)相反,其是在具體的查詢運(yùn)行之前才進(jìn)行代碼編譯,此時(shí),查詢中需要處理的列類型,用到的算子和函數(shù)都已經(jīng)確定,可以為該查詢生成特定版本的處理函數(shù)。如下圖所示:

左側(cè)是通用的從文件讀取記錄(tuple)并解析的行數(shù),外層一個(gè)for循環(huán)用于對每一列進(jìn)行處理,內(nèi)層的switch用于判斷列的類型并調(diào)用特定的解析函數(shù)。如果我們已經(jīng)知道該記錄由三列組成,類型分別為int,bool和int,那么JIT技術(shù)就可以生成如圖右側(cè)的函數(shù)版本,不需要for循環(huán),也不需要switch判斷,顯然,執(zhí)行效率更高。
總的來說,Impala使用LLVM來進(jìn)行JIT優(yōu)化,生成對于某個(gè)具體查詢最優(yōu)的函數(shù)實(shí)現(xiàn)。其優(yōu)化項(xiàng)具體包括移除條件分支(Removing conditionals,如上所示)、移除內(nèi)存加載和內(nèi)聯(lián)虛函數(shù)調(diào)用等。
啟用動(dòng)態(tài)代碼生成時(shí),在查詢執(zhí)行前需要先動(dòng)態(tài)生成其執(zhí)行代碼,因此有一定的時(shí)間消耗,對于小查詢,動(dòng)態(tài)代碼生成可能是有害的,生成代碼的時(shí)間都有可能超過SQL執(zhí)行時(shí)間。Impala提供了DISABLE_CODEGEN_ROWS_THRESHOLD參數(shù),默認(rèn)為50000,如果SQL需要處理的記錄數(shù)小于該值,則不會(huì)使用動(dòng)態(tài)代碼生成進(jìn)行執(zhí)行優(yōu)化。Impala 4.0版本對JIT進(jìn)行了進(jìn)一步優(yōu)化,采用異步化改造來避免生成JIT代碼對查詢性能的影響,當(dāng)編譯未完成時(shí)使用原函數(shù),完成后無縫切換成優(yōu)化后的函數(shù)代碼。
2.2 計(jì)算資源
Impala屬于SQL on Hadoop的一種,基于MPP(Massively Parallel Processing,即大規(guī)模并行處理)架構(gòu),正常情況下,查詢涉及的各種操作均在內(nèi)存中完成的,因此,可用內(nèi)存的多少及對其的利用效率,對Impala查詢性能有極大影響。同樣地,作為一個(gè)OLAP查詢引擎,可用的CPU資源對查詢性能也至關(guān)重要。Impala雖提供了少數(shù)CPU相關(guān)配置項(xiàng),如num_threads_per_core 等,但對CPU使用的控制能力較差。本小節(jié)后續(xù)僅介紹內(nèi)存資源相關(guān),CPU計(jì)算后續(xù)另開一篇單獨(dú)介紹。
Impala資源池
Impala有比較豐富的資源使用限制方式,稱為準(zhǔn)入控制。其中資源池(resource pool)是Impala進(jìn)行并發(fā)控制的主要手段,可以決定某個(gè)查詢是否會(huì)被拒絕,或執(zhí)行,或排隊(duì)。其主要有兩種控制方式,一種是手動(dòng)設(shè)置最大并發(fā)數(shù)控制,超過閾值的請求會(huì)進(jìn)行排隊(duì),可以設(shè)置允許排隊(duì)的最大請求數(shù)和排隊(duì)時(shí)長,超過閾值的請求直接返回失??;另一種是基于內(nèi)存的并發(fā)控制,下面進(jìn)行重點(diǎn)介紹。
基于內(nèi)存的并發(fā)控制
Impala集群支持通過fair-scheduler.xml設(shè)置多個(gè)資源池并規(guī)定其最大可用內(nèi)存(maxResources),再通過llama-site.xml為每個(gè)資源池設(shè)置請求級別的內(nèi)存限制,包括內(nèi)存分配上下限max-query-mem-limit和min-query-mem-limit,及clamp-mem-limit-query-option。除了通過資源池相關(guān)配置控制請求的內(nèi)存使用,還可以通過MEM_LIMIT請求選項(xiàng)設(shè)置內(nèi)存限制。而clamp-mem-limit-query-option就是設(shè)置是否允許MEM_LIMIT設(shè)置的內(nèi)存突破資源池內(nèi)存配置的限制。
需要注意的是,max-query-mem-limit,min-query-mem-limit和MEM_LIMIT設(shè)置的是請求在每個(gè)executor節(jié)點(diǎn)允許申請的最大內(nèi)存,請求申請的總內(nèi)存還需要乘上執(zhí)行該請求的executor節(jié)點(diǎn)個(gè)數(shù)。
若Impala通過預(yù)估發(fā)現(xiàn)查詢所需的內(nèi)存資源超過集群總內(nèi)存資源,該查詢會(huì)被拒絕;若總資源滿足,但由于部分資源已被其他查詢占用,則會(huì)將其放入請求隊(duì)列,待可用資源滿足查詢要求時(shí)再按查詢提交的先后順序調(diào)度執(zhí)行。
若預(yù)估的內(nèi)存資源超過了設(shè)置的max-query-mem-limit,則以max-query-mem-limit為準(zhǔn),若小于min-query-mem-limit,則以min-query-mem-limit為準(zhǔn)。假設(shè)查詢請求設(shè)置了MEM_LIMIT,需先判斷clamp-mem-limit-query-option的值,若為true,則仍然受max-query-mem-limit,min-query-mem-limit約束。下面舉個(gè)例子進(jìn)行說明:
假設(shè)一個(gè)Impala集群有5個(gè)executor節(jié)點(diǎn),集群配置了一個(gè)最大可用內(nèi)存為100GB的資源池。查詢請求的內(nèi)存上下限為10GB和2GB,若clamp-mem-limit-query-option為true,Impala為某個(gè)查詢請求A預(yù)估的內(nèi)存為14GB(或設(shè)置了MEM_LIMIT為14GB),則查詢A在每個(gè)executor最多只能分配10GB內(nèi)存。若clamp-mem-limit-query-option為false,查詢A最多可分配14GB內(nèi)存。
假設(shè)clamp-mem-limit-query-option為true,則該資源池最多只能同時(shí)執(zhí)行2個(gè)查詢A這樣的請求(2 * 5 * 10GB)。
通過上面的例子可知Impala的準(zhǔn)入控制會(huì)在每個(gè)executor為查詢請求預(yù)留所需的內(nèi)存,因此,所預(yù)留的內(nèi)存應(yīng)該盡可能接近實(shí)際所需內(nèi)存,預(yù)留過少會(huì)導(dǎo)致查詢失敗或中間結(jié)果溢出,預(yù)留過多會(huì)導(dǎo)致集群資源沒有被充分利用。在內(nèi)存資源管理的精確性方面,Impala還有較多需優(yōu)化的點(diǎn)。
準(zhǔn)入控制存在的問題
(1)集群同步
Impala進(jìn)行準(zhǔn)入控制的載體是coordinator節(jié)點(diǎn),由于一個(gè)集群至少有2個(gè)及以上的coordinator節(jié)點(diǎn),但準(zhǔn)入控制是針對整個(gè)集群的。Impala通過statestore的impala-request-queue topic機(jī)制在coordinator間周期性地同步每個(gè)coordinator上的查詢并發(fā)和內(nèi)存使用情況。
Impala采用去中心化的設(shè)計(jì)來實(shí)現(xiàn)準(zhǔn)入控制,而不是通過一個(gè)中心節(jié)點(diǎn)來統(tǒng)一決策,雖然在性能和可用性上有優(yōu)勢,但是這會(huì)導(dǎo)致coordinator獲取的其他coordinator信息過舊的問題,尤其是在查詢并發(fā)度較高時(shí),會(huì)導(dǎo)致準(zhǔn)入控制模塊做出錯(cuò)誤的決策。
(2)內(nèi)存預(yù)估精度
Impala需要基于統(tǒng)計(jì)信息來評估查詢需要消耗多少內(nèi)存,因?yàn)榻y(tǒng)計(jì)信息里面會(huì)記錄表的記錄數(shù),列的類型和大小等。沒有統(tǒng)計(jì)信息,就無法正確評估內(nèi)存消耗,也就無法以較優(yōu)的方式執(zhí)行該查詢。(統(tǒng)計(jì)信息相關(guān)的詳細(xì)描述見下一小節(jié))
但就算是有統(tǒng)計(jì)信息,仍有可能依然沒法正確估算需消耗的內(nèi)存量。如下所示:

上圖第一張的"Mem Usage"和"Mem Estimate"分別表示查詢實(shí)際消耗和預(yù)估消耗的總內(nèi)存,可見明顯差別。上圖下面兩張為通過compute incremental stats/compute stats前后通過explain看到的內(nèi)存預(yù)估情況,可見每個(gè)節(jié)點(diǎn)均22MB(共67個(gè)executor節(jié)點(diǎn)),即該查詢內(nèi)存預(yù)估不精確不是因?yàn)闆]有統(tǒng)計(jì)信息導(dǎo)致的。
數(shù)據(jù)溢出(spill to disk)
內(nèi)存不夠怎么辦?
如果因?yàn)榧和窖舆t或內(nèi)存預(yù)估低于實(shí)際所需內(nèi)存,導(dǎo)致查詢執(zhí)行過程中消耗的內(nèi)存超過準(zhǔn)入控制的計(jì)算值,此時(shí)數(shù)據(jù)溢出功能可以派上用場。數(shù)據(jù)溢出是Impala一種兜底機(jī)制,避免因中間結(jié)果集過大導(dǎo)致內(nèi)存不足,進(jìn)而引起查詢失敗。當(dāng)然,并不是所有情況的內(nèi)存不足都會(huì)啟用數(shù)據(jù)溢出,能夠進(jìn)行數(shù)據(jù)溢出的算子主要包括group by,order by,join,distinct和union;
數(shù)據(jù)溢出機(jī)制的用處在于,能夠最大限度避免查詢失敗。OLAP場景由于SQL復(fù)雜度遠(yuǎn)高于OLTP,耗時(shí)也明顯更長,查詢失敗的代價(jià)更大。其帶來的問題是因?yàn)樾枰獙⒅虚g結(jié)果寫盤并讀取,SQL查詢性能會(huì)明顯下降,因此,應(yīng)該通過查詢優(yōu)化盡可能避免數(shù)據(jù)溢出。
關(guān)閉數(shù)據(jù)溢出
有兩種方法可以關(guān)閉數(shù)據(jù)溢出,均是通過query option來設(shè)置,分別是SCRATCH_LIMIT和DISABLE_UNSAFE_SPILLS。
SCRATCH_LIMIT用于設(shè)置溢出目錄的大小,當(dāng)設(shè)為0時(shí),即關(guān)閉了數(shù)據(jù)溢出特性。
DISABLE_UNSAFE_SPILLS更加智能,用于禁止不安全的數(shù)據(jù)溢出。Impala認(rèn)為下列情況屬于不安全的溢出:查詢中存在沒有統(tǒng)計(jì)信息的表,或沒有為join設(shè)置hint,或?qū)Ψ謪^(qū)表進(jìn)行insert ... select操作。
優(yōu)化資源分配效率
(1)集中式準(zhǔn)入控制
從Impala社區(qū)了解到,目前Impala在開發(fā)新的準(zhǔn)入控制實(shí)現(xiàn),預(yù)計(jì)后續(xù)會(huì)提供集中式的準(zhǔn)入控制方案,詳見 Single Admission Controller per Cluster;
(2)降低準(zhǔn)入信息同步延時(shí)
雖可以通過statestore_update_frequency_ms縮短topic更新周期來緩解,但無法從根本上解決。除此之外,在Impala部署時(shí),還應(yīng)該控制coordinator的數(shù)量,對于50個(gè)節(jié)點(diǎn)以下的集群,一般情況下配置2個(gè)coordinator實(shí)現(xiàn)高可用即可;
(3)豐富統(tǒng)計(jì)信息類型
至于為什么在有統(tǒng)計(jì)信息情況下預(yù)估還是不夠精確,原因也很好理解,即統(tǒng)計(jì)信息本身過于粗粒度,缺乏像直方圖這樣細(xì)粒度的數(shù)據(jù)統(tǒng)計(jì)。
基于歷史查詢的內(nèi)存估算優(yōu)化(HBO)
從前述的例子可知,有數(shù)的Impala版本通過管理服務(wù)器保存了Impala執(zhí)行過的歷史查詢信息,其中就包括了查詢的實(shí)際內(nèi)存使用量。在BI場景,報(bào)表SQL會(huì)重復(fù)執(zhí)行,往往一天一次或數(shù)次,完全可以將該SQL第一次執(zhí)行的內(nèi)存使用量作為后面幾次的內(nèi)存預(yù)估值。進(jìn)一步,可以提取同類SQL查詢模板,計(jì)算該模板下SQL的最大及平均內(nèi)存使用量作為內(nèi)存預(yù)估值。
此外,由于BI報(bào)表的SQL都來源于事先創(chuàng)建的數(shù)據(jù)模型,可以預(yù)先計(jì)算數(shù)據(jù)模型SQL的內(nèi)存消耗,在執(zhí)行該模型對應(yīng)的報(bào)表SQL時(shí),模型部分的內(nèi)存消耗無需再次計(jì)算,直接代入即可?;诖耍覀兺瓿闪朔桨冈O(shè)計(jì)和功能實(shí)現(xiàn),下圖為一個(gè)查詢開啟HBO優(yōu)化前后的內(nèi)存估算值變化

下圖為將HBO用在某業(yè)務(wù)集群上啟用前后的效果對比。


2.3 基于代價(jià)優(yōu)化(CBO)
CBO與RBO(基于規(guī)則優(yōu)化)都是傳統(tǒng)的執(zhí)行計(jì)劃優(yōu)化方式。CBO主要基于索引和統(tǒng)計(jì)信息等元數(shù)據(jù)來選擇更優(yōu)的執(zhí)行計(jì)劃。傳統(tǒng)的商業(yè)數(shù)倉和OLTP系統(tǒng)有健全的索引系統(tǒng),并且會(huì)自動(dòng)計(jì)算表的統(tǒng)計(jì)信息。因此,CBO往往能夠較充分發(fā)揮。但目前開源的分析型數(shù)倉查詢引擎(下稱OLAP),做得并不好,以Impala為例,自身并沒有索引系統(tǒng),主要依賴底層的存儲(chǔ)系統(tǒng),雖提供了統(tǒng)計(jì)信息計(jì)算的命令,但不會(huì)自動(dòng)進(jìn)行統(tǒng)計(jì)信息計(jì)算。在此,我們先介紹其對統(tǒng)計(jì)信息的使用。
統(tǒng)計(jì)信息的用途
在Impala中,統(tǒng)計(jì)信息主要用于準(zhǔn)入控制和確定Join方式等場景。
確定Join方式包括Join的先后順序和Join的方法,Join方式有shuffle和broadcast兩種。如下文描述:
Impala does not consider all possible join orderings, focusing instead on the subset of left deep join plans. This usually means joins are arranged in a long chain where the left input is preferred to be larger than the right input.
三個(gè)及以上的表進(jìn)行Join時(shí),一般選擇將結(jié)果集最小的Join先算掉,對于兩表Join,若是大表和小表,由于Impala使用Hash Join,采用大表左(probe table),小表在右(build table)的方式,將小表broadcast到大表分片所在的各個(gè)executor節(jié)點(diǎn),若是大表跟大表,則采用shuffle的方式,兩表都會(huì)進(jìn)行Hash分片,各個(gè)executor節(jié)點(diǎn)對兩表相同Hash值的分片進(jìn)行Join。
如果SQL中的表缺失了統(tǒng)計(jì)信息,如查詢所涉及的記錄數(shù),所涉及的各列的大小等,則無法準(zhǔn)確預(yù)估該SQL的內(nèi)存消耗,導(dǎo)致準(zhǔn)入控制模塊出現(xiàn)誤判,生產(chǎn)環(huán)境中常會(huì)出現(xiàn)因executor節(jié)點(diǎn)可用內(nèi)存不足導(dǎo)致查詢排隊(duì)的情況,但其實(shí)此時(shí)內(nèi)存是夠的,這里有多方面的原因,比如該查詢SQL所需內(nèi)存預(yù)估值過大,或已經(jīng)在執(zhí)行的查詢的配額過大等。相反的,如果預(yù)估所需內(nèi)存過小,則可能導(dǎo)致查詢在執(zhí)行過程中因?yàn)閑xecutor節(jié)點(diǎn)無法分配所需內(nèi)存而導(dǎo)致SQL執(zhí)行失敗。
同樣的,如果沒有統(tǒng)計(jì)信息,也就無法判斷兩表參與Join的記錄數(shù)和大小,出現(xiàn)大小表Join時(shí)大表被廣播的情況。在Impala中,兩表Join,沒有統(tǒng)計(jì)信息的表會(huì)被放在右邊,所以,對大表做統(tǒng)計(jì)信息計(jì)算顯得更加重要。
除此之外,統(tǒng)計(jì)信息用于調(diào)優(yōu)前文提到的動(dòng)態(tài)代碼生成。上文提到的DISABLE_CODEGEN_ROWS_THRESHOLD參數(shù)需要在有統(tǒng)計(jì)信息的情況下使用,無統(tǒng)計(jì)信息,意味著不知道需要處理多少條記錄,該參數(shù)也就無法生效。
統(tǒng)計(jì)信息計(jì)算
Impala為什么不像MySQL等數(shù)據(jù)庫一樣自動(dòng)計(jì)算和更新表統(tǒng)計(jì)信息呢?個(gè)人認(rèn)為,主要是不好做,MySQL自動(dòng)進(jìn)行統(tǒng)計(jì)信息更新的方式是監(jiān)測表中的記錄,如果更新的記錄數(shù)超過設(shè)定的閾值,則自動(dòng)觸發(fā)更新。當(dāng)Impala對接Hive表時(shí),往往僅用于查詢而不是數(shù)據(jù)產(chǎn)出,數(shù)據(jù)產(chǎn)出由Spark或Hive負(fù)責(zé),因此也就無法自動(dòng)感知表中數(shù)據(jù)的變化。
如果能夠及時(shí)感知Hive表的數(shù)據(jù)變化情況,那么就有辦法驅(qū)動(dòng)統(tǒng)計(jì)信息更新。Impala可以通過訂閱有數(shù)大數(shù)據(jù)開發(fā)及管理平臺(tái)的數(shù)據(jù)產(chǎn)出日志,感知Hive表的數(shù)據(jù)變化。具體的統(tǒng)計(jì)信息計(jì)算由Impala管理服務(wù)器執(zhí)行。Impala 3.4版本下,表和列的統(tǒng)計(jì)信息字段如下所示。
[localhost:21000] > show table stats t1;Query: show table stats t1+-------+--------+------+--------+| #Rows | #Files | Size | Format |+-------+--------+------+--------+| -1 | 1 | 33B | TEXT |+-------+--------+------+--------+Returned 1 row(s) in 0.02s[localhost:21000] > show column stats t1;Query: show column stats t1+--------+--------+------------------+--------+----------+----------+| Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size |+--------+--------+------------------+--------+----------+----------+| id | INT | -1 | -1 | 4 | 4 || s | STRING | -1 | -1 | -1 | -1 |+--------+--------+------------------+--------+----------+----------+Returned 2 row(s) in 1.71s
進(jìn)行統(tǒng)計(jì)信息計(jì)算的“compute stats”命令本質(zhì)是通過兩條SQL分別獲取表/分區(qū)和列粒度的信息:即為上述兩個(gè)查詢結(jié)果中的為“-1”的字段進(jìn)行賦值,如下所示:

兩個(gè)SQL均需在全表掃描的基礎(chǔ)上進(jìn)行聚合操作。對于大表,這需要消耗可觀的計(jì)算資源,而且,若表中的列個(gè)數(shù)非常多,則統(tǒng)計(jì)信息的存儲(chǔ)空間也是需要考慮的因素(需要持久化到HMS元數(shù)據(jù)庫中,并緩存在catalogd和impalad)。對于分區(qū)表,一般使用“compute incremental stats”每次僅計(jì)算一個(gè)分區(qū),但有時(shí)仍會(huì)因?yàn)榻y(tǒng)計(jì)信息過大而出錯(cuò),這是由于超過了增量統(tǒng)計(jì)信息計(jì)算的“inc_stats_size_limit_bytes”參數(shù)設(shè)定值導(dǎo)致:
org.apache.impala.common.AnalysisException: Incremental stats size estimate exceeds 200.00MB.參考Impala的文檔,統(tǒng)計(jì)信息計(jì)算可以進(jìn)行如下優(yōu)化:
對于分區(qū)表,僅對頻繁查詢的分區(qū)計(jì)算統(tǒng)計(jì)信息,并定期刪除舊分區(qū)統(tǒng)計(jì)信息;
對于寬表,僅對頻繁查詢的列計(jì)算統(tǒng)計(jì)信息;
對于記錄數(shù)過多的表,啟用統(tǒng)計(jì)信息高級特性:推斷和采樣(Extrapolation and Sampling)
從性能優(yōu)化角度,統(tǒng)計(jì)信息顯然是越精細(xì)越好。但在OLAP這種大數(shù)據(jù)量場景下,越精細(xì)意味著越龐大的統(tǒng)計(jì)信息計(jì)算和存儲(chǔ)開銷,使用何種粒度的統(tǒng)計(jì)信息是個(gè)需要權(quán)衡的問題。
2.4 查詢的分布式執(zhí)行
在大數(shù)據(jù)場景下,單靠單臺(tái)服務(wù)器執(zhí)行分析型查詢操作顯然過于單薄,所以分析型數(shù)倉一般基于MPP(Massively Parallel Processing,即大規(guī)模并行處理)架構(gòu),Impala就是基于MPP,可以將一個(gè)查詢分為多個(gè)片段分布式執(zhí)行。在Impala上,分布式執(zhí)行又可分為節(jié)點(diǎn)間和節(jié)點(diǎn)內(nèi)。
為了能夠在分布式執(zhí)行的同時(shí),能夠?qū)Σ煌瑯I(yè)務(wù)或不同類型的SQL進(jìn)行隔離,避免相互影響,有數(shù)的Impala版本進(jìn)一步引入了虛擬數(shù)倉概念,能夠有效的進(jìn)行資源隔離,同時(shí)有兼顧資源的有效利用。對于虛擬數(shù)倉,我們會(huì)在后續(xù)單獨(dú)寫一篇文章進(jìn)行介紹。
節(jié)點(diǎn)間并行
在“Impala簡介”小節(jié)提到,Impala有多個(gè)executor節(jié)點(diǎn),在確定執(zhí)行計(jì)劃時(shí),Impala會(huì)充分考慮并發(fā)執(zhí)行該查詢,盡可能將需要掃描的數(shù)據(jù)分成range分發(fā)到各executor節(jié)點(diǎn)上執(zhí)行,并響應(yīng)對數(shù)據(jù)進(jìn)行查詢所需的算子進(jìn)行分布式計(jì)算。
因此,在數(shù)據(jù)量足夠大的情況下,增加executor節(jié)點(diǎn)數(shù)可以提升查詢性能。若executor所在服務(wù)器的計(jì)算資源充足,可以考慮同一臺(tái)服務(wù)器上部署多個(gè)executor節(jié)點(diǎn)。
節(jié)點(diǎn)內(nèi)并行
Impala還可以通過MT_DOP參數(shù)配置查詢在executor節(jié)點(diǎn)內(nèi)的執(zhí)行并發(fā)線程數(shù)。對于統(tǒng)計(jì)信息計(jì)算產(chǎn)生的SQL,Impala自動(dòng)將MT_DOP設(shè)置為4以提升計(jì)算性能。相比節(jié)點(diǎn)間并行,節(jié)點(diǎn)內(nèi)并行通過query option設(shè)置,更加靈活可控。在Impala 3.4及之前版本,MT_DOP不夠完善,無法支持分布式Join等操作,從Impala 4.0開始,MT_DOP已支持絕大部分算子。我們在TPCH和TPCDS場景下的測試數(shù)據(jù)表明,將MT_DOP設(shè)置為16的性能明顯好于不設(shè)置或?qū)⑵湓O(shè)置為1時(shí)的性能,絕對性能有數(shù)倍提升。
合理配置并行數(shù)
顯然,查詢的執(zhí)行并行度不是越高越好,需要考慮Impala集群的查詢并發(fā)數(shù)以及executor節(jié)點(diǎn)的計(jì)算資源可用量。一般建議executor節(jié)點(diǎn)所在服務(wù)器的計(jì)算資源和網(wǎng)絡(luò)資源的利用率應(yīng)該小于80%。
對于節(jié)點(diǎn)內(nèi)并行,需要考慮impala profile輸出對性能的影響,在配置高M(jìn)T_DOP時(shí),應(yīng)啟用精簡模式的profile-v2(gen_experimental_profile=true),防止profile過大。
2.5 查詢重試和改寫
查詢重試
查詢出錯(cuò)的原因有很多,比如執(zhí)行該查詢的任意一個(gè)executor不可用(宕機(jī)或網(wǎng)絡(luò)隔離等),或因排隊(duì)過久導(dǎo)致執(zhí)行超時(shí),或因元數(shù)據(jù)過舊導(dǎo)致執(zhí)行出錯(cuò)等。
在Impala 4.0版本,引入了查詢透明重試的特性,該特性會(huì)判斷引起查詢出錯(cuò)的原因,目前支持對因executor不可用而出錯(cuò)的查詢進(jìn)行自動(dòng)重試,無需用戶/客戶端參與。
元數(shù)據(jù)過舊重試
因元數(shù)據(jù)過舊導(dǎo)致執(zhí)行出錯(cuò)是Impala特有的錯(cuò)誤場景,最典型的錯(cuò)誤形如:“Failed to open HDFS file .....”。有數(shù)的Impala版本還支持對該類錯(cuò)誤進(jìn)行透明重試,coordinator節(jié)點(diǎn)通過匹配錯(cuò)誤關(guān)鍵字識(shí)別錯(cuò)誤類型。在重試前會(huì)解析HDFS文件路徑獲取庫名和表名,并獲取當(dāng)前該表的元數(shù)據(jù)版本,重試時(shí)若元數(shù)據(jù)版本未變化,這會(huì)將對應(yīng)的表元數(shù)據(jù)失效掉,重新加載元數(shù)據(jù)。
對元數(shù)據(jù)錯(cuò)誤進(jìn)行查詢重試,是一種把錯(cuò)誤內(nèi)部化的一種優(yōu)化方式。元數(shù)據(jù)過舊是由于Impala出于性能考慮對其進(jìn)行了緩存,對用戶來說元數(shù)據(jù)緩存應(yīng)該是黑盒的,因緩存過舊導(dǎo)致的錯(cuò)誤,不應(yīng)該直接暴露給使用者,應(yīng)該在系統(tǒng)設(shè)計(jì)時(shí)消化掉。
SQL改寫
常規(guī)改寫
Impala提供了表達(dá)式級別的改寫優(yōu)化,改寫規(guī)則主要包括常量折疊、通用表達(dá)式提取和,全部規(guī)則如下所示:
List<ExprRewriteRule> rules = new ArrayList<>();// BetweenPredicates must be rewritten to be executable. Other non-essential// expr rewrites can be disabled via a query option. When rewrites are enabled// BetweenPredicates should be rewritten first to help trigger other rules.rules.add(BetweenToCompoundRule.INSTANCE);//between轉(zhuǎn)大小比較// Binary predicates must be rewritten to a canonical form for both Kudu predicate// pushdown and Parquet row group pruning based on min/max statistics.rules.add(NormalizeBinaryPredicatesRule.INSTANCE);//規(guī)范化二元謂語,如“5 + 3 = id"改為"id = 5 + 3"if (queryCtx.getClient_request().getQuery_options().enable_expr_rewrites) {rules.add(FoldConstantsRule.INSTANCE);//常量折疊,如"1 + 1"改為"2"rules.add(NormalizeExprsRule.INSTANCE);//規(guī)范化表達(dá)式,如"id = 0 OR false"改為"FALSE OR id = 0"rules.add(ExtractCommonConjunctRule.INSTANCE);//通用表達(dá)式提取,如"(int_col < 10 and bigint_col < 10) or " + "(string_col = '10' and int_col < 10)"改為"int_col < 10 AND ((bigint_col < 10) OR (string_col = '10'))"// Relies on FoldConstantsRule and NormalizeExprsRule.rules.add(SimplifyConditionalsRule.INSTANCE);//簡化條件判斷,場景覆蓋較廣,包括if、case等等,如"if(true, id, id+1)"改為"id"rules.add(EqualityDisjunctsToInRule.INSTANCE);//or轉(zhuǎn)in,如"int_col = 1 or int_col = 2"改為"int_col IN (1, 2)"rules.add(NormalizeCountStarRule.INSTANCE);//count(常量)轉(zhuǎn)為count(*),如"count(1)"改為"count(*)"rules.add(SimplifyDistinctFromRule.INSTANCE);//簡化條件判斷(is distinct, <, >, <=>等)語句,如"if(bool_col <=> bool_col, 1, 2)"改為"1"rules.add(SimplifyCastStringToTimestamp.INSTANCE);//簡化將字符串轉(zhuǎn)為時(shí)間戳,如"cast(unix_timestamp(date_string_col) as timestamp)"改為"CAST(date_string_col AS TIMESTAMP)"}exprRewriter_ = new ExprRewriter(rules);}
從上面的代碼可知,大部分的改寫規(guī)則需要通過ENABLE_EXPR_REWRITES 這個(gè)query option開啟。
定制改寫
有數(shù)的Impala版本在上述基礎(chǔ)上,結(jié)合BI工具和業(yè)務(wù)屬性進(jìn)行針對性的優(yōu)化,有助于提升BI查詢性能。其中一項(xiàng)優(yōu)化是簡化時(shí)間比較表達(dá)式。舉例如下:
SELECT TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) `d0`,`t2`.`itemname` `d1`,COUNT(DISTINCT `t1`.`user_id`) `m0`FROM `music_dws`.`dws_log_music_xxx_aggr_di` `t1`WHERE (((TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) >= CAST('2021-01-01' AS TIMESTAMP)) AND (TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) < CAST('2022-01-01' AS TIMESTAMP)))
已知t1表為按天分區(qū),dt為分區(qū)字段,結(jié)構(gòu)為'yyyy-mm-dd',那么在此條件下,可以將dt字段與時(shí)間字符串進(jìn)行比較,去掉CAST AS TIMESTAMP和TO_DATE操作,上述SQL可改寫為:
SELECT `t1`.`dt` `d0`,`t2`.`itemname` `d1`,COUNT(DISTINCT `t1`.`user_id`) `m0`FROM `music_dws`.`dws_log_music_xxx_aggr_di` `t1`WHERE (`t1`.`dt` >= '2021-01-01') AND (`t1`.`dt` < '2022-01-01')
高級改寫
從上面Impala原生支持的改寫規(guī)則可以看出,其支持的改寫規(guī)則都比較初級,實(shí)現(xiàn)上是將SQL拆解為SelectList、FromClause、WhereClause、GroupByExpr和OrderByExpr等片段后,對各片段進(jìn)行改寫。并沒有對SQL進(jìn)行整體的,跨片段的改寫。
有數(shù)的Impala版本還可進(jìn)一步對SQL整體進(jìn)行改寫優(yōu)化,其中最為重要的是基于物化視圖的SQL透明改寫,我們會(huì)在后續(xù)單獨(dú)寫一篇文章進(jìn)行介紹。除了物化視圖改寫,還有其他一些優(yōu)化手段,如左連接(left join)消除等。
左連接消除
一般來說,BI軟件基于某個(gè)數(shù)倉模型(寬表,星型,雪花型等)創(chuàng)建報(bào)告,其中包括一張或多張報(bào)表,舉一個(gè)網(wǎng)易云音樂使用有數(shù)BI報(bào)表模型為例,SQL形如:
SELECT `t1`.`os`, other select listFROM `music_impala`.`left_join_table1` `t1`LEFT JOIN `music_impala`.`left_join_table2` `t2` ON ((`t1`.`is_new` = `t2`.`is_new`) AND (`t1`.`anchor_id` = `t2`.`anchor_id`) AND (`t1`.`app_ver` = `t2`.`app_ver`) AND (`t1`.`os` = `t2`.`os`) AND (TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) = `t2`.`report_date`))LEFT JOIN `music_impala`.`left_join_table3` `t3` ON ((`t1`.`is_new` = `t3`.`is_new`) AND (`t1`.`anchor_id` = `t3`.`anchor_id`) AND (`t1`.`app_ver` = `t3`.`app_ver`) AND (`t1`.`os` = `t3`.`os`) AND (TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) = `t3`.`report_date`))LEFT JOIN `music_iplay`.`left_join_table4` `t4` ON ((`t1`.`dt` = `t4`.`dt`) AND (`t1`.`anchor_id` = CAST(`t4`.`anchor_id` AS VARCHAR(255))))
可以認(rèn)為,該模型是將下面這4個(gè)數(shù)倉表通過左連接打?qū)挸梢粋€(gè)邏輯大寬表。
music_impala.left_join_table1、music_impala.left_join_table2、music_impala.left_join_table3music_impala.left_join_table4`
下面是產(chǎn)生的一個(gè)報(bào)表的列表篩選器組件產(chǎn)生的SQL。
SELECT `t1`.`os` `d0`FROM `music_impala`.`left_join_table1` `t1`LEFT JOIN `music_impala`.`left_join_table2` `t2` ON ((`t1`.`is_new` = `t2`.`is_new`) AND (`t1`.`anchor_id` = `t2`.`anchor_id`) AND (`t1`.`app_ver` = `t2`.`app_ver`) AND (`t1`.`os` = `t2`.`os`) AND (TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) = `t2`.`report_date`))LEFT JOIN `music_impala`.`left_join_table3` `t3` ON ((`t1`.`is_new` = `t3`.`is_new`) AND (`t1`.`anchor_id` = `t3`.`anchor_id`) AND (`t1`.`app_ver` = `t3`.`app_ver`) AND (`t1`.`os` = `t3`.`os`) AND (TO_DATE(CAST(`t1`.`dt` AS TIMESTAMP)) = `t3`.`report_date`))LEFT JOIN `music_iplay`.`left_join_table4` `t4` ON ((`t1`.`dt` = `t4`.`dt`) AND (`t1`.`anchor_id` = CAST(`t4`.`anchor_id` AS VARCHAR(255))))GROUP BY `t1`.`os`LIMIT 20000
對于BI軟件來說,基于模型產(chǎn)生該SQL非常合理。但考慮到模型是邏輯的大寬表,在Impala層面,可以對SQL進(jìn)行改寫以優(yōu)化查詢性能。
該篩選器用于在報(bào)告中對music_impala.left_join_table1的os字段進(jìn)行選擇,且模型中各表Join的條件(ON和WHERE)均沒有對os字段進(jìn)行過濾性操作。在這種情況下,如果在select list中沒有對os字段進(jìn)一步做SUM/AVG/COUNT等聚合操作(可以是MIN/MAX/DISTINCT等聚合操作),那么可以去掉left join算子,改寫成如下形式:
SELECT `t1`.`os` `d0`FROM `music_impala`.`left_join_table1` `t1`GROUP BY `t1`.`os`LIMIT 20000
小結(jié)
本文簡單說明了Impala的系統(tǒng)架構(gòu)和元數(shù)據(jù)管理,介紹了我們內(nèi)部版本引入的集中式管理服務(wù)器。重點(diǎn)介紹了在分析型數(shù)倉技術(shù)中執(zhí)行引擎這塊的主要技術(shù)點(diǎn)和常見優(yōu)化方法,并結(jié)合Impala展開進(jìn)行了分析,包括動(dòng)態(tài)代碼生成、基于準(zhǔn)入控制的資源管理、基于統(tǒng)計(jì)信息的代價(jià)計(jì)算、查詢并行執(zhí)行、SQL優(yōu)化和錯(cuò)誤重試等。
下一篇我們會(huì)重點(diǎn)分析由云原生數(shù)倉Snowflake引入的虛擬數(shù)倉特性。
