MySQL 8.0:无锁可扩展的 WAL 设计

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 从MySQL 8.0开始,设计了一套无锁的写log机制,其核心思路是引入recent_written,允许不同的mtr,同时并发地写Log Buffer的不同位置。

这篇文章翻译自MySQL官方文档介绍了8.0在预写式日志上实现上的修改,核心观点总结如下:

在8.0以前,为了保证flush list的顺序,redo log buffer写入过程需要加锁,无法实现并行,高并发的环境中,会同时有非常多的min-transaction(mtr)需要拷贝数据到Log Buffer,如果通过锁互斥,那么毫无疑问这里将成为明显的性能瓶颈。

为此,从MySQL 8.0开始,设计了一套无锁的写log机制,其核心思路是引入recent_written,允许不同的mtr,同时并发地写Log Buffer的不同位置。

 

1、写入RedoLog存在性能瓶颈

 

预写日志 (WAL) 是数据库最重要的组件之一,对数据文件的所有更改都记录在 WAL 中(在 InnoDB 中称为Redo日志),并且推迟将修改的页面刷新(Flushed)到磁盘的时间,同时仍然防止数据丢失。

在写入Redo日志时,数据密集型写入服务的性能受到线程同步的限制会明显下降。在具有多个 CPU 内核和快速存储设备(例如现代 SSD 磁盘)的服务器上测试性能时,这一点尤为明显。

 

 

 

 

 

我们需要一种新的设计来解决我们的客户和用户现在和将来面临的问题。通过新设计,我们希望确保它可以与现有 API 一起使用,最重要的是不会破坏 InnoDB 其余部分所依赖的契约,在这些限制条件下,这是一项具有挑战性的任务。

 

 

 

 

重做日志可以看作是一个生产者/消费者持久化队列。执行更新的用户线程可以看作是生产者,当 InnoDB 必须进行崩溃恢复时,恢复线程就是消费者。  InnoDB服务在预期内正常工作时不需要读取Redo log。

 

 

 

 

 

 

2、多线程写Redo日志的顺序问题

实现具有多个生产者的写日志可扩展模型只是问题的一部分。还有一些 InnoDB 特定的细节也需要起作用。最大的挑战是保持buffer pool中flush list上的脏页要满足按照LSN递增排布。

首先, 一个buffer pool实例维护一个flush list, 由mtr(mini transaction)负责原子的应用对物理页的修改, 因此, mtr是InnoDB对物理文件操作的最小事务单元。 redo log由mtr产生, 通常先写在mtr的cache里, 在mtr提交时, 将cache中的redo log刷入log buffer(公共buffer), 同时递增全局维护的日志编号(LSN, Log Sequence Number)。

随后Mtr负责将修改的脏页(或一列表脏页)加入到flush list上, 且满足flush list上的脏页是按照LSN递增排序的。

在8.0之前的实现中, 我们通过加内部锁log_sys_t::mutex 和 log_sys_t::flush_order_mutex 来保证flush list上页按照写log buffer 拿到的LSN有序。

 

 

 

 

因此, 8.0前的工作方式如下: 某个mtr将脏页输入flush list时, 会持有锁flush_order_mutex, 这时, 即便另外一个线程A需要添加脏页到其他bp(buffer pool)的flush list, 也必须陷入等待。 这种情况下, 这个线A程通过持有log_sys_t::mutex, 阻塞其他线程写log buffer。 单纯移除这两个锁结构, 会使得flush list中LSN递增的约束不工作

 

 

 

 

 

3、解决log buffer上的LSN可能不连续

我们还面临的第二个问题是, 由于各个事务可以交叉拷贝redolog 到 log buffer中, log buffer上的LSN可能存在空洞(如下图所示), 所以log buffer是不可以一口气flush full log buffer。

 

 

 

我们通过跟踪已经完成的写log buffer操作(如下图所示的)来解决第二个问题。

在设计上我们引入一个新的无锁数据结构(元素排列与原先log buffer对应关系如下图)。

 

 

 

 

数据结构如下图所示。 首先这是一个定长数组, 并且保证数组元素(slot)更新是原子的, 以环形形式复用已经释放的空间(所以是个环形数组啊哈)。 并启用单独的线程负责数组的遍历和空间回收, 线程在遇到空元素(empty slot)时暂停。 因此这个thread中还维护了这个数据结构中的最大可达LSN, 我们假设值为M。

 

 

 

 

 

 

我们引入了这个数据结构的两个变量: recent_written 和 recent_closed。 recent_written 维护写log buffer的完成状态, recent_written中维护的最大LSN, M表示, 所有小于这个M的LSN都已经将它的redo log写入log buffer。 而这个M也是(如果这下crash, 可能会触发的)崩溃恢复的截止位点, 同时也是下一个写log buffer操作的开始位点。

 

刷log buffer到磁盘和遍历recent_written是交由一个线程完成, 因此对log buffer的内存读写操作通过recent_written上顺序访问元素(slots)形成的屏障保证。

 

 

 

假设当前log buffer和recent_written状态如上图所示, 然后完成了一次buffer log 写, 使得log buffer状态如下图所示。

 

 

 

log buffer状态更新触发特定线程(log_writter)向后扫描recent_written, (如下图)

 

 

 

然后更新它维护的最大可达LSN值(可以保证无空洞的), 写入到变量buf_ready_for_write_lsn (这个变量顾名思义 XD)

 

 

我们还引入另一个无锁结构体的变量recent_closed, 用来完成原先log_sys_t::flush_order_mutex锁所作的工作, 用来维护flush list的LSN递增性质。 在介绍实现细节前, 我们还需要解释一些细节来, 才能清晰的阐释图和使用无锁结构维护(flush list/bp)整体的LSN单调。

 

4、使用CheckPoint保证flush list正确

那么首先, 每个bp中的flush list有专门的内部锁保护。 但是我们已经移除了了锁结构log_sys_t::flush_order_mutex, 这就使得并发写flush list的LSN递增性质保证不了。

虽然如此, flush list正确工作仍然必须满足以下两个原生约束:

  1. Checkpoint - 对于检查点推进的约束: 假设存在脏页P1, LSN = L1, 脏页P2, LSN = L2, 如果L2 > L1, 且P1脏页未落盘, 不允许刷新L2对应的脏页到磁盘。
  2. FLushing - flush list 上刷脏策略约束: 每次flush必须从oldest page(即, page对应的LSN最小)开始。 这个操作保证最早被修改的也最先从flush list更新到磁盘, 同时还向后推进checkpoint_lsn。

 

新引入的无锁结构recent_closed, 用来跟踪并发写flush list的状态, 同时也维护一个最大LSN, 我们标记为M, 满足, 小于当前LSN的脏页都已经加入到flush list中。

只有在M值与当前线程不那么远的时候, 才能将它的脏页刷flush list。 在线程将脏页写入flush list后, 更新recent_closed中的状态信息。

 

 


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
负载均衡 算法 关系型数据库
mysql的弹性扩展和负载均衡
mysql的弹性扩展和负载均衡
175 0
|
3月前
|
存储 关系型数据库 MySQL
深入解析 MySQL 中的扩展
【8月更文挑战第31天】
54 0
|
5月前
|
Ubuntu 关系型数据库 MySQL
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
77 0
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
|
6月前
|
SQL 关系型数据库 分布式数据库
rds迁移与扩展
rds迁移与扩展
109 12
|
关系型数据库 MySQL Java
|
6月前
|
存储 固态存储 关系型数据库
rds垂直扩展
rds垂直扩展
51 2
|
6月前
|
存储 监控 关系型数据库
rds迁移与扩展
rds迁移与扩展
89 4
|
Oracle 安全 关系型数据库
如何在openGauss/PostgreSQL手动清理XLOG/WAL 文件?
openGauss/PostgreSQL中的预写式日志WAL(Write Ahead Log),又名Xlog或redo log,相当于oracle的online redo log, 不同的是oracle online redo log是提前创建几组滚动使用,但在opengauss中只需要本配置参数控制WAL日志的周期,数据库会一直的创建并自动清理,但存在一些情况WAL日志未清理导致目录空间耗尽,或目录空间紧张时手动删除wal日志时,比如如何确认在非归档模式下哪些WAL日志文件可以安全删除?
946 0
|
6月前
|
算法 关系型数据库 MySQL
三高Mysql - 搭建“三高”架构之扩展与切换
三高Mysql - 搭建“三高”架构之扩展与切换
89 0
|
6月前
|
存储 关系型数据库 MySQL
MySQL中的WAL技术
MySQL中的WAL技术