演講實錄|馬曉宇:When TiDB Meets Spark

PingCAP發表於2017-09-04

本文整理自 TiSpark 專案發起人馬曉宇在 Strata Data Conference 上分享的《When TiDB Meets Spark》演講實錄。

先介紹我自己,我是 PingCAP 的馬曉宇,是 TiDB OLAP 方向的負責人,也是 TiSpark 專案的發起人,主要是做 OLAP 方面的 Feature 和 Product 相關的工作,之前是網易的 Big Data Infra Team Leader,先前的經驗差不多都是在 SQL、Hadoop 和所謂大資料相關的一些東西。

今天主要會講的議程大概這麼幾項。

首先稍微介紹一下 TiDB 和 TiKV,因為 TiSpark 這個專案是基於它們的,所以你需要知道一下 TiDB 和 TiKV 分別是什麼,才能比較好理解我們做的是什麼事情。

另外正題是 TiSpark 是什麼,然後 TiSpark 的架構,除了 Raw Spark 之外,我們提供了一些什麼樣的不一樣的東西,再然後是 Use Case,最後是專案現在的狀態。

首先說什麼是 TiDB。你可以認為 TiDB 是現在比較火的 Spanner 的一個開源實現。它具備線上水平擴充套件、分散式 ACID Transaction、HA、Auto failover 等特性,是一個 NewSQL 資料庫。

然後什麼是 TiKV,可能我們今天要說很多次了。TiKV 其實是 TiDB 這個產品底下的資料庫儲存引擎,更形象,更具體一點,這是一個架構圖。

大家可以看到,TiDB 做為一個完整的資料庫來說,它是這樣的一個架構,上層是 DB 層,DB 層是負責做 DB 相關的東西,比如說一部分的 Transaction,SQL 的解析,然後執行 Query Processing 相關的一些東西。

底下是 KV 層,儲存層。儲存層就是儲存資料,通過 Raft 協議來做 Replica 的,旁邊還有 Placement Driver(簡稱 PD),如果對 Hadoop比較瞭解,你可以認為它有點像 NameNode,它會儲存每一個 Region 分別存了哪些 Key,然後 Key Range 是什麼。當然它在需要的時候也會做一些資料搬遷的排程,以及 Leader 的自動負載均衡等。最後 PD 提供了統一中央授時功能。

所有這些元件,都是通過 gRPC 來進行通訊的。

我們回到正題來說,什麼叫 TiSpark。TiSpark 就是 Spark SQL on TiKV。為什麼說是 on TiKV,而不是 on TiDB,因為我們讓 Spark SQL 直接跑在分散式儲存層上而繞過了 TiDB。這三個元件,TiDB / TiKV / TiSpark 一起,作為一個完整的平臺,提供了 HTAP(Hybrid Transactional/Analytical Processing)的功能。

再具體一點說 TiSpark 實現了什麼:首先是比較複雜的計算下推,然後 Key Range Pruning,支援索引(因為它底下是一個真正的分散式資料庫引擎,所以它可以支援索引),然後一部分的 Cost Based Optimization 基於代價的優化。

CBO 這裡有兩部分,一部分是說,因為我們有索引,所以在這種情況下,大家知道會面臨一個問題,比如說我有十個不同索引,我現在要選擇哪一個索引對應我現在這個查詢的謂詞條件更有利。選擇好的索引,會執行比較快,反之會慢。
另外一個是,剛才大家可能有聽華為的 Hu Rong 老師介紹,他們在 Spark 上面做 Join Reorder,對於我們來說,也有類似的東西,需要做 Join Reorder 。這裡底下有兩個是 Planned 但還沒有做。一個是回寫,就是說現在 TiSpark 是一個只讀的系統。另外我們考慮把常用的一些傳統資料庫的優化手段,也搬到我們這邊來。

現在開始說一下整個架構是什麼樣的。後面會有一個具體的解說,先看一下架構圖。

在 Spark Driver 上,需要接入 TiSpark 的介面,現在 TiSpark 也支援 JDBC。Worker / Executor 那邊也需要一個這樣的架構。 整個部署,採用 Spark 外接 JAR 的方式,並沒有說需要到我整個把 Spark 部署全都換掉屬於我們的版本,只需要提交一個 JAR 包就可以。每個 TiSpark 元件會與 TiKV 進行通訊,Driver 這邊會和 Placement Driver 進行通訊,然後這邊具體幹了什麼,後面會解釋。

在 Spark Driver 這邊,因為這個架構上沒有 TiDB 什麼事,所以說 DB 本身乾的事情,我們需要再幹一遍,比如說 Schema 存在 TiKV 儲存引擎裡面,然後裡面包括 Tables 的元資訊,也就是告訴你資料庫裡面,分別有什麼表,每個表裡面分別有什麼列,這些東西都屬於 Schema 資訊。因為我們沒有直接連線 TiDB,所以說 Schema 資訊需要我們自己去解析。

比較重要的功能通過將 Spark SQL 產生的 LogicalPlan,Hook LogicalPlan,然後去做過濾,主要是:

  1. 哪一些謂詞可以轉化成索引相關的訪問;

  2. 哪一些可以轉化成 Key Range 相關的,還有哪一些其它計算可以下推,這些 Plan 節點我們會先過濾處理一遍。然後把 TiKV 可以算的部分推下去,TiKV 算不了的反推回 Spark;

  3. 在基於代價的優化部分 Join Reorder 只是在 Plan 狀態;

  4. Data Location 是通過 Placement Driver 的互動得到的。Java 這邊,會跟 Placement Driver 進行互動,說我要知道的是每個(Task)分別要發哪一臺機器,然後分別要知曉哪一塊的資料。

之後切分 Partition 的過程就稍微簡單一點,按照機器分割區間。之後需要做 Encoding / Decoding:因為還是一樣的,拋棄了資料庫之後,所有的資料從二進位制需要還原成有 Schema 的資料。一個大資料塊讀上來,我怎麼切分 Row,每個 Row 怎麼樣還原成它對應的資料型別,這個就需要自己來做。

計算下推,我需要把它下推的 Plan 轉化成 Coprocessor 可以理解的資訊。然後當作 Coprocessor 的一個請求,傳送到 Coprocessor,這也是 TiKV-Client 這邊做的兩個東西。

這些是怎麼做的?因為 Spark 提供的兩個所謂 Experimental 介面。這兩個分別對應的是 Spark Strategy 和 Spark Optimizer,如果做過相關的工作你們可能會知道,你 Hook 了 SQL 引擎的優化器和物理計劃生成部分。那兩個東西一旦可以改寫的話,其實你可以更改資料庫的很多很多行為。當然這是有代價的。什麼代價?這兩個看名字,Experimental Methods,名字提示了什麼,也就是在版本和版本之間,比如說 1.6 升到 2.1 不保證裡面所有暴露出來的東西都還能工作。可以看到,一個依賴的函式或者類,如果變一些實現,比如說 LogicalPlan 這個類原來是三個引數,現在變成四個引數,那可能就崩了,會有這樣的風險。

我們是怎麼樣做規避的呢?這個專案其實是切成兩半的,一半是 TiSpark,另一半是重很多的 TiKV-Client 。TiKV Java Client是負責解析對 TiKV 進行讀取和資料解析,謂詞處理等等,是一個完整的 TiKV 的獨立的 Java 實現的介面。也就是說你有 Java 的系統,你需要操作 TiKV 你可以拿 TiKV Client 去做。底下專案就非常薄,你可以說是主體,就是真的叫 TiSpark 的這個專案,其實也就千多行程式碼。做的事情就是利用剛才說的兩個 Hook 點把 Spark 的 LogicalPlan 匯出來,我們自己做一次再變換之後,把剩下的東西交還給 Spark 來做的。這一層非常薄,所以我們不會太擔心每個大版本升級的時候,我們需要做很多很多事情,去維護相容性。

剛才說的有幾種可能比較抽象,現在來一個具體的例子,具體看這個東西怎麼 Work,可以看一個具體的例子。

這是一個查詢,根據所給的學號和學院等條件計算學生平均值。這張表上,有兩個索引,一個索引是主鍵索引,另外一個索引是在 Secondary Index ,建立在 School 上。lottery 是一個使用者自定義函式,並不在 TiDB 和 TiKV 的支援範圍之內。

首先是說謂詞怎麼樣被處理,這裡有幾種不同的謂詞,比如關於學生 ID 的:大於等於 8000,小於 10100,以及有兩個單獨學號;然後是一個 school = ‘engineer’,還有一個 UDF 叫 lottery,單獨挑選一些運氣不好的學生。

第一步,整個處理,假設說我們索引選中的是在 studentID 上的聚簇索引。studentID 相關的謂詞可以轉化為區間 [8000, 10100), 10323, 10327。然後是 school=‘engineer’,因為它沒有被任何索引選擇,所以是一個獨立的條件。這兩種不同的條件,一個是跟聚簇索引相關的,可以轉化成 Key Range,另外一個是跟索引沒有關係的獨立的謂詞。兩者會經過不同的處理,聚簇索引相關的謂詞轉化成 Key Range,獨立的謂詞 school=‘engineer’ 會變成 Coprocessor 的 Reqeust,然後進行 gRPC 的編碼,最後把請求發過去。聚簇索引相關謂詞轉化的 Key Range 會通過查詢 Placement Driver 取得 Region 的分佈資訊,進行相應的區間切割。假設說有三個 Region。Region 1 是 [0, 5000),是一個閉開區間,然後 Region 2 是 [5000, 10000)。接著 Region 3 是 [10000, 15000)。對應我們上面的 Request 下推的區間資訊你可以看到,謂詞區間對應到兩個 Region:Region 2 和 Region 3,Region1 的資料根本不用碰,Region 2 的資料會被切成 [8000, 10000),因為對應的資料區間只有 [8000, 10000)。然後剩下的 [10000, 10100) 會單獨放到 Region 3 上面,剩下的就是編碼 school=‘engineering’ 對應的 Coprocessor Request。最後將編碼完成的請求傳送到對應的 Region。
上面就是一個謂詞處理的邏輯。

多個索引是怎麼選擇的呢?是通過統計資訊。

TiDB 本身是有收集統計資訊的, TiSpark 現在正在實現統計資訊處理的功能。TiDB 的統計資訊是一個等高直方圖。例如我們剛才說的兩個索引,索引一在 studentId 上,索引二是在 school 上。查詢用到了 studentId 和 school 這兩個列相關的條件,配合索引,去等高直方圖進行估算,直方圖可以告訴你,經過謂詞過濾大概會有多少條記錄返回。假設說使用第一個索引能返回的記錄是 1000 條,使用第二個能返回的記錄是 800 條,看起來說應該選擇 800 條的索引,因為他的選擇度可能更好一點。但是實際上,因為聚簇索引訪問代價會比較低,因為一次索引訪問就能取到資料而 Secondary Index 則需要訪問兩次才能取到資料,所以實際上,反而可能 1000 條的聚簇索引訪問是更好的一個選擇。這個只是一個例子,並不是說永遠是聚簇索引更好。
然後還有兩個優化,一個優化是覆蓋索引,也就是說索引是可以建多列的,這個索引不一定是隻有 school 這個列,我可以把一個表裡面很多列都建成索引,這樣有一些查詢可以直接用索引本身的資訊計算,而不需要回表讀取就可以完成。比如,

select count(\*) from student where school=’engineer’

整個一條查詢就只用到 school 這個列,如果我的索引鍵就是 school,此外並不需要其他東西。所以我只要讀到索引之後,我就可以得到 count(*) 是多少。類似於這樣的覆蓋索引的東西,也有優化。TiSpark 比較特殊的是,下層接入的是一個完整的資料庫而資料庫把控了資料入口,我每個 Update 每個 Insert 都可以看到。這給我們帶來什麼方便,就是說每個更新帶來的歷史資料變更可以主動收集。

基於代價優化的其他一些功能例如 Join Reorder 還只是計劃中,現在並沒有實現。剛才有跟 Hu Rong 老師有討論,暫時 Spark 2.2 所做的 CBO,並不能接入一個外部的統計資訊,我們暫時還沒想好,這塊應該這麼樣接。
接下來是聚合下推,聚合下推可能稍微特殊一點,因為一般來說,Spark 下面的資料引擎,就是說現在 Spark 的 Data Source API 並不會做聚合下推這種事情。

還是剛才的 SQL 查詢:

這個例子稍微有一點特殊,因為他是計算平均值,為什麼特殊,因為沒有辦法直接在 TiKV 做 AVG 平均值計算,然後直接在 Spark 再做直接聚合計算,因此這種情況會有一個改寫,將 AVG 拆解成 SUM 和 COUNT,然後會把他們分別下推到 Coprocessor,最後在 Spark 繼續做聚合計算。

TiSpark 專案除了改寫 Plan 之外,還要負責結合做型別轉換和 Schema 轉換。因為 TiKV 這個專案,本身並不是為了 TiSpark 來設計的,所以整個 Schema 和型別轉化的規則都是不一樣的。Coprocessor 部分聚合 (Partial Aggregation) 的結果,資料的型別和 Spark 是完全不一樣的,因此這邊還會做一次 Schema 的橋接。之後其他的就是跟前面一樣了,會把請求發到對應的 Region。

現在來講 TiSpark 和 TiDB/TiKV,因為是整個一個產品的不同元件,所以說 TiSpark 的儲存,也就是 TiDB 的儲存,TiKV 會針對 TiSpark 這個專案來做一些 OLAP 相關定的 Feature。

比如說在 OLTP 的模式下我們使用的是 SI 隔離級別,就是 Snapshot Isolation。在 OLTP 這邊,需要面對一個 Lock Resolving 問題和開銷。如果要看的話可以看一下 Percolator Transaction 的論文。為了避免 Lock Resolving 帶來的開銷,我們使用了一個 Read Committed 的模式。如果需要的話,後面再加 SI 也並不是非常難,只是現在這個版本並不會這樣做。

之後還有 OLTP 和 OLAP 混跑,大家可能會覺得有很大問題,就是資源怎麼樣隔離。現在資源隔離是這樣的:對於比較大的查詢,在 KV 那層會傾向於用更少的執行緒。當然是說你如果是在空跑,這臺機器上沒有其他人在跑的話,其實還是會用所有的資源,但如果你有跟其他 OLTP 查詢對比的話,你會發現雖然我是請求了很多但你可能未必會拿到很多。使用者也可以手動來降低優先順序,例如,我明天就要給老闆出一個報表,一個小時候之後就要拿結果,我可以手動提高一個分級。
所有剛剛講的這些,基本上都是 TiSpark 本身提供了一些什麼東西。現在說在一個類似於 Big Picture 的語境之下,怎麼樣去看這個專案。除了 Raw Spark 的功能之外,我們提供了什麼多的東西。最不一樣的地方就是 SQL-On-Hadoop,基本上來說,你可以認為它並不控制儲存,儲存並不在你這裡,你灌的資料,可能是通過 Flume/Kafka 某一個 Sink 灌進來,或通過 Sqoop 導過來,整個不管是 Hive,還是 Spark SQL,他並不知道資料進來。對於一個資料庫來說,每一條資料插入,全是經過資料庫本身的,所以每一條資料怎麼樣進來,怎麼樣存,整個這個產品是可以知道的。

另外就是說相對於 SQL-On-Hadoop,我們做一個資料庫,肯定會提供 Update 和 Delete 這是不用多說的。因為 TiKV 本身會提供一些額外計算的功能,所以我們可以把一些複雜的查詢進行下推。現在只是說了兩個,一個是謂詞相關的 下推,還有一個是剛才說的聚合下推,其他還有 Order,Limit 這些東西,其實也可以往下放。

接下來就屬於腦洞階段了,除了剛才說的已經“高瞻遠矚”的東西之外,腦洞一下,接下來還可以做一些什麼(當然現在還沒有做),這個已經是 GA 還要再往後的東西了。

首先說儲存,TiKV 的儲存是可以給你提供一個全域性有序,這可能是跟很多的 SQL-On-Hadoop 的儲存是不一樣的。Global Order 有什麼好處,你可以做 Merge Join,一個 Map Stage 可以做完,而不是要做 Shuffle 和 Sort。Index lookup join 是一個可以嘗試去做的。

Improved CBO,我們資料庫團隊現在正在開發實時收集統計資訊。
其他一些傳統資料庫才可能的優化,我們也可以嘗試。這裡就不展開多說了。
整個系統,一個展望就是 Spark SQL 下層接資料庫儲存引擎 TiKV ,我可以希望說 Big Data 的那些平臺是不是可以和傳統的資料庫就合在一起。因為本身 TiDB 加 TiKV 就是一個分散式的資料庫。然後可以做 Online Transaction,類似於像 Spanner 提供的那些功能之外,我們加上 Spark 之後,是不是可以把一些 Spark 相關的 Workload 也搬上來。

然後是 Use Case:首先一個平臺,可以做兩種不同的 Workload,Analytical 或者 Transactional 都可以在同一個平臺上支援,最大的好處你可以想象:沒有 ETL。比如說我現在有一個資料庫,我可能通 Sqoop 每小時來同步一次,但是這樣有一個延遲。而使用 TiSpark 的話,你查到的資料就是你剛才 Transaction 落地的資料而沒有延遲。另外整個東西加在一起的話,就是有一個好處:只需要一套系統。要做資料倉儲,或者做一些離線的分析,現在我並不需要把資料從一個平臺匯入資料分析平臺。現在只要一套系統就可以,這樣能降低你的維護成本。

另外一個延伸的典型用法是,你可以用 TiDB 作為將多個資料庫同步到一起的解決方案。這個方案可以實時接入變更記錄,比如 Binlog,實時同步到 TiDB,再使用 TiSpark 做資料分析,也可以用它將 ETL 之後的結果寫到 HDFS 數倉進行歸檔整理。

需要說明的是,由於 TiDB / TiKV 整體是偏重 OLTP,暫時使用的是行存且有一定的事務和版本開銷,因此批量讀的速度會比 HDFS + 列存如 Parquet 要慢,並不是一個直接替代原本 Hadoop 上基於 Spark SQL / Hive 或者 Impala 這些的數倉解決方案。但是對於大資料場景下,如果你需要一個可變資料的儲存,或者需要比較嚴格的一致性,那麼它是一個合適的平臺。

後續我們將寫一篇文章詳細介紹 TiSpark 的 Use Case,對 TiSpark 感興趣的小夥伴,歡迎發郵件到 info@pingcap.com 與我們交流。

整個這個專案的狀態是在 9 月跟整個 TiDB 、TiKV 同步做 release。現在的話,剛剛把 TPC-H 跑通的狀態,像剛才說的有些 Feature,例如 CBO 那些還沒有完全做完。Index 也只是做了 Index 讀取,但是說怎麼樣選 Index 還沒有做,正在 Bug fix 以及 Code Cleanup。在 GA 之前會有一個 Beta 大家可以部署了玩一次。目前 TiSpark Beta 已經發布。

相關文章