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

zzzzMing發表於2020-09-16

前陣子工作上需要用到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

實際效果大概是這樣:
Calcite csv查詢

上述例子通過從配置檔案中獲取定義的Schema資訊,然後就能編寫對應的SQL進行查詢。

從設計特點來說,因為Calcite的目的是提供一個通用的查詢引擎,所以它的設計目標就是flexible, embeddable, and extensible,即靈活,可插拔,可擴充。這裡可以順便看一下它的架構圖:

Calcite架構

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下推

上面圖片的意思很明顯,我們都知道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測試樣例的邏輯,其中的輸出結果大概是這樣:
CBO優化

這裡有比較多自定義的內容,不過也很好理解。最開始就是簡單地將SQL解析成RelNode樹(RelNode可以理解樹節點吧)。然後提供自定義的rule,使用RBO將對應RelNode轉成CSV型別的RelNode(RBO optimizer 1),改變下rule順序,會發現生成了NewCsvProject而不再是CSVProject(RBO optimizer 2)。

最後是CBO,程式碼實現是自定義了一個CsvProject->NewCsvProject的rule,新增到VolcanoPlanner中。最終會發現,修改NewCsvProjectcomputeSelfCost()方法返回的Cost資訊,該條rule會產生不同的效果,即CBO的體現。

以上就是本篇的全部內容,下面一篇主要介紹hive Sql解析的流程,以及在這個過程中如何應用Calcite來進行優化。

參考文章:

相關論文:

相關文章