PgSQL · 特性分析 · 内存管理机制
背景
数据库物理结构
$PGDATA/pg_tblspc/$tablespace_oid/$database_oid/$relation_oid.no
$PGDATA/base/$database_oid/$relation_oid.num
typedef struct buftag
{
RelFileNode rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
} BufferTag;
typedef struct RelFileNode
{
Oid spcNode; /* tablespace */
Oid dbNode; /* database */
Oid relNode; /* relation */
} RelFileNode;
缓存管理结构
BufferBlocks = (char *) ShmemInitStruct("Buffer Blocks", NBuffers * (Size) BLCKSZ, &foundBufs);
BufferDescriptors = (BufferDescPadded *)
ShmemInitStruct("Buffer Descriptors",
NBuffers * sizeof(BufferDescPadded),
&foundDescs);
缓存池,是一个数组,每个元素其实就是一个缓存页,下标buf_id 标示一个缓存页。 缓存描述符,也是一个数组,而且和缓存池的缓存一一对应,保存每个缓存页的元数据信息。 -
缓存hash 表,是存储BufferTag 和缓存描述符之间映射关系的hash 表。
缓存hash 表
获取BufferTag 对应的哈希值hashvalue 通过hashvalue 定位到具体的bucket slot 遍历bucket slot 找到具体的data entry,其数据结构BufferLookupEnt 如下:
/* entry for buffer lookup hashtable */
typedef struct
{
BufferTag key; /* Tag of a disk page */
int id; /* Associated buffer ID */
} BufferLookupEnt;
InitBufTable(NBuffers + NUM_BUFFER_PARTITIONS);
缓存描述符
typedef struct BufferDesc
{
BufferTag tag; /* ID of page contained in buffer */
int buf_id; /* buffer's index number (from 0) */
/* state of the tag, containing flags, refcount and usagecount */
pg_atomic_uint32 state;
int wait_backend_pid; /* backend PID of pin-count waiter */
int freeNext; /* link in freelist chain */
LWLock content_lock; /* to lock access to buffer contents */
} BufferDesc;
tag 指的是对应缓存页存储的数据页的标示 buffer_id 指的是对应缓存页的下标,我们通过它可以直接访问对应缓存页 state 是一个无符号32位的变量,包含: 18 bits refcount,当前一共有多少个后台进程正在访问该缓存页,如果没有进程访问该页面,本文称为该缓存描述符unpinned,否则称为该缓存描述符pinned 4 bits usage count,近一共有多少个后台进程访问过该缓存页,这个属性用于缓存页淘汰算法,下文将具体讲解。 10 bits of flags,表示一些缓存页的其他状态,如下:
#define BM_LOCKED (1U << 22) /* buffer header is locked */
#define BM_DIRTY (1U << 23) /* data needs writing */
#define BM_VALID (1U << 24) /* data is valid */
#define BM_TAG_VALID (1U << 25) /* tag is assigned */
#define BM_IO_IN_PROGRESS (1U << 26) /* read or write in progress */
#define BM_IO_ERROR (1U << 27) /* previous I/O failed */
#define BM_JUST_DIRTIED (1U << 28) /* dirtied since write started */
#define BM_PIN_COUNT_WAITER (1U << 29) /* have waiter for sole pin */
#define BM_CHECKPOINT_NEEDED (1U << 30) /* must write for checkpoint */
#define BM_PERMANENT (1U << 31) /* permanent buffer (not unlogged,
* or init fork) */
freeNext,指向该缓存之后个空闲的缓存描述符 content_lock,是控制缓存描述符的一个轻量级锁,我们会在缓存锁管理具体分析其作用
从freelist 中找到个缓存描述符,并且把该缓存描述符pinned (增加refcount和usage_count) 在缓存hash 表中插入这个数据页面的BufferTag 与buf_id 的对应新的data entry 从磁盘中将数据页面加载到缓存池中对应缓存页面中 在对应缓存描述符中更新该页面的元数据信息
数据页面对应的表或者索引被删除 数据页面对应的数据库被删除 数据页面被VACUUM FULL 命令清理
缓存池
缓存锁管理
BufMappingLock
content_lock
插入或者删除/更新该缓存页的元组 vacuum 该缓存页 freeze 该缓存页
io_in_progress_lock
其他
缓存页淘汰算法
获取个候选缓存描述符,存储在freelist 控制信息的数据结构BufferStrategyControl 的nextVictimBuffer 属性中 如果该缓存描述符unpinned,则跳到步骤3,否则跳到步骤4 如果该候选缓存描述符的usage_count 属性为0,则选取该缓存描述符为要淘汰的缓存描述符,跳到步骤5,否则,usage_count–,跳到步骤4 nextVictimBuffer 赋值为下一个缓存描述符(当缓存描述符全部遍历完成,则从第0个继续),跳到步骤1继续执行,直到发现一个要淘汰的缓存描述符 返回要淘汰的缓存描述符的buf_id
总结
根据请求的数据页面形成BufferTag,假设为Tag_M,用Tag_M 去从缓存hash 表中检索data entry,很明显这里没有发现该BufferTag 使用clock-sweep 算法选择一个要淘汰的缓存页面,例如这里buf_id=5,该缓存页的data entry 为’Tag_F, buf_id=5’ -
如果是脏页,将buf_id=5的缓存页刷新到磁盘,否则跳到步骤4。刷新一个脏页的步骤如下: a. 获得buffer_id=5的缓存描述符的content_lock 共享锁和io_in_progress 排它锁(步骤f会释放) b. 修改该描述符的state,BM_IO_IN_PROGRESS 和BM_JUST_DIRTIED 字段设为1 c. 根据情况,执行 XLogFlush() 函数,对应的wal 日志刷新到磁盘 d. 将缓存页刷新到磁盘 e. 修改该描述符的state,BM_IO_IN_PROGRESS 字段设为1,BM_VALID 字段设为1 f. 释放该缓存描述符的content_lock 共享锁和io_in_progress 排它锁 获取buf_id=5的bucket slot 对应的BufMappingLock 分区排他锁,并将该data entry 标记为旧的 获取新的Tag_M 对应bucket slot 的BufMappingLock 分区排他锁并且插入一条新的data entry 删除buf_id=5的data entry,并且释放buf_id=5的bucket slot 对应的BufMappingLock 分区锁 从磁盘上加载数据页面到buf_id=5的缓存页面,并且更新buf_id=5的缓存描述符state 属性的BM_DIRTY字段为0,初始化state的其他字段。 释放Tag_M 对应bucket slot 的BufMappingLock 分区排他锁 从缓存中访问该数据页面
相关文章