速记一发PolarDB-MP的paper
有几个和他架构类似的系统,属于是shared memory的:
* Oracle RAC
* IBM pureScale
* Aurora MM
* Taurus MM
和TaurusMM的区别是,TaurusMM的数据同步是通过共享存储的page server + log replay做的,而PolarDB则是在此之上搞了一个共享的buffer pool
Transaction Fusion
PolarDB-MP这一套的并发控制是做在PolarDB-X之上的,为了支持全局的snapshot,commit事务的时候,会取一次tso,然后把commit timestamp标记到事务上。这里他们没有回写每一行数据,所以在做可见性判断的时候,就需要根据事务ID,查找事务对应的commit ts
每个节点都有一个本地的事务状态表,他们设计了一个(我个人感觉应该是无锁的)数据结构,用来存储事务id到事务状态的映射。事务状态包含了事务的commit timestamp,以及是否有其他事务在等锁这个信息
事务id是包含了node id,隐含了可能不支持动态拓展节点的能力,或者还有一层映射分配node id和具体的机器
当事务全局可见的时候,比如所有的snapshot对当前事务都可见,那么他的旧版本就可以回收了,此时也就不需要TIT中的内容了,可以安全的做回收。后续其他的请求来查找TIT表的时候,如果发现不存在,则一定可见。
(不过这里貌似没有做读取TIT表的优化,毕竟每次都去远端读一个空值还是有一些开销的)
Buffer Fusion
共享的,分布式的buffer pool,叫做DBP,distrubted buffer pool。可以看作是把page的上锁放锁放到buffer pool了,和MESI也是类似的
每个local buffer pool里记录的addr和valid,如果valid,那么就可以one-sided RDMA直接访问r_addr的地址,从而获取到数据
DBP负责做page之间的协调。比如有新的版本的page下刷到DBP了,他会负责把其他的副本invalid掉。
然后再下刷脏页到DBP之前,会要求把对应的log下刷到storage层,保证如果DBP的数据丢了,也可以通过log恢复
Lock Fusion
包含Page lock和Row lock
page lock是node级别负责管理的,每个节点负责追踪他持有的page lock。当node请求一个page lock的时候,首先看他本地的lock manager里是否已经有这样的锁了,如果没有,则会去请求lock fusion,lock fusion负责检查冲突,如果有冲突就等待,否则就会准入
为了利用局部性,page lock放锁的时候会lazy的放锁,即如果没有其他人请求这个page lock的话,他会在本地持有这个锁,而如果有其他人在等待的话,才会通知lock fusion
为了保证持锁可以读到最新的数据,在释放x锁后,如果对page有更新,则会把更新push到DBP,把目前的page invalid掉。后续的page请求就会从dbp中读取到最新的数据了
有关row lock,polardb只实现的x row lock,就是写到page里的锁。lock fusion中管理的wait-for的信息。对于每一行,上面记录了目前修改他的事务ID(其实就是未获取commit ts的时候)
检查持锁信息的话,就直接读最新的数据,然后去对应的TIT上看这个事务是否活跃即可。为了避免对TIT的频繁访问,PolarDB周期性的在后台去同步一个最小的global txn id,小于这个id的事务一定都提交了。(要么是个vector clock的感觉,要么是在txn id前面拼一个epoch)
Logs ordering
这里比较无聊,他说没有page之间的依赖,只有page内的,通过page上的lsn传递即可,和TUM等其他人是一个做法。就是去中心化log的设计
之所以没有搞page之间的依赖有几个可能,一个是有依赖的回放都在一个事务/MTR中了,回放可能是按序的。要么也可以通过多版本来做,一批一批的更新可见的LSN。感觉这块细节比较恶心
还需要注意的是,每个节点写自己的redo log,回放的时候需要做一些merge的逻辑。比如RO tail日志的时候,如果是按照日志文件一个一个回放的话,针对每一个page,还需要把对应的page的老的日志抠出来,可能还是需要一些细节的处理的。
文章评论