TiDB HTAP 的架構演進及實踐

80 年代到 90 年代左右,數倉開始興起,很多企業開始建設數倉,但是有一些數倉與傳統的 TP 業務不一樣的 Workload,於是數據庫界開始研究以傳統的形式與技術去應對 Workload 。這時候開始出現針對數倉產品特化的數據庫,例如 Teradata、Vertica、Greenplum、Apache Hadoop 生態引擎等,這些產品的出現也帶來了技術的變革。例如,傳統的數據庫使用的 MPP 引擎都是單機的,那麼隨着數倉的誕生,它們是在 MPP 上的一個先鋒。C-Store、KDB 以及 Mona DB 開始研究由行轉成列的數據存儲方式,Mona DB 的項目分支,很早開始進行向量化引擎。

與此同時,2005 年老祖師爺 Michael Stonebraker 談到:“One size fit all idea has gone”,言下之意是數據庫將要開始各種分支——各種特型的場景需要用特型數據庫。在祖師爺看來,傳統廠商需要在已有的一套巨大代碼上進行維護,是一件非常低效並且麻煩的事情,傳統數據廠商肯定不太願意做這件事情。它認爲每個不同的場景需要不同的數據庫,比如分型使用分型數據庫,圖樣使用圖樣據庫。於是它身體力行,開發了 C-Store——非常早的列存數據庫,後來 C-Store 演變成爲了 Vertica。

在那之後,數據庫 AP 和 TP 開始分支,分支的結果和下圖一些相近。

例如,TP 數據通過某種方式 ETL 到數據庫,數據庫跑一些報表或建模,需要做一些流注入的時候,會跑到 HBase,HBase 和數據倉庫或數據湖進行溝通,然後將數據湖計算完成的數據進行高速的服務,但是這些工作沒有辦法直接在 Hadoop 或者數倉的軟件上進行,所以又需要把這些數據再轉到前臺 Data Serving 的數據集羣。不同的平臺上數據,需要用不同的技術將數據搬來搬去,這是 AP 和 TP 之間分叉之後的架構設計。

這時候,數據庫界着手研究不使用 TP 和 AP 拆分的方式去處理數據。Gartner 提出用內存來做存儲介質,把 AP 和 TP 兩套引擎統一,2015 年 Gartner 提出的專有名詞 HTAP(Hybrid Transactional Analytical Processing),本質上就是 TP 和 AP 的合流。

如果可以滿足需求,用戶也希望用更簡單的方案去架構,從業務角度出發,人們希望更快地分析數據,例如作爲風控公司,提早分析風控數據可以避免更多的損失;物流生鮮電商可以實時地進行資源的調配;電商公司能夠更快地調整促銷實時推薦;公共服務也可以提供更方便快捷的渠道。

2AP & TP 設計選擇

TP 和 AP 是很難分清楚的兩個詞,業務側和技術側有不盡相同的理解。從技術面來說 TP 是 Transactional Processing,TP 數據庫會有以下幾種特性:

AP(Analytical Processing)對數據庫的要求則完全相反。比如,從引擎的角度來說,分型處理的引擎都是低併發的,因爲每次都需要處理大量數據,例如當產生含有幾十億數據的年度報表之時,必須把這一年所有的訂單處理,纔可以看到整張明細報表。這與 TP 數據是完全不同的,TP 海量數據選中訂單、修改以及塞回去就可以。並且 AP 的查詢是低併發,它並不需要佔用這麼多資源去處理報表。另外,由於傳統數倉的處理模式,分型數據庫支持批次更新,而不支持實時更新。

最後,分型數據庫需要處理大量的歷史數據。TP 數據庫往往只存儲幾天之內的數據,但是 AP 數據庫存儲所有數據,所以 AP 需要可擴展的計算和存儲能力。

傳統的存儲設計 TP 與 AP 的設計需求完全不同。

TP 用法是點狀更新或者少量數據的更新和查找,整個設計的目標是更少的行訪問和更好的實時更新能力。Ap 方面,它需要最快速地訪問大批量數據,整套的流程可以進行平衡,犧牲實時更新的能力來換取批量掃描的速度。

存儲方面的取捨,根據不同的訪問模式,儘可能地讓被訪問的數據呆在一起,在一次訪問、查詢、定位就可以找到所需數據。TP 類的單行訪問,單行的數據儘可能放在一起,一次訪問讀取完整的一行;AP 類掃批訪問,儘可能把一列的數據放在一起,這是 TP 和 AP 設計最大的區別。

展開來說,TP 往往使用行存,少量讀盤便可以將這行的數據掃完。如果是列存,當我們希望計算報表裏面所有訂單總量的時候,總出貨量做一次聚合,然後在訂單出貨的這一個字段上,做一個 Aggregation,這時候我們肯定希望所有這一列的出貨量數據儘可能放在一起,定位之後進行一次平掃,就可以達到較好的速度。

從直觀上來說,列存是這張圖上的樣子。行存之時,所有的列是對齊的,一列加一列放置在一起,只要定位到某一個 ID,往後掃描一點數據,就可以將這行讀出來。列存之時,先分若干組,按照列的方式去豎着切,然後放在一起,只要定位到一列的起始,順序向下讀就可以,這分別就是行存和列存訪問最快的方式。

計算層方面,TP 的計算層是單機的,最近 NoSQL 出現之後,把 TP 做到了分佈式上,以往對 TP 業務來說,數據量會比 AP 更少,所以 TP 不依賴類似於像 MPP 的架構。

從引擎設計的角度而言,經典的火山模型對 TP 來說已經足夠用。相反來說,AP 則需要用大量的資源去堆積,換取同一個查詢可以以很快的方式快速地響應。很早之前, AP 的標配是 MPP 或者以並行計算的方式去計算同一個查詢。

另外,有一些執行優化的技術只與 AP 有關。例如圖中表示的向量化引擎,傳統的 TP 類的數據庫(火山引擎)每次計算迭代,每個算子迭代只訪問一行,這對於 TP 類的引擎火山模型非常合適。在工程上,火山模型維護的代價和工程封裝都是最好的。

如果現在在 AP 的場景中,同時計算大量的數據,火山引擎的成本會非常高,所以大家開始採用向量化引擎,每次會迭代一批的列數據,引擎當中的 if-else 判斷會達到比較好的效果。

HTAP 把兩套設計目標完全南轅北轍的東西放到一起,這將會帶來哪些挑戰?

首先架構會變得更加複雜,代碼的複雜度必然會呈指數級上升。引擎做的非常複雜,可以做到自適應,當感知到 TP 的場景之時,讓它體現出 TP 的效果,感知到 AP 的場景時,體現出 AP 的效果,以這種單引擎的適配方式,複雜度非常大。

其次是資源隔離,資源隔離是必須要面對的話題。假設有一套數據庫需要同時跑 TP 和 AP,那麼 TP 的交易將會非常脆弱,TP 交易需要交易的延遲或吞吐保持非常穩定的狀態。但是 AP 使用資源的方式——當 AP 查詢之後,所有機器瘋狂的運轉,所有的 CPU 都在計算查詢,然後計算之後,儘可能快速地呈現結果。這種使用資源的方式,如果 TP 和 AP 放在一起,一個 IP 任務查詢之後,AP 瘋狂抖動,TP 馬上沒有吞吐,所以隔離是整個架構中很大的挑戰。

引入資源的強隔離之後,勢必會帶來其它的問題。首先是兩組節點之間的數據如何進行同步的問題,如何把 TP 的數據,以快速實時地方式傳輸到 AP 節點上,傳輸的同時又如何儘可能地保證數據的一致性?例如,前臺有很多同時發生交易 TP 的節點, AP 則需要進行持續不斷的對賬,像類似於這樣比較嚴肅的場景,弱一致同步將會減少很多可用場景。

其次是如何保證用戶入口儘可能透明的問題,儘可能地讓用戶使用統一的入口,而不是一套 TP、AP 的集羣。對於 TiDB 來說,架構的 TP 和 AP 是完全隔離的,TiDB 使用了兩組不同的資源節點,可以最大程度的保證 TP 和 AP 之間沒有任何干擾。

拆分了兩組節點之後,兩組節點之間如何進行通訊?TiDB 使用的是 Raft 一致性複製的協議,通過 Raft 一致性複製協議,可以保障 TP 的高可用,所以我們選用 Raft 的協議來做 TP 和 AP 之間的溝通。

AP 節點其實是 TP 節點的特殊副本,TiDB server 作爲 SQL 引擎統一的入口,共享優化器與執行單元,配合 Flink 和 Spark,可以進行深度的大數據集成。

上面是今年的總體架構,存儲層分成兩部分,一部分是列存左邊的 TiFlash 節點(AP 的節點),右邊是 TiKV 節點(TP 的節點),每一筆數據都有若干副本(同樣顏色的數據塊是一個數據塊不同的副本),副本和副本之間通過共識協議進行串聯,而共識協議可以保證數據的一致性。

通過 Raft 協議,在左邊虛線的部分,通過同樣的協議複製一套列存的副本,這套列存的副本單獨作爲 AP 的使用,列存副本的整個複製不會影響 TP 的可用性。即使列存網絡中斷或延遲抖動,那麼 TP 函數可以進行交易,一筆交易寫完之後,TP 可以直接返回,並不需要等待列存這邊複製完成才能返回。

即使 TP 這邊寫完沒有等到 AP 的回執,也可以繼續提供服務,如果這時候 TiDB 發起一套查詢,如何確定數據是最新的。假設現在向列存這邊發起一次查詢,列存並不會馬上提供服務,而是先向行存主節點或者說主副本發起一個校對請求,整個複製是線性的 log 複製,這個請求本身僅僅包含很短的一段信息——在收到這個請求的同時,需要告知最新的複製進度,行存的主副本收到這份校對請求之後,就會返回。

舉例說明,圖中行存的主副本最新請求的複製進度是 4 ,列存在收到 4 之後,等待 4 進度被滿足之後,才正常發起讀取,再配合上層 MVCC 的時間戳隔離機制, TiFlash 會 根據 TiDB 發起的時間戳,發起請求的時間戳進行過濾,把所有大於時間戳的數據全都過濾掉,最後再返回。

這樣的讀取過程將會保證 T0 時間寫下去的數據,在 T+1 時間經過校對之後,肯定能讀取成功,另外整個數據可以保證快照的切片是完整的,不會缺少數據,也不會增多數據。換句話說,這種異步方式提供的服務,提供了與 KB 一樣的隔離性與一致性保證。

這是 Suspense + AP 測試,Suspense 在讀寫發生的同時,約有 10 秒左右的延遲,在 AP 查詢發生的同時,TP 的延遲以及吞吐量基本都沒有任何變化。

傳統的數倉選擇批量更新,除了批量更新本身需要支持 ETL 的流程之外,其最大的好處是可以換取更快的讀取速度。我們常常認爲列存只能進行批量更新,其實並不是完全正確,市面上很多列存更新的系統本質是 Delta mean 的設計機制。

再來談談,列存本身爲什麼不好更新?

當行存寫入一條數據,依順序讀取便可以完成,而進行一次列存更新,需要數據的所有列分別拆開,分別寫入到數據應該在的地方,需要把一條順序磁盤訪問寫入轉化成多次的 random access,這導致了整個列存無法做很好地更新。如何讓 random access 變得開銷更小?進行攢批的時候,把 random access 均攤到大量數據當中,隨機訪問的開銷就會變得非常小,所謂的 data main 方式就是這種處理方式。

攢批最常見的 LSM 結構,這個結構也可以用來做列存更新,但是我們選擇另外一套設計模式。

上圖是整個 LSM 結構示意圖,LSM 把數據分成不同的層,層和層之間通過 compassion 的方式進行歸整(規整可以使得數據按照組件或 key 的方式去組織,查找以及更新將會變得非常容易以及快速)。

LSM 漸進式的整理方式會帶來很多分層,這爲批量掃描帶來了很多困難,因爲每次掃描都需要訪問很多層,然後這很多層需要做一次多路歸併,才能獲取最後的掃描結果。

我們決定放棄 LSM 的設計,採用了另外一套設計。這套設計是一個巨大子葉節點的必加數,每一個子葉節點分成兩部分,一部分是用來快速寫入和快速更新 Delta 部分,所有的寫入和更新的數據會不斷地 PAD 到 Delta 的部分,然後 Delta 部分會經過不斷的 compassion,compact 重新的 stable,所有的子葉節點之間沒有 overlap,所有的數據與讀取是兩層的歸併,速度會比傳統的 LSM 列存更新速度更快速。

我們明年將會發布新特性——列存直接寫入。

HTAP 計算層挑戰方面,TP 處理單筆事務數據量小的任務, AP 處理單筆事務數量非常大的任務,最主要的挑戰是單機和分佈式進行某種程度上的融合。

TiDB 在 4.0 之前,藉助外部的大數據引擎來完成 AP 和 TP 之間不同的引擎。TiDB 計算層使用的是 TiDB 的單機引擎,配合所有存儲層進行一部分的計算分攤。

如果進行計算本身,解決方案是 TiSpark,也就是 Spark 的 connecter,對 connecter 做了比較深的定製,可以認爲它改寫了整個 Spark SQL 的執行計劃。

TiDB 5.0 發佈之後,將會支持原生的 MPP 引擎,這套原生的 MPP 引擎在 TP 入口連接 TiDB server,因爲 TP 不需要 MPP 的架構,所以當做單機引擎的方式來處理。當處理 AP 作業的時候,同樣地先行連接 TiDB 本身,但是所有的 MPP 計算節點會分攤計算,類似於傳統的 MPP 的架構來方式來執行。

然後這套引擎所有的 SQL 的進入點仍然是 TiDB server 本身,無需處理兩套語法不同的引擎,權限管理以及整個 SQL 的兼容以及其它的入口都是統一的,最後調優化器的調優行爲也完全一樣。

用戶的 SQL 進入 TiDB 的 Server,然後 SQL 層經過 Parser 共享一套 Optimizer,如果需要進行 TP 的處理,它直接由單機的方式去 TP 讀取,讀取它 KV 的數據。如果需要一個 MPP 進行大型數量的的 drawing 或 discount,那麼它會路由到 MPP 節點,然後由 Optimizer 優化器之後,追加分佈式執行計劃處理,最後發送到 MPP worker 去讀取。

2021 年第一季度,我們將會發布 5.0 GA 版本。上圖是前一段時間測試 Benchmark 的效果,存儲引擎是與 TiFlash 一樣的列存,計算引擎分別是 Spark 和 TiFlash MPP,大家可以看到未經優化的版本,性能已經有很大的優勢。

4TiDB 的使用狀況

TiDB HTAP 列存 + 行存引擎發佈後,到現在大約有 9 個月時間,通過 telemetry 監控剔除玩具和實驗性部署之外,TiFlash 大概有 100 - 200 的使用規模。

關於 TiDB HTAP 的主流用法有以下幾種情況。首先使用 TiDB TP 部分作爲交易庫,通過 TiFlash 或 AP 節點,進行實時交易數據的查詢。

其次 TiDB 本身帶有 TP 屬性,可以作爲一個實時熱數據偏分析場景的應用,TiDB 在沒有列存的版本之下,其實是很主流的用法,比如 Single View,前端有不同的數據源,通過實時的 CDC 彙總到 TiDB 裏。在這個場景下,TiDB 會有很大的優勢,除去 TiDB 本身具有 TP 屬性之外,TiDB 還可以進行擴展,並且可以隨時在線變更表結構。多元匯聚用戶非常希望可以進行重型分析,我們在多元匯聚的場景下增加列存之後,可以三位一體地進行重型的分析、在線的多元匯聚以及在匯聚層進行數據服務。

另外利用 TiDB 和大數據進行數據層整合的用法也非常常見。即使客戶已經有完整的大數據團隊、大數據數倉或者整個這套架構,也完全適合 TiDB 的使用,TiDB 本身可以補充整個數倉層的實時能力,使用 Data Warehouse 或 Data Lake,數據有很大的可能性是通過 T+1 方式來處理,用戶非常希望有一套數據庫,既能提供實時的數據服務,也能提供實時的數據變更和同步。

接着,針對數據計算和數據流式計算的全能的 Sink 方案也很常見。例如,使用 Flink 往下游灌數據的時候,傳統的方案是需要實時變更的數據流去同步 CDC 的數據。首先先做一個 CDC,然後再 Flink 寫下去,下游可能是個 NoSQL 。這種方案犧牲了查詢性能與 SQL。一般來說,使用 No SQL 只能作爲點查點寫的介質,進行實時分析,性能不足夠好。其次如果選擇 Hadoop 進行落地,數據可能沒有辦法直接進行更新,那麼簡單方式可能做 T+1,把 Delta 數據與歷史數據做一次 Stage Emerge,才能得到最新的數據。

對於 TiDB 來說,它既可以體現出 NoSQL 的點狀更新和可擴展的優勢,又同時兼具了使用 Hadoop 之時,需要的列存以及列存上的分析型性能。所以流計算加 TiDB 是非常有意思的選擇。我們的社區用戶已經在使用 Flink 的同時也在使用 TiDB。

常見的典型場景是用戶使用 MySQL 的時候,希望 MySQL 同步到某個數據分析數據庫,然後提供高效的分析型作業,現在用戶可以直接使用 TiDB 整套服務,TP 接入行存,AP 接入列存,這個架構對於一些業務,特別是一些專項的業務場景很實用,比如用戶把原有的 CRM 或財務系統接到這上面。

另外實時數倉方面,上圖是某一位用戶的架構圖,用戶的數據從多個數據源,通過 Syncer 或者某種同步工具放到 TiDB 裏面,把 TiDB 當作爲一個 ODS(操作數據存儲)來使用。除了 ODS 之外,TiDB 可以提供直接的數據查詢和數據訪問。

用戶使用 TiSpark 結合數倉的數據,進行數據建模(數倉裏的數據是偏冷數據,在 TiDB 裏的數據是偏熱的數據),兩套數據進行整合、建模,建模的數據回寫到 TiDB ,這時候 TiDB 就是它的服務層。Hadoop 直接對外進行數據服務會有一些問題,這套數據做完建模和整理之後,寫到整個 TiDB ,這時候 TiDB 是數據應用層。

這是某一家著名的社交電商的用法,很有意思。它們將一部分的電商社交數據和訂單數據存儲在 TiDB,存儲的數據是 TP 類的表設計,並不是適合分析,用戶把 TiDB 集羣,通過 TiDB 本身的 TiCDC 通一份實時數據出來,放置到 Flink 中,然後 Flink 會和其它的開拓過來的數據進 Join,再進行實時的聚合,寫入到 TiDB ,這寫回的 TiDB 可以作爲用戶的看板與報表數據。總體來說,實時的訂單數據、整體用戶的分析以及實時報表數據都使用同一套 TiDB。

5 總結和展望

TiDB HTAP 不是簡單的 1+1 的一個效果,很多所謂 “差不多” 的數據庫,也有 TP 和 AP ,但是與兩個積木拼在一起沒有什麼區別。真正的 HTAP 並不是選擇 TP 還是 AP,而是這套數據庫兩種功能同時都有,在同一個業務場景當中可以同時使用這個功能,並且可以簡化業務架構以及帶來更好的實時分析能力。

Raft 的這套架構讓我們有機會以優雅的方式去做松耦合,這將會帶來一致性、高可用以及實時的複製能力。

結合 Flink 生態,我們可以更深入地把 TP 向實時和 AP 的方向進行推進,原始的架構 TiDB 加上 Raft 的複製一套列存副本,其實缺了非常重要的 ETL 環節,一部分數據的表現形式並不完全地適合做分析場景,所以配合 Flink 的實施能力,可以把整套 ETL 的環節很大程度上退回到實施層。

這套架構在雲上將會有很大的想象空間。對用戶來說,雲下搭配多種不同的數據引擎,存在很大的部署和困難,不同的數據引擎組合,需要考慮放置多少節點,上雲之後部署和運維會變得非常簡單,所以這套架構在雲上會發揮更大的威力。

數據庫本身是一個合久必分,分久必合的過程,原生的數據庫如 Oracle 或者 DBTool 代表性的數據庫都是 HTAP,本質上是兩者合一且多能的。在不斷的需求向前推動的時候,在某些場特定場景下,這些數據庫會被會一些特型的數據庫所代替。比如,當我們選擇數倉的時候,可以選擇 Teradata,可以選擇 Hadoop,而不是選擇使用 Oracle。這僅僅是很簡短的發展過程,當需求推進更復雜的時候,像 Teradata,Greenplum 曾經被大數據的東西代替了。

作爲用戶,它們非常期待一個數據庫——我無需知道數據庫裏面都在做什麼,也無需知道數據庫有多少引擎,我希望儘可能地減少心智負擔,僅僅用一個簡單的盒子就可以完成,因此我們認爲至少在某些領域上數據庫在向更融合的方向發展。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/hLTh6-KLX2nb_cTxo5cq8A