在5.7.5中提供了一个新功能,能够动态的对buffer pool size进行调整。
对应的changelog entry:
InnoDB: The innodb_buffer_pool_size parameter is now dynamic, allowing you to resize the buffer pool without restarting the server. The resizing operation, which involves moving pages to a new location in memory, is performed chunks. Chunk size is configurable using the new innodb_buffer_pool_chunk_size configuration option. You can monitor resizing progress using the new Innodb_buffer_pool_resize_status status variable. For more information, see Resizing the InnoDB Buffer Pool Online.
See Resizing the InnoDB Buffer Pool Online for more information.
从功能来看,当前只支持总体bp size的调整,并不支持bp instance调整,因此实现会比较简单,表是根据space id模 bp instance来选择的,如果允许动态调整bp instance,还需要处理bp实例间的迁移。
另外在resize的过程中,buffer pool是不可用的,用户请求将会被堵塞住一会,这意味着还无法做到真正的ONLINE。不过幸好这种resize操作,通常都还算比较快,偷偷在半夜业务负载低的时候操作下就行了。。。
其大约实现思路为:
0.background
buffer pool新成员变量 (buf_pool_t)
volatile ulint n_chunks | number of buffer pool chunks |
volatile ulint n_chunks_new | new number of buffer pool chunks |
buf_chunk_t* chunks_old | old buffer pool chunks to be freed after resizing buffer pool |
ulint old_size | previous pool size in pages |
ulint withdraw_target | target length of withdraw block list, when withdrawing |
UT_LIST_BASE_NODE_T(buf_page_t) withdraw; | base node of the withdraw block list. It is only used during shrinking buffer pool size, not to reuse the blocks will be removed |
新的全局变量
volatile bool buf_pool_resizing | true when resizing buffer pool is in the critical path |
volatile bool buf_pool_withdrawing | true when withdrawing buffer pool pages might cause page relocation |
volatile ulint buf_withdraw_clock; | the clock is incremented every time a pointer to a page may become obsolete; if the withdrwa clock has not changed, the pointer is still valid in buffer pool. if changed, the pointer might not be in buffer pool any more |
1.发起请求
设置变量innodb_buffer_pool_size时,触发函数innodb_buffer_pool_size_update,在必要的检查后(例如避免重复发送请求,或者resize的太小),发送信号量srv_buf_resize_event.然后立刻返回
因此设置变量成功,不等于bp 的size已经调整好了,只是出发了 一个resize请求而已.
不过我们可以通过如下status变量来监控resize的过程,例如:
root@(none) 09:47:43>set global innodb_buffer_pool_size = 42949672960; show status like ‘innodb_buffer_pool_resize_status';
Query OK, 0 rows affected (0.00 sec)
+———————————-+————————————+
| Variable_name | Value |
+———————————-+————————————+
| Innodb_buffer_pool_resize_status | Withdrawing blocks to be shrunken. |
+———————————-+————————————+
1 row in set (0.00 sec)
在新的逻辑里,所有的buffer pool内存被划分为chunk单位,每个chunk的大小默认为128M,这种方式给增加/减少bp size带来了极大的便利,使得实现更简单了。
2.执行请求
新的独立线程buf_resize_thread专门用于后台进行buffer pool的size调整,它会监听用户发出的请求事件srv_buf_resize_event
当收到resize请求后,会进行如下动作(函数buf_pool_resize)
a) 计算调整后,每个bp instance的内存大小
b)更新每个bp instance的相关变量:
buf_pool->curr_size = new_instance_size;
buf_pool->n_chunks_new = new_instance_size * UNIV_PAGE_SIZE
/ srv_buf_pool_chunk_unit;
其中srv_buf_pool_chunk_unit为只读变量,默认为,受参数innodb_buffer_pool_chunk_size控制。Resize都是以CHUNK为单位进行的。
c)禁止自适应hash
btr_search_disable()
清楚所有索引的index->search_info->ref_count,设置所有的block->index = NULL,以及清理btr_search_sys->hash_index
d)如果降低buffer pool size,则计算需要缩小的chunk数目并设置:
buf_pool->withdraw_target = withdraw_target; ——>需要缩小的block数
设置buf_pool_withdrawing为TRUE
e)当需要缩小bp size时,将withdraw_target 这么多的block转移到新的区域,需要首先搜集对应的page,到buf_pool->withdraw链表.
函数buf_pool_withdraw_blocks 的流程大概为:
(1)buf_buddy_condense_free(buf_pool);
合并所有空闲的buddy,主要是用于压缩表的.
(2)扫描buffer pool
…首先扫描free list,将需要withdraw的block(buf_block_will_withdrawn) 从free list上取出,加入到buf_pool->withdraw链表
…然后刷一次LRU LIST,以期望刷出更多的空闲Page (buf_flush_do_batch),flush LRU长度受限
…重新为withdraw区域的block/buddies分配到无需withdraw的block上.从LRU的第一个block开始
上述过程在buf_pool->withdraw长度达到buf_pool->withdraw_target时结束,是个循环重复的过程
f).完成搜集需要缩减的block加入到buf_pool->withdraw后,设置buf_pool_withdrawing为false;
注意在这之前还是允许并发负载的,但随后进入的区域是不允许用户进行任何buffer pool操作的。
g)设置buf_pool_resizing为true.
获取所有的buffer pool mutex, 即buffer pool page_hash的x lock.
h)进入增加/删除chunk阶段
…当缩小bp size时,删除多余的chunk.释放相应的资源,清空buf_pool->withdraw
…重新分配buf_pool->chunks
…如果增长bp size的话,初始化新的chunk(buf_chunk_init(buf_pool, chunk, unit)),分配新的内存区域,初始化新的block等..
…最后设置buf_pool->curr_size为新的size
i)重置每个bp instance的read_ahead_area 和curr_pool_size
j) 如果bp size的改动超过上次的一半或2倍,则重设page hash,和zip hash
调用函数buf_pool_resize_hash(buf_pool)
k)设置buf_pool_resizing为false,并释放所有的buffer pool锁, 释放老的chunk
l)如果bp size的改动超过上次的一半或2倍,则重置lock sys ,AHI及数据词典的大小
2648 /* normalize lock_sys */
2649 srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE);
2650 lock_sys_resize(srv_lock_table_size);
2651
2652 /* normalize btr_search_sys */
2653 btr_search_sys_resize(
2654 buf_pool_get_curr_size() / sizeof(void*) / 64);
2655
2656 /* normalize dict_sys */
2657 dict_resize();
m)更新change buffer的大小并开启AHI
ibuf_max_size_update(srv_change_buffer_max_size);
当然过程远没这么简单,还涉及到大量相关的代码路径,感兴趣的可以看看以下的rev:
worklog:
http://dev.mysql.com/worklog/task/?id=6117
官方文档:
http://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool-online-resize.html
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8479
http://bazaar.launchpad.net/~mysql/mysql-server/5.7/revision/8383
总的来说,这个功能没有让人感到特别惊喜,如果bp instance也能调整了,才是真正的resize,当然那样代码会复杂的很多很多。