openGauss源码解析(46)
openGauss源码解析:存储引擎源码解析(14)
4.2.6 行存储缓存机制
行存储缓存加载和淘汰机制如图4-20所示。
图4-20 行存储缓存和淘汰机制示意图
行存储堆表和索引表页面的缓存和淘汰机制主要包含以下几个部分。
1. 共享缓冲区内存页面数组下标哈希表
共享缓冲区内存页面数组下标哈希表用于将远大于内存容量的物理页面与内存中有限个数的内存页面建立映射关系。该映射关系通过一个分段、分区的全局共享哈希表结构实现。哈希表的键值为buftag(页面标签)结构体。该结构体由“rnode”、“forkNum”、“blockNum”三个成员组成。其中“rnode”对应行存储表物理文件名的主体命名;“forkNum”对应主体命名之后的后缀命名,通过主体命名和后缀命名,可以找到唯一的物理文件;而“blockNum”对应该物理文件中的页面号。因此,该三元组可以唯一确定任意一个行存储表物理文件中的物理页面位置。哈希表的内容值为与该物理页面对应的内存页面的“buffer id”(共享内存页面数组的下标)。
因为该哈希表是所有数据页面查询的入口,所以当存在并发查询时在该哈希表上的查询和修改操作会非常频繁。为了降低读写冲突,把该哈希表进行了分区,分区个数等于NUM_BUFFER_PARTITIONS宏的定义值。在对该哈希表进行查询或修改操作之前首先需要获取相应分区的共享锁或排他锁。考虑到当对该哈希表进行插入操作时待插入的三元组键值对应的物理页面大概率不在当前的共享缓冲区中,因此该哈希表的容量等于“g_instance.attr.attr_storage.NBuffers + NUM_BUFFER_PARTITIONS”。该表具体的定义代码如下:
typedef struct buftag {
RelFileNode rnode; /* 表的物理文件位置结构体 */
ForkNumber forkNum; /* 表的物理文件后缀信息 */
BlockNumber blockNum; /* 页面号 */
} BufferTag;
2. 共享buffer desc数组
该数组有“g_instance.attr.attr_storage.NBuffers”个成员,与实际存储页面内容的共享buffer数组成员一一对应,用来存储相同“buffer id”(即这两个全局数组的下标)的数据页面的属性信息。该数组成员为BufferDesc结构体,具体定义代码如下:
typedef struct BufferDesc {
BufferTag tag; /*缓冲区页面标签 */
pg_atomic_uint32 state; /* 状态位、引用计数、使用历史计数 */
int buf_id; /*缓冲区下标 */
ThreadId wait_backend_pid;
LWLock* io_in_progress_lock;
LWLock* content_lock;
pg_atomic_uint64 rec_lsn;
volatile uint64 dirty_queue_loc;
} BufferDesc;
(1) tag成员是该页面的(relfilenode,forknum,blocknum)三元组。
(2) state成员是该内存状态的标志位,主要包含BM_LOCKED(该buffer desc结构体内容的排他锁标志)、BM_DIRTY(脏页标志)、BM_VALID(有效页面标志)、BM_TAG_VALID(有效tag标志)、BM_IO_IN_PROGRESS(页面I/O状态标志)等。
(3) buf_id成员,是该成员在数组中的下标。
(4) wait_backend_pid成员,是等待页面unpin(取消引用)的线程的线程号。
(5) io_in_progress_lock成员,是用于管理页面并发I/O操作(从磁盘加载和写入磁盘)的轻量级锁。
(6) content_lock成员,是用于管理页面内容并发读写操作的轻量级锁。
(7) rec_lsn成员,是上次写入磁盘之后该页面第一次修改操作的日志lsn值。
(8) dirty_queue_loc成员,是该页面在全局脏页队列数组中的(取模)下标。
3. 共享buffer数组
该数组有“g_instance.attr.attr_storage.NBuffers”个成员,每个数组成员即为保存在内存中的行存储表页面内容。需要注意的是,每个buffer在代码中以一个整型变量来标识,该值从1开始递增,数值上等于“buffer id + 1”,即“数组下标加1”。
4. bgwriter线程组
该数组有“g_instance.attr.attr_storage.bgwriter_thread_num”个线程。每个“bgwriter”线程负责一定范围内(目前为均分)的共享内存页面的写入磁盘操作,如图4-20中所示。如果全局共享buffer数组的长度为12,一共有3个“bgwriter”线程,那么第1个“bgwriter”线程负责“buffer id 0 - buffer id 3”的内存页面的维护和写入磁盘;第2个“bgwriter”线程负责“buffer id 4 - buffer id 7”的内存页面的维护和写入磁盘;第3个“bgwriter”线程负责buffer id 8 - buffer id 11的内存页面的维护和写入磁盘。每个“bgwriter”进程在后台循环扫描自己负责的那些共享内存页面和它们的buffer desc状态,将被业务修改过的脏页收集起来,批量写入双写文件,然后写入表文件系统。对于刷完的内存页,将其状态变为非脏,并追加到空闲buffer id队列的尾部,用于后续业务加载其他当前不在共享缓冲区的物理页面。每个“bgwriter”线程的信息记录在BgWriterProc结构体中,该结构体的定义代码如下:
typedef struct BgWriterProc {
PGPROC *proc;
CkptSortItem *dirty_buf_list;
uint32 dirty_list_size;
int *cand_buf_list;
volatile int cand_list_size;
volatile int buf_id_start;
pg_atomic_uint64 head;
pg_atomic_uint64 tail;
bool need_flush;
volatile bool is_hibernating;
ThrdDwCxt thrd_dw_cxt;
volatile uint32 thread_last_flush;
int32 next_scan_loc;
} BgWriterProc;
其中比较关键的几个成员含义是: