Vastbase G100核心技术介绍之【WAL写入优化】

2022-02-18 00:00:00 数据 缓存 日志 写入 自旋

预写日志(WAL)是Vastbase数据库中用来保证数据完整性的手段,本文将围绕WAL这一问题,比较开源PostgreSQL和Vastbase在实现临界区保护上的差异以及Vastbase为了提升性能而做的优化。



Write-Ahead Logging(WAL)


预写日志(WAL)是Vastbase数据库中用来保证数据完整性的手段,任何对数据(表文件、索引文件等)的修改都必须等待对应的日志先落盘,不难发现,日志的写入速度将会影响事务的提交,从而影响整个系统的吞吐量。


日志文件在逻辑意义上是一个大长度为64位无符号整数的连续文件,产生的日志都采用顺序追加的方式写入该文件中。为了便于管理(例如删除无用的日志),在物理上该逻辑文件按照XLOG_SEG_SIZE大小(默认为16MB)分割。考虑到事务运行时会频繁写该文件,故在数据库运行时会分配一块大小默认16MB的共享内存缓存尚未写入磁盘的日志,这样做带来了一个问题:各事务产生的日志都会写入该缓存,为了保证日志顺序,写入时需要进行临界区保护。后文将围绕这一问题,比较开源PostgreSQL和Vastbase在实现临界区保护上的差异以及Vastbase为了提升性能而做的优化。


PostgreSQL中WAL的写入


本文讨论的只是日志生成后写入WAL BUFFER共享缓冲区这一过程,并不包含从缓冲区写入磁盘的过程(Vastbase在这块也有针对性的优化)。


PostgreSQL在将日志写入缓冲区时分为两步:


1

根据当前日志写入位置CurrBytePos和本条日志的大小计算所需的内存空间,这一过程通过自旋锁insertpos_lck保护,在一个繁忙的系统中(日志产生的频繁)该自旋锁会有很严重的争用问题。

2

将日志内容拷贝到缓冲区中。这一步允许多个进程并发执行,但由于实际环境中WAL BUFFER的大小远小于WAL文件的大小,无法缓存全部的日志内容,故会发生缓存置换,需要将缓存中的内容先写入磁盘,而为了跟踪之前的写入操作是否完成,PostgreSQL内置了8把WALInsertLock锁,每一个写入日志的进程都需要获取锁才可以继续执行(内核实现时是先获取WALInsertLock锁,然后才获取保护CurrBytePos的自旋锁),若获取失败则会陷入休眠。



如上图所示,WAL和WAL BUFFER都以8KB为单位划分为多个块,CurrBytePos指向下一条日志写入起始位置,缓存映射算法保证WAL中的一个数据块在WAL BUFFER中的位置是固定的,且允许多个数据块映射到同一个缓存块。在将日志拷贝到缓冲区时,会判断该日志所在的数据块是否在缓存中,若不在会等待该数据块写满,将其内容落盘后重新初始化。


Vastbase中WAL的写入


Vastbase针对PostgreSQL中锁争用的问题做了优化。


对临界区的保护采用免锁CAS操作替换insertpos_lck自旋锁。分析PostgreSQL中WAL写入缓冲区的步,我们需要计算WAL写入的起始和结束位置从而在WAL BUFFER中预留空间,两者均采用64位无符号整型存储,为了便于CAS操作,将两者合并为128位信息,利用128位原子操作实现免锁。


CPU在访问主存时一次会获取整个缓存行的数据,缓存行大小在x86平台上典型值是64字节,ARM平台则有可能是128字节,这种数据获取方式本身可以极大提升数据访问的效率,但假如同一个缓存行中不同位置的数据频繁被不同的线程读取和写入,由于写入时会造成其他CPU下同一个缓存行失效,从而使得CPU按缓存行来获取主存数据的努力不但白费,反而成为性能负担,这就是伪共享(false sharing)。


在日志频繁写入WAL BUFFER的情况下,CurrBytePos的值将被频繁修改,为了避免伪共享的问题,Vastbase在底层数据结构中填充一些无意义的字节避免CurrBytePos和其他数据位于同一个缓存行。


虽然利用CAS替换了自旋锁,但在冲突很高的情况下CAS操作耗时还是会非常长,且会消耗大量的CPU,使得WAL的写入成为性能瓶颈,为此Vastbase引入了WAL分组写入机制,示意图如下:



1

当事务产生日志后并不会立即计算需要在WAL BUFFER中保留多少空间,而是先加入到一个分组中,个加入分组的线程成为Leader,后续加入的成为Follower;

2

 Follower加入分组后会陷入休眠等待Leader唤醒,Leader先将自身的日志拷贝到WAL BUFFER,然后关闭该分组的通道,计算期间加入的所有Follower的日志所需的空间并将其全部拷贝到WAL BUFFER中;

3

Leader唤醒分组内的Follower,由于Follower需要写入的日志已经被Leader写入,不需要再进行CAS操作,直接进入后续流程。



由此可见,通过分组的方式成功降低了CAS操作发生冲突的可能,提升了WAL写入的效率。

原文链接:https://mp.weixin.qq.com/s/W-c90UOD9AlHgx-02zVdFA

相关文章