1. 程式人生 > >深入淺出Calcite與SQL CBO(Cost-Based Optimizer)優化

深入淺出Calcite與SQL CBO(Cost-Based Optimizer)優化

[toc] 前陣子工作上需要用到Calcite做一些事情,然後發現這個東西也是蠻有意思的,就花了些時間研究了一下。本篇主要圍繞SQL 優化這塊來介紹Calcite,後面會介紹Hive如何Calcite進行SQL的優化。 此外,也將Calcite的一些使用樣例整理成到github,[https://github.com/shezhiming/calcite-demo](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](https://github.com/shezhiming/calcite-demo/tree/master/src/main/java/pers/shezm/calcite/csv) 實際效果大概是這樣: ![Calcite csv查詢](https://img2020.cnblogs.com/blog/1011838/202009/1011838-20200912161940563-323666751.png) 上述例子通過從配置檔案中獲取定義的Schema資訊,然後就能編寫對應的SQL進行查詢。 從設計特點來說,因為Calcite的目的是提供一個通用的查詢引擎,**所以它的設計目標就是flexible, embeddable, and extensible,即靈活,可插拔,可拓展**。這裡可以順便看一下它的架構圖: ![Calcite架構](https://img2020.cnblogs.com/blog/1011838/202009/1011838-20200914230213908-21072235.png) 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下推: ![filter下推](https://img2020.cnblogs.com/blog/1011838/202009/1011838-20200912162508155-602530341.png) 上面圖片的意思很明顯,我們都知道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優化的原理,一篇從原始碼實現的角度剖析優化流程。 - [SQL 查詢優化原理與 Volcano Optimizer 介紹](https://io-meter.com/2018/11/01/sql-query-optimization-volcano/) - [Apache Calcite 優化器詳解(二)](https://matt33.com/2019/03/17/apache-calcite-planner/) 同時我的github程式碼中也有提供RBO和CBO相關的測試樣例(主要是Test5和Test6),可以通過debug來看具體的執行流程,再結合理論和上述文章的解析,相信會有更加深入的理解。 ### Calcite優化樣例程式碼介紹 github程式碼中`Test6`主要對比了RBO和CBO的差異,這裡再順便說下Test6測試樣例的邏輯,其中的輸出結果大概是這樣: ![CBO優化](https://img2020.cnblogs.com/blog/1011838/202009/1011838-20200912164135549-2066625242.png) 這裡有比較多自定義的內容,不過也很好理解。最開始就是簡單地將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來進行優化。 參考文章: - [Apache Calcite 處理流程詳解(一)](https://matt33.com/2019/03/07/apache-calcite-process-flow/) - [Cascades Optimizer](https://zhuanlan.zhihu.com/p/73545345) - [Calcite官方文件](https://calcite.apache.org/docs/) 相關論文: - [Apache Calcite: A Foundational Framework for Optimized Query Processing Over Heterogeneous Data Sources](https://arxiv.org/pdf/1802.10233.pdf) - [The Volcano Optimizer Generator: Extensibility and Efficient Search](https://cs.uwaterloo.ca/~david/cs848/volcano.pdf) - [The Cascades Framework for Query Optimization](https://pdfs.semanticscholar.org/c1a3/9da04a072f695e9a7f36bf397fba5c19b93c.pdf?_ga=2.162106044.1003201390.1552806109-329306565.155