Contention-aware lock scheduling论文

论文:Tian, B., et al. (2018). “Contention-aware lock scheduling for transactional databases.” Proceedings of the VLDB Endowment 11(5): 648-662.
该方法已被MySQL采用,并在8.0.3+被设为默认方法,应该是很成功的一个设计了。

背景

互斥锁与共享锁:锁是实现并发访问控制的常用手段,锁系统里面有两种锁:共享锁(S)和互斥锁(X)。在事务读一个对象前,需要先获得共享锁,同样在事务写或者更新一个对象前,需要获得互斥锁。只要当对象没有被加互斥锁时,就可以给其加共享锁,而互斥锁需要保证对象上没有任何锁才能加。

两阶段锁协议(2PL):一个事务会经历加锁阶段和释放锁阶段。在加锁阶段不会释放任何锁,等事务执行结束,会进入释放锁阶段,因此在释放锁阶段不会加任何锁。

在这里插入图片描述

依赖图:图中的顶点有两种,一种是事务T,一种是对象O;图中的边有两种有向边,一种是T指向O的,就是T在等待O上面的锁,一种是O指向T的,是指T已经拥有了O上面的锁;每条边有自己的标签,表明是共享锁还是互斥锁。假设不存在死锁(这个有由死锁检测模块处理,不在本文考虑范围之内),因此本文涉及的依赖图都是有向无环图(DAG)。

锁调度问题:给定一个依赖图G,当一个事务T释放对象O上的一个锁(S或X),将这个锁调度给正在请求该锁的事务,使得所有事务的平均时延(执行时间+等待时间)最小。作者证明了这是个NP-hard的问题,即使假设所有事务拥有相同的执行时间并且这些事务未来不会请求更多的锁。导致这个问题变得复杂的重要原因是共享锁的存在。

本文提出的算法能以常数因子接近最优调度。

方法

本文所谓的竞争感知调度是指,能够根据事务对整个系统的竞争产生的影响(贡献)来确定事务优先级。

竞争的程度:并发访问冲突锁的事务的个数。

如果某个事务持有某个热点数据的锁,而该事务被其他锁阻塞,这会导致这个事务会阻塞比较多的事务,从而增加系统的竞争程度。

Most Locks First(MLF)

在这里插入图片描述

一个事务持有的锁越多,其优先级越高。这个启发式想法是考虑拥有越多的锁就越可能阻塞其它的事务。但是这些锁可能是一些非热点对象,此时提高其优先级并不会降低系统的竞争程度。如图1所示,事务t1持有更多的锁,但是由于是非热点数据,其阻塞的事务并不多,这时候将对象O1的锁给事务t2更好。

Most Blocking Locks First (MBLF)

在这里插入图片描述

一个事务拥有的阻塞其他事务的锁越多,优先级越高。如图2所示,这种情况下,事务t1拥有1个阻塞其它事务的锁,事务t2拥有2个阻塞其他事务的锁,因此该策略会将锁分配给t2。然而t1阻塞的事务t3却阻塞了3个事务,将锁分配给t2而不是t1会导致整个系统更长的等待时间。

Deepest Dependency First (DDF)

在这里插入图片描述
根据对象O1的依赖子图的深度来确定优先级,越深优先级越高。如图3,由于事务t1所在的依赖子图深度为3,大于事务t2的依赖子图深度2,因此将锁分配给t1,但是将锁分配给t2等t2执行完成后,依赖t2的两个事务可以并发执行。如果依赖t2的事务多于2个,将分配给t2会更好点。

Largest-Dependency-Set-First (LDSF)

在这里插入图片描述

这个是本文提出的算法,即考虑整个依赖子图的事务个数(深度和广度兼顾),如图4,左边的依赖子图有5个事务,大于右边子图3个,因此将锁分配给t1。更加形式化这个算法,假设对于一个对象O,有m个事务需要它的共享锁,有n个事务需要它的互斥锁。对于共享锁的事务,这个算法会将这m个共享锁的依赖子图大小加起来,记作 τ ( T i ) = ∣ ⋃ i = 1 m g ( t i i ) ∣ \tau\left(T^{i}\right)=\left|\bigcup_{i=1}^{m} g\left(t_{i}^{i}\right)\right| τ(Ti)=i=1mg(tii);对于互斥锁的事务,会分别计算每个依赖子图的大小,并找出最大的一个 ∣ g ( t x ^ ) ∣ = max ⁡ t i x ∈ T x ∣ g ( t i x ) ∣ \left|g\left(\hat{t^{x}}\right)\right|=\max _{t_{i}^{x} \in T^{x}}\left|g\left(t_{i}^{x}\right)\right| g(tx^)=maxtixTxg(tix)。如果 τ ( T i ) \tau\left(T^{i}\right) τ(Ti) ∣ g ( t x ^ ) ∣ \left|g\left(\hat{t^{x}}\right)\right| g(tx^)大,那么将锁分配给所有等待共享锁的事务,否则将锁给依赖子图最大的那个等待互斥锁的事务。伪代码如下:

在这里插入图片描述

显然这个算法在没有共享锁时,能保证平均时延最短。但是在有共享锁的时候情况就不一样了,因为共享锁可能被多个事务同时持有,这时候这些持有共享锁的事务如果等待互斥锁的话,那么将锁给这些持有共享锁的事务中的任何一个都不能直接对整个系统的竞争程度做出贡献,因为需要持有共享锁的事务全部执行完才能真正释放共享锁。

在这里插入图片描述

如图5所示,如果我们将事务t1在依赖图中能到达的并且被正在执行的事务(t2, t3, t4)持有的对象o1和o2作为事务t1的关键对象。t5在等待o1的互斥锁,而t2和t3共同持有o1的共享锁,这时候如果t2在请求某个对象的锁,并且计算出t2的优先级较高,这时候将锁分配给t2其实不能直接能解开O1导致的阻塞,需要t3也执行完才会释放o1的共享锁,因此该场景下无法保证平均时延最低。虽然按照LDSF算法,t2和t3是同时被分配共享锁的,但是持有共享锁的事务越多,就越可能出现拖后腿的(执行最慢的那个事务),因此情况就比较复杂。本文证明,如果等待共享锁的情况能够给个范围限制,LDSF算法能以一个常熟系数接近最优。

因此,一种改进的方法是,不一次性全部分配所有共享锁,而只是分配其中一部分最大子图的共享锁,其它共享锁等下次再分配,这就是bLDSF算法。

batch Largest-Dependency-Set-First bLDSF

假设将所有等待同一个共享锁的事务的剩余执行时间(Remaining time)当作随机数 R i r e m R^{rem}_i Rirem ,定义
R m a x , m r e m = m a x { R 1 r e m , . . . , R m r e m } R_{max,m}^{rem} = max\{R_1^{rem}, ... , R_m^{rem} \} Rmax,mrem=max{R1rem,...,Rmrem}
R m a x , m r e m R_{max,m}^{rem} Rmax,mrem 为m个随机数的最大值,如果这些事务同时得到这个共享锁,那么等待这个对象互斥锁的事务就需要等待最后一个持有共享锁的事务执行完,显然这个时间就是 R m a x , m r e m R_{max,m}^{rem} Rmax,mrem,而它本身也是随机数。记其期望值为 E [ R m a x , m r e m ] E[R_{max,m}^{rem}] E[Rmax,mrem] 。假设这m个随机数具有相同的范围,记 R r e m R^{rem} Rrem 的期望值为 E [ R r e m ] E[R^{rem}] E[Rrem] 。只要随机数 R i r e m R^{rem}_i Rirem的方差不为零,当m增大时, E [ R m a x , m r e m ] E[R_{max,m}^{rem}] E[Rmax,mrem]也会增大,通俗点说,就是当一次性分配给越多事务共享锁,stragglers问题就越严重。因此我们定义一个拖延系数 f ( m ) = E [ R m a x , m r e m ] E [ R r e m ] f(m)= \frac{E[R_{max,m}^{rem}]}{E[R^{rem}]} f(m)=E[Rrem]E[Rmax,mrem] 来量化这个这个问题。

在这里插入图片描述

如图6所示,设f(3)=2,如果是LSDF的方案,先将共享锁同时分配给t1,t2和t3,那么t4所在的子图需要等待2R的时间,t4所在子图的所有事务总的需要等待10R。而bLSDF的方案是,先将共享锁分配给t1,然后再分配给t4,最后再分配给t2和t3,那么总的需要等待9R(t2和t3一共等待4R,t4等待5R),因此该方案比LSDF更优。

更加形式化的定义bLSDF: 我将作者的表述根据我的理解加以更改,用收益/代价的比值(性价比)这个来量化到底该如何做出选择。收益我定义为将锁给某个事务能解锁的子图的大小,而代价则是这个事务所执行的时间。对于等待互斥锁的事务来说,分配给它的代价(即执行时间)就是R,而对于共享锁的事务,则是f(m) * R,其中m是同时分配共享锁的事务数量。这样,在选择分配共享锁时,需要找到性价比最高的事务子集,将它的性价比与等待互斥锁的相比较,谁的性价比高优先级就高。如果性价比一样,则优先给共享锁的事务,从而加大系统的并发程度。

显然,bLSDF算法需要依赖拖延系数f(m),这个系数现有工作已经总结了一些常见分布的拖延系数,虽然不知道事务的执行时间分布,不过作者表明,在事务数量不多的时候,不同分布的拖延系数差距不会太大,本文使用的是 f ( k ) = l o g 2 ( 1 + k ) f(k) = log_2(1+k) f(k)=log2(1+k)。伪代码如下

在这里插入图片描述

实现

首先是如何跟踪事务的依赖图的大小,由于依赖图是DAG而非树形结构,另外实时搜素每个事务的依赖图以及为所有事务存储依赖图集合,都是开销比较大的操作。因此本文采用近似的方法:如果事务t不含有阻塞其它事务的锁,则|g(t)|=1,否则用 T t T_t Tt表示所有等待被事务t持有的对象锁的事务结合, ∣ g ( t ) ∣ ≈ ∑ t ′ ∈ T t ∣ g ( t ′ ) ∣ + 1 |g(t)| \approx \sum_{t^{\prime} \in T_{t}}\left|g\left(t^{\prime}\right)\right|+1 g(t)tTtg(t)+1 这里之所以是约等于,是因为可能存在公共子节点。如图7所示,t1的依赖子图大小应该是4,但是逐层计算时,t2和t3由于持有相同的共享锁,导致t4被重复算了一次,得到t1的依赖子图为5。

在这里插入图片描述

另一个挑战就是在bLDSF算法中寻找性价比最高的等待共享锁的事务集合。本文也是采用比较贪婪的算法得到:对所有共享事务按照依赖子图大小降序排序,然后计算前k个事务的性价比,得到一个最优的 k ∗ k^* k 使得性价比最高。

避免饥饿:在Mysql的FIFO中,使用的是如下策略:当队列中出现一个互斥锁,则互斥锁之后到来的共享锁不会被分配锁,从而避免互斥锁饿死。在本文算法实现方案中,则是设置一个barrier,barrier之后到的事务不被考虑进去,只有barrier之前的事务全部执行完,才考虑barrier之后的事务。作者还介绍两外两种实现方案:比如加入事务的年龄,或者当事务等待时间超过某个阈值则强行增加其依赖子图大小为足够大的数。

空间开销:LSDF和bLSDF都需要维护每个事务的依赖集合的近似大小,因此复杂度为O(|T|)。

时间复杂度:对于FIFO来说,会将所有对于同一个对象的锁请求放入一个链表中(包括已分配的和未分配的)。当一个事务释放了该对象的锁,则扫描这个链表找到未分配锁的请求。对于每个这些未分配锁的请求,会再次扫描链表看是否与链表中已分配的锁冲突。因此假设在一个对象上有N个锁请求,则FIFO复杂度为 O ( N 2 ) O(N^2) O(N2)。LDFS和bLDSF都会使用同样的方法找到未分配锁的并且不冲突的请求,因此时间复杂度是 O ( N 2 ) O(N^2) O(N2)。对于bLSDF,还需要对事务进行排序需要 O ( N l o g N ) O(NlogN) O(NlogN)的复杂度,因此总的复杂度还是 O ( N 2 ) O(N^2) O(N2)

结果

实验环境:MySQL 5.7 , 单机配置5GB的buffer pool。

实验方法:使用OLTP-Bench tool 来跑TPC-C workload。因为OLTP-Bench 以固定的速率生成事务,作者将每秒发出的事务数控制在安全范围内,以防止MySQL陷入混乱状态。整个实验过程中,相对于测试中庞大的事务数量,死锁的数量可以忽略不计。

TPC-C workload:使用32-warehouse 的配置。为了模拟不同程度的竞争系统,作者通过控制以下两个参数来实现:(i)clients的数量。(ii)吞吐量(每秒发送事务的数量)。每个客户端线程在上一个事务结束后会提交新事务。通过控制客户端的数量来控制并发程度,通过固定客户端线程的发送速率来控制吞吐。

Microbenchmark:创建一个只有2000条记录的数据库,客户端发送的事务包含5个查询,从选择型查询(共享锁)和更新型查询(互斥锁)随机选。表中被访问的记录遵循Zipfian分布。为了模拟不同程度的竞争系统,作者通过控制以下两个参数:zipf 分布的参数(倾斜度),互斥锁的比例(即更新型查询的比例)。

对比方法:

  • First In First Out (FIFO):根据排队先后确定队列优先级,遇到互斥锁,后面的共享锁就被阻塞防止互斥锁饿死。
  • Variance-Aware Transaction Scheduling (VATS):根据age大小确定队列优先级,同样遇到互斥锁就不再分配共享锁。该算法目的是要最小化事务延迟的方差,与FIFO不同的是,该age是事务到达系统的时间开始算,而不是开始请求锁的时间。
  • Largest Dependency Set First (LDSF):本文提出的,根据依赖子图的大小确定优先级, 一次性分配所有共享锁。
  • Most Locks First (MLF):拥有最多的锁的事务优先级最高。
  • Most Blocking Locks First (MBLF):拥有最多的阻塞其它事务的锁的事务优先级最高。
  • Deepest Dependency First (DDF):拥有子图最深的事务优先级最高。

实验结果就是各种好,没啥好说的。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

相关工作

总的来说,现有的事务调度算法并不适合数据库锁调度的场景,因为锁的协议限制(共享锁、互斥锁等)。虽然已有一些工作用于实时数据库的锁调度,但是他们目标是支持显示ddl的而不是最小化事务的平均时延。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值