天天看點

[MySQL學習]Innodb壓縮表之記憶體配置設定/回收A.配置設定記憶體B.回收記憶體

最近看到yoshinori matsunobu在官方buglist上送出的一個bug#68077,大意是說,當使用壓縮表時,在bp吃緊時,存在過度碎片合并的情況。innodb壓縮表由于存在不同的page size,是以使用buddy allocator的方式進行記憶體配置設定,他的記憶體塊來自于buffer pool中。

如bug#68077所提到的,如果我們使用的全部是4kb的記憶體塊,那麼把他們合并成8k的又有什麼意義呢?并且在碎片整理的過程中,函數buf_buddy_free

的效率是很低的。在之前已經碰到過很多類似的案例,例如drop table,在周遊block,釋放adaptive hash index記錄時,可能會在這裡hang住。

但從marko的角度來看,由于記憶體是從buffer pool中配置設定的,如果不做碎片合并的話,很容易導緻大量的4kb的頁面存留在記憶體中,這對非壓縮表而言是不可用的;如果你釋放了4*n*4k的壓縮頁記憶體,那在buffer pool中就有n個16kb的block是可以為非壓縮表所用的。

不過合并成8kb的block對隻使用4kb和16kb的場景用處并不大,在回收記憶體時,也存在時間複雜度較高的情況

簡單看看buddy allocator如何配置設定和回收記憶體。

接口函數:buf_buddy_alloc

參數描述:

buf_pool_t* buf_pool //目前page所在的bp對象

ulint size  //壓縮頁需要的記憶體大小

ibool* lru  //表示是否是從bp->lru上配置設定的記憶體

通過size,計算出其在buf_pool->zip_free[]數組中的slot(buf_buddy_get_slot),例如1kb,對應slot 0,4kb對應slot 2

具體記憶體配置設定實作函數是buf_buddy_alloc_low,注意調用這個函數需要持有bp->mutex,并且不可持有bp->zip_mutex或者任何block->mutex。記憶體配置設定的方式也是典型的binary buddy allocator 算法

1.首先嘗試從buf_pool->zip_free[]數組中查找(buf_buddy_alloc_zip),優先從buddy system中配置設定block

>>根據slot,檢視bp->zip_free對應數組元素連結清單,如果有的話,則将其從zip_free中移除,以占用這個block(buf_buddy_remove_from_free)

>>如果沒有對應slot的空閑塊,則查找更大的空閑塊,例如,如果沒有4kb的空閑塊,我們就去看有沒有8kb的空閑塊,這裡采用遞歸調用buf_buddy_alloc_zip(buf_pool, i + 1)的方法來傳回block。

雖然遞歸算法存在效率問題,不過幸好這裡的遞歸深度不會很大,最多4層調用

例如,我們申請4kb的記憶體塊,這裡我們獲得一個8kb的記憶體塊,隻将低位的拿來用,而高位的4kb則放到bp->zip_free連結清單上,狀态設定為buf_block_zip_free

如果我們申請的是2kb的記憶體塊,目前隻有8kb的空閑塊,那麼會産生一個新的4kb的和2kb的空閑塊

這種情況下,我們可以直接傳回獲得的block首位址;

2.如果bp->zip_free上沒有空閑塊,就從bp->free上取一個block(buf_lru_get_free_only)

如果buffer pool沒有的話,就調用buf_lru_get_free_block,獲得的block的狀态被設定為buf_block_ready_for_use

這裡存在的困惑是,buf_lru_get_free_block同樣也會調用buf_lru_get_free_only,因為它是一個通用的擷取空閑塊的函數,同樣也會先去看看free list上有沒有空閑塊,這裡似乎存在着重複調用,盡管這對性能影響不大。

3.當我們從lru或free list上得到一個空閑塊後,就:

首先将其狀态設定為buf_block_memory,再将其插入到bp->zip_hash中(buf_buddy_block_register)

再從其中首位址取得想要的記憶體塊大小,剩下的進行切分,放到bp->zip_free中(buf_buddy_alloc_from)

接口函數:buf_buddy_free

參數描述

buf_pool_t* buf_pool  //目前page所在的bp對象

void* buf   //将被釋放的塊首位址

ulint size  //塊大小

相對于配置設定,回收記憶體要複雜一些,因為這裡還涉及到記憶體整理。回收記憶體具體函數為buf_buddy_free_low,由于函數中存在大量的goto,以下按照正常思維的邏輯,而非代碼的至上而下分析:

首先,判斷是否需要進行碎片整理:

1.如果相同大小的碎片數小于16,不進行整理(ut_list_get_len(buf_pool->zip_free[i]) < 16);

2.看看相鄰的block(buf_buddy_get),稱為buddy

如果這個block被标記為buf_block_zip_free,會周遊目前slot對應的bp->zip_free,看看buddy是不是在其中。

這一步的目的實際上是為了确認鄰居block和目前要釋放的block是否是同樣大小的,但不幸的是,這裡采用周遊bp->zip_free的方法,複雜度為o(n)。如果zip_free連結清單很長的話,顯然開銷是不可接受的。

如果鄰居節點可以用來合并,那就将其從zip_free中移除(buf_buddy_remove_from_free),并繼續去嘗試合并;

例如,我們要釋放2kb的block,發現鄰近有個空閑2kb的,合并成4kb後,如果發現鄰近還有個4kb的,就會去合并成8kb的block。

當合并成16kb的block後,已經和普通非壓縮表使用的塊一樣了,就将其“還”給bp,從bp->zip_hash中删除,并将其加入到bp->free空閑連結清單中(buf_lru_block_free_non_file_page),這種情況就直接傳回,不進行下面的邏輯了。

當發現無法再合并buddy後,檢視目前slot在zip_free中是否存在空閑塊,如果有的話,則先将這個page從zip_free中移除(buf_buddy_remove_from_free),然後嘗試把buddy的資料轉移到zip_free連結清單頭(buf_buddy_relocate,在某些情況下不可以遷移,例如這個buddy是被io fix的),将其置為buf_block_zip_free,再去進行合并。如果不能塊遷移,就再把這個block加入到free list上。

以上都是比較典型的binary buddy allocator算法。但也可以看到,這裡面涉及到大量的記憶體操作和記憶體塊遷移,難怪會被人吐槽該函數的效率太低下。。。 

~~~~~~~~~~~

繼續閱讀