天天看點

MySQL 5.7: Page Cleaner的刷髒問題

本文的目的主要是補充下5.7目前所做的多個page cleaner的實作思路,社群相關的bug讨論,以及我近期對page cleaner所做的一些優化工作。

為了提升擴充性和刷髒效率,在5.7.4版本裡引入了多個page cleaner線程。進而達到并行刷髒的效果。

在該版本中,page cleaner并未和buffer pool綁定,其模型為一個協調線程 + 多個工作線程,協調線程本身也是工作線程。是以如果innodb_page_cleaners設定為8,那麼就是一個協調線程,加7個工作線程

協調線程的入口函數為buf_flush_page_cleaner_coordinator

工作線程的入口函數為buf_flush_page_cleaner_worker

在啟動時會初始化一個slot數組,大小為buffer pool instance的個數(buf_flush_page_cleaner_init)。

協調線程在決定了需要flush的page數和lsn_limit後,會設定slot數組,将其中每個slot的狀态設定為page_cleaner_state_requested, 并設定目标page數及lsn_limit,然後喚醒worker線程 (pc_request)

worker線程被喚醒後,從slot數組中取一個未被占用的slot,修改其狀态,表示已被排程,然後對該slot所對應的buffer pool instance進行操作。

為了支援對單個bp instance進行lru/flush_list的重新整理,對原有代碼做了大量的改動,worker線程可以直接調用buf_flush_lru_list 及buf_flush_do_batch 指定buffer pool進行flush操作。 互相之間不幹擾,是以可以并行刷髒。 改動整體而言比較簡單。

由于目前版本page cleaner是不和buffer pool綁定的,是以,最好不要将page cleaner的個數設定的大于buffer pool instance的個數。

如果目前執行個體不活躍,即沒有負載時,則單獨由協調線程做100%的刷髒。

worklog:

代碼比較簡單,不展開闡述,感興趣的可以讀讀更新檔:

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7189">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7189</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7244">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/7244</a>

在mysql 5.7.5裡進一步改進了multi page cleaner特性,讓其支援在crash recovery和shutdown時能夠應用多個page cleaner特性,來加快崩潰恢複和關閉執行個體的速度。

實作方式也比較簡單,例如對于recovery過程中,如果需要刷髒時,不主動刷髒,而是喚醒page cleaner,然後等待page cleaner完成。

patch:

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8489">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8489</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8522">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8522</a>

<a href="http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8532">http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8532</a>

<a href="http://bugs.mysql.com/bug.php?id=68481">http://bugs.mysql.com/bug.php?id=68481</a>

期望允許page cleaner做furious flush,而不是每秒做一次,對于寫入負載很猛的場景,很容易到達同步checkpoint點

<a href="http://bugs.mysql.com/bug.php?id=70500">http://bugs.mysql.com/bug.php?id=70500</a>

不管是否active,總是做lru flush。 因為在某些場景下,可能沒什麼使用者負載,checkpoint age很低,但諸如purge操作需要很多free page時, 這時候我們其實期望page cleaner 負責清出更多的空閑block。

事實上, webscalesql已經這麼做了。感興趣的可以直接去checkout webscalesql的代碼看看

<a href="http://bugs.mysql.com/bug.php?id=71411">http://bugs.mysql.com/bug.php?id=71411</a>

buf_do_lru_batch函數的傳回值表示從lru上flush的髒頁數,但卻把壓縮表上驅逐的解壓頁也納入了統計。這可能會引起某些innodb_metrics的錯誤值。 這還會影響到page cleaner的刷髒政策。

<a href="http://bugs.mysql.com/bug.php?id=74637">http://bugs.mysql.com/bug.php?id=74637</a>

讓page cleaner的重新整理政策更加自适應。應該根據redo space 和free list做出自适應的page cleaner政策調整,我(id zhai weixiang) 在bug中也回複了可行的政策:對于lru_list,percona server有更加聰明的做法,獨立出lru線程并根據free list長度調整sleep時間;對于flush_list,我們可以對sleep的時間做動态調整,根據max_modified_age_sync來進行估算。

針對page cleaner的問題,我基于阿裡内部的版本中做了些修改 (感興趣的阿裡同學可以找我拿測試branch),大概包含如下幾點:

分拆lru flush到獨立線程,根據free list長度自适應調整刷lru政策 (from percona)

page cleaner支援multi page cleaner (from 5.7)。并根據redo space 計算自适應的刷髒頻率

從使用者線程移除single page flush,總是由lru flush線程來釋放空閑page.

multi page cleaner依然會引起double write buffer的單點競争。為了解決這個問題,引入多個double write file,每個double write buffer檔案對應特定的buffer pool instance.

繼續閱讀