當前GaussDB(for MySQL)的Purge優化功能,通過任務流水線化、線程優先順序調整、二次分發等手段,避免資料庫undo log堆積,極大提升Purge的性能,大幅改善用戶體驗。 ...
本文分享自華為雲社區《【華為雲MySQL技術專欄】GaussDB(for MySQL) Purge優化》,作者:GaussDB 資料庫。
在MySQL中,尤其是在使用InnoDB引擎時,Purge機制至關重要。它可以回收undo log【1】,清理過期數據,減少磁碟占用,維護資料庫的整潔與高效。
Purge機制
MySQL InnoDB引擎使用undo log來保存事務修改記錄的歷史信息。事務提交後, update undo log(指在delete和update操作中產生的回滾日誌)未被立即刪除,而是會被記錄到undo history list【2】,等待Purge線程進行最後的刪除。當undo log不再被任何事務依賴時,系統就會掃描這個undo log,將過期的索引數據刪除並清理undo log本身,這個整體的清理過程就是MySQL InnoDB引擎的Purge機制。
Purge的操作可以分成三個主要步驟:1.遍歷已經提交的update undo log
根據undo log header中的信息判斷這些事務是否存在過期數據,如果判斷存在過期數據,就要觸發Purge機制進行清理,否則就不需要。
2.通過undo log進行數據的物理刪除
其過程主要包括:掃描undo 頁面來獲取undo記錄,遍歷undo記錄以確認是否要進行清理,如果需要則刪除索引中的過期數據。
3.回收undo log
purge的速度會影響已提交的undo log的清理回收,如果purge的清理速度趕不上事務生成undo log的速度,undo log就會出現堆積。如果條件允許的話,對undo tablespace進行truncate(截斷)來實現undo空間的回收。
undo log堆積主要會產生兩方面的影響:第一, 額外的存儲占用
undo log和索引都有歷史版本數據,undo log未及時清理會導致undo log自身以及undo關聯的過期索引數據產生堆積,占用額外的存儲空間,容易引起不必要的擴容和開銷,對客戶業務而言是不利的。
第二,性能下降
undo log堆積會導致過期索引數據堆積,索引頁面中會存在大量已經標記為delete的數據,這時候每個索引頁面實際的有效數據占比就會很低,業務SQL執行中會出現獲取少量數據卻需要進行大量頁面IO的情況,最終導致SQL執行性能劣化。
Purge優化實現
當前undo堆積已經成為資料庫運行的一個痛點問題,因此華為雲GaussDB(for MySQL)團隊專門針對Purge機制存在的問題進行瞭如下優化。
優化點一:coordinator 與 worker流水線化
Purge任務是由Purge線程完成的,是InnoDB的常駐線程。其線程主要分成兩個角色,分別是一個purge coordinator線程和若幹個purge worker線程,以下簡稱為coordinator和worker。
事務的修改可能會殘留過期數據在索引中,而老數據的相關信息又存留在undo記錄中,事務提交時的undo log會被記錄在回滾段的History List中,coordinator負責從History List中獲取undo log,讀取其中的undo記錄,並將可能殘留過期數據的undo記錄分發給worker來進行處理。所以,當前purge的流程可以簡單劃分成以下兩個階段:
階段一:coordinator讀取undo log包含的undo頁面,獲取undo記錄。
階段二:coordinator將讀取到的undo記錄批量分發給worker來進行處理,線程之間併發執行,且coordinator也會負責一部分undo記錄的處理,待coordinator和所有worker都執行完畢後開啟下一輪處理。
圖1 purge原生流程
圖1所示為原本的Purge流程,階段一coordinator是單線程執行,階段二worker是多線程併發,即並行執行,但整體兩個階段是串列執行,即階段一執行完,階段二才會繼續執行。如果階段一存在大量undo 頁面都需要進行IO,串列執行就會嚴重影響整體的執行效率。不過,這種兩階段串列化執行的方式是可以優化的。
優化的整體思想即是:將coordinator和worker的執行流水線化。從圖1可以看到,在原生Purge流程中,coordinator除了負責掃描獲取undo記錄的任務,本身也會承擔一部分Purge任務,且必須同步等待所有已分發的Purge任務完成之後,才能開始下一批次的undo記錄掃描任務,這樣就導致階段一和階段二是一個串列過程。
若令coordinator只單一地負責undo記錄的掃描,不再兼職Purge清理任務,這樣在worker執行的過程中,coordinator就能直接繼續去獲取undo記錄, 不再需要同步等待所有的Purge任務完成,這樣就可以達到將原本串列的兩階段執行變成流水線執行的目的。
圖2 purge優化流程
圖2為優化後的Purge流程,Purge優化會先將innodb_purge_batch_size指定的undo頁面拆成若幹個小的batch, 拆分的批次數量由innodb_rds_purge_subbatch_size決定。coodinator會先掃描第一個小batch的undo 記錄,然後分發給各個worker並行處理,在worker處理過程中,coordinator可以去掃描第二個小batch的undo記錄,等到所有worker執行完第一批purge任務後,coordinator將立即分發第二批undo記錄,worker繼續並行執行,而coordinator則立即開始並行地解析第三個小batch的undo記錄,以此類推。
-
優化效果評估
當coordinator獲取完第一個小batch之後,後續coordinator和worker都是同時運行的,每個batch運行的時間由coordinator和worker運行時間較長者決定,為了避免不必要的線程間等待,需要通過調整參數來令兩個線程的耗時接近。
目前通過開啟innodb_monitor_enable的module_purge監控,觀察coordinator的耗時以及worker的耗時,再通過調整innodb_purge_batch_size和innodb_rds_purge_subbatch_size參數,將coordinator和worker的耗時調整到接近,讓每個batch整體耗時降低。當前測試結果,innodb_purge_batch_size(600),innodb_rds_purge_subbatch_size(150)為最優配置,在此配置下purge速度有1倍提升。
優化點二:undo記錄二次分發
我們發現當前在Purge過程中,coordinator分發undo記錄給worker的策略是基於InnoDB層的table id,即對於同一個表的undo記錄都會分發給一個worker。
這就會出現一種場景:當單個熱點表被頻繁執行DML時,會生成大量基於這個表的undo記錄,此時無論innodb_purge_threads值設置多大,Purge的任務都只會由一個線程承擔,Purge機制由原本設計上的併發執行回退成單線程執行,Purge效率降低。
針對這個問題,我們在保持基於table id分發邏輯的基礎上,增加一輪二次分發機制。
-
整體思路
01 根據Purge線程數將undo記錄進行分組(如圖3),Purge線程數量為4,因此將undo記錄劃分為四個分組。
02 第一次分發(基於table id分發):將獲取出來的undo記錄根據table id進行分配,如果當前分組已存在該table id對應的undo記錄,則繼續分配至該分組;如果不存在包含該table_id的undo記錄的分組,則尋找一個容量最小的分組,然後分配給小容量分組。
03 檢測當前分配是否均衡,確認當前是否每個分組的記錄數都小於max_n= (m_total_rec + n_purge_threads - 1) / n_purge_threads。
如果是大於max_n則說明當前分組過載,否則就認為是均衡的。如圖3中,max_n = (11+4 - 1) / 4 = 3,因此第二個、第四個分組均過載,需要進行二次分發。
04 第二次分發(基於分組負載分發):如果當前分組過載,則將過載部分數據轉移至相鄰下一個分組,然後校驗下一個分組是否過載,迴圈校驗,最多遍歷2次所有分組。圖4中,最終每個分組的記錄數不超過3條,相同table id的undo記錄也會分發到不同分組,例如rec3和rec4屬於相同的table,經過二次分發最終會分發到不同的分組,但是分發到不同分組對應最後的Purge結果並不影響。
圖3 二次分發
圖4 最終分髮結果
優化點三:Purge線程優先順序調整
考慮到Purge相關的線程均為後臺常駐線程,對於GaussDB(for MySQL)而言,有較多後臺線程,因此本身Purge相關線程的調度不處於高優先順序,Purge線程不會被系統頻繁調度。所以,第三個優化點是調整Purge相關線程的優先順序,GaussDB(for MySQL)將以高優先順序啟動Purge線程,確保Purge任務能夠及時被調度。
測試驗證
預備條件
innodb_rds_fast_purge_enabled: 以上三個優化開啟的特性開關,開啟後特性全部生效。
innodb_purge_batch_size :單個大batch處理的undo頁面數,設置為600,一個大batch會被劃分成多個小batch。
innodb_rds_purge_subbatch_size:單個小batch處理的undo頁面數,設置為150。
測試模型一:驗證空載場景下Purge速度
-
實例規格:8U32G
-
資料庫版本:GaussDB(for MySQL)-2.0.51.240300
-
操作系統:EulerOS 2.0(SPS)
-
數據量:64 張sysbench寬表,單表1000萬行數據
-
併發數: 1/4/8/16/32/64/128/256/512 線程併發,讀寫模型
-
數據文件 :為了精確對比purge速度提升效果,我們對比空載場景即無業務負載。使用history length已經達到5億的數據文件,在空載場景下對比開啟優化和未開啟優化的清理速度。
實際開啟優化後,空載Purge清理速度大約有1倍提升,具體結果展示如下:
圖5 purge速度對比
測試模型二:驗證特性開啟後對SQL執行性能的影響
-
實例規格:8U32G
-
資料庫版本:GaussDB(for MySQL)-2.0.51.240300
-
操作系統:EulerOS 2.0(SPS)
-
數據量:64 張sysbench寬表,單表1000萬行數據
-
併發數: 1/4/8/16/32/64/128/256/512 線程併發,讀寫模型
開啟和關閉開關後,執行sysbench不同併發下的性能表現如下,開啟關閉優化特性後,QPS基本持平,說明優化特性對於業務性能基本可以忽略。
圖6 性能對比
總結
Purge機制負責GaussDB(for MySQL) InnoDB過期數據的清理,對於資料庫高性能平穩運行起到至關重要的作用。Purge機制不及時不僅會導致過期數據的堆積,占用大量磁碟空間,還會影響SQL執行效率。當前GaussDB(for MySQL)的Purge優化功能,通過任務流水線化、線程優先順序調整、二次分發等手段,避免資料庫undo log堆積,極大提升Purge的性能,大幅改善用戶體驗。
相關概念
【1】Undo Log:即回滾日誌,保存了記錄修改前的數據。在InnoDB存儲引擎中,undo log分為insert undo log和update undo log。
insert undo log是指在insert操作中產生的undo log。由於insert操作的記錄,只是對本事務可見,其它事務不可見,所以undo log可以在事務提交後直接刪除,而不需要額外操作。
update undo log是指在delete和update操作中產生的undo log。該undo log可能要用於多版本併發控制的老版本數據獲取,因此不能在提交的時候刪除。
【2】Undo History List:即記錄所有已提交事務的undo log的鏈表,可以通過該鏈表找到所有未被清理的已提交事務關聯的undo log。事務提交時,insert undo log會被回收掉(reused或者free), update undo log則會被移動到Undo History List鏈表。因此Undo History List的長度即History Length反應了未被處理和回收的update undo log的數量,我們一般通過History Length來評估undo log堆積的情況,可以通過 show engine innodb status實時獲取這個值。