(CMU15-721) An Evaluation of Concurrency Control with One Thousand Cores 论文阅读笔记

https://github.com/AlexanderChiuluvB/db-learning-material
欢迎star! 我做的数据库学习资料整理

摘要

CPU核数的增多,给并发控制带来了巨大的压力。

该论文实验环境为1024核,并且尝试了七种并发控制的方法,但都失败了。

启示我们,面对多核芯片应该要重新设计数据库的架构

导论

现如今的CPU速度提升往往是靠增大核数,而不是提高单核的处理能力。那么对应的给数据库的并发控制带来了非常大的压力,性能的瓶颈在于多个线程之间的竞争与调度。

本文的主要贡献:

  • 对于7种并发控制策略扩展性的讨论
  • 第一篇在1000核上的OLTP DBMS性能测量
  • 分析并发控制策略的瓶颈

并发控制策略

可以分为两大类:

  • 2PL Two Phase-Locking

本质是一种悲观锁策略。

事务进行读操作之前要尝试获得读锁,写操作之前要获得死锁。

1.不同事务不能同时拥有冲突锁。
2.如果一个事务开始
在同一元素上读锁与写锁冲突,写锁与写锁冲突。
2PL中的两个阶段介绍: 第一阶段,允许事务获得需要的任意数量的锁,不能释放锁。当事务释放锁时,它进入第二个阶段,此时禁止获取其他锁。当事务终止时(通过提交或异常中止),所有剩余的锁将自动释放。

2PL是悲观锁思想。如果一个事务不能为一个元素获取锁,那么它将被迫等待,直到锁变得可用。那么DBMS就会有产生死锁的可能性。因此,2PL的各种变体之间的主要区别在于它们如何处理死锁,以及在检测到死锁时所采取的操作。

  • TO Timestamp Ordering

每一个事务都会分配一个单调递增的时间戳来解决冲突

而具体的细节不同在于:解决冲突的粒度与何时解决冲突(是在事务运行时候还是在结束时候)

具体可以细分成七种来讨论:
(CMU15-721) An Evaluation of Concurrency Control with One Thousand Cores 论文阅读笔记_第1张图片

  • 2PL with 死锁探测
    DBMS会把事务组织成一张图,然后会有一个中心化的死锁探测器会检测图是否有环。如果有的话,这个探测器会根据每个事务所拥有的资源来综合选择一个需要停止的事务,目的是尽可能缩短事务重启的时间。

  • 2PL 死锁预防(No waiting)
    当某个事务尝试抢锁失败之后,会立马中止事务,不会等待去尝试获得锁。

  • 2PL 死锁预防 (Waiting)
    NO_WAIT方案技术的一种非抢占式(non-preemptive)变体,允许事务等待持有所需锁的事务(如果该事务比持有锁的事务更早)。如果请求的事务时间戳较小,则它将被中止并重新启动。每个事务在执行之前都需要获得一个时间戳,而时间戳排序保证不会发生死锁。

  • Basic T/O (TIMESTAMP)
    每一个事务在对数据库的元组进行读写的时候,会与上一个事务的时间戳进行比较。如果当前事务的时间戳小于上一个事务时间戳就会被拒绝执行。

  • MVCC
    每个写请求都会产生一个元组的新版本。读请求的时候会决定读取那个版本的数据。保证了所有操作都是串行化组织的
    不会拒绝读请求

  • OCC
    在事务提交的阶段去解决冲突

  • T/O with Partition-level Locking(H-STORE)
    数据库分成不相交的部分,每一个部分都有一个锁和一个独占线程,事务需要获得它访问的所有部分的锁。每个分区都会维护一个队列,事务请求到达的时候,会给事务分配一个时间戳,并且会把事务添加到所有分区的队列中。然后每次需要从该分区进行操作的时候,从队列取出一个时间戳最小的事务来操作。

测量指标

  • 事务执行时间

  • 事务被放弃而产生的回滚开销

  • 时间戳分配花费时间

  • 索引定位时间

  • 事务所要等待的时间

  • lock manager和timestamp manager花费时间

论文所做优化

General Optimization

内存分配

malloc,TcMalloc(Thread cached malloc), jemalloc 效果都不好
论文自己实现了一个malloc,也是每个线程分配一个内存池,不过会根据workload动态调整大小。

Lock Table

没有使用中心化的锁表,每个事务只用锁住操作的元组

Mutexes

对于2PL,死锁检测器的互斥锁是瓶颈
对于T/O,分配时间戳的互斥锁是瓶颈

2PL所做的优化

死锁探测

使得维护wait-for无锁化

锁的抖动

事务在提交之前都会一直持有锁,使得其他事务一直阻塞

等待vs丢弃事务

给每个事务一个时间阈值,如果事务等待时间超过了就丢弃事务

T/O所做的优化

核心是给每个事务分配一个唯一的时间戳,如果用mutex来做吞吐量会很受影响

解决方法:atomic addition 原子加,但是效率还是不行

atomic addition batching

时间戳管理器一次返回多个时间戳

CPU clocks

通过每一个核的线程ID+逻辑时钟构成时间戳
但需要支持原子时钟的CPU

hardware counters

CPU需要支持硬件层面上内置单周期内自增时间戳,但是现在没有任何一款CPU支持这个功能

Discussion

实验结果

1.只读场景:

DL_DETECT 和NO_WAIT 都没有contention,所以不会 abort,效率高。
WAIT_DIE 和 MVCC 因为 allocate timestamp为时间瓶颈
OCC 和 TIMESTAMP 因为要复制元组保证重复读,所以性能低。

2.只写场景:
DL_DETECT效率不好,因为wait for graph出现了大量的lock thrashing。NO_WAIT是最scalable的因为取消了等待。
NO_WAIT WAIT_DIE 有很高的abort 概率,但是因为一般等待的时间比重启的时间要更长,所以效果仍然是比较好的。(但是现实里,可能多个table index等需要回滚,所以效果可能没有试验中那么好)
MVCC 比较好,因为多版本能够为 时间戳较小的读操作提供服务。
OCC 因为每个事务都必须在冲突解决之前完成,所以被abort的事务仍然需要花费时间。

在high contention下。

NO_WAIT 持有锁时间较短。
OCC 至少可以提交一个ts,所以性能要好一些

3.读写混合

TIMESTAMP 和 OCC 要表现不好因为需要读数据的时候进行复制
MVCC 在 write request 较少时性能较高,因为不阻塞读操作

DBMS瓶颈分析

实验结果说明现有的几种并发控制方法面对多核CPU的scalability非常的差

瓶颈主要可以分为:

  • lock thrashing
  • 抢占式事务abort
  • 死锁
  • 时间戳分配
  • 内存间复制

lock thrashing发生在需要事务等待的策略中,可以通过主动中止事务来缓解
(无等待的的死锁预防就比需要等待的算法要性能好)

比较理想的做法是结合多种并发策略:

如在竞争没那么激烈的情况下可以用带等待的死锁检测并发控制,如果事务因为thrashing而导致等待时间过长的话,可以切换回使用无等待和基于时间戳的算法。

像MySQL实际上就利用了 DL_DETECT + MVCC的策略,只读事务使用的是MVCC,其他事务使用2PL.

下面给出多种并发策略的瓶颈:
(CMU15-721) An Evaluation of Concurrency Control with One Thousand Cores 论文阅读笔记_第2张图片
论文提到,未来CPU的趋势是转变为去中心化或者分层的内存控制管理以加快内存分配的速度

论文最后还提到了分布式数据库会带来的额外开销:分布式事务。
节点之间的协调是通过网络进行的,通常这个网络通信过程会成为性能的瓶颈。
比较理想的做法是节点间采用该shared-nothing架构(各节点无状态,session统一保存在数据库+缓存),然后每个节点采用multi-threaded shared-memory的DBMS。

ref

https://blog.csdn.net/maxsel/article/details/104685097/

你可能感兴趣的:((CMU15-721) An Evaluation of Concurrency Control with One Thousand Cores 论文阅读笔记)