Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PostgreSQL Buffer管理机制

PostgreSQL Buffer管理机制

原创
作者头像
yzsDBA
修改于 2020-01-20 01:43:40
修改于 2020-01-20 01:43:40
2.1K0
举报

一、共享缓冲区数据结构

1、Buffer由数组BufferDescriptor[]数组进行管理。该数组由函数InitBufferPool创建,大小为NBuffers个成员即BufferDesc。该数组创建后由StrategyControl进行管理,firstFreeBuffer为链表头,指向链表第一个成员;lastFreeBuffer指向链表尾;所有free list中成员由freeNext串起来,该值为数组下标。

2、BufferDescriptor数组是共享内存中申请,所有进程共享。

进程1:

(gdb) p BufferDescriptors

$1 = (BufferDescPadded *) 0xa615fb80

(gdb) p *BufferDescriptors

$2 = {bufferdesc = {tag = {rnode = {spcNode = 1664, dbNode = 0,

relNode = 1262}, forkNum = MAIN_FORKNUM, blockNum = 0}, buf_id = 0,

state = {value = 2199126016}, wait_backend_pid = 0, freeNext = -2,

content_lock = {tranche = 53, state = {value = 536870912}, waiters = {

head = 2147483647, tail = 2147483647}}}, pad = "\200"}

进程2

(gdb) p BufferDescriptors

$1 = (BufferDescPadded *) 0xa615fb80

(gdb) p *BufferDescriptors

$2 = {bufferdesc = {tag = {rnode = {spcNode = 1664, dbNode = 0,

relNode = 1262}, forkNum = MAIN_FORKNUM, blockNum = 0}, buf_id = 0,

state = {value = 2199126016}, wait_backend_pid = 0, freeNext = -2,

content_lock = {tranche = 53, state = {value = 536870912}, waiters = {

head = 2147483647, tail = 2147483647}}}, pad = "\200"}

3、同时还会通过一个环形区进行管理这些数组成员。当进行大表扫描时使用。由strategy->buffers[]数组管理,该数组存储的是BufferDescriptors[]数组的下标+1后的值,而每次取buf描述符时,从strategy->current值开始进行选择。选出的不可用后,依次向后进行遍历,遍历到头后从头再来进行选择,即形成一个环。是否可用的标准后文详述。

4、下面说下BufferDesc成员变量。

1)BufferTag tag为一个描述符对应磁盘物理页的映射。即space ID+database ID+文件ID -- forkNum(表文件还是fsm文件或者vm文件)-- 页号

2)buf_id为buffer数组BufferBlocks[]的下标

3)state为状态标记,包括该buffer的refcount和usagecount以及是否合法valid等待

4)wait_backend_pid:若进程A需要删除的元组所在缓冲块有其他进程访问,即refcount>0时,进程A不能物理上删除元组。系统将该进程的ID记录在wait_backend_id上,然后对缓冲块加pin,并阻塞自己。当refcount为1时最后一个使用该缓冲块的进程释放缓冲区时,会向wait_backend_id进程发送消息。

5)FreeNext为链表的下一个节点的下标

6)content_lock为buffer锁,当进程访问缓冲块时加锁,读加LW_SHARE锁,写加LW_EXCLUSIVE锁

二、共享缓冲区分配buffer原理

1、共享buffer的分配

1、前期准备:

1)该buffer分配有4种情况:从hash表SharedBufHash中查找;从环形缓冲区查找;从free list查找以及驱逐策略进行分配。

2)hash表SharedBufHash同样是共享内存全局的,所有进程公有。下面分别是两个会话连接的server端进程打印出的hash表。

(gdb) p SharedBufHash

$1 = (HTAB *) 0x87f5b04

(gdb) p SharedBufHash

$1 = (HTAB *) 0x87f5b04

该hash表同样在InitBufferPool中进行创建:

StrategyInitialize->InitBufTable(NBuffers + NUM_BUFFER_PARTITIONS)->

SharedBufHash = ShmemInitHash

3)该hash表中条目为:[BufferTag,id]即key值为物理磁盘页的标志,id为对应buffer的ID

3)首先需要创建一个newTag,对应物理文件的一个页

4)通过newTag到函数BufTableHashCode中计算hash表的key值newHash

5)共有128个buffer partition锁,通过hash的key值以轮询的方式取锁

6)此时对key值对应的buffer partition加LW_SHARED锁

2、此时进入第一种获取buffer描述符的方法:所有进程共享的SharedBufHash

1)根据newTag从hash表SharedBufHash中查找对应的buffer

2)buf_id>0则表示数据页在hash表中找到,即对应数据页以加载到内存

3)根据buf_id获取buffer的描述符BufferDescriptors[buf_id)].bufferdesc

4)通过函数PinBuffer将对应buffer pin住,然后就可以将buffer的partition锁释放

即,将buf的state的refcount+1,usagecount根据情况+1,具体流程下文分析。

5)pin失败,通过StartBufferIO判断,返回TRUE,缓冲区无效,此时foundPtr为false,并返回对应buf;返回false,表示别人正在使用,直接返回对应buf。foundPtr表示是否在缓冲区命中

3、若hash表中不存在,则需要从磁盘读取。首先释放buf的partition锁,进入循环。

1)StrategyGetBuffer取出一个buf描述符,具体原理见下文。

2)PinBuffer_Locked将buf的refcount+1

3)此时该buf为脏块BM_DIRTY,则对buf->content_lock加LW_SHARED锁,加锁失败释放pin,返回1)。加锁成功根据strategy是否为空处理。

4)使用环形缓冲区,即strategy不为空:BM_LOCKED锁内获取buf脏页的lsn,根据lsn判断其日志是否已经刷写到磁盘,若未则将该buf从环形缓冲区删除;释放buf->content_lock锁及pin,返回1)重新循环进行选择。

5)使用环形缓冲区且日志已刷或者未使用环形缓冲区,则调用FlushBuffer将脏数据刷写磁盘,最后释放buf->content_lock锁。

6)接着进入4,当该页不为脏时也进入4

4、替换为自己的tag

1)先获取buf的oldTag,是谁用过。其oldPartitionLock和newTag的newPartitionLock按顺序加锁,若同一个则只加一个锁。LW_EXCUSIVE

2)将newTag对应的条目插入到hash表SharedBufHash

3)buf_id>=0,表示该条目已在hash表,那么unpin、oldPartitionLock锁释放后,获取老buf,pin后释放newPartitionLock

4)pin失败,通过StartBufferIO判断,返回TRUE,缓冲区无效,此时foundPtr为false,并返回对应buf;返回false,表示别人正在使用,直接返回对应buf。foundPtr表示是否在缓冲区命中

5)buf_id<0,即未在hash表SharedBufHash:buf_state的refcount==1且不为BM_DIRTY,表示无人使用该buf,退出循环,将buf->tag=newTag,最后释放相关锁

6)否则,需要释放相关锁,并将newTag对应的条目从hash表删除后,重新回到3进行选择。

2、PinBuffer

1、若buffer的state已为BM_LOCKED即已加锁,则需等待,该锁是pin锁

2、GetPrivateRefCountEntry获取ref,若ref不为NULL,则表示别人在使用,然后TRUE。是这样理解吗?需要理解这个函数

3、原子操作读取state值old_buf_state,并将之保存为buf_state

4、buf_state的refcount+1

5、默认策略下,即从free list中选择空闲描述符,buf_state的usagecount+1;环形缓冲区策略下,buf_state的usagecount保持为1

6、通过CAS操作将buf->state的值替换为buf_state的值

7、函数返回TRUE表示该buffer的数据有效,即合法的数据已经加载到内存;返回false表示数据无效,即数据未加载到内存

3、StartBufferIO:开启IO,将buf状态置为BM_IO_IN_PROGRESS

1、每个buffer都有一个IO锁(BufferIOLWLockArray[(bdesc)->buf_id]).lock

2、获取buf_state状态,需要先将其置为BM_LOCKED

3、该buf此时已为BM_IO_IN_PROGRESS,即正在读写,需要将上面两个锁释放后WaitIO等待状态变化

4、forInput为TRUE:要向里面写,需要其为!BM_VALID,若是BM_VALID表示有人已经向里写了合法数据;FALSE:需要向外读,若为!BM_DIRTY表示已有人刷写了。释放两个锁返回

5、将buf_state置为BM_IO_IN_PROGRESS。

6、返回TRUE,表示buf中数据无效,可以使用。False,表示别人正在使用

4、StrategyGetBuffer

1、如果使用strategy,则从环形缓冲区取一个空闲的描述符:bufnum=strategy->buffers[strategy->current];buf = GetBufferDescriptor(bufnum - 1);,若没有可用的则GetBufferFromRing返回NULL,否则直接返回该buf。

2、环形缓冲区取buffer失败,则去free list取

3、StrategyControl->firstFreeBuffer>0,此时list不为空,

4、则先申请spin锁StrategyControl->buffer_strategy_lock,再次判断链表情况,若StrategyControl->firstFreeBuffer<0链表空了,则释放锁后退出循环,进入第8步

5、获取StrategyControl->firstFreeBuffer指向的buffer描述符,并将该节点从free list删除

6、释放StrategyControl->buffer_strategy_lock锁

7、该buf的refcount和usagecount都为0,则表示我们可以用,若strategy不为NULL,则现将该buf放到环形缓冲区,返回该buffer描述符;否则再次到第4步循环

8、此时free list都取了一遍,但是没有可用的,通过时钟算法,即循环StrategyControl->nextVictimBuffer取该buf,看其是否可用。同样如果找到后,根据strategy是否为NULL,将其放到环形缓冲区。将所有buf都取了一遍后,仍没有可用的话就报错:no unpinned buffers available

三、本地缓冲区数据结构

1、数组LocalBufferDescriptors[]为本地缓冲块的描述符,buf_id从-2开始,都是负数。为和共享缓冲区有区别,通过是否为负值就可以判断是否是本地缓冲区

2、数组大小由变量num_temp_buffers控制

3、LocalBufferBlockPointers数组为本地缓冲块的指针。这些数组的初始化由函数InitLocalBuffers完成,此时并没有分配本地缓冲块内存。

4、本地缓冲块内存的申请和分配由函数GetLocalBufferStorage完成。第一次时从内存上下文LocalBufferContext中分配16个页大小的block,然后将第一个页地址分配给LocalBufferBlockPointers[i],并不是一下子全部分配,下次使用时再分配数组下一个页。

5、16个页的block用完,再次申请32个页的block;同理32个页的block用完,申请64个页的block,每次都是之前的两倍。

6、最大一次分配1个GB的block

四、本地buffer的分配

1、本地buffer的分配由函数LocalBufferAlloc来完成,用于临时表的读写。

2、同样需要先初始化tag:newTag,唯一标记一个物理页

3、第一次使用临时表时,需要通过calloc创建一系列缓冲区(InitLocalBuffers完成):几个数组,大小为num_temp_buffers,即该变量控制大小。

LocalBufferDescriptors[]:存储本地缓冲块的描述符

LocalBufferBlockPointers[]:本地缓冲块指针数组,存储指向块的指针

LocalRefcount[]:每个描述符引用次数

LocalBufHash:用户管理本地缓冲块的hash表,key为tag,value为buffer的数组下标。

4、通过tag从LocalBufHash表中查找,看有没有,是否已经加载到内存。

5、LocalBufHash表中已存在:

1)获取其ID,然后获取local buffer的描述符bufHdr

2)原子操作读出bufHdr的state即buf_state

3)buf_state的usagecount保持为1?refcount+1

4)buf为BM_VALID,则foundPtr为TRUE,表示命中缓冲,否则为false

5)返回该描述符bufHdr

6、LocalBufHash不存在:

1)通过该值nextFreeLocalBuf遍历本地缓冲区,若超过NlocBuffer值则从头开始遍历

2)LocalRefcount对应的引用值不为0,则返回到1);否则取出其bufHdr的state

3)usagecount=0,表示没有人在用,则LocalRefcount=1后退出循环,进入步骤7

4)usagecount>0,则将usagecount-1后返回1)重新选择下一个

7、buf_satate为BM_DIRTY,则需要刷脏:

1)获取具体页localpage,smgropen一个oreln,如打开checksum则计算checksum并写入localpage;smgrwrite将其写入到磁盘;最后将状态置为非BM_DIRTY

8、如果第一次使用本地buffer,则需要调用GetLocalBufferStorage将其挂到TopMemoryContext

9、如果该buffer缓冲区的数据有效,则需要更新hash表:将该tag从LocalBufHash中删除,并将状态置为 ~(BM_VALID | BM_TAG_VALID),其bufHdr->tag需要清空

10、将新的tag newTag插入hash表LocalBufHash

11、将bufHdr->tag替换成newTag,此时是我们的tag了。

12、buf_state清空,并且置为BM_TAG_VALID,且usagecount加1,foundPTR为false

13、返回buf描述符bufHdr

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
图解PostgreSQL-buffer管理(二)
1、Buffer由数组BufferDescriptor[]数组进行管理。该数组由函数InitBufferPool创建,大小为NBuffers个成员即BufferDesc。该数组创建后由StrategyControl进行管理,firstFreeBuffer为链表头,指向链表第一个成员;lastFreeBuffer指向链表尾;所有free list中成员由freeNext串起来,该值为数组下标。
yzsDBA
2020/10/28
8790
图解PostgreSQL-buffer管理(二)
Postgresql源码(2)缓冲区管理
学习笔记:https://www.interdb.jp/pg/pgsql08.html
mingjie
2022/05/12
7790
Postgresql源码(2)缓冲区管理
图解PostgreSQL-local buffer管理
1、数组LocalBufferDescriptors[]为本地缓冲块的描述符,buf_id从-2开始,都是负数。为和共享缓冲区有区别,通过是否为负值就可以判断是否是本地缓冲区
yzsDBA
2020/10/28
7730
图解PostgreSQL-local buffer管理
图解PostgreSQL-buffer管理(三)
1、本地buffer的分配由函数LocalBufferAlloc来完成,用于临时表的读写。
yzsDBA
2020/10/28
5500
图解PostgreSQL-buffer管理(三)
Postgresql源码(8)重温BufferDesc
desc结构中的Buffer header lock没有单独的变量,保存在state中的第22位。 #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) */
mingjie
2022/05/12
2460
Postgresql源码(9)Pin/UnpinBuffer流程图
流程图 私有缓存的维护 等待进程唤醒 拿的锁在state中的一位,原子操作 spin等锁 PinBuffer static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy) { Buffer b = BufferDescriptorGetBuffer(buf); bool result; PrivateRefCountEntry *ref; ref = GetPrivateRefCountEntry(b, true
mingjie
2022/05/12
2040
Postgresql源码(9)Pin/UnpinBuffer流程图
PG明明业务进行的是SELECT,为什么监控磁盘,写负载那么大呢?
进行测试时,预制完数据后立即进行SELECT测试查询场景,或者预制完数据立即重启服务,然后进行SELECT场景测试,监控磁盘负载时,发现写负载特别大。
yzsDBA
2021/04/26
4340
Postgresql源码(34)写操作如何获取buffer与页面扩展
PG的insert和update操作都会用到RelationGetBufferForTuple函数,例如insert:
mingjie
2022/05/12
5390
Postgresql在SyncOneBuffer时,为什么可以不加锁判断页面是否为脏(race condition第三篇)
buffer标记脏在写xlog前,那么如果checkpoint在sync时没发现buffer为脏:
mingjie
2022/09/23
4100
Postgresql在SyncOneBuffer时,为什么可以不加锁判断页面是否为脏(race condition第三篇)
PostgreSQL的clog属于日志还是数据,需要遵守write-WAL-before-data吗?
如果不看实现只看概念,不活跃事务提交状态也可以在XLOG中查询,CLOG可以视作一种XLOG commit/rollback日志的缓存、映射,一种事务提交状态的快速查询方式。
mingjie
2023/02/28
6140
Postgresql源码(20)ReadBuffer_common核心流程简化
总结: 先走BufferAlloc拿一个buffer 如果在内存中直接返回,如果不在内存中需要分配一个位置。 如果有位置直接分配,如果没有位置需要clock sweep淘汰一个。 拿到一个页面后,数据肯定是不对的,需要IO上来数据,如果页面本身有脏数据,需要先把页面Flush到存储再用。 BufferAlloc出来的页面如果需要IO读会带着IO_IN_PROGRESS标志位和IO锁。 ReadBuffer_common检查BufferAlloc有没有找到页面,如果没找到页面,也已经拿到一个带着IO标
mingjie
2022/05/12
1870
Postgresql中无锁修改buffer状态
Postgresql中缓冲块的状态操作是非常频繁的,尤其是pin/unpin的操作。
mingjie
2023/03/01
7410
图解PostgreSQL-buffer管理(一)
通过两种方式管理buffer的描述符。1)free list链表;2)环形缓冲区。
yzsDBA
2020/10/28
7050
图解PostgreSQL-buffer管理(一)
PostgreSQL技术大讲堂 - 第23讲:缓冲区管理器
PostgreSQL从小白到专家,是从入门逐渐能力提升的一个系列教程,内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容,希望对热爱PG、学习PG的同学们有帮助,欢迎持续关注CUUG PG技术大讲堂。
用户5892232
2023/07/20
4750
PostgreSQL技术大讲堂 - 第23讲:缓冲区管理器
Linux V4L2子系统-videobuf2框架分析
Video设备产生的数据较多,传统的缓冲机制已不能满足需求。为此,Linux内核抽象出了videobuf2机制,用于管理存放视频图像的帧缓冲。videobuf2抽象层像一座桥梁,将用户空间和V4L2 driver连接起来。videobuf2抽象层向用户空间提供了标准POSIX I/O系统调用,包括read、poll及mmap等,同时还提供了大量与流式I/O相关的V4L2 ioctl调用,包括缓冲区分配、缓冲区入队、缓冲区出队及流控制。虽然使用videobuf2会给驱动程序强加一些设计决策,但是使用它的收益是videobuf2可以减少驱动程序代码和保持V4L2子系统在用户空间API的一致性,显然使用videobuf2更为合理。
用户6280468
2023/08/31
1.4K0
Linux V4L2子系统-videobuf2框架分析
缓冲区管理器:解读年度数据库PostgreSQL
PostgreSQL 已获得 DB-Engines 排行榜 2017 年和2018年的“年度数据库”称号,发展如此迅猛,它究竟有什么内幕呢?接下来,我们将选择PostgreSQL重要的子系统之一缓冲区管理器展开介绍,探讨它的工作原理。
用户1682855
2019/06/03
1.4K0
图解 | Linux进程通信 - 管道实现
处于安全的考虑,不同进程之间的内存空间是相互隔离的,也就是说 进程A 是不能访问 进程B 的内存空间,反之亦然。如果不同进程间能够相互访问和修改对方的内存,那么当前进程的内存就有可能被其他进程非法修改,从而导致安全隐患。
用户7686797
2021/10/19
4.2K1
图解 | Linux进程通信 - 管道实现
PostgreSQL的heapgetpage函数解析
本文介绍heapgetpage函数的流程,这个函数的功能是从磁盘中读取一个页到内存,并将可见的记录的记录号,即第几个记录,保存到扫描描述符的rs_vistuples数组中。
yzsDBA
2020/10/28
8020
PostgreSQL的heapgetpage函数解析
【JUC进阶】12. 环形缓冲区
上一篇《【JUC进阶】11. BlockingQueue》中介绍到ArrayBlockingQueue,在物理上是一个数组,但在逻辑上来说是个环形结构。这就衍生出来我们今天要介绍的主题,环形缓冲区。
有一只柴犬
2024/01/25
3890
【JUC进阶】12. 环形缓冲区
深入浅出PostgreSQL性能调优
PostgreSQL性能调优概览 PostgreSQL 参数类型 struct config_generic定义了所有PG参数的通用结构;struct config_{int/real/string/bool}在struct config_generic之上进行分组定义了不同数据类型的参数类型 深入PostgreSQL 性能参数 shared_buffers参数 PostgreSQL中按照参数的类型(int/bool/string/real)等类型进行分组进行解析。但是针对shared_buff
用户4700054
2023/02/26
1.4K0
深入浅出PostgreSQL性能调优
相关推荐
图解PostgreSQL-buffer管理(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档