本文爲我司 Engineering VP 申礫在 TiDB DevCon 2019 上的演講實錄。在 上篇 中,申礫老師重點回顧了 TiDB 2.1 的特性,並分享了咱們對「如何作好一個數據庫」的見解。 本篇將繼續介紹 TiDB 3.0 Beta 在穩定性、易用性、功能性上的提高,以及接下來在 Storage Layer 和 SQL Layer 的規劃,enjoy~算法
2018 年年末咱們開了一次用戶吐槽大會,當時咱們請了三個 TiDB 的重度用戶,都是在生產環境有 10 套以上 TiDB 集羣的用戶。那次大會規則是你們不能講 TiDB 的優勢,只能講缺點;研發同窗要直面問題,不能辯解,直接提解決方案;固然咱們也保護用戶的安全(開個玩笑 :D),讓他們放心的來吐槽。剛剛的社區實踐分享也有點像吐槽大會第二季,咱們也但願用戶來提問題,分享他們在使用過程遇到什麼坑,由於只有直面這些問題,纔有可能改進。因此咱們在 TiDB 3.0 Beta 中有了不少改進,固然還有一些會在後續版本中去改進。數據庫
TiDB 3.0 版本第一個目標就是「更穩定」,特別是在大規模集羣、高負載的狀況下保持穩定。穩定性壓倒一切,若是你不穩定,用戶擔驚受怕,業務時斷時續,後面的功能都是沒有用的。因此咱們但願「先把事情作對,再作快」。安全
首先來看 TiDB 3.0 一個比較亮眼的功能——多線程 Raft。我來給你們詳細解釋一下,爲何要作這個事情,爲何咱們之前不作這個事情。性能優化
<center>圖 8 TiKV 抽象架構圖</center>多線程
這是 TiKV 一個抽象的架構(圖 8)。中間標紅的圖形是 RaftStore 模塊,全部的 Raft Group 都在一個 TiKV 實例上,全部 Raft 狀態機的驅動都是由一個叫作 RaftStore 的線程來作的,這個線程會驅動 Raft 狀態機,而且將 Raft Log Append 到磁盤上,剩下的包括髮消息給其餘 TiKV 節點以及 Apply Raft Log 到狀態機裏面,都是由其餘線程來作的。早期的時候,可能用戶的數據量沒那麼大,或者吞吐表現不大的時候,實際上是感知不到的。可是當吞吐量或者數據量大到必定程度,就會感受到這裏實際上是一個瓶頸。雖然這個線程作的事情已經足夠簡單,可是由於 TiKV 上全部的 Raft Peer 都會經過一個線程來驅動本身的 Raft 狀態機,因此當壓力足夠大的時候就會成爲瓶頸。用戶會看到整個 TiKV 的 CPU 並無用滿,可是爲何吞吐打不上去了?架構
<center>圖 9 TiDB 3.0 Multi-thread RaftStore</center>併發
所以在 TiDB 3.0 中作了一個比較大的改進,就是將 RaftStore 這個線程,由一個線程變成一個線程池, TiKV 上全部 Raft Peer 的 Raft 狀態機驅動都由線程池來作,這樣就可以充分利用 CPU,充分利用多核,在 Region 特別多以及寫入量特別大的時候,依然能線性的提高吞吐。異步
<center>圖 10 TiDB 3.0 Beta oltp_insert</center>高併發
經過上圖你們能夠看到,隨着併發不斷加大,寫入是可以去線性擴展的。在早期版本中,併發到必定程度的時候,RaftStore 也會成爲瓶頸,那麼爲何咱們以前沒有作這個事情?這個優化效果這麼明顯,之因此以前沒有作,是由於以前 Raft 這塊不少時候不會成爲瓶頸,而在其餘地方會成爲瓶頸,好比說 RocksDB 的寫入或者 gRPC 可能會成爲瓶頸,而後咱們將 RaftStore 中的功能不斷的向外拆,拆到其餘線程中,或者是其餘線程裏面作多線程,作異步等等,隨着咱們的優化不斷深刻,用戶場景下的數據量、吞吐量不斷加大,咱們發現 RaftStore 線程已經成爲須要優化的一個點,因此咱們在 3.0 中作了這個事情。並且以前保持單線程也是由於單線程簡單,「先把事情作對,而後再作快」。工具
第二個改進是 Batch Message。咱們的組件之間通信選擇了 gRPC,首先是由於 gRPC 是 Google 出品,有人在維護他,第二是用起來很簡單,也有不少功能(如流控、加密)能夠用。但其實不少人吐嘈它性能比較慢,在知乎上你們也能看到各類問題,包括討論怎麼去優化他,不少人也有各類優化經驗,咱們也一直想怎麼去優化他。之前咱們用的方法是來一個 message 就經過 gRPC 發出去,雖然性能可能沒有那麼好,或者說性能不是他最大的亮點,但有時候調性能不能單從一個模塊去考慮,應該從架構上去想,就是架構須要爲性能而設計,架構上的改進每每能帶來性能的質變。
因此咱們在 TiDB 3.0 Beta 中設計了 Batch Message 。之前是一個一個消息的發,如今是按照消息的目標分隊列,每一個隊列會有一個 Timer,當消息湊到必定個數,或者是你的 Timer 到了時間(如今應該設置的是 1ms,Batch 和這個 Timer 數量均可以調),纔會將發給同一個目的地的一組消息,打成一個包,一塊兒發過去。有了這個架構上的調整以後,咱們就得到了性能上的提高。
<center>圖 11 TiDB 3.0 Beta - Batch Message</center>
固然你們會想,會不會在併發比較低的時候變慢了?由於你湊不到足夠的消息,那你就要等 Timer。實際上是不會的,咱們也作了一些設計,就是由對端先彙報「我當前是否忙」,若是對端不忙,那麼選擇一條一條的發,若是對端忙,那就能夠一個 Batch 一個 Batch 的發,這是一個自適應的 Batch Message 的一套系統。圖 11 右半部分是一個性能對比圖,有了 Batch Message 以後,在高併發狀況下吞吐提高很是快,在低併發狀況下性能並無降低。相信這個改進能夠給你們帶來很大的好處。
第三點改進就是 Titan。CEO 劉奇在 Opening Keynote 中提到了咱們新一代存儲引擎 Titan,咱們計劃用 Titan 替換掉 RocksDB,TiDB 3.0 中已經內置了 Titan,但沒有默認打開,若是你們想體驗的話,能夠經過配置文件去把 RocksDB 改爲 Titan。咱們爲何想改進 RocksDB 呢?是由於它在存儲大的 Key Value 的時候,有存儲空間放大和寫放大嚴重的問題。
<center>圖 12 TiDB 3.0 中內置的新存儲引擎 Titan</center>
因此咱們嘗試解決這個問題。當你寫入的 Key Value 比較大的時候,咱們會作一個檢查,而後把大的 Value 放到一個 Blob File 裏去,而不是放到 LSM-Tree。這樣的分開存儲會讓 LSM-Tree 變得很小,避免了由於 LSM-Tree 比較高的時候,特別是數據量比較大時出現的比較嚴重的寫放大問題。有了 Titan 以後,就能夠解決「單個 TiKV 服務大量數據」的需求,由於以前建議 TiKV 一個實例不要高於 1T。咱們後面計劃單個 TiKV 實例可以支持 2T 甚至 4T 數據,讓你們可以節省存儲成本,而且能在 Key Value 比較大的時候,依然能得到比較好的性能。
除了解決寫放大問題以外,其實還有一個好處就是咱們能夠加一個新的 API,好比 KeyExist,用來檢查 Key 是否存在,由於這時 Key 和 Value 是分開存儲的,咱們只須要檢查 Key 是否在,不須要把 Value Load 進去。或者作 Unique Key 檢查時,能夠不須要把 Key Value 取出來,只須要加個接口,看這個 Key 是否存在就行了,這樣可以很好的提高性能。
第四點是保持查詢計劃穩定。這個在數據庫領域實際上是一個很是難的問題,咱們依然沒有 100% 解決這個問題,但願在 2019 年第一季度,最多到第二季度,能有一個很是好的解決方案。咱們不但願當數據量變化 、寫入變化、負載變化,查詢計劃忽然變錯,這個問題在線上使用過程當中是災難。那麼爲何會跑着跑着變錯?首先來講咱們如今是一個 Cost-based optimizers,咱們會參考統計信息和當前的數據的分佈,來選擇後面的 plan。那麼數據的分佈是如何得到的呢?咱們是經過統計信息,好比直方圖、CM Sketch來獲取,這裏就會出現兩個問題:
1. 統計信息多是不許的。統計信息畢竟是一個採樣,不是全量數據,會有一些數據壓縮,也會有精度上的損失。
2. 隨着數據不斷寫入,統計信息可能會落後。由於咱們很難 100% 保證統計信息和數據是 Match 的。
<center>圖 13 查詢計劃穩定性解決方案</center>
一個很是通用的思路是, 除了依賴於 Cost Model 以外,咱們還要依賴更多的 Hint,依賴於更多啓發式規則去作 Access Path 裁減。舉個例子:
select * from t where a = x and b = y; idx1(a, b) idx2(b) -- pruned
你們經過直觀印象來看,咱們必定會選擇第一個索引,而不是第二個索引,那麼咱們就能夠把第二個索引裁掉,而不是由於統計信息落後了,而後估算出第二個索引的代價比較低,而後選擇第二個索引。上面就是咱們最近在作的一個事情,這裏只舉了一個簡單的例子。
TiDB 3.0 第二個目標是可用性,是讓 TiDB 簡單易用。
在 TiDB 2.0 中,你們看一個 Query 爲何慢了依賴的是 Explain,就是看查詢計劃,其實那個時候你們不少都看不懂,有時候看了也不知道哪有問題。後來咱們在 TiDB 2.1 中支持了 Explain Analyze,這是從 PG 借鑑過來一個特性,就是咱們真正的把它執行一邊,而後再看看每一個算子的耗時、處理的數據量,看看它到底幹了一些什麼事情,但其實可能還不夠細,由於尚未細化到算子內部的各類操做的耗時。
<center>圖 14 TiDB 3.0 - Query Tracing</center>
因此咱們又作了一個叫 Query Tracing 的東西,其實在 TiDB 2.1 以前咱們已經作了一部分,在 TiDB 3.0 Beta 中作了一個收尾,就是咱們能夠將 Explain 結果轉成一種 Tracing 格式,再經過圖形化界面,把這個 Tracing 的內容展現出來,就能夠看到這個算子具體幹了一些什麼事,每一步的消耗到底在哪裏,這樣就能夠知道哪裏有問題了。但願你們都能在 TiDB 3.0 的版本中很是直觀的定位到 Query 慢的緣由。
而後第二點 Plan Management 其實也是爲了 Plan 不穩定這個問題作準備的。雖然咱們但願數據庫能本身 100% 把 Plan 選對,可是這個是很是美好的願望,應該尚未任何一個數據庫能保證本身能 100% 的解決這個問題。那麼在之前的版本中,出現問題怎麼辦?一種是去 Analyze 一下,不少狀況下他會變好,或者說你打開自動 Analyze 這個特性,或者自動 FeedBack 這個特性,能夠必定程度上變好,可是還可能過一陣統計信息又落後了,又不許了,Plan 又錯了,或者因爲如今 cost 模型的問題,有一些 Corner Case 處理不到,致使即便統計信息是準確的, Plan 也選不對。
<center>圖 15 TiDB 3.0 Beta - Plan Management</center>
那麼咱們就須要一個兜底方案,讓你們遇到這個問題時不要一籌莫展。一種方法是讓業務去改 SQL,去加 Hint,也是能夠解決的,可是跟業務去溝通可能會增長他們的使用成本或者反饋週期很長,也有可能業務自己也不肯意作這個事情。
另一種是用一種在線的方式,讓數據庫的使用者 DBA 也能很是簡單給這個 Plan 加 Hint。具體怎麼作呢?咱們和美團的同窗一塊兒作了一個很是好的特性叫 Plan Management,就是咱們有一個 Plan 管理的模塊,咱們能夠經過 SQL 接口給某一條 Query,某一個 Query 綁定 Plan,綁定 Hint,這時咱們會對 SQL 作指紋(把 Where 條件中的一些常量變成一個通配符,而後計算出一個 SQL 的指紋),而後把這個 Hint 綁定在指紋上。一條 Query 來了以後,先解成 AST,咱們再生成指紋,拿到指紋以後,Plan Hint Manager 會解析出綁定的 Plan 和 Hint,有 Plan 和 Hint 以後,咱們會把 AST 中的一部分節點替換掉,接下來這個 AST 就是一個「帶 Hint 的 AST」,而後扔給 Optimizer,Optimizer 就能根據 Hint 介入查詢優化器以及執行計劃。若是出現慢的 Query,那麼能夠直接經過前面的 Query Tracing 去定位,再經過 Plan Management 機制在線的給數據庫手動加 Hint,來解決慢 Query 的問題。這樣下來也就不須要業務人員去改 SQL。這個特性應該在 TiDB 3.0 GA 正式對外提供,如今在內部已經跑得很是好了。在這裏也很是感謝美團數據庫開發同窗的貢獻。
TiDB 3.0 中咱們增長了 Join Reorder。之前咱們有一個很是簡單的 Reorder 算法,就是根據 Join 這個路徑上的等值條件作了一個優先選擇,如今 TiDB 3.0 Beta 已經提供了第一種 Join Reorder 算法,就是一個貪心的算法。簡單來講,就是我有幾個須要 Join 的表,那我先從中選擇 Join 以後數據量最小的那個表(是真正根據 Join 以後的代價來選的),而後我在剩下的表中再選一個,和這個再組成一個 Join Path,這樣咱們就能必定程度上解決不少 Join 的問題。好比 TPC-H 上的 Q5 之前是須要手動加 Hint 才能跑出來,由於它沒有選對 Join 的路徑,但在 TiDB 3.0 Beta 中,已經可以自動的選擇最好的 Join Path 解決這個問題了。
<center>圖 16 TiDB 3.0 Beta - Join Reorder</center>
咱們接下來還要再作一個基於動態規劃的 Join Reorder 算法,頗有可能會在 3.0 GA 中對外提供。 在 Join 表比較少的時候,咱們用動態規劃算法能保證找到最好的一個 Join 的路徑,可是若是表很是多,好比大於十幾個表,那可能會選擇貪心的算法,由於 Join Reorder 仍是比較耗時的。
說完穩定性和易用性以外,咱們再看一下功能。
<center>圖 17 TiDB 3.0 Beta 新增功能</center>
咱們如今作了一個插件系統,由於咱們發現數據庫能作的功能太多了,只有咱們來作其實不太可能,並且每一個用戶有不同的需求,好比說這家想要一個可以結合他們的監控系統的一個模塊,那家想要一個可以結合他們的認證系統作一個模塊,因此咱們但願有一個擴展的機制,讓你們都有機會可以在一個通用的數據庫內核上去定製本身想要的特性。這個插件是基於 Golang 的 Plugin 系統。若是你們有 TiDB Server 的 Binary 和本身插件的 .so,就能在啓動 TiDB Server 時加載本身的插件,得到本身定製的功能。
圖 17 還列舉了一些咱們正在作的功能,好比白名單,審計日誌,Slow Query,還有一些在 TiDB Hackathon 中誕生的項目,咱們也想拿到插件中看看是否可以作出來。
<center>圖 18 TiDB 3.0 Beta - OLTP Benchmark</center>
從圖 18 中能夠看到,咱們對 TiDB 3.0 Beta 中作了這麼多性能優化以後,在 OLTP 這塊進步仍是比較大的,好比在 SysBench 下,不管是純讀取仍是寫入,仍是讀加寫,都有幾倍的提高。在解決穩定性這個問題以後,咱們在性能方面會投入更多的精力。由於不少時候不能把「性能」單純的看成性能來看,不少時候慢了,可能業務就掛了,慢了就是錯誤。
固然 TiDB 3.0 中還有其餘重要特性,這裏就不詳細展開了。(TiDB 3.0 Beta Release Notes )
剛纔介紹是 3.0 Beta 一些比較核心的特性,咱們還在繼續作更多的特性。
<center>圖 19 TiDB 存儲引擎層將來規劃</center>
好比在存儲引擎層,咱們對 Raft 層還在改進,好比說剛纔我提到了咱們有 Raft Learner,咱們已經可以極大的減小因爲調度帶來的 Raft Group 不可用的機率,可是把一個 Learner 提成 Voter 再把另外一個 Voter 幹掉的時間間隔雖然比較短,但時間間隔依然存在,因此也並非一個 100% 安全的方案。所以咱們作了 Raft Joint Consensus。之前成員變動只能一個一個來:先把 Learner 提成 Voter,再把另外一個 Voter 幹掉。但有了 Raft Joint Consensus 以後,就能在一次操做中執行多個 ConfChange,從而把由於調度致使的 Region 不可用的機率降爲零。
另外咱們還在作跨數據中心的部署。前面社區實踐分享中來自北京銀行的于振華老師提到過,他們是一個兩地三中心五部分的方案。如今的 TiDB 已經有一些機制能比較不錯地處理這種場景,但咱們可以作更多更好的東西,好比說咱們能夠支持 Witness 這種角色,它只作投票,不一樣步數據,對帶寬的需求比較少,即便機房之間帶寬很是低,他能夠參與投票。在其餘節點失效的狀況下,他能夠參與選舉,決定誰是 Leader。另外咱們支持經過 Follower 去讀數據,但寫入仍是要走 Leader,這樣對跨機房有什麼好處呢? 就是能夠讀本地機房的副本,而不是必定要讀遠端機房那個 Leader,可是寫入仍是要走遠端機房的 Leader,這就能極大的下降讀的延遲。除此以外,還有支持鏈式複製,而不是都經過 Leader 去複製,直接經過本地機房複製數據。
以後咱們還能夠基於 Learner 作數據的 Backup。經過 learner 去拉一個鏡像,存到本地,或者經過 Learner 拉取鏡像以後的增量,作增量的物理備份。因此以後要作物理備份是經過 Learner 實時的把 TiKV 中數據作一個物理備份,包括全量和增量。當須要恢復的時候,再經過這個備份直接恢復就行了,不須要經過 SQL 導出再導入,能比較快提高恢復速度。
<center>圖 20 TiDB 存儲引擎層將來規劃</center>
在 SQL 層,咱們還作了不少事情,好比 Optimizer 正在朝下一代作演進,它是基於最早進的 Cascades 模型。咱們但願 Optimizer 可以處理任意複雜的 Query,幫你們解決從 OLTP 到 OLAP 一整套問題,甚至更復雜的問題。好比如今 TiDB 只在 TiKV 上查數據,下一步還要接入TiFlash,TiFlash 的代價或者算子其實不同的,咱們但願可以在 TiDB 上支持多個存儲引擎,好比同一個 Query,能夠一部分算子推到 TiFlash 上去處理,一部分算子在 TiKV 上處理,在 TiFlash 上作全表掃描,TiKV 上就作 Index 點查,最後彙總在一塊兒再作計算。
咱們還計劃提供一個新的工具,叫 SQL Tuning Advisor。如今用戶遇到了慢 Query,或者想在上線業務以前作 SQL 審覈和優化建議,不少時候是人肉來作的,以後咱們但願把這個過程變成自動的。
除此以外咱們還將支持向量化的引擎,就是把這個引擎進一步作向量化。將來咱們還要繼續兼容最新的 MySQL 8.0 的特性 Common Table,目前計劃以 MySQL 5.7 爲兼容目標,和社區用戶一塊兒把 TiDB 過渡到 MySQL 8.0 兼容。
說了這麼多,我我的以爲,咱們作一個好的數據庫,有用的數據庫,最重要一點是咱們有大量的老師,能夠向用戶,向社區學習。無論是分享了使用 TiDB 的經驗和坑也好,仍是去提 Issue 報 Bug,或者是給 TiDB 提交了代碼,都是在幫助咱們把 TiDB 作得更好,因此在這裏表示一下衷心的感謝。最後再立一個 flag,去年咱們共寫了 24 篇 TiDB 源碼閱讀文章,今年還會寫 TiKV 源碼系列文章。咱們但願把項目背後只有開發同窗才能理解的這套邏輯講出來,讓你們知道 TiDB 是怎樣的工做的,但願今年能把這個事情作完,感謝你們。
1 月 19 日 TiDB DevCon 2019 在北京圓滿落幕,超過 750 位熱情的社區夥伴參加了這次大會。會上咱們首次全面展現了全新存儲引擎 Titan、新生態工具 TiFlash 以及 TiDB 在雲上的進展,同時宣佈 TiDB-Lightning Toolset & TiDB-DM 兩大生態工具開源,並分享了 TiDB 3.0 的特性與將來規劃,描述了咱們眼中將來數據庫的模樣。此外,更有 11 位來自一線的 TiDB 用戶爲你們分享了實踐經驗與踩過的「坑」。同時,咱們也爲新晉 TiDB Committer 授予了證書,併爲 2018 年最佳社區貢獻我的、最佳社區貢獻團隊頒發了榮譽獎盃。