postgres的共享buffer有两层访问机制
- Pin(引用计数):访问前必须先Pin(引用计数+1),防止页面被回收
- buffer content lock:分为共享锁和排他锁,用于控制页面的并发访问
共享buffer池是基于共享内存,也就是postgres的相关进程可以共享的内存数据。这里有几个关键的结构
- BufferDesc:管理buffer的元数据,状态,标识等。与buffer一一对应
- BufferBlocks:实际的buffer。存储从磁盘读取的数据。每个buffer的大小和磁盘块大小一致。默认为8KB。这个大小同样限制了一条tuple的大小
- ConditionVariable:协调多个进程对同一buffer的IO操作。和buffer一一对应
- CkptSortItem:checkpoint排序数组。checkpoint写磁盘过程中对要写入的buffer进行排序。以实现顺序IO提高性能。
需要注意的是,为了高效地内存访问,上面这些结构的数据都进行了内存对齐,比如BufferDesc和ConditionVariable在实际使用时都是用的内存对齐的版本
#define BUFFERDESC_PAD_TO_SIZE (SIZEOF_VOID_P == 8 ? 64 : 1)
typedef union BufferDescPadded
{
BufferDesc bufferdesc;
char pad[BUFFERDESC_PAD_TO_SIZE];
} BufferDescPadded;
#define CV_MINIMAL_SIZE (sizeof(ConditionVariable) <= 16 ? 16 : 32)
typedef union ConditionVariableMinimallyPadded
{
ConditionVariable cv;
char pad[CV_MINIMAL_SIZE];
} ConditionVariableMinimallyPadded;BufferDescPadded在64位机器上64位对齐,让每个BufferDesc占用一个缓存行来避免false sharing(如果64位能够存储多个BufferDesc,一个cache line中有多个BufferDesc,多核就是操作的是相互独立的BufferDesc也会导致这个缓存行在两个cpu核都失效)。通过对齐让每个BufferDesc对应一个cache line,避免互相干扰,用空间换取性能。ConditionVariableMinimallyPadded也是同理,不过64位对齐有些太浪费空间了,因此采用了16或者32位对齐
至于buffer还按照IO进行了对齐,在下面的代码中会保证BufferBlocks的开始地址是按照PG_IO_ALIGN_SIZE进行对齐的。
BufferBlocks = (char *)
TYPEALIGN(PG_IO_ALIGN_SIZE,
ShmemInitStruct("Buffer Blocks",
NBuffers * (Size) BLCKSZ + PG_IO_ALIGN_SIZE,
&foundBufs));shared buffer pool初始化
源码在buf_init.c的BufferManagerShmemInit函数当中。