MYSQL  POLARDB  学习系列之  拆解 POLARDB 4   cache 与 b+tree  (翻译)_分布式

 本地 cache :基于昂贵远程内存网络访问的时延,不能像访问本地内存那么直接,同时CPU 将耗费更多的时间来等待从网络传送来的数据来填满它自己cache。所以基于这样的问题,必须从远程网络读取页面单元数据到本地内存,然后像本地机一样去使用内存的数据。这里有一个公式,就是本地内存是远程内存的八分之一。如果不足这个设置,通过TPC-C TPC-H 测试,系统的性能会降低30% 以上。

未来我们将计划动态调整本地内存cache,通过我们对客户的工作负载来和buffer hit 来控制。

如果访问的页面不能寄存在远程内存中,数据库将开始通过系统的POLARFS 文件系统的接口 libpfs 来读取页面并将通过librmem 写入到远程内存中。

但需要说明的这并不是一存储到内存直接的方式,不是所有的页面都需要通过polarfs 写入到远程内存。举例需要继续FULL TABLE 的计算,就不会这样。因为,页面针对FULL TABLE SCAN 不会像普通读取数据,会持续在内存总驻留,全表扫描的页面会被从内存中抛弃,如果写入内存他们会将其他的页面挤出系统。

当本地需要读取数据不再内存中,数据库将开始等待这个页面,并且等待他总远程内存或远程存储中获取,这显然是对比本地访问要慢。因此增加本地的buffer hit 是一个非常重要的工作,也是性能优化的关键。

当本地cache 满了,页面就需要被驱逐出去,页面被驱逐的逻辑就是LRU算法,清除页面是直接的,但是针对UDPATE数据的页面在他们被释放前需要回写到远程内存中。于此同时还会调用page_unregister 去增加空白页面的数量。

3.1.4   cache coherency

在POLARDB 中的buffer pool 对于节点是自有的,一个RO接单不能直接访问RW 最后版本的页面,因此RO节点捕捉从REDO LOG 中复制过来的页面。在RO 节点。因此在polardb 中RW 可以将修改后的页面写入远程的内存中,与此同时这些页面会立即被RO节点看到,因此RO几点不在需要回放REDO LOG来获得这些信息。然而为了减少通过网络来访问改变的页面,RW改变的页面会写到本地的CACHE 中不会立即同步到远程的内存中。

更有意思的是RO 节点也有本地CACHE,这就需要一个同步的机制在RW节点改变页面内容后,RO 和远程内存知道自己的cache中的信息是过时的。这个机制我们称为cache invalidation ,通过这样的方法来确保缓存的一致性。

MYSQL  POLARDB  学习系列之  拆解 POLARDB 4   cache 与 b+tree  (翻译)_数据结构_02

上图描述了整体invalidation 的工作步骤,以及PIB, PRD 数据结构的状态图。在RW 中在本地的cache 中更新了一个页面,我们称之为 page_invalidate ,如图工作步骤如下

1  它将在主几点上设置PIB 的标志为

2  查看PRD 并且获得一个RO 的节点在本地local cache 的列表

3  同时在RO 节点中设置对应的 PIB 

4  page_invalidate是一个同步阻塞的过程,仅当PIB在所有成员中被设置后才能返回成功。

5  此时如果一个RO 节点不能响应统一的设置,则超过时间后,数据库管理系统将会把这个节点剔除集群,并确认 page_invalidate 的成功。

一组事务被分割为多个小型的事务,而这些事务是通过REDO LOG 来进行串联的, 而redo log 在被刷入到POLARFS 前,所有在分割后的小事务中的页面必须通过page_invalidate 接口使其失效。要确保写入到 POLARFS 的页面的版本一定比内存中的页面的版本要老,并且这些页面一定是有效的。数据库节点恢复就依赖与这个属性。

3.2  B+tree 结构的一致性

在多个处理对数据进行处理中B+TREE 的并发控制是一个需要研究的事情,下面包含两个方面的工作,第一个问题是如何保证当多个进程工作在同一个索引上的工作的物理一致性【10,15,25,30】。 如果没有适当的保护,有一些线程可能会访问到正在被修改的数据。 第二个问题是当并发事务工作操作同样的数据,如何保证在多种隔离级别下的并发处理数据的逻辑一致性【29,32-34】

在 Polardb serverless,RW作为唯一可以进行页面修改的节点,所以这里不存在多个节点修改数据对写操作进行保护的问题,然而DDL 操作会在一个时间修改多个页面,引起RO 节点的物理B+TREE 结构的不一致。

举例RW 分割了一个页面节点变为2个,并且他们叶子节点要插入他们的父节点,并改变父节点B变为 B', 但RO 节点看到的根节点还是B所以只读节点会看不到新加入的数据。

我们解决这个问题通过PL, PL是一个全局物理闩锁,由两个锁组成,S 和X锁,他们并不是来和本地页锁来抢地盘的,而是他们用于多节点环境中确保索引结构的完整性,并与crabbing/lock耦合等算法一起工作。所有进行DDL的页面将被X-locked 锁定,直到DDL 操作完毕,相反的所有的读在RO节点需要检测PLT,如果页面读到了X-LOCKED ,就将这个页面锁替换为S-lock. 上面的一段,当根节点进行改变的时候,RO首先添加一个S-LOCK 到父节点,同时会添加S-LOCK 给叶子节点,然后在将父节点的锁释放。

对于RW 节点的Insert 和 delete操作,Polardb serverless一直采用两个步骤来进行操作,第一忽略DDL操作,针对INSERT 和 DELETE 进行乐观方式的遍历,在这种情况下,仅仅需要本地锁来处理INSERTED 和 DELETEED 的操作。如果乐观扫描的方式发现叶子节点需要进行分割,则会产生针对根节点一个悲观的遍历方式。然后在DDL 中设计变动的页面都放置X 闩锁和X-PL 锁,在上面的例子中,RW 需要添加一个X-LOCK 给父节点B,同时添加一个X-LOCK给A 叶子节点,锁在页面分割完成后,不会被释放。这样的锁的顺序可以确保,RO 节点看到的结果,要不就是未变化的节点状态,要不就是变动的节点状态因为,X-LOCK 在叶子节点和父节点都产生了闩锁。

为了减少PL锁的成本,PL并不会在使用完毕后立即释放,而是需要等待任何RO 节点对他产生操作,才进行释放。这里的好处是当再次需要进行PLT 锁定的时候,不需要在进行锁定,因为锁一直存在。

在加速PL获取的操作,我们已经使用了RDMS CAS 的操作方式来获取锁,(页面的PL地址会和page_register一起被返回),如果获取锁的快速方式不奏效,举例在加载PL X-LOCK 到节点时发现节点已经被S-LOCKED 锁定,那么获取锁就需要在  home  node 和 数据库节点之间进行协商,直到互相协商一致,才能获取所需的锁。

MYSQL  POLARDB  学习系列之  拆解 POLARDB 4   cache 与 b+tree  (翻译)_数据库_03

MYSQL  POLARDB  学习系列之  拆解 POLARDB 4   cache 与 b+tree  (翻译)_java_04

MYSQL  POLARDB  学习系列之  拆解 POLARDB 4   cache 与 b+tree  (翻译)_java_05