2010VLDB-(如何让计算向数据移动)Data orient

2021-05-06  本文已影响0人  Caucher

标题:面向数据的事务处理
一篇研究事务领域并行处理的论文。

ABSTRACT

并行环境下的事务处理,竞争的临界区成为扩展性的问题,这其中,中心化的锁管理器(下简称全局锁)成为性能瓶颈。
我们定位到传统的thread-to-transaction分配策略是竞争的主要原因。
本文设计的DORA系统,将事务拆解成Actions,把Action根据其访问的数据分配到各个线程去,这让每个线程访问的数据绝大多数都是thread-local的数据,减少和全局锁的交互。
DORA维护了所有的ACID特性,提高了4.8x的吞吐量。

1. INTRODUCTION

事务处理系统里的临界区太多了,严重地拖慢了多核环境下的系统性能。

1.1 Thread-to-transaction vs. thread-to-data

传统事务处理系统出现问题的主要原因就是数据访问的不协调。事务被任意分配给一些线程,然而事务处理临界区又多,每个事务执行时间又很短,因此同步问题会随着并行度增加而放大。下面两张实验图,可以说明临界区在并行处理时的瓶颈问题。


image.png image.png

3. THE LOCK MANAGER AS SOURCE OF CONTENTION

一句话概括:锁上的线程请求很多,加上执行事务和竞争锁的线程数量的增长,共同造成了性能的退化。
如下图,系统负载增加的时候,大部分时间都花在了竞争锁上。


image.png

4. A DATA-ORIENTED ARCHITECTURE FOR OLTP

4.1 Design overview

DORA的功能主要分成三个部分:

  1. 把工作线程绑定到不相交的数据子集上;
  2. 把每个事务都分布到多个工作线程上(根据需要的数据);
  3. 尽可能避免了和中心化的锁管理器交互。

4.1.1 Binding threads to data

这一部分主要介绍几个概念:

routing rules由DORA资源管理器动态维护,这些规则会在运行时阶段性更新以负载均衡。分配给每个表的executor数量也根据负载和size各不相同。

4.1.2 Transaction flow graphs

4.1.3 Executing requests

每个Executor要负责处理很多Actions,关键之处在于要保持隔离性,对有冲突的actions进行排序。
DORA Executor包含三个内部数据结构:

Actions按到达顺序执行,Executor利用本地锁表对冲突进行检测,在action identifier的级别进行冲突解决(进入锁表的是action identifiers)。
本地锁只有两种模式:共享和互斥。而由于identifier通常是primary key的子集,本地锁的模式更类似于前缀锁。
总体来说,本地的事务处理就是串行化处理的。每个Action一旦拿到了本地锁,直到action所属的整个事务结束才会释放。事务结束后,所有的action才会被放进完成action队列里,action占有的lock也会被清除。后续被阻塞的action可以进行占用。

4.2 Challenges

4.2.1 Record inserts and deletes

数据更新只需要本地锁即可,但是数据的插入和删除仍然需要全局锁。原因就是物理冲突(不访问同一个数据,但访问了同一个位置),举个例子:

  1. 事务A找到记录R1,删除之;
  2. 事务B发现R1的位置空出来了,在原位置上插入了一条新Record R2;
  3. 此时A abort,想要回滚会失败,因为R1的位置没法再写回了。

行级锁可以处理这个问题,insert和delete时会对RID(和对应的物理slot)进行加锁。这里就用到了全局锁。实际上,行级锁一般不会造成过度的竞争。

4.2.2 Secondary Actions

刚才提到了一个问题,对于一些访问二级索引的Action,不会涉及到routing fields,那么就不知道这些Action具体要访问哪个我们逻辑上划分好的dataset,从而不知道把这个Action分给哪个executor。
为了解决这个问题,我们在被访问的二级索引上加上覆盖索引(cover index)。即在叶子节点上的每个entry中,补上routing fields的值,这样在访问二级索引时,就可以知道routing field是多少,从而分配给对应的executor。
那么谁来访问这些二级索引,以决定Executor分配呢?前一个phase的RVP执行线程。如果是第一个phase,那么就是接受请求的dispatcher线程。注意到访问二级索引的线程对于绑定数据来说是任意的,也可能不绑定任意数据。

至目前为止,对于insert和update的workload由于Executor的序列化执行可以做到隔离性。但是delete操作仍然容易出现问题,考虑以下场景:

  1. 事务A在主索引上删除了Record R1,在二级索引上也删除了对应的entry;
  2. 事务B访问二级索引该entry,返回为null;
  3. 事务A abort, rollback,此时事务B没有做到隔离性,Read uncommited。

为了解决该问题,我们将二级索引的删除滞后,删除事务只先删主索引,然后提交,之后再去给二级索引打一个Delete flag。
也就是说无论在删除事务执行的任意时刻,访问二级索引的事务都可以访问到对应的entry,然后其被要求访问主索引,发现record已经被删除了,或者正在被删除(行级锁,释放后可以看到最终结果)。事务执行后的一段时间内,二级索引对应的entry被打上Delete flag,在叶子节点合并时机进行垃圾清理。

4.2.3 Deadlock Detection

local lock table可能会block,因此底层存储引擎会开放一个死锁检测器接口提供给DORA使用。
然而DORA在设计上尽量避免了死锁。对于某个transaction,一个phase最后一个action的提交,会按顺序latch住所有的下一个phase的所有并行执行的executor的incoming queues。然后将下一phase的所有actions逐个进队,进队后释放latch,把这个过程做成一个原子性的。
注意到所说的顺序实际上是事务提交时,各个语句的顺序。各个语句被组合成Action,那么Action也是有顺序的,对应的executor也是有顺序的,即使会并行执行。
基于这样的设计,两个拥有同样事务流图的事物,不会造成死锁。因为executor的申请是原子性的,会先满足一个事务的所有需求,然后另一个事务会在第一个phase就阻塞掉,直到前一个事务彻底完成。

5. PERFORMANCE EVALUATION

5.1 Experimental setup & workloads

5.2 Eliminating the lock manager contention

可以看到,由于弱化了对中心化锁管理器的需求,DORA即使在高负载的情况下,也可以将主要CPU用于执行事务本身,而非竞争锁。


image.png

下图量化了在三种workload下,DORA和baseline对锁的请求量。DORA对锁的请求主要是thread-local锁表,对行级锁和全局锁的需求量很小。


image.png
下图研究了DORA和BaseLine的扩展性,虽然CPU利用率的升高,吞吐量(TPS)的变化。对于像TM1这样的小查询,baseline中对锁的竞争量非常高,严重影响了吞吐量;对于后两种,baseline的情况就好一些。一旦负载量超过CPU100%,baseline的吞吐都受到了影响,因为抢占的出现,很多临界区的代码被迫暂停,产生了这样的问题。对于DORA,则没出现严重的退化,主要由于对于锁的竞争没有那么严重。
image.png
image.png

5.3 Intra-transaction parallelism

DORA除了减少了对全局锁的过度依赖,实际上,也充分发挥了事务执行过程中的并行性,这样在CPU负载不满的情况下,加快了事务的响应速度。


image.png

5.4 Maximum throughput

经过合适的调优,测试DORA和baseline在不同Workload下的极限吞吐量(每CPU利用率)。总的来说,测试集对全局锁竞争越多,DORA提升越大。


image.png
上一篇下一篇

猜你喜欢

热点阅读