天天看點

MySQL 5.7 對buffer pool list scan的優化

worklog: http://dev.mysql.com/worklog/task/?id=7047

原作者innam 跳槽到twitter後 将該特性合并到webscalesql: https://github.com/webscalesql/webscalesql-5.6/commit/c834781321d97b914c3712c663bd7209175a3fe2#diff-07468177c19cc3f32d25913fe731f936r1814

在該worklog中,主要對lru list 和flush list的掃描做了優化,通過設定hazard pointer的pointer的方式,避免多線程操作同一個buffer pool導緻的回溯掃描list.該優化對buffer pool小于資料集的場景比較有益。使用者線程驅逐擷取空閑block,使用者線程在同步checkpoint時做pre flush dirty page,以及page cleaner線程之間 都可能産生沖突。

新對象

通過如下幾個指針作為hazard pointer,在需要掃描list時,存儲下一個即将掃描的目标page,根據不同的目的:

flush_hp: 用作對flush list batch操作

lru_hp: 用作對lru list batch 操作

lru_scan_itr: 用作從lru連結清單上替換驅逐一個幹淨的page,總是從上一次掃描結束的位置開始,而不是lru尾部。

single_scan_itr: 用作從lru上重新整理或驅逐一個page, 總是從上一次結束的位置開始,而不是lru尾部

後兩類的hp,在到達lru->old時,會被重置到lru尾部

初始化

在buf_pool_init_instance初始化每個bp instance時配置設定并初始化hp,每個bp instance都擁有自己的hp

更新hazard pointer

當某個線程對buffer pool中的page進行時,例如需要從lru中移除page時,如果目前的page被設定為hp,就要将hp更新為目前page的前一個page

flush_hp: 如下函數中調整:buf_flush_remove, buf_flush_relocate_on_flush_list,每次batch flush list後清空

lru_hp: 在函數buf_lru_adjust_hp中調整,buf_lru_remove_block, buf_relocate,每次batch lru list後清空

lru_scan_itr/single_scan_itr, 對其adjust操作同樣封裝在buf_lru_adjust_hp中,另外在lruitr::start中有判斷是否到達lru->old,如果到達了,hp也會重新指向到lru尾部。

幾個主要的更改

buf_flush_lru_list_batch

在調用函數該函數掃描page時,從尾部開始,依次通過bpage = buf_pool->lru_hp.get()來操作下一個,每操作依次page,更新lru_hp為prev

而原始邏輯中,

當可以驅逐時,每次驅逐一個page, 都需要從lru尾部重新開始掃描。

當不可以驅逐時,儲存前一個page, 調用buf_flush_page_and_try_neighbors 會釋放并重新擷取buffer pool mutex,是以需要從page hash中再次确認prev page

——新的邏輯省略了查找page hash 以确認prev page存在的開銷 (因為中間會release bp mutex)

buf_do_flush_list_batch

—同樣的設定flush_hp上的page指針

—5.6原始版本中,當發現了指針被重置後,會從尾部重新掃描;而新的邏輯不會回溯連結清單。

從最差o(n^2) 到真正的o(n)

buf_flush_single_page_from_lru

根據buf_pool->single_scan_itr掃描lru

lruitr是lruhp的子類

增加新的行為:

—原始行為:

從lru尾部開始,找一個髒頁,flush it (buf_flush_ready_for_flush, buf_flush_page)

重新掃描lru,從尾部開始,找到一個可替換的page,将其加入到free list

—現在的行為:直接在一次掃描中完成,省去一次掃描

如果發現可replace的page,直接放到free list上

否則如果發現髒頁,flush it, 并加入到free list上

buf_flush_lru_tail

—— 不再按照chunk by chunk的方式flush lru

劃分成chunk(每次從lru上刷100個page)的原因是因為可能有使用者線程可能在函數buf_lru_get_free_block中等待flush結束,但新的邏輯中,使用者線程不再需要等待batch flush操作結束。

buf_lru_get_free_block

現在邏輯:

–不再判斷buffer pool是否正在進行flush lru操作,是以無需 buf_flush_wait_batch_end

—嘗試從lru上釋放一個空閑block到free list上 (會調用到buf_lru_free_from_common_lru_list),如果失敗,則喚醒page cleaner線程

buf_lru_free_from_common_lru_list

通常為使用者線程調用 (上層函數buf_lru_scan_and_free_block)來擷取一個空閑block

—原始邏輯:掃描lru深度最大為srv_lru_scan_depth

—現在邏輯:掃描lru深度最大為buf_lru_search_scan_threshold(100)

使用lru_scan_itr來進行掃描,找到一個空閑的block 加入到free list 。根據官方worklog的解釋,如果掃描了100個page都沒有clean的,那麼最好傳回做一次single page flush

繼續閱讀