從Purge機制說起,詳解GaussDB(for MySQL)的最佳化策略

华为云开发者联盟發表於2024-06-17

本文分享自華為雲社群《【華為雲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.png

圖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.png

圖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.png圖3 二次分發

4.png

圖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.png

圖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.png

圖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實時獲取這個值。

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章