- 专栏内容:postgresql内核源码分析
- 个人主页:我的主页
- 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
目录
前言
概述
同步控制方法
详细分解
结尾
本文是基于postgresql 15的代码进行分析解读,演示是在centos8系统上进行。
在postgresql 为了加速数据的读写性能,在共享内存中增加了数据缓冲区,也就是我们常说的buffer。buffer是在多进程之间共享,所以需要增加冲突处理,也就是并发控制,本文主要介绍并发控制的方法,以及该种方法的用途。
1、查找缓冲区时的并发控制
从buffer的结构来讲,为了提升性能,设计了三层结构:buffermapping hash table, buffer describe array, buffer array,所以每层都会涉及到并发同步控制。
至此就可以查找到有效的buffer了。
2、从磁盘加载/flush时的并发控制
当查找到buffer后,有两种情况:
一是正常使用时,buffer state还不是valid时,说明磁盘中没有块,需要从磁盘加载;
二是当查找到块后需要flush时,就要检查块是否为脏BM_DIRTY,如果非脏就不需要flush,如果为脏时;
以上两种情况下,会增加 BM_IO_IN_PROGRESS IO处理标志,表明本进程正在处理IO,其它backend或辅助进程就需要等待;当然设置标记时,需要加描述符锁;
3、引用使用时的并发控制
查找到buffer后,需要标记当前buffer被引用,避够被替换出去的可能。
此处调用PinBuffer 来设置buffer state上的 usage count和ref count,同时在本地也记录ref count,在设置时必须等到锁释放;当然此时buffer是否valid并不确定。
此处buffer 的本地ref count是为了提升性能,减少冲突竞争,当本地进程再次引用时,就只增加本地的ref count;
(1)在buffer中查找内容时的并发控制
(2) 修改buffer时的并发控制
1、BufMappingPatitionLock
BufferMapping 分段hash加分段锁,避够与修改冲突,同时此处进行了分段加锁,对hash分了NUM_BUFFER_PARTITIONS段,避免了对整个hash的加锁,减少冲突的范围,提升并发访问性能,当然有更大并发需求时,也可以调大这个值。
static inline LWLock *
BufMappingPartitionLock(uint32 hashcode)
{
return &MainLWLockArray[BUFFER_MAPPING_LWLOCK_OFFSET +
BufTableHashPartition(hashcode)].lock;
}
LWLockAcquire(newPartitionLock, LW_SHARED);
2、BufferHeaderLock
对于buffer描述符加锁操作接口:
buf_state = LockBufHdr(bufHdr);
3、BufferHeader state
Buffer state在 Buffer 描述符中,是一个32bit的字段,按位被分成了三段。
低18bits,0-17位是refcount值
18-21位,是usage count值
22-31位,也就是高10位,是flag值,
其中重要的两个flag:加锁BM_LOCKED标记是22位,BM_VALID是24位; 当flag为BM_VALID时,才能被使用,也就是数据是有效的。
4、引用计数和使用计数
当共享内存中已经有查找的tag对应的buffer,此时在buffer 描述符上更新ref count和usage count,表明有backend在引用,不能被替换出去,也不能进行页面prune。本backend第一次查找该buffer时,还会加到本地资源管理,并对本地refcount递增1。如果找到的buffer,已经在上一次ReadBuffer时增加到本地资源管理了,此时只增加本地引用计数就可以,避够并发竞争。
获取到buffer后,refcount,与usagecount,在buffer描述符上state进行递增,如果是有替换策略,非默认usagecount只加到1。
5、IO控制标记 IO_IN_PROGRESS
当找到的buffer的state不是BM_VALID,说明buffer没有被加载或正在被别的backend加载,此时通过IO_IN_PROGRESS标记来判断,如果有其它backend正在加载,当前backend就需要等待,等IO_IN_PROGRESS标记撤销,如果自己需要加载,就增加标记,然后进行磁盘加载,结束后,取消标记。
6、buffer内容锁
对于buffer内容的lock操作接口:
LWLockAcquire(BufferDescriptorGetContentLock(bufHdr),
LW_EXCLUSIVE);
对于buffer的lock操作其它接口:
extern void UnlockBuffers(void);
extern void LockBuffer(Buffer buffer, int mode);
extern bool ConditionalLockBuffer(Buffer buffer);
extern void LockBufferForCleanup(Buffer buffer);
extern bool ConditionalLockBufferForCleanup(Buffer buffer);
LockBuffer |
普通buffer加锁接口 |
LockBufferForCleanup |
在删除内容时调用,与LockBuffer的区别是,会等待pin为1,也就是其它backend没有使用时,再加锁;主要在vacuum,恢复,备机回放场景下调用。 |
7、释放buffer引用
extern void ReleaseBuffer(Buffer buffer);
extern void UnlockReleaseBuffer(Buffer buffer);
作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!