PG shared buffers
简介
shared buffers是数据库内存共享缓冲区。
当Postgres想要从磁盘获取数据(page)时,先搜索shared_buffers,确认该page是否在shared_buffers中,如果存在,则直接命中,返回缓存的数据以避免I/O。如果不存在,再到OS缓存查找,最后才会通过I/O访问disk获取数据。
结构
PostgreSQL 缓存管理器分成三层结构:缓冲表(buffer table)、缓冲区描述符(buffer descriptors)和缓冲池(buffer pool)
buffer table - 缓冲表
是一个哈希表,它存储着页面的buffer_tag
与描述符的buffer_id
之间的映射关系。
例如数据项Tag_A,id=12
表示,buffer_id=2
对应的缓冲区描述符中,存储着页面Tag_A
的元数据。
PostgreSQL中的每个数据文件页面都可以分配到唯一的标签,即缓冲区标签(buffer tag)。 当缓冲区管理器收到请求时,PostgreSQL会用到目标页面的缓冲区标签。
缓冲区标签(buffer_tag) 由三个值组成:关系文件节点(relfilenode),关系分支编号(fork number),页面块号(block number)。
例如,缓冲区标签
{(16821, 16384, 37721), 0, 7}
表示,在oid=16821
的表空间中的oid=16384
的数据库中的oid=37721
的表的0号分支(关系本体)的第七号页面。再比如缓冲区标签{(16821, 16384, 37721), 1, 3}
表示该表空闲空间映射文件的三号页面。
buffer descriptors - 缓冲区描述符
是一个由缓冲区描述符组成的数组。
缓冲区描述符保存着页面的元数据,这些与缓冲区描述符相对应的页面保存在缓冲池槽中
buffer pool - 缓冲池
缓冲池只是一个用于存储关系数据文件(例如表或索引)页面的简单数组。缓冲池数组的序号索引也就是buffer_id
。
缓冲池槽的大小为8KB,等于页面大小,因而每个槽都能存储整个页面。
锁
-
BufMappingLock:用于保护整个缓冲表的数据完整性。
在缓冲表中查询条目时,后端进程会持有共享的
BufMappingLock
。插入或删除条目时,后端进程会持有独占的BufMappingLock
。 -
content_lock - 内容锁:是一种强制限制访问的锁
-
io_in_progress_lock - IO进行锁
io_in_progress_lock
用于等待缓冲区上的I/O完成。当PostgreSQL进程加载/写入页面数据时,该进程在访问页面期间,持有对应描述符上独占的io_in_progres_lock
。 -
spin lock - 自旋锁
当检查或更改标记字段与其他字段时(例如
refcount
和usage_count
),会用到自旋锁。- refcount:保存当前访问相应页面的PostgreSQL进程数,也被称为钉数(pin count)
- usage_count:保存着相应页面加载至相应缓冲池槽后的访问次数。(时钟扫描会用到)
页面替换算法
当所有缓冲池槽位都被占用,且其中未包含所请求的页面时,缓冲区管理器必须在缓冲池中选择一个页面逐出,用于放置被请求的页面。 在计算机科学领域中,选择页面的算法通常被称为页面置换算法(page replacement algorithms),而所选择的页面被称为受害者页面(victim page)。
PostgreSQL使用**时钟扫描(clock-sweep)**算法
nextVictimBuffer
指向第一个描述符(buffer_id = 1
);但因为该描述符被钉住了,所以跳过。nextVictimBuffer
指向第二个描述符(buffer_id = 2
)。该描述符未被钉住,但其usage_count
为2;因此该描述符的usage_count
将减1,而nextVictimBuffer
迭代至第三个候选描述符。nextVictimBuffer
指向第三个描述符(buffer_id = 3
)。该描述符未被钉住,但其usage_count = 0
,因而成为本轮的受害者。
当nextVictimBuffer
扫过未固定的描述符时,其usage_count
会减1。因此只要缓冲池中存在未固定的描述符,该算法总能在旋转若干次nextVictimBuffer
后,找到一个usage_count
为0的受害者。
环形缓冲区 - ring buffer
在读写大表时,PostgreSQL会使用环形缓冲区(ring buffer)。是一个临时缓冲区,在使用后被立即释放
PostgreSQL在共享内存中分配一个环形缓冲区的条件:
-
批量读取
当扫描关系读取数据的大小超过缓冲池的四分之一(
shared_buffers/4
)时,在这种情况下,环形缓冲区的大小为_256 KB_。 -
批量写入
当执行下列SQL命令时,这种情况下,环形缓冲区大小为_16 MB_。
-
清理过程
当自动清理守护进程执行清理过程时,这种情况环形缓冲区大小为256 KB。
详情参考 https://www.interdb.jp/pg/pgsql08.html