Databend SQL Planner 全新設計

databend 發表於 2022-06-22
SQL

前言

為了支援複雜的 SQL 查詢,並且提供更好的使用體驗,我們在最近的幾個月裡對 Databend 的 SQL planner 進行了大規模的重構。目前重構已經接近尾聲,感興趣的朋友可以通過修改 Databend 的 Session settings

SET enable_planner_v2 = 1

來啟用新 planner 進行搶先體驗。

功能亮點

更加友好的查詢體驗

無論是資料分析師還是開發人員,在編寫 SQL 查詢的時候總會遇到各種各樣的報錯。尤其是在 SQL 查詢較為複雜的情況下,排查報錯成了許多人的噩夢(筆者本人曾經修改過有數十個 JOIN 子句的 MySQL 查詢,從此對 MySQL 的錯誤提示深惡痛絕)。

為了改善這方面的使用者體驗,我們在新的 Planner 中引入了嚴格的語義檢查環節,使得大部分的錯誤可以在查詢編譯階段就被攔截。同時為了方便使用者定位錯誤的位置,我們也引入了全新的錯誤提示演算法。

當你的 SQL 查詢使用了錯誤的語法時(比如寫錯了關鍵字,或者遺漏了某些子句),Databend 會為你提供提示資訊:

Databend SQL Planner 全新設計

當你的 SQL 查詢出現語義上的錯誤時(比如使用了不存在的 Column,或者 Column 具有歧義),Databend 也會為你指出錯誤出現的位置:

Databend SQL Planner 全新設計

在編寫複雜查詢時,依然可以獲得較好的體驗:

Databend SQL Planner 全新設計

支援 JOIN 查詢與關聯子查詢

在新的 SQL planner 中,我們支援了 JOIN 查詢(INNER JOIN,OUTER JOIN,CROSS JOIN)與關聯子查詢,並且提供了 Hash Join 演算法用以執行 JOIN 查詢。

JOIN 查詢的相關文件已經發布在 https://databend.rs/doc/refer...,你可以查閱文件以瞭解 Databend 中 JOIN 查詢的使用方式。

在 OLAP 查詢中,JOIN 是非常重要的一部分。在傳統的星型模型雪花模型中,我們都需要通過 JOIN 查詢將維度表事實表連線起來以生成結果報表。

TPCH Benchmark 是由 TPC 委員會制定的一套 OLAP 查詢基準測試標準,用於評測資料庫系統的 OLAP 能力。其中包含了 8 張表,分別是:

  • Lineitem:產品專案
  • Orders:訂單資訊
  • Customer:顧客資訊
  • Part:零部件資訊
  • Supplier:供應商資訊
  • Partsupp:零件與供應商的關係表
  • Nation:國家資訊
  • Region:地區資訊

TPCH 中有 22 條複雜的查詢,對應不同的商業需求。這裡以 Q9 查詢為例,它的用途是計算指定年度地區利潤額,其中包含了大量的 JOIN 計算。在新的 Planner 中,我們已經可以支援該查詢:

Databend SQL Planner 全新設計

關聯子查詢同樣也是 SQL 中的重要組成部分,通過關聯子查詢可以輕鬆表示複雜的查詢邏輯。TPCH 的 Q4 就是一個例子,它的用途是計算一段時間內各優先順序的訂單的交付情況。其中使用了 EXISTS 關聯子查詢來篩選逾期收貨的訂單:

Databend SQL Planner 全新設計

目前 Databend 僅支援了關聯子查詢的簡單執行,相關的查詢優化工作仍在進行中,敬請期待。

全新架構

新的 SQL planner 中我們對 SQL 解析的流程進行了重新設計,以支撐更加複雜的語義分析和 SQL 優化。在新的 SQL planner 中,一條 SQL 語句通過客戶端傳送到 databend-query server 後,會按照下圖所示的順序由不同的元件進行處理,最終將查詢的結果返回給客戶端:

Databend SQL Planner 全新設計

收到 SQL 查詢後,Parser 元件會對其進行解析。在此步驟中如果遇到了語法錯誤則會直接將錯誤資訊返回給客戶端,解析成功則會生成查詢對應的 AST(抽象語法樹)。

Parser

為了提供更豐富的語法分析功能和更好的開發體驗,我們開發了一套基於 nom Parser combinator 的 DSL (領域特定語言) nom-rule,並基於該框架重新編寫了 SQL Parser。

在這套框架下我們可以非常輕鬆地定義一條 Statement 的語法,以 CREATE TABLE 語句為例,我們可以使用 DSL 將其簡單描述為:

CREATE ~ TABLE ~ #identifier ~ "(" ~ (#column_def)+ ~ ")" ~ ";"

優雅的語法大大提高了編寫 Parser 的樂趣,歡迎有興趣的朋友們進行嘗試。

Binder

由 Parser 成功解析出 AST 後,我們會通過 Binder 對其進行語義分析,並且生成一個初始的 Logical Plan(邏輯計劃)。在此過程中,我們會進行不同型別的語義分析:

  • Name resolution:通過查詢 Databend Catalog 中相關的 Table, Column 物件資訊,來檢查 SQL 查詢中引用的變數的合法性。並將合法的變數與對應的物件進行繫結,以進行後續的分析。
  • Type check:根據 name resolution 中拿到的資訊,對錶達式的合法性進行檢查,並且為表示式尋找合適的返回型別。
  • Subquery unnesting:將表示式中的子查詢提取出來,翻譯成關係代數的形式
  • Grouping check: 對於含有聚合計算的查詢,分析是否在聚合函式以外引用了非聚合列

通過語義分析,我們可以排除掉絕大多數的語義錯誤,並在編譯階段將其返回給使用者,以提供最佳的錯誤排查體驗。

Optimizer

得到初始的 Logical Plan 後,優化器會對其進行改寫和優化,最終生成一個可執行的 Physical Plan 。

在新的 Planner 中,我們引入了一套基於 Transformation Rule 的優化器框架(Volcano/Cascades)。通過定義一個關係代數子樹結構的 Pattern 以及相關的 Transform 邏輯,即可實現一個獨立的 Rule。

以簡單的 Predicate Push Down 為例:

Databend SQL Planner 全新設計

我們只需要定義輸入的 Plan 的 Pattern:

impl RulePushDownFilterProject {
    pub fn new() -> Self {
        Self {
            id: RuleID::PushDownFilterProject,
            // Filter
            //  \
            //   Project
            //    \
            //     *
            pattern: SExpr::create_unary(
                Pattern {
                    plan_type: RelOp::Filter,
                },
                SExpr::create_unary(
                    Pattern {
                        plan_type: RelOp::Project,
                    },
                    SExpr::create_leaf(
                        Pattern {
                            plan_type: RelOp::Pattern,
                        },
                    ),
                ),
            ),
        }
    }
}

並且實現一個進行轉換的函式:

impl RulePushDownFilterProject {
    pub fn apply(&self, s_expr: SExpr) -> Result<SExpr> {
        let filter = s_expr.plan().into();
        let project = s_expr.child(0).plan().into();
        let result = SExpr::create_unary(
            project, 
            SExpr::create_unary(
                filter,
                s_expr.child(0).child(0)
            )
        );
        
        Ok(result)
    }
}

Interpreter

通過 Optimizer 生成 Physical Plan 後,我們會將其翻譯成可執行的 Pipeline,並交由 Databend 的 Processor 執行框架進行計算。至此 Planner 的工作就告一段落,相信讀者也對新 Planner 的架構有了一個初步的瞭解。更多的技術細節請關注我們的後續文章。

未來規劃

從頭構建一個 SQL Planner 是一件十分具有挑戰性的事情,但是通過重新的設計和開發,我們可以找到最適合系統本身的架構與功能。在未來的一段時間裡,我們將持續完善和鞏固新的 SQL Planner,功能方面則會注重於:

  • Cost-based Optimization(CBO, 基於代價的優化)
  • 分散式查詢優化
  • 更多的優化規則

目前,新的 SQL planner 的遷移工作已經接近尾聲,你可以通過該 issue 追蹤進度。預計在七月份內所有的遷移工作將會完成, 屆時我們將會發布版本更新的公告,敬請期待。

關於 Databend

Databend 是一款開源、彈性、低成本,基於物件儲存也可以做實時分析的新式數倉。期待您的關注,一起探索雲原生數倉解決方案,打造新一代開源 Data Cloud。

Databend SQL Planner 全新設計
文章首發於公眾號:Databend