如何對分散式 NewSQL 資料庫 TiDB 進行性能調優
協作翻譯
原文:How to Do Performance Tuning on TiDB, A Distributed NewSQL Database
鏈接:https://dzone.com/articles/how-to-do-performance-tuning-on-tidb-a-distributed
譯者:Tocy, 邊城, 白又白呀, imqipan, rever4433, 雨田桑, 涼涼_
在分散式系統中進行調優不是開玩笑的事情。分散式系統中調優比單節點伺服器調優複雜得多,它的瓶頸可能出現在任何地方,單個節點上的系統資源,子組件,或者節點間的協作,甚至網路帶寬這些都可能成為瓶頸。
性能調優就是發現並解決這些瓶頸的實踐,直到系統達到最佳性能水平。我會在本文中分享如何對 TiDB 的「寫入」操作進行調優,使其達到最佳性能的實踐。
TiDB是開源的混合事務處理/分析處理(HTAP)的 NewSQL 資料庫。一個 TiDB 集群擁有幾個 TiDB 服務、幾個 TiKV 服務和一組 Placement Deiver(PD)(通常 3-5 個節點)。
TiDB 服務是無狀態 SQL 層,TiKV 服務是鍵值對存儲層,PD 則是管理組件,從頂層視角負責存儲元數據以及負載均衡。下面是一個 TiDB 集群的架構,你可以在TiDB 官方文檔中找到每個組成部分的詳細描述。
採集監控數據
Prometheus是一個開源的系統監測的解決方案,採集每個內部組件的監控數據,並定期發給Prometheus。藉助開源的時序分析平台Grafana,我們可以輕易觀測到這些數據的表現。使用Ansible部署 TiDB 時,Prometheus 和 Grafana 是默認安裝選項。通過觀察這些數據的變化,我們可以看到每個組件是否處於運行狀態,可以定位瓶頸所在,可以調整參數來解決問題。
?插入 SQL 語句的寫入流(Writeflow)
假設我們使用如下 SQL 來插入一條數據到表 t
上面是一個簡單而直觀的簡述,介紹了 TiDB 如何處理 SQL 語句。TiDB 伺服器收到 SQL 語句後,根據索引的編號將語句轉換為一個或多個鍵值對(KV),這些鍵值對被發送到相關聯的 TiKV 伺服器,這些伺服器以 Raft 日誌的形式複製保存。最後,Raf 日誌被提交,這些鍵值對會被寫入指定的存儲引擎。
在此過程中,有 3 類關鍵的過程要處理:轉換 SQL 為多個鍵值對、Region 複製和二階段提交。接下來讓我們深入探討各細節。
?從 SQL 轉換為鍵值對
與其他資料庫系統不同,TiDB 只存儲鍵值對,以提供無限的水平可伸縮性以及強大的一致性。那麼要如何實現諸如資料庫、表和索引等高層概念呢?在 TiDB 中,每個表都有一個關聯的全局唯一編號,被稱為 「table-id」。特定表中的所有數據(包括記錄和索引)的鍵都是以 8 位元組的 table-id 開頭的。每個索引都有一個名為 「index-id」 的表範圍的唯一編號。下面展示了記錄鍵和索引鍵的編碼規則。
?Region(區域)的概念
在 TiDB 中,Region 表示一個連續的、左閉右開的鍵值範圍 [start_key,end_key)。每個 Region 有多個副本,並且每個副本稱為一個 peer 。每個 Region 也歸屬於單獨的 Raft 組,以確保所有 peer 之間的數據一致性。(有關如何在 TiKV 中實現 Raft 一致性演算法的更多信息,請參閱 PingCAP 傑出工程師唐劉的相關博文。)由於我之前提到的編碼規則的原因,同一表的臨近記錄很可能位於同一 Region 中。
當集群第一次初始化時,只存在一個 Region 。當 Region 達到特定大小(當前默認值為96MB)時, Region 將動態分割為兩個鄰近的 Region ,並自動將數據分布到系統中以提供水平擴展。
?二階段提交
我們的事務處理模型設計靈感來源於Percolator,並在此基礎上進行了一些優化。簡單地說,這是一個二階段提交協議,即預寫入和提交。
每個組件中都有更多的內容,但從宏觀層次來理解足以為性能調優設置場景。現在我們來深入研究四種調優技術。
調優技巧 #1: 調度器
所有寫入命令都被發送到調度器模型,然後被複制。調度器模型由一個調度線程和幾個工作線程組成。為什麼需要調度器模型?在向資料庫寫入數據之前,需要檢查是否允許這些寫命令,以及這些寫命令是否滿足事務約束。所有這些檢查工作都需要從底層存儲引擎讀取信息,它們通過調度由工作線程來進行處理。
如果看到所有工作線程的 CPU 使用量總和超過 scheduler-worker-pool-size * 80% 時,就需要通過增加調度工作線程的數理來提高性能。
可以通過修改配置文件中 『storage』 節的 『scheduler-worker-pool-size』 來改變調度工作線程的數量。對於 CPU 核心數目小於 16 的機器,默認情況下配置了 4 個調度工作線程,其它情況下默認值是 8。參閱相關代碼部分:scheduler-worker-pool-size = 4
調優技巧 #2:raftstore進程與apply進程
像我前邊提到的,我們在多節點之間使用Raft實現強一致性。在將一個鍵值對寫入資料庫之前,這個鍵值對首先要被複製成Raft log格式,同時還要被寫入各個節點硬碟中保存。在Raft log被提交後,相關的鍵值對才能被寫入資料庫。
這樣就產生兩種寫入操作:一個是寫Raft log,一個是把鍵值對寫入資料庫。為了在TiKV中獨立地執行這兩種操作,我們創建一個raftstore進程,它的工作是攔截所有Raft信息,並寫Raft log到硬碟中;同時我們創建另一個進程apply worker,它的職責是把鍵值對寫到資料庫中。在Grafana中,這兩個進程顯示在TiKV面板的子面板Thread CPU中(如下圖所示)。它們都是極其重要的寫操作負載,在Grafana中我們很容易就能發現它們相當繁忙。
為什麼需要特別關注這兩個進程?當一些TiKV伺服器的apply或者raftstore進程很繁忙,而另一些機器卻很空閑的時候,也就是說寫操作負載不均衡的時候,這些比較繁忙的伺服器就成了集群中的瓶頸。造成這種情況的一種原因是使用了單調遞增的列,比如使用AUTOINCREMENT指定主鍵,或者在值不斷增加的列上創建索引,例如最後一次訪問的時間戳。
要優化這樣的場景並消除瓶頸,必須避免在單調增加的列上設計主鍵和索引。
在傳統單節點資料庫系統上,使用AUTOINCREMENT關鍵字可以為順序寫入帶來極大好處,但是在分散式資料庫系統中,使所有組件的負載均衡才是最重要的。
調優技巧#3:RocksDB
RocksDB是一個高性能,有大量特性的永久性 KV 存儲。 TiKV 使用 RocksDB 作為底層存儲引擎,和其他諸多功能,比如列族、範圍刪除、前綴索引、memtable 前綴布隆過濾器,sst 用戶定義屬性等等。 RocksDB 提供詳細的性能調優文檔。
每個 TiKV 伺服器下面都有兩個 RocksDB 實例:一個存儲數據,我們稱之為 kv-engine ,另一個存儲 Raft 日誌,我們稱之為 raft-engine 。kv-engine 有4個列族:「default」 、「lock」、 「write」 和 「raft」 。大多數記錄存儲在 「default」 列族中,所有索引都存儲在 「write」 列族中。
你可以通過修改配置文件關聯部分中的 block-cache-size 值來調整這兩個 RocksDB 實例,以實現最佳性能。相關部分是: [rocksdb.defaultcf]block-cache-size = 「1GB」和 [rocksdb.writecf]block-cache-size = 「1GB」
我們調整 block-cache-size 的原因是因為 TiKV 伺服器頻繁地從「write」 列族中讀取數據以檢查插入時是否滿足事務約束,所以為 「write」 列族的塊緩存設置合適的大小非常重要。當 「write」 列族的 block-cache 命中率低於 90% 時,應該增加 「write」 列族的 block-cache-size 大小。
「write」列族的 block-cache-size 的默認值為總內存的 15% ,而 「default」 列族的默認值為 25% 。例如,如果我們在 32GB 內存的機器上部署 TiKV 節點,那麼對於 「default」 列族, 「write」 列族的 block-cache-size 的值約為 4.8GB 到 8GB 。
在繁重的寫入工作量中, 「default」 列族中的數據很少被訪問,所以當我們確定 「write」 列族的緩存命中率低於 90%(例如50%)時,我們知道 「write」 列族大約是默認 4.8 GB的兩倍。
為了調優以獲得更好的性能,我們可以明確地將 「write」 列族的 block-cache-size 設置為 9GB 。但是,我們還需要將 「default」 列族的 block-cache-size 大小減少到 4GB ,以避免 OOM(內存不足)風險。你可以在 Grafana 的 「RocksDB-kv」 面板中找到 RocksDB 的詳細統計信息,以幫助進行調優。
RocksDB-kv 面板
調優技巧#4: 批量插入
使用批量插入可以實現更好的寫入性能。從 TiDB 伺服器的角度來看,批量插入不僅可以減少客戶端與 TiDB 伺服器之間的 RPC 延遲,還可以減少 SQL 解析時間。在 TiKV 內部,批量插入可以通過將多個記錄合併到一個 Raft 日誌條目中來減少 Raft 信息的總數量。
根據我們的經驗,建議將批量大小保持在 50?100 行之內。當一個表中有超過 10 個索引時,應減少批量處理的大小,因為按照上述編碼規則,插入一行類似數據將創建超過 10 個鍵值對。
總結
我希望本文能夠在使用TiDB時幫助你了解一些常見的瓶頸狀況,以及如何調優這些問題,以便在「寫入」過程中實現最優性能。綜上所述:
不要讓一些 TiKV 節點處理大部分「寫入」工作負載,避免在單調增加的列上設計主鍵和索引。
當 TiKV 調度模型中的調度器的總 CPU 使用率超過 scheduler-worker-pool-size*80% 時,請增加 scheduler-worker-pool-size 的值。
當寫入任務頻繁讀取"write"列族並且塊緩存命中率低於 90% 時,在 RocksDB 中增加 block-cache-size 的值。
使用批量插入來提高「寫入」操作的性能。
我們的許多客戶,從電子商務市場和遊戲,到金融科技、媒體和旅行,已經在生產中使用這些調優技術,以充分利用 TiDB 的設計、體系結構和優化。期待在不久的將來分享他們的使用案例和經驗。


※分散式共享 Session之SpringSession 源碼細節
※Spring Boot 2.0.0 終於正式發布,重大修訂版本
TAG:開源中國 |