TiDB 的 HTAP 之路:過去,現在和將來

  • 2019 年 11 月 22 日
  • 筆記

TiDB 有很多故事

每個故事都可以有多個視角,這是一個從 AP 視角講 HTAP 故事的分享,當然還有技術討論。

我們開始做 TiDB 時只是想做 TP 資料庫,替換 MySQL 分庫分表方便用戶使用,但是後續用戶使用中發現,除了 TP 功能之外,還有很多分析場景也期望資料庫來解決。今天主要講一下 TiDB 在 HTAP 這個領域過去的探索、現在狀態和將來的計劃。

從 TiDB 的上古時代說起

最開始做的低版本就是想實現水平擴展:數據存儲進來將數據分片,加節點能夠解決存儲空間不夠的問題,支援 MySQL 的語法、協議和行為,方便用戶將數據存儲進去。比如在多個節點中單個節點失效時,保證整個集群還是能夠提供服務,做一個實現高可用、分散式的資料庫。

上圖是 TiDB 的架構,TiKV 是將數據存儲進去,PD 是其調度節點,數據從 DistSQL 寫進去。從外面就是 MySQL 服務,調用 MySQL 相關協議進行數據同步下一個 TiDB,這樣只要加節點就能實現無限存儲,前期測試擴展到幾十 T 甚至上百 T 都是可以實現的。早期更多的是類似於一個存儲系統,計算能力較弱。相對於 MySQL,MySQL 是小號 T 恤,而 TiDB 是大號 T 恤,其他功能儘可能接近。

用戶回饋體驗很好,先前需要分庫分表,現在不需要分庫分表,當業務場景很多時,推動一次業務變更非常困難,擁有一個簡單擴展的資料庫能解決很多問題。但是在早期對一個資料庫而言,尤其是存儲交易數據的資料庫而言,用戶會對其安全性不放心。因此可以存儲一些邊緣場景,如存日誌數據,當其數據丟失可以從 Hadoop 中恢復。但用戶回饋資料庫挺好,可以做一些數據分析,而前期我們的想法是想做一個承載交易數據的資料庫,而三四個用戶前期都是做分析的。如遊戲客戶將遊戲投放到廣告頁面,分析廣告投放的效果或者分析流水型的數據分析,當時做了一個並行的 hash join,而 MySQL 沒有 hash join,運行還挺快的。

後來從 TP 領域貼近用戶場景中,解決有些現有資料庫無法解決的問題,如將上百個分片的 MySQL 數據同步,做一些規則變換允許用戶進行裁剪和過濾,隨著用戶增多,越來越多的數據同步過來,對資料庫的承載能力也是一個考驗。

在進行數據同步過程中需要進行計算,在 TiDB alpha 階段計算很簡單,包括優化器、執行引擎都很粗糙。我們在此基礎下進行優化,1.0 beta 版本的時候做了一個分散式計算框架,讓 TiKV 能夠承載一部分計算開銷,在 TiDB 進行 SQL 優化,選擇一部分運算元,儘可能多的將運算元推到 TiKV 中,將數據從本地導出,在本地做 filter、聚合、limit 等流式運算元,然後將數據返回到 TiDB Server 中做一個最終的聚合,同時我們做了很多自己的 join,可以在很多感測器中調用 MySQL,運行效率比較快,能夠允許用戶在數據量大的時候增加節點,提高計算並發解決問題。

我們分析了用戶用我們產品的初衷,在產品早期讓用戶相信產品是穩定的是很難的事情,除了使用還有資料庫運維、日常處理等都需要學習。如遊戲運營人員分析遊戲日誌時分了很多 MySQL 庫,寫很多 SQL 操作,多個資料庫將數據取出來放到 Excel 中,然後通過 Excel 公式進行最後的數據聚合,這樣做非常麻煩,現在使用 TiDB 可以直接聚合,非常簡單方便。能夠實現無障礙跨分片操作,不用額外操作,同時還有實時性,以前是很多 MySQL 數據通過 ETL 拉出來放到 Hadoop 中,再用 Hive 計算,雖然計算能力強,但無論是 ETL 實時性也好,還是維護流水線的複雜度,都很難實時。使用 TiDB 後沒有這些操作後可以專註於業務,更加方便,當數據丟失還有備庫,這種方式叫中台,可以實現海量存儲匯聚。

Everyone Happy Now ?

這樣實現後還是出現了一些問題,在簡單的 TP 場景下,點查聯繫、低頻率寫入對優化器負擔較小,對執行引擎也沒什麼壓力。但是用戶在 AP 場景中越用越深,數據量越來越多,一些場景中,會遇到問題,比如一些 Query 跑得慢,還有就是會遇到 OOM。因為計算是在 TiKV 上經過聚合、filter 後還需要在 TiKV server 中單點聚合,可能會遇到 CPU 瓶頸,TiKV 記憶體小,使用也不精細,還有就是沒有自動運算元選擇,還是手動設置,對用戶來說很不方便;還有就是只能通過 MySQL 協議和外界交互,沒有和其他資料庫平台打通,還是需要 ETL 將數據從 TiDB 中通過 MySQL 協議讀取寫到其他地方或者通過 Spark、JDBC 連接資料庫。

不匹配的算力

隨著用戶越來越多,場景也越來越多,TiDB 的存儲擴展能力是非常強的,能存儲很多數據,但是早期版本中,計算能力相對存儲能力偏弱。這方面不光包括複雜的計算如何去處理,也包括整個計算的負載怎麼去分配;存儲的調度相對簡單,但是計算的調度很難分配,這樣導致一些業務出現問題。

我們的數據最終都要匯聚到一個 TiDB 上做聚合、join 等操作,每個 TiDB 間無法交換數據,當兩個大表做 hash join 時,如果使用記憶體過多會出現 OOM 的問題,如果通過分散式 join 就能很好解決這個問題。TiKV 間也不能交換數據,只是相對於多個 Map 加一個 Reduce 的模式;還有就是 join 和 Distinct 無法推到 TiKV 上做,在 TiKV 只能進行流式計算。

TiSpark

那麼解決方案可以是自己去做執行優化運算元,借鑒 Spark、Hive 等專業分析引擎,運用所有節點集群,做一個真正的 MPP 執行引擎;或者借用一些現有的成熟的分析系統,更好地運用存儲進來的數據。我們的選擇是藉助 TiSpark,在 Spark 中加入 TiDB driver,Spark 在優化器中有部分擴展點,可以將一部分查詢優化截取出來,告訴優化器哪些可以做優化哪些運算元不能優化,最後返回所需要的數據格式,將 table scan、 inner scan、聚合等簡單操作通過 driver 讓 TiKV 做初步數據預處理,再將數據返回 Spark 做更重的 join。這樣不僅有了一個很好的計算平台,也有了一個良好的計算生態。TiSpark 具有完整繼承 Apache Spark 生態圈,無縫銜接大數據生態圈、成熟的分散式計算平台等優點。

應用 TiSpark 比較重的用戶是易果生鮮,先前應用的是 SQL Server、MySQL,想做數據分析,維護的是 ETL 流水線。應用 TiSpark 後基本能做到實時分析,將多個數據源的數據匯總到一個 TiDB 集群中與各種下游分析需求對接,能做實時分析。實現中台數據匯總加實時數據分析。

應用 TiSpark 並不能解決所有問題,運行速度並不是很快,不能利用底層的統計資訊做更精細的分析,再者在做有一定並發能力但並不複雜的場景時,分析能力不強,如利用 TiDB 做點查,有少量 key 的點查還是很快的,但是在 Spark 中並發達不到那麼高,其 job 啟動、調度並不輕量。其次其運維和使用沒有 TiDB 簡單,因為 TiDB 的介面管理使用與 MySQL 類似。

除了 TiSpark 之外,我們在 TiDB 本身也做了很多工作,優化 TiDB 優化器,早期做大數據分析要寫很多 join 運算元 hint、並發度多少、聚合運算元選擇都需要人為選擇。一步步優化,從 RBO 到 CBO 到更好的 CBO,不但包括優化器本身、data 模型、plan 搜索,以及如何快速搜集大量統計資訊,利用搜集的、掃描的真實數據資訊來修正現有的統計資訊。在執行模型方面,由一個純的經典火山模型變成 Batch 加部分向量化的的執行引擎,當優化器預測基礎 size 比較大時,在運算元之間調用時,不是一次拿一條而是一次拿一批,batch size 可以實時調節,降低了記憶體消耗,因為記憶體分配次數比分配大小更重要些,對整個性能會有很好的優化。在 batch 內部通過 Apache Arrow 格式實現,數據會有一定的壓縮,更經典,更重要的是可以對記憶體使用進行記賬,分批次記賬比一條條記賬更高效;同時對部分運算元進行向量化處理,後期將向圖形向量化方向發展。TiDB 是一個混合負載機制,目的也是做一個通用負載資料庫,不想藉助其他特許硬體;同時也支援分區表,對數據進行裁剪。

TiDB 2.0 和 1.0 對比,TPC-H benchmark 結果中 query 運行速度明顯要快很多,有些查詢在1.0版本中無法查詢現在也可以查詢。但是在2.0中有些查詢比較慢,原因是在 hash 聚合是一個單執行緒運算元。在分析中比較重要的運算元就是聚合和 join,join 在 tpch 中佔一半,聚合佔三分之一。我們的目標就是希望 TiDB 在不藉助 Spark 情況下也能和主流分析工具媲美。在 TiDB 2.1 版本中,我們做了進一步的優化,特別是多執行緒的聚合運算元,對 TPC-H 結果帶來了進一步的提升。

核心矛盾

TiDB 2.1 版本在很多分析場景中已經跑的不錯,但是在面對需要對大表進行全表掃描時,行存模式明顯不滿足需求,無論是 scan 或者簡單 filter 或者 IO 壓力都明顯不如記憶體。還有在 HTAP 場景中需要解決 AP 負載不影響 TP 負載。TiDB 內部提供的解決方案就是讓分析的負載優先順序更低,可以運行低優先 SQL,還有就是依據優先順序做作業調度等,還有就 TiDB 實例可以無限拓展,可以一部分給 TP 用,一部分給 AP 用,但是最終 TiKV 對磁碟、網路的佔用還是無法避免;還有就是大查詢執行下,有幾個在線小查詢,也沒有 CPU 搶佔機制;這些問題都是無法避免的。

TiFlash

TP 和 AP 之間對於存儲格式的偏好不同,TP 一定要行存,需要低延遲,而 AP 一定要列存。一些資料庫嘗試過一個集群中同時有行存和列存,比如 Oracle 有列存插件。我們也開發了一個新項目——TiFlash,簡單來說是一個 TiKV 的列存擴展。可以通過 Raft 日誌將 TiKV 使用的數據實時同步出來,通過 Raft log 將數據同步到 TiFlash 實例,它會將行存的數據轉到列存,然後存到本地列存引擎中。開發借用了部分 ClickHouse 程式碼,因為 ClickHouse 是一個以速度見長的引擎,在向量化方面做的比較細。通過 Raft 日誌可以同步 TiKV 上所有數據,包括 MVCC、事務狀態,能夠實現數據的事務一致性讀取,唯一影響的是 TiKV 要同步一分日誌過來,Raft learner 只會一次寫入不會參與投票,對線上寫入延遲影響不大。

TiFlash 當前版本架構:數據從 TiDB 集群中寫入進來後,通過 Raft 日誌同步到 TiFlash 節點,通過 Spark 進行數據讀取。TiFlash 以 Raft Learner 方式接入 Raft Group,使用非同步方式傳輸數據,對 TiKV 產生非常小的負擔。當數據同步到 TiFlash 時,會被從行格式拆解為列格式。在讀取時會有一個校驗機制,在讀數據時會進行一個 RaftCommand 判斷是否有讀取數據,當數據同步到讀取請求發送時間點的進度時,TiFlash 才提供讀取。雖然有一次 Command,但是如果分析比較重,整個負載還是比較小的。

TiFlash 的下一個版本會做計算的融合,將入口切換轉為一個,實現 TiDB 可以同時讀取行存和列存副本。有些 SQL 部分選擇列存部分選擇行存,交給優化器來選擇,原理是將列存當做一個特殊的索引,讓列存讀到與行存一樣的數據。目前 TiFlash 已經進入 beta 版,在一些用戶那邊已經開始進行 PoC,預計在年內發布第一個正式版本。

目前的 TiDB

將存儲引擎的數據通過 Raft learner 同步出去可以做更多的事情,保證日誌是一個近實時同步方式。計算引擎也可以替換成不同的東西,寄存到自己的引擎中做自己的事情。希望用這套場景解決 AP 與 TP 相互干擾的狀況,幫助用戶簡化使用。在 TP 中統計資訊收集比較麻煩,當數據有上百億表,這種收集統計資訊是非常耗時的,當統計資訊不準可能會導致業務選錯索引。對於這種不需要完全實時,也不要求百分百準確的統計資訊收集工作,可以讓其後台執行,交給 TiFlash 引擎來做。

存儲集群不再區分是何種引擎,是一個行列共存的存儲引擎。可以實現數據實時同步,可以實現快速將幾 T 或者幾十 T 的數據快速導入集群中。有了這麼多組件後維護起來比較麻煩,可以使用 TiDB Operator,讓 K8s 幫助管理實例。

還有哪些問題?

目前的架構已經足夠強大,但是還有些問題沒解決。Spark+TiFlash 架構中,底層引擎還不支援 MR 模型,Spark 的 MR 模型還是略重,跑起來速度一般。後期希望自己寫一套 MPP 引擎,讓 TiFlash 節點之間可以交換數據,減少 Spark 端的工作任務,加快查詢速度;還有 SQL 語句推下去做計算,統一協處理器層,讓 TiKV 和 TiFlash 都能組成 MPP 集群。同一套程式碼,同一套引擎,讓 TiFlash 也支援 SQL 行為,這樣才能接上 MySQL 引擎。

還有寫入還是要藉助 TiKV,通過 TiDB 和 TiKV 寫入行存,Raft 日誌同步到列存中,在寫入過程中吞吐不夠,將數據從行存轉入列存流水線較長,開銷也會加大。

另一個就是需要加強寫入,TiDB 對一次性寫入的數據大小是有限制的,我們希望提供一套快速且支援大數據量的寫入,支援幾個 G 的數據原子寫入。TiFlash+TiDB 能解決海量數據計算問題,但是計算後不能將數據原子寫回,會導致整條業務邏輯不通。有了大數據量回寫之後,TiFlash 可以承擔 ETL 的功能。

最後就是可以用不同引擎解決不同問題,就像將行存列存引擎接到 TiKV 中來解決不同的問題,我們還可以添加更多的引擎。此外通過更好的調度,將不同的引擎調度到不同的機器實例上,實現完美的負載隔離,並且分散不同類型的業務負載到不同類型的引擎。

嘉賓介紹

申礫,PingCAP 技術 VP,TiDB Tech Lead,前網易有道、360 搜索資深研發。

——END——

分享嘉賓:申礫 PingCAP 技術VP

編輯整理:Hoh Xil

內容來源:大數據開源技術論壇 · 01

出品社區:DataFun

註:歡迎轉發,轉發請註明出處。