億優百倍|商品資料服務TiDB效能優化

語言: CN / TW / HK

作者|陳彥傑

編輯|林穎

供稿|Marketing Tech Team

本文共6873字,預計閱讀時間15分鐘

更多幹貨請關注“eBay技術薈”公眾號

導 讀

“億優百倍”是eBay智慧營銷團隊推出的系列文章,分享了在營銷商品資料服務系統的架構、設計、程式碼方面的一些理解和研究。在上期的“ 億優百倍|商品資料服務百倍效能優化之路 (點選閱讀) 裡,我們介紹了專案的背景、總體設計和優化路線圖。 本期“億優百倍”,我們分享了對MIS的優化方法,以提高了TiDB在eBay平臺上使用的效能和穩定性。

1

TiDB簡介

在2020之前,我們主要使用NoSQL和關係型資料庫來儲存資料。NoSQL資料庫有著不錯的效能和擴充套件性,但是很少有完備的二級索引支援;而關係型資料庫有完整的索引支援,但是擴充套件性有限,並且商業版本的關係型資料庫往往成本高昂。另外,我們有大量資料儲存在Hadoop上,需要進行線上線下同步,並使用Spark處理。Spark和常見資料庫進行資料同步時,通常使用JDBC(Java Database Connectivity)作為介面,但由於JDBC本身的限制,在進行超大資料併發時會成為嚴重的頻寬瓶頸。

從2020年開始,我們嘗試使用TiDB,代替NoSQL和關係型資料庫,並嘗試使用TiDB來構建MIS的核心儲存。

TiDB是一個HTAP(混合事務/分析處理,Hybrid Transactional/Analytical Processing)資料庫,它可以同時服務於批處理資料和事務型資料查詢。TiDB包含三個核心元件—— PD、TiDB和TiKV

PD(Placement Driver) 是有狀態元資料節點,可以儲存元資料併為叢集授時。PD收集叢集狀態資訊,並負責叢集排程。

TiDB伺服器 (區分於TiDB叢集)是無狀態查詢節點,負責接受客戶端請求,將SQL查詢轉換成TiKV能接受的Key查詢,並負責事務處理。

TiKV 是有狀態儲存節點,它將所有資料以Key-Value的形式儲存在底層的RocksDB [1] (一個性能優異的單機NoSQL資料庫) 中。 TiKV使用了兩個RocksDB例項,分別儲存查詢資料和日誌資料。 因此TiKV底層資料的儲存形式和大部分NoSQL資料庫一樣,都是SSTable。 SSTable的特性之一就是Key有序,這個特性決定了TiDB資料分片的模式——將連續Key分段,而不是按Key做雜湊。 另外,TiDB資料管理的基本單位是Region(即Key連續的資料塊)。 由於資料塊是以SSTable形式實現,而且一個Region內部本身就是有序的,TiDB只要保證Region相互之間是有序的,就可以得到一個全域性有 序的資料集。

除了TiDB三個核心元件之外,還有使用Spark查詢TiDB的元件—— TiSpark 。TiSpark是Spark聯結器,可以繞過JDBC效能的限制,直接讀寫TiKV底層資料,這極大地提升了讀寫效能。

以下是TiDB整體架構圖,感興趣的朋友可以進一步檢視官網上對儲存 [2] ,計算 [3] ,排程 [4] 的詳細介紹。

圖1 TiDB整體架構

(點選可檢視大圖)

我們目前擁有一個生產叢集,包含3個PD節點、25個TiDB節點和27個TiKV節點。TiDB叢集中儲存的資料集有30億左右,欄位數量大約有70個。我們在TiDB使用過程中主要在HTAP、跨資料中心和地域親和性、查詢計劃穩定性等方面對TiDB進行調優,以提高TiDB在eBay平臺上使用的效能和穩定性。

2

HTAP帶來的效能挑戰和解決方案

2.1

問題的發現

TiDB從一開始就支援混合線上離線資料處理,它可以同時支援Spark這種批處理查詢和JDBC這種線上查詢的需求。在MIS的系統中,我們對每日資料校正的任務需要通過Spark讀全量TiDB資料,並且和Hadoop上的商品快照(Snapshot)資料作對比從而進行誤差校正。而在實踐中我們發現,當Spark查詢和JDBC查詢集中在同一個時間段時,查詢延遲會大幅上升,並且其效能會劇烈抖動。Spark會在1小時內讀取超過20億行資料,達到600K IPS。在這種情況下其他查詢延遲P95會從平均30ms左右上升到800ms。由於所有的線上和離線查詢都會請求TiKV資料,這就導致當大量線上和離線查詢都堆積到TiKV時,會產生HTAP干擾的情況,從而嚴重影響效能。下圖展示了JDBC延遲與TiKV負載關係,從圖中可以發現:在有Spark任務時TiKV負載大幅上升,相應的TiDB查詢時間劇增。

圖2 JDBC延遲與TiKV負載關係

(點選可檢視大圖)

2.2

解決方案

對於上述這個問題,一種比較簡單想法是:拉長Spark讀取TiDB的時間, 以降低讀取TiDB的速率。但這種方法只能相對降低延遲,無法從根本解決干擾問題。如果Spark讀取TiDB的時間過長,就會導致一致性問題,使得Spark讀取到的TiDB資料可能已經不是最新的。另外Spark讀取TiDB時需要有合適的時間戳(TSO),如果時間戳超過了GC Safepoint,讀取會被TiDB拒絕而導致報錯。

那麼如何解決這種HTAP干擾的問題呢?官方其實已經給出了答案。

從TiDB 4.x開始,官方推出TiFlash [5] 作為列式儲存引擎。相比於TiKV封裝了RocksDB,TiFlash封裝了Clickhouse,把資料從TiKV同步到TiFlash。

在TiDB的資料模型中,Region是一個邏輯上的概念,是一個Key連續的資料段。但物理上,一份Region會有多個副本,每個副本被稱為Peer,這些Peer用Raft協議組成Group,分佈在TiDB叢集中。

Peer在Raft協議下有三種角色:

Leader :負責響應客戶端的讀寫請求;

Follower :被動地從 Leader同步資料,當Leader失效時會進行選舉產生新的Leader;

Learner :只參與同步raft log而不參與投票。

在TiDB 3.x階段,Learner只短暫存在於新增副本的中間步驟。進入TiDB 4.x之後,TiFlash內所有Peer全部作為Learner加入Raft Group,只同步資料而不響應客戶端的讀寫請求。這樣既不影響現有的Peer,又使TiFlash可以同步TiKV資料。Spark任務可以選擇讀取TiFlash,直接請求列資料,不影響TiKV中線上查詢的請求。

我們搭建了測試環境,對TiDB 4.x進行充分測試。我們發現使用Spark查詢時,讀取TiFlash可以比TiKV快3倍,同時對線上查詢沒有影響。我們將TiDB生產環境升級到4.x版本,並且新增TiFlash元件。生產環境和測試環境得到的結果基本一致,在Spark讀取TiDB時,所有的請求都會讀取TiFlash,而且讀取TiKV的JDBC請求不受任何影響。如圖3所示,(對比圖2)TiDB的負載只出現在TiFlash中,並且查詢延時在有Spark任務時變得非常穩定。延遲從之前最高800ms下降到30ms,IPS從之前全部讀取TiKV時的5K,提升到分別讀取TiKV和TiFlash時的30K。

圖3 JDBC延遲與TiFlash負載關係

(點選可檢視大圖)

3

TiDB跨資料中心和地域親和性優化

3.1

背景介紹

3.1.1 時間戳(TSO)

在TiDB中,全域性時間戳(TSO)是一個非常重要的概念,它有兩個核心應用場景:事務處理(Transaction)和多版本併發控制(MVCC)。

TiDB使用Percolator分散式事務模型。Percolator採用的是一種兩階段提交(two phase commit)的方式,分別為預寫階段(Pre_write)和提交階段(Commit)。預寫階段需要一個開始時間戳,作為當前事務的開始版本號。當所有預寫完成之後,TiDB獲取提交時間戳,將事務狀態儲存在TiKV上。

TiDB對多版本併發控制以‘Key+時間戳’作為主鍵,因此需要獲取一個全域性單調遞增的時間戳。該時間戳將作為Key的版本號,通過Seek的方式拿到當前最高版本的Key,也就是Key上最新的資料。

在工業界,分散式環境下的時間戳獲取主要有非集中授時和集中授時兩種。

非集中授時的代表是Google Spanner和CockroachDB,前者採用硬體級別的時間授時機制(原子鐘+GPS),再加上演算法層面的控制,將授時延遲控制在1ms-7ms之內;而後者採用NTP(Network Time Protocol)加演算法進行授時,但會犧牲了一些事務上功能。

集中授時的代表是TiDB,TiDB將一臺PD節點作為全域性唯一的授時伺服器,所有時間戳請求都會集中到這一個節點。這樣做的好處是使TiDB能完整支援事務處理需求,並且無需硬體支援。但缺點也很明顯,當我們需要在多個數據中心做災備或者服務跨資料中心的流量請求時,授時伺服器的請求就會跨資料中心,延時時間為10ms-20ms不等。另外,由於跨資料中心網路請求更容易受到網路波動的影響,導致跨資料中心的授時伺服器請求不穩定。

圖4 跨資料中心的授時伺服器請求

(點選可檢視大圖)

3.1.2 Leader讀取

前面已經說過,在TiDB的資料模型中,Region有多個副本,這些副本分佈在所有TiKV節點上,只有副本中的Leader節點負責響應客戶端的讀寫請求。當Leader分佈在多個數據中心時,TiDB節點訪問Region時必然需要跨資料中心讀取Region的Leader。請求量越大,讀取Leader的網路延遲開銷影響越大。

TiDB之所以一定要求只從Leader讀寫資料,這是為了保證強一致性。雖然TiDB從4.x開始支援Follower讀取,但是Follower讀取之前,先要向Leader請求時間戳,保證Follower和Leader之間版本一致,然後再從Follower讀取資料。這相當於多了一次網路開銷,與在跨資料中心的網路環境下和直接讀取Leader需要的網路開銷沒有太大改善,所以並沒有解決跨資料中心的問題。

TiDB本身支援讀取歷史資料,因此有一種變通的辦法可以不用經過Leader,直接從Follower讀取該節點最新資料。但這種方法也有兩個問題:一個是原來讀取Leader可以保證強一致性,而讀取Follower的最新資料只能保證最終一致性,一致性保證降低;二是從實際操作角度上說,直接讀取Follower最新資料是讀取歷史資料的一個變種,需要顯式地帶上歷史資料的時間戳,因此實際查詢中需要在SQL的最後帶上和查詢邏輯無關而和系統有關的時間,這就令人難以理解和維護。

3.2

問題的發現

eBay擁有多個數據中心。當我們最初使用TiDB時,並沒有注意到將TiDB部署在多個數據中心會影響SQL查詢的效能。因此我們最初將TiDB的各個節點平均部署在多個數據中心,用以服務來自多個數據中心的流量,並用多個數據中心作災備。

在問題發現前,我們先來理解一下Grafana儀表盤上PD客戶端中PD TSO RPC Duration(如下圖所示)的含義:該圖表現了獲取時間戳過程中的純網路開銷。P99在平時不超過5ms,而高峰時不超過10ms。讀取Region和獲取時間戳的網路開銷基本相同。

圖5 獲取時間戳的網路開銷

(點選可檢視大圖)

通過理解上圖的含義,我們在後來的回溯中發現,當跨資料中心的請求出現時,純網路開銷延遲在低谷時期(IPS<1K)P99大約是20ms。一旦IPS上升到10K,P99會達到60ms,由此帶來的整體查詢延遲會超過200ms。IPS越高,網路抖動造成的延遲上升越明顯。在跨資料中心的網路條件下,能滿足我們延遲需求的IPS最高在25K到30K之間,並且伴隨著極為明顯效能抖動。

3.3

解決方案

TiDB叢集搭建在兩個資料中心上,以下簡稱為A中心和B中心。為了縮小網路延遲開銷,提升IPS必須去除影響讀寫效能的跨資料中心網路延遲開銷,同時保證內部系統的效能和穩定性。跨資料中心的請求存在於PD、TiDB和TiKV之間,主要由時間戳請求和Region讀取造成。因此,我們的目標是讓獲取時間戳和讀取Region這兩個操作只發生在本地資料中心,以消除跨資料中心的操作。

我們選擇A中心作為主中心,將授時伺服器和Region Leader集中在A中心,服務於外部請求;而B中心只作為同步備份中心,包含PD Follower和Region Follower,不對外服務。B中心只有當A中心完全不可用時才會起到災備作用。A中心和B中心的具體配置方法如下圖所示:

圖6 兩資料中心架構

(點選可檢視大圖)

1、PD

上述介紹過PD是元資料管理和授時伺服器。所有的PD節點共同組成了Raft Group,而Raft Group內同一時間只有一個Leader節點,其餘的都是Follower節點。只有Leader節點作為PD的服務端點,而Follower只負責同步。因此所有的Region位置請求以及時間戳請求都會請求同一個PD節點。我們將三個PD節點中的兩個放在A中心(隨機選擇其中一個作為Leader節點,另一個作為Follower以防Leader下線),另一個放在B中心(只有當A中心整體不可用時該節點才會作為Leader,以保證資料不丟)。

2、TiDB

TiDB是無狀態查詢節點,由於B中心不對外服務,因此不保留TiDB節點,將其全部放在A中心。

3、TiKV

TiKV儲存著所有資料,資料以Region為劃分。TiDB可以承受的最大副本失效個數為“(副本數/2)-1”。我們目前將副本數量設為7,TiDB可以在3個TiKV節點同時失效的情況依然保持可用狀態。資料副本數量越多可靠性越高,但同時由於寫副本增加,效能也會受到越多影響。我們將27個TiKV中的18個節點放在A中心,9個節點放在B中心。同時在佈局規則中設定A中心6個副本,這些副本有機會成為Leader,而B中心保留1個副本,不能作為Leader,只能作為Follower同步資料。

如下圖TiDB內部的監控可以看到,經過優化後,當所有Leader節點都在同一資料中心時,授時請求P99延遲在4ms左右,並且當IPS持續上升到200K時,延遲也不會超過10ms。對比之前在IPS在10K時 P99達到60ms,延遲有非常明顯的下降。由於網路延遲下降並且保持穩定,查詢延遲P95下降到30ms,在IPS 200K時不超過50ms。

圖7 QPS 20K(IPS 200K)情況下的網路延遲

(點選可檢視大圖)

4

查詢計劃穩定性

TiDB和大部分資料庫一樣,採用了基於成本的查詢優化(Cost-Based Optimization,CBO)。TiDB會自動收集每一條記錄對應的更改,並據此給出每張表的一些基本資訊,比如總行數、欄位上的非重複值數量、空值數量、平均長度等。TiDB會根據統計資訊生成物理執行計劃,這些計劃可以通過“explain”檢視。

4.1

問題的發現

我們發現在執行的大量查詢中,有些查詢特別慢。在通過查詢執行計劃過程中,我們驚訝地發現,對於同一個SQL statement出現了兩個執行計劃。其中一個執行計劃是根據索引查詢,而另一個執行計劃進行的是掃表查詢。當SQL查詢不幸使用了掃表查詢計劃,查詢時間會遠遠大於正常使用索引查詢的時間,往往幾條掃表SQL查詢就會導致整個叢集查詢效能下降。我們發現出現這種情況的原因是:當沒有納入統計的行數佔總行數的比例超過一個閾值(pseudo-estimate-ratio)時,TiDB會認為統計資訊不準,轉而使用偽估計(Pseudo Estimate)方式。而偽估計得到的結果集大小可能會大幅波動,導致出現索引和掃表兩種查詢計劃。

4.2

解決方案

我們知道統計資訊本身也是一張表,這張表需要進行執行收集分析(analyze)才會更新。TiDB本身會根據更改的行數與總量的比例(tidb_auto_analyze_ratio)進行自動收集。但實踐中我們發現,一方面自動收集的觸發不是很穩定,有時自動收集的比例已經達到,但自動收集分析的行為並沒有如期被觸發。另一方面收集分析的行為是一個相對來說比較重的操作,如果自動收集和業務高峰正好撞車,就會容易導致業務不穩定。所以,在實踐中通常建議使用Crontab定時進行收集分析。

但通過上述定時收集分析統計資訊並沒有完全解決我們的問題。由於我們更新非常頻繁(平峰4K IPS,高峰16K IPS),可能會出現某一段時間更新行數暴漲而定時收集分析還未開始的情況。這時由於沒有納入統計的記錄超過了閾值,導致依然會觸發偽估計。一旦偽估計得出掃表執行計劃,問題就會出現。

TiDB官方已經意識到由於統計資訊不準會導致查詢計劃出現掃表的問題,因此推出了執行計劃繫結(SQL Binding)的方式。TiDB可以將一個SQL繫結到一個等價但帶索引提示(Index Hint)的SQL上,所有和這個SQL使用同一種statement的SQL都會被繫結到這個帶索引提示的SQL上。這樣就可以繞過統計資訊,直接使用繫結的物理計劃查詢。

但不幸的是我們的業務並不適合這種方案,因為繫結執行計劃需要固定AND/OR/NOT這種邏輯關係,上游SQL查詢中會帶不定長度的OR條件,用於查詢多個Item的屬性,Item數量最少是1,最大可能有100。每個Item數量都需要繫結一種執行計劃,也就是說如果我們採用這種方案,繫結的執行計劃會超過100種。如果將來查詢的屬性發生變動,這些執行計劃繫結都需要更改。這顯然是我們無法接受的維護成本。還有一種辦法是我們將同一層級的OR邏輯合併成IN邏輯,這樣所有的Item查詢就可以使用同一個繫結。但這樣就是對特定場景的補丁,不符合通用性原則,並且在架構上會額外多出一層邏輯,增加未來的維護成本,因此該方法也不適合我們。

為了徹底解決這個問題,我們回顧業務場景,發現雖然我們的更新量非常大,但整體資料分佈並不會大幅改變,因此使用更新之前的統計資訊從概率分佈的角度上並沒有區別。因此我們將偽估計觸發閾值(pseudo-estimate-ratio)設為1,以此來完全禁用偽估計,使用收集分析之前的統計資訊來生成物理執行計劃。結果證明:由於整體概率分佈沒有改變,使用沒有更新過的統計資訊也能非常準確地生成執行計劃。 有時與其去按照某種演算法估計一個值,還不如使用過去的統計資訊。

5

其他TiDB的調優

5.1

橫向擴充套件

從工程角度來說,提升效能最簡單的方式是增加機器數量。TiDB具有非常好的擴充套件性,可以通過ansible(3.x)或者tiup(4.x)很好的擴充套件叢集。通過測試我們發現,將TiKV從22臺擴充套件到27臺之後(22%),效能提升了約10%到15%。由此可以看出,在實際情況下,TiDB雖然可以進行橫向擴充套件,但效能並不是隨著機器數量的增加而線性提升,由於TiDB架構中存在一些全域性唯一伺服器(如上文說的PD授時),實際增加的效能略小於節點數量的增加,但增加的節點數量依然是提升效能的有效手段。

5.2

磁碟選擇

TiDB線上資料查詢幾乎都是對磁碟的隨機讀寫, 所以對於TiKV節點的磁碟讀寫有比較高的要求,不僅要求是SSD(最好是NVMe)的磁碟,以滿足高併發隨機讀寫的高頻寬和低延時需求。由於我們使用的是虛擬機器節點,即使是SSD磁碟,效能也不一定能達到TiDB對磁碟的Benchmark要求,所以我們只能從eBay雲環境中挑選一些磁碟效能相對比較好的機器作為我們TiDB的節點。另外為了能通過Benchmark,我們更改了Ansible指令碼中對於磁碟性 能測試的閾值。

6

總結與展望

本篇通過解決HTAP相互干擾問題、解決跨資料中心資料傳輸問題、穩定查詢計劃並對其他方面進行調優 ,我們將 TiDB的性 在整體延 遲P95 <50ms的要求下,從最初的5K IPS提升到了 200K IPS。

但是,在跨資料中心的優化中,由於目前TiDB在異地多資料中心場景下的侷限,我們犧牲了一些災備能力來提高效能。假設TiDB搭在三個資料中心而不是兩個資料中心上,那麼禁止TiDB產生跨資料中心的流量會在一個數據中心不可用時失去整個叢集的可用性(TiDB搭在兩個資料中心上時則沒有區別,因為無論如何配置都無法在一個擁有多數節點的資料中心不可用時繼續服務);而如果允許TiDB產生跨資料中心的流量,當三中心中的一個發生不可用,整個叢集依然可以對外服務。未來,希望能有更好的方案來平衡效能和災備。

下一期 ,我們將分享商品資料服務系統中 快取層和程式碼層面的優化經驗 敬請期待!

Reference

[1]http://rocksdb.org

[2]https://docs.pingcap.com/zh/tidb/stable/tidb-storage

[3]https://docs.pingcap.com/zh/tidb/stable/tidb-computing

[4]https://docs.pingcap.com/zh/tidb/stable/tidb-scheduling

[5]https://docs.pingcap.com/zh/tidb/v4.0/tiflash-overview

往期推薦

億優百倍|商品資料服務百倍效能優化之路

eBay支付核心賬務系統之“展”翅高飛

乾貨|eBay基於Istio的應用閘道器的探索和實踐

eBay支付賬務系統架構解析之“讀”一無二

點選 閱讀原文 ,一鍵投遞

eBay大量優質職位虛席以待

我們的身邊,還缺一個你