來自:開源中國 協作翻譯
原文: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 的設計、體系結構和最佳化。期待在不久的將來分享他們的使用案例和經驗。
●編號310,輸入編號直達本文
●輸入m獲取文章目錄
大資料與人工智慧
更多推薦《18個技術類公眾微信》
涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。