原來提升一個數據庫的性能並沒有那麼難!TiDB 性能挑戰賽完結撒花

在這裏插入圖片描述

2019 年 11 月初,我們開啓了「TiDB 挑戰賽第一季之 性能挑戰賽」,比賽爲期三個月,期間選手將通過完成一系列難度不同的任務來獲得相應的積分。賽程過去三分之一時,已經取得了十分耀眼的 階段性成果。三個月過去,性能挑戰賽已經圓滿落幕,最終的積分排行也新鮮出爐,選手們的參賽成果讓人非常驚喜,讓我們回顧一下選手們是如何在“TiDB 性能提升”之路上,過五關斬六將的吧~

最終積分排名與獎項揭曉

積分排行榜

注:本次比賽的完整積分榜詳見 活動頁面

本次 TiDB 性能挑戰賽,總共有 165 位社區開發者參賽,包括 23 支參賽隊伍和 122 位個人參賽者(按照比賽規則,有 PingCAP 人員參與的小組不計入挑戰賽最終排名,即上圖中有 TiDB Logo 標示的選手)。

本次比賽獎項設置爲:一等獎 1 名,二等獎 2 名,三等獎 3 名,其餘分數高於 600 分的團隊或個人爲優秀獎,各團隊和個人的獲獎情況如下:

  • 一等獎:.* Team(15050 積分)。

  • 二等獎:niedhui(4300 積分)和 catror(3500 積分)。

  • 三等獎:pingyu(2600 積分)、Renkai(2550 積分)和 js00070(1800 積分)。

  • 優秀獎:ekalinin(1450 積分)、mmyj(1050 積分)、AerysNan(750 積分)、MaiCw4J(650 積分)、Rustin-Liu(650 積分)和 koushiro(650 積分)。

感謝這些非常優秀的團隊和個人參賽者,在他們的貢獻下,TiDB 各方面的性能都有了飛躍式的提升(後文會爲大家展示其中幾個優秀項目的提升效果)。此外,非常感謝 PingCAP 內部的參賽同學,他們利用自己的業餘時間參賽,爲 TiDB 的性能提升做出了突出的貢獻,他們將獲得我們頒發的突出貢獻獎:

選手感想

“作爲內部人員參加這次大賽,最大的體驗就是週末工作還是蠻累的;),但和日常工作不同的是,PCP 的難題更具探索性和未知性。作爲參與者擔當了一次業界前沿工業實踐的先頭兵,忘掉 OKR 輕裝上馬,重新找回了初戀代碼的滋味。最後,儘管貴司從來不缺誇獎,我還是得誇一誇這賞心悅目的代碼庫,功能擴展不費吹灰之力,當然還要感謝 mentor 兼同事們對選題的前期探索,在寶貴週末共同探討難題,我的工作只是從紙面邁出的一小步,優秀的團隊給了我最大的鼓勵。”

——tabokie

“我們參加了去年的 hackathon 比賽並斬獲了二等獎。這次性能挑戰賽在隊長的帶領下也取得了總積分榜第二的好成績。導師很認真負責,交流起來完全沒有架子。前期的分數有時候有 bug 但反饋之後很快修復,希望下一屆規則可以更完善一些,學到了很多東西(比如 Rust),下一屆會繼續參賽!”

—— .* team

“參與性能挑戰賽收穫很大,有厲害的導師針對選定問題進行指導,把以前很多零碎的知識匯成了完成的知識體系,最終能看到自己的代碼對 TiDB / TiKV 的性能提升是一件非常有成就感事(TiDB Robot 插播:niedhui 已經是 TiKV Committer 了!)”

—— niedhui

“TiDB 的知乎和公衆號我一直在關注,看到這個活動覺得還挺有意思的,做開源貢獻的同時竟然還有獎品。另外因爲去年下半年學習了 Go 語言就藉此機會多練習一下。比賽體驗很好,稍微難一點的題目都有導師指導,而且 code review 也做的很細心,這對剛開始接觸 TiDB 代碼的人十分友好。要說獲得了什麼,那就是還在你們手裏沒有給我寄的獎品哈哈(TiDB Robot:等我們回公司了就給你寄~)”

——Catror

優秀成果展示

在比賽開始一個月的時候我們曾經做過一次 成果展示,已經過去了兩個月,讓我們再來回顧一下兩個月中參賽選手們取得的優秀成果吧!

PCP-21: Titan GC doesn’t affect online write

這是整個賽季中唯一一個被完成的 Hard 等級的任務,tabokie 憑藉該任務直接獲得 27000 分,在比賽的最後一天逆襲絕殺,登頂性能挑戰賽榜首!

題目簡介

Titan 是 TiKV 開發的一款實現鍵值分離的 RocksDB 插件,簡單來說,就是將較長的用戶鍵值對單獨存儲在 Blob 文件中,而將數據在 Blob 文件中的物理地址寫在 RocksDB 中。當用戶刪除 RocksDB 中的數據時,物理地址對應的數據隨之失效,Titan 通過對 Blob 文件的定時垃圾回收來清理這些無效的數據。GC 的過程就產生了本題目所描述的問題:數據清理後多個 Blob 文件的重新整合產生了新的物理地址,我們需要把它們一一寫回 RocksDB 中,而 Titan 當前的 GC 機制要求寫回 RocksDB 的同時阻塞用戶對 RocksDB 的寫入操作。

具體來說,GC 寫回時執行了讀操作,當且僅當需要寫回的數據較新時纔會確認寫回,整個過程中 RocksDB 不能有新數據插入。這一機制嚴重影響了數據庫的寫入性能,尤其對於更新頻繁進而導致 GC 頻繁的場景,寫入性能將急劇下降。

實現方法

tabokie 採用了一種稍微妥協的方式,利用 RocksDB 提供的 Merge Operator 特性,優化 GC 性能。開啓 Merge Operator 後,除了正常數據,還可以插入 Merge 類型的數據,RocksDB 會自行將正常數據與其後的 Merge 數據按照插入時序進行合併,這樣的合併發生在 Read/Flush/Compaction 過程中,在讀寫性能之間找到了一個可以接受的平衡。使得後臺 GC 不再影響寫入,大大提升了 Titan 的寫入性能。

效果展示

我們使用 YCSB(一款專門針對 NoSQL 數據庫的基礎測試工具)測試開啓了 Titan 的 TiKV 數據庫,本例中使用了純 update 配置,後臺 GC 線程爲 6,測試結果如下:

YCSB

在持續 8 分鐘的測試中,因爲測試前期 GC 頻率較輕,優化前後兩種 GC 機制的寫入性能差距很小。隨着寫入時間的增加,到後期,兩種 GC 機制下的寫入性能差距迅速擴大,二者的 QPS 差距可達到 3000!可以期待的是,在長時的生產環境中這樣的優勢能夠持續保持,將顯著地提升用戶的寫入體驗!

PCP-6: Optimize the performance of builtin function IN

題目簡介

內置函數 IN() 被用來處理 SQL 中的 in 操作,如 select id, age from students where age in (18, 19, 20),是一個比較常見的函數。有時應用拼接的 SQL 中 IN() 表達式的參數個數能夠達到上萬個,且基本上都是常量,如上面的例子。在此種情況下,每收到一行待處理的數據,TiDB 都會去這些常量做一次重複的求值計算,非常低效。

實現方法

該任務由 js00070(張之逸)完成,主要思路是在內部構造 IN() 表達式時,區分出常量和非常量參數,用一個 HashMap 保存常量的值,避免運行時的重複計算。對於上面例子中的 SQL,18、19 和 20 這三個常量就會被保存在 HashMap 中。經過這個優化後,對於常量參數,其計算複雜度從原來的 O(n) 降低到了 O(1)。大大提升了這種情況下 IN() 表達式的運行效率。

效果展示

優化的效果主要取決於參數內的常量個數,我們以 IN 包含 2 個常量參數,1 個非常量參數作爲輸入,對各類型數據處理 1024 行的 benchmark 結果如下圖:

PCP-4: Improve the performance of WindowExec by using multi-thread hash grouping

題目簡介

TiDB 的 Window 算子原來實現是單線程的,對於 Window 算子的每個窗口,因爲是數據隔離的,所以每個窗口之間可以通過並行計算來提升計算效率。

實現方法

算法的原理很簡單,按照窗口函數的 partition by 從句指定的列來進行哈希分組,再對於每個分組,單獨起一個線程做計算。pingyu 經過多次實驗、測試和改進,把 Window 算子和 Sort 算子結合起來,一起進行哈希分組,在每個線程內先將數據排序,再做窗口函數計算。最終得到了非常好的性能提升,超出預期的完成了此 PCP 題目。

附上 pingyu 本人對這項工作的分享:Optimize the Performance of Window Executor

目前 pingyu 正在研究周靖人的 Paper 《Incorporating Partitioning and Parallel Plans into the SCOPE Optimizer》,嘗試將 partitioning 屬性集成到 TiDB 的優化器當中去,使優化器可以根據代價來選擇是否插入 shuffle 算子,這一優化有望改變 TiDB 執行引擎的併發模型,使其充分利用計算機的 CPU 資源,提升執行引擎性能,非常值得期待!

效果展示

如下圖,橫軸代表併發數量,縱軸代表一個有窗口函數的 SQL 的 QPS,併發數量爲 1 時和原來單線程的執行性能一樣。可以看到,在併發數爲 4 時,Window 算子的計算效率達到了單併發執行的 2.2 倍:

PCP-2: Improve the performance of groupChecker by vectorization

該任務由杭州電子科大的鄢程鵬同學完成,他去年參加了 Talent Plan 並順利結業,除了參加性能挑戰賽以外,也正在積極參加 Cascades Planner 的優化器重構工作,爲優化器添加了很多優化規則。

題目簡介

groupChecker 在 TiDB 中被用來分組,會被 Stream Aggregate,Merge Join,Window 這三個算子使用。爲保證正確性,它要求輸入數據是有序的,通過兩兩比較的方式判斷前後兩行數據是否屬於同一個數據分組。

在分組過程中,有可能按照某個表達式來進行分組,如 GROUP BY col1 + col2groupChecker 會逐行的調用表達式的 Eval() 接口進行計算,這個過程的計算開銷非常大。

實現方法

TiDB 在計算時,內存中的數據是按列存放的,考慮到 Cache Locality,按列計算性能會更快。針對這個特點,程鵬做了兩個優化:

  1. 使用表達式最新的列式計算接口,一次性求解一列的值,降低 Cache Miss。

  2. 分組時也借用向量化的思想,按列進行比較,進一步降低 Cache Miss。

效果展示

後續程鵬幫助我們把優化後的 vecGroupChecker 用在了 Window 和 Stream Aggregate 算子內,另一位同學 Catror 把它用在了 Merge Join 算子內,都對這三個算子產生了很大的性能提升。

效果如下圖所示,Window 算子優化前後的執行時間對比,越低性能越好:

PCP-24: Improve the performance of the HTTP API for getting all regions

該任務由俄羅斯小哥 ekalinin 完成,這位小哥曾憑藉一己之力拿到 PCP 單日榜首,目前已完成 20+ 向量化表達式的工作。

題目簡介

在生產環境中,有時需要通過獲取所有的 region 信息來幫忙分析集羣的狀態。在實驗環境中,有時也需要通過收集 region 的信息對集羣訪問模式進行一些分析。當集羣存儲的數據越來越多,region 的個數達到百萬級別以上後,獲取全量的 region 信息所需要的計算和時間開銷變得巨大無比。本題目希望優化該 API 的性能,減少資源使用,降低對 PD 在線服務的影響。

實現方法

在獲取 Region 過程中,主要消耗在於中間的內存拷貝和序列化,因此這兩塊是優化的大頭:

  1. 從 []byte 到 string 的轉化做到 zero-copy。

  2. 優化 Hex Encoding 和大小寫轉換中的內存消耗,減少內存的申請。

  3. 使用 Streaming 的方式序列化輸出。

效果展示

在我們簡單測試場景中 100w regions 對比中,API 的整體性能提升了 3.5 倍,尤其是 Hot Path 上的 RenderJSON() 函數,其運行時間和內存開銷都被大大減小,前後對比的 benchmark 結果如下圖所示:

總結與展望

目前這些優化都會合進 4.0 分支,將隨着 TiDB 4.0 版本發佈並交付給用戶,預計 5 月底 4.0 的用戶就能夠享受到這些性能優化帶來的體驗改進了,讓我們小小的期待下 4.0 版本的驚豔表現。

至此 TiDB 挑戰賽第一季落幕,錯過比賽或沒玩夠的小夥伴們不用遺憾,第二季挑戰賽也即將開啓!

第二季主題的靈感來自去年 AskTUG 上發起的 “我的 TiDB 聽我的” 活動,該活動累計收到 TiDB 用戶們關於 DDL、分區表、性能優化、TiKV、PD 等方面的近 40 個需求。經過一輪篩選,我們列出了 20 個尚未實現的需求 向用戶徵集投票,後續我們將結合用戶的投票結果及其他 TiDB 易用性相關的問題,開啓第二季 TiDB 挑戰賽,敬請期待!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章