前陣子工作上需要用到Calcite做一些事情,然后發現這個東西也是蠻有意思的,就花了些時間研究了一下。本篇主要圍繞SQL 優化這塊來介紹Calcite,后面會介紹Hive如何Calcite進行SQL的優化。
此外,也將Calcite的一些使用樣例整理成到github,https://github.com/shezhiming/calcite-demo。里面包含了基礎的CSV適配器例子,從這個例子延伸出的SQL解析,校驗,RBO優化,CBO優化,以及自定義RelNode,自定義Cost信息,自定義rule等使用用例。如果覺得有幫助不妨點個start吧。
Calcite簡介與CBO介紹
Calcite背景與介紹
先來說Calcite出現的背景,在上世紀,關系型數據庫系統基本主導了數據處理領域,但是在Google三篇創世紀論文發表后,大家開始意識到,一種適合所有場景的數據庫是不存在的。事實上,今天也確實是這樣,許多特定場景下的數據處理系統已經成為主流,比如流處理領域的Flink,Storm,批處理領域的Spark SQL,文本搜索領域的Elasticsearch等等。而在開發不同特定場景的數據處理系統的時候,有兩個主要問題。
- 一個是每種系統基本都需要查詢語言(SQL)及相關拓展(比如流式SQL查詢),或是開發過程中碰到查詢優化問題,沒有一個統一框架,那么每個系統都要一套自己的查詢解析框架,那無疑是在重復造輪子。
- 另一個問題是,開發的這些系統通常要對接或集成其他系統,比如Kylin集成MR,Spark,Hbase等,如何支持跨異構數據源也是一個問題。
Calcite就是為了解決這些問題而生的。
說完背景,再來簡單介紹Calcite。從功能上說,Calcite提供了通過SQL管理數據的能力,但是它本身不存儲數據。最簡單的例子,假如你有一些CSV文件,想通過SQL來查詢這些CSV文件,那Calcite就很適合。要做到這一點,只需要提供一個有關CSV的適配器,告訴Calcite文件位置,字段這些信息(這些信息稱為Schema)。Calcite就可以幫你實現SQL查詢這些CSV文件,這只是最基礎的功能。當然這個例子有些雞肋,將CSV可以直接導入到Mysql同樣能用SQL查詢,但換個東西,Elasticsearch的數據,你總不能導到Mysql再用SQL查吧,這時候就能用Calcite實現SQL檢索ES的數據。
具體CSV適配器例子,可以看我在最開始的github代碼里面看到,:Calcite-demo-csv
實際效果大概是這樣:
上述例子通過從配置文件中獲取定義的Schema信息,然后就能編寫對應的SQL進行查詢。
從設計特點來說,因為Calcite的目的是提供一個通用的查詢引擎,所以它的設計目標就是flexible, embeddable, and extensible,即靈活,可插拔,可拓展。這里可以順便看一下它的架構圖:
Calcite中的各個模塊,Parse,Validate,Optimizer,都是可以單獨拿出來用,並且可以方便得對其進行拓展。比如你想拓展你的SQL解析,想支持諸如"my select t1.a from t1"這樣的語句,Calcite就有提供對應的接口(當然這算是比較高級的用法)。所以諸多開源框架,包括大家耳熟能詳的Apache Hive,Apche Storm,Apache Flink,Apache Kylin等等,都會選擇使用Calcite作為自己的SQL解析引擎,因為通用的東西直接用,定制化的東西可以方便得自定義。
此外,Calcite還有一些高級用法,比如物化視圖,流式SQL支持等等,這里就不做展開。
接下來再介紹下SQL優化。
SQL優化與CBO
SQL從誕生到現在已經有幾十年的時間了,盡管前幾年nosql一度自我感覺良好號稱要去掉sql,卻也被現實教做人不得不改口,說是自己其實是not only sql,從這點可以看出sql語言的強大和通用。
說回sql,sql是一種聲明式語言,所謂聲明式,就是用戶只需要告訴機器我要什么樣的結果,機器會自己摸索並幫助用戶找到結果返回。與之相比的是命令式語言,需要詳細告訴機器如何執行,比如常見的編程語言。
那么作為聲明式語言,如何幫助用戶高效准確地獲取到結果,這就是機器的責任。在這其中,SQL優化是許多研究者一直在探索的一個領域。
SQL優化的發展,則可以分為兩個階段,即RBO(Rule Base Optimization),和CBO(Cost Base Optimization)。
先簡單說下RBO,RBO主要是開發人員在使用SQL的過程中,有些發現有些通用的規則,可以顯著提高SQL執行的效率,比如最經典的filter下推:
上面圖片的意思很明顯,我們都知道join是非常耗時的一個操作,且性能與join雙方數據量大小呈線性關系(通常情況下)。那么很自然的一個優化,就是盡可能減少join左右雙方的數據量,於是就想到了先filter再join這樣一個rule。而非常多個類似的rule,就構成了RBO。
但后面開發者發現,RBO確實能夠對通用情況下對SQL進行優化,但在有些需要本地狀態才能優化的場景卻無能為力。比如某個計算引擎,在數據量小於XXX的時候,可以做一些特殊的優化操作,這種場景下RBO無能為力。
而這就是CBO出現的背景了,CBO全稱Cost Base Optimization,基於Cost的優化,其中Cost指的是執行SQL所需要的資源,通常是行數rowcount,CPU,內存,IO等等。基於Cost意思就是根據需要的資源,做更加智能的優化。
最典型的例子,就是Spark的join的選擇。在Spark中,join會觸發Shuffle操作,這種操作類型是非常消耗資源的。而Spark有三種類型的join,分別是broadcase join,將小的表廣播到所有節點,在內存中hash碰撞進行join,這種join避免節點間shuffle操作,性能最好,但條件也苛刻。第二種是hash join,就是普通的shuffle join。第三種是sort merge join,先排序然后join,類似歸並的思想,排序后能減少一些hash碰撞后的數據掃描,在join雙方都是大表的情況下性能較好。
選擇哪種類型的join,就要根據數據類型來選擇,如果一方是小表,就用broadcase join,如果雙方都是大表,就用sort merge join,否則就是 hash join。而這就需要用到Cost的信息了。
小結一下,RBO和CBO的區別大概在於,RBO只會無腦得應用提供的rule,而CBO會根據給出的Cost信息,智能應用rule,求出一個Cost最低的執行計划。需要糾正很多人誤區的一點是,CBO其實也是基於rule的,接觸到RBO和CBO這兩個概念的時候,很容易將他們對立起來。但實際上CBO,可以理解為就是加上Cost的RBO。
Calcite優化器
HepPlanner優化器與VolcanoPlanner優化器
Calcite提供了兩類型的優化器,即上述所說的RBO優化器和CBO優化器,在Calcite中的具體實現類對應HepPlanner
(RBO)和VolcanoPlanner
(CBO)。
其中HepPlanner
簡單理解就是兩個循環,第一個循環會遍歷用戶提供的rule,第二個循環會遍歷SQL樹的節點,每當rule匹配到對應樹節點的時候,會重新進行一遍循環。這個比較好理解。
而VolcanoPlanner
則相對復雜一些,它不是簡單地應用rule,而是會使用動態規划算法,計算每種rule匹配后生成新的SQL樹的Cost信息,與原先SQL樹的Cost信息相比較,如果新的樹的Cost比較低,那么才會真正應用對應的rule。
當然這里都只是簡單介紹,更加具體的內容,可以看看下面的兩篇文章,一篇主要從理論的角度介紹了Calcite優化的原理,一篇從源碼實現的角度剖析優化流程。
同時我的github代碼中也有提供RBO和CBO相關的測試樣例(主要是Test5和Test6),可以通過debug來看具體的執行流程,再結合理論和上述文章的解析,相信會有更加深入的理解。
Calcite優化樣例代碼介紹
github代碼中Test6
主要對比了RBO和CBO的差異,這里再順便說下Test6測試樣例的邏輯,其中的輸出結果大概是這樣:
這里有比較多自定義的內容,不過也很好理解。最開始就是簡單地將SQL解析成RelNode樹(RelNode可以理解樹節點吧)。然后提供自定義的rule,使用RBO將對應RelNode轉成CSV類型的RelNode(RBO optimizer 1),改變下rule順序,會發現生成了NewCsvProject
而不再是CSVProject
(RBO optimizer 2)。
最后是CBO,代碼實現是自定義了一個CsvProject
->NewCsvProject
的rule,添加到VolcanoPlanner
中。最終會發現,修改NewCsvProject
的computeSelfCost()
方法返回的Cost信息,該條rule會產生不同的效果,即CBO的體現。
以上就是本篇的全部內容,下面一篇主要介紹hive Sql解析的流程,以及在這個過程中如何應用Calcite來進行優化。
參考文章:
相關論文: