云原生数据库 TDSQL-C 使用计算存储分离的架构,计算资源和存储资源解耦,可以提供PB级的存储容量供用户按需使用。而 Serverless 架构是将计算资源做到极致弹性,和购买的实例规格解耦,根据用户数据库实际的负载,自动启停,自动扩缩容,用户按使用计费。其中计算资源主要是 CCU(CPU+内存),CPU 可以由 cgroup 或者 docker 等技术限制,内存分配给数据库进程,大部分由 Buffer Pool 模块使用,目的是缓存用户数据,Buffer Pool 内存的分配与释放过程涉及用户数据的分布,搬迁,还有内核中全局资源的互斥等等。本文将详细介绍 TDSQL-C Serverless 在内核做的一系列优化,让数据库弹的更稳。
01
—
Buffer Pool 内存管理

Buffer Pool resize
mysql> SET GLOBAL innodb_buffer_pool_size=402653184;
扩容逻辑相对简单,设置新的 Buffer Pool 大小后,会计算出需要增加的 chunk 数量,为新的 chunk 分配内存,初始化 block 和 frame,然后把 block 挂到 free list 上,后续使用仍然从 free list 上分配即可。图中绿色为新分配的 chunk 以及 chunk 上初始化的 block:


主要流程如下:
持有 free_list_mutex,遍历 free list,如果 block 属于被回收的 chunk,就添加到 withdraw list 中。
如果 free list 遍历完之后,未全部回收,说明需要移动 lru list 上的 block。然后释放 free_list_mutex,尝试去 flush lru list,释放一些 block 出来。其中 scan_depth 是扫描 lru list 的长度,这里取 srv_LRU_scan_depth 和剩余需要移动的 block 数量的较大值。
持有 LRU_list_mutex,遍历 lru list,如果 block 属于被回收的 chunk,就尝试从 free list 分配一个 block,把数据移动过去。
重复上述流程,直到完成所有 block 的回收。
/* cap scan_depth with current LRU size. */scan_depth = ut_min(ut_max(buf_pool->withdraw_target -UT_LIST_GET_LEN(buf_pool->withdraw),static_cast<ulint>(srv_LRU_scan_depth)),lru_len);mutex_exit(&buf_pool->free_list_mutex);buf_flush_do_batch(buf_pool, BUF_FLUSH_LRU, scan_depth, 0, &n_flushed);buf_flush_wait_batch_end(buf_pool, BUF_FLUSH_LRU);

获取 Buffer Pool Instance 所有的 mutex。
缩容:遍历需要被释放的 chunk 中的 block, free mutex/lock。
mutex_free(&block->mutex);
rw_lock_free(&block->lock);
根据新的 chunk 数量,重新分配 reallocate buf_pool->chunks。
扩容:分配内存,init block,添加到 free list。
设置新的 Buffer Pool size。
根据扩缩容大小,超过 2 倍的话,就执行 resize buffer pool page hash 操作。
释放 Buffer Pool Instance 所有的 mutex。
其它子系统 resize hash。
02
—
瓶颈分析以及解决方案
IO 瓶颈

free/lru list mutex 瓶颈
优化方案
尝试 flush lru list,获取足够的空闲 block。和上述缩容流程中第 2 步相同。因为 IO 瓶颈已经从架构上规避,整个过程中也不会长时间持锁,相对可控。
遍历 chunk 中的 block:
如果已经在 withdraw list 中,跳过。
如果属于 free list,获取 free list mutex,移动 block 到 withdraw list,立刻释放 free list mutex。
如果属于 lru list,获取 LRU_list_mutex,尝试从 free list 分配一个 block,把数据移动过去,立刻释放 lru list mutex。
重复上述流程,直到完成所有 block 的回收。

全局锁瓶颈
回收 chunks 内存并且 free blocks mutex
分配 chunks 内存并且 init blocks
Resize Hash
const bool new_size_too_diff =srv_buf_pool_base_size > srv_buf_pool_size * 2 ||srv_buf_pool_base_size * 2 < srv_buf_pool_size;
buf_pool->page_hash/buf_pool->zip_hash
lock_sys->rec_hash/lock_sys->prdt_hash/lock_sys->prdt_page_hash;
btr_search_sys->hash_tables
dict_sys->table_hash/dict_sys->table_id_hash
优化方案
获取 buffer pool mutex 之前,为每个 chunk 申请空间,初始化所有的 blocks,并且把新分配的 block 加入 temp free list 里。
获取 buffer pool mutex。
需要为新申请的 chunks 分配空间,直接从预分配的 chunk 里把 mem 指针赋值给需要申请空间的 chunk。
把 temp free list 整个链表一起加入 buffer pool free list,O(1) 复杂度。
释放 buffer pool mutex。
获取 buffer pool mutex。
把需要 free 的 chunks 存储在 temp chunks 里。
释放 buffer pool mutex。
遍历 temp chunks,free block mutex,释放内存。
03
—
优化效果






