首页 > 其他分享 >03PG缓存-写入写出

03PG缓存-写入写出

时间:2024-08-04 10:58:48浏览次数:16  
标签:缓存 HASH 03PG 写入 buffer 描述符 受害者 页面

一、缓存区的写入

当后端进程想要访问一个页面时,会调用ReadBufferExtended,一般后端进程访问页面有三种情况

  • 页面在缓存池中
  • 页面不在缓存池中,从磁盘中将页面加载到缓存描述符空槽
  • 页面不在缓存池,从磁盘中将页面加载到缓存描述符受害者槽

1.读取存储在缓存池中的页面

以t1表为例,查询相关视图,得知目前此表在缓存中

test=#  SELECT * FROM buffercache('t1');
 bufferid | relfork | relblk | isdirty | usagecount | pins 
----------+---------+--------+---------+------------+------
      253 | main    |      0 | t       |          1 |    0
(1 row)

test=# explain (analyze,buffers,costs off,timing off,summary off) select * from t1;
               QUERY PLAN               
----------------------------------------
 Seq Scan on t1 (actual rows=1 loops=1)
   Buffers: shared hit=1
(2 rows)

第二行shared  hit 表示缓存命中,即从缓存中读取页面

从缓存中读取页面大致步骤如下:

  • 后台进程根据所需页面,创建页面的buffer_tag,上述例子buffer_tag为{(1634,16384,16396),0,0}
  • 以buffer_tag作为HASH键,通过HASH函数计算其对应的HASH值,根据HASH值定位到缓存中缓存表对应的HASH桶
  • 获取此HASH桶所在分区的BufMappinglock的共享锁。
  • 查找此HASH桶下的链表,找到buffer_tag为{(1634,16384,16396),0,0}的数据项,从数据项中获取buffer_id,返回给后台进程
  • 后台进程PIN住 该buffer_id对应在缓存池中的buffer,然后释放BufMappinglock
  • 获取缓存区首部锁(其实就是 32位 state 字段的1 bit,22位),将state中的18-21 位进行修改,表示usagecount 加1
  • 获取缓存内容锁共享模式,访问缓存池

2.磁盘中将页面加载到缓存描述符空槽

重启pg数据库以清空缓存

[pg14@test ~]$ pg_ctl restart -D $PGDATA -l /tmp/logfile
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
[pg14@test ~]$ psql -Upostgres -d test 
psql (14.6)
Type "help" for help.

test=#  SELECT * FROM buffercache('t1');
 bufferid | relfork | relblk | isdirty | usagecount | pins 
----------+---------+--------+---------+------------+------
(0 rows)

test=# explain (analyze,buffers,costs off,timing off,summary off) select * from t1;
               QUERY PLAN               
----------------------------------------
 Seq Scan on t1 (actual rows=1 loops=1)
   Buffers: shared read=1
 Planning:
   Buffers: shared hit=16 read=6
(4 rows)

第二行shared  read 表示缓存未命中,即从磁盘中读取页面

假设所需页面不在缓存池中,而且空闲链表有空槽,将页面从磁盘读取到缓存,大致步骤如下:

(1)查找缓存表

  • ​ 后台进程根据所需页面的buffer_tag,并计算其HASH值
  • ​ 以共享模式获取HASH值对应分区的BufMappingLock
  • ​ 查找缓存表,没有找到对应的Tag
  • ​ 释放BufMappingLock

(2)从freelist 中获取空缓存描述符,并将其钉住

(3)以独占模式获取相应分区的BufMappingLock

(4) 创建一条新的缓存表数据条目 ,(后台进程所需页面的buffer_tag,和钉住的空缓存描述符的buffer_id),并将其连链接对应的HASH 桶链表

(5)获取Buffer I/O锁,buffer io锁不是真正意义上的锁,只是state字段的一个标识位(26位),将所需页面从存储加载到缓存池槽中,修改,修改relcount、usagecount、io标识位(三个操作组合成一个原子操作)。原子操作完成,Buffer I/O锁也就释放了。

(6) 释放相应分区的BufMappingLock

(7)获取buffer 内容锁,访问加载到缓存池中的页面

3.磁盘中将页面加载到缓存描述符受害者槽

当PostgreSQL实例刚启动时,freelist很多,随着不断地从磁盘中缓存页面到缓存池中,freelist最终会被占满,这是就需要选已经被使用的buffer,被选中的buffer 就是“受害者”。PostgreSQL中寻找“受害者”的算法是时钟算法。

时钟算法

时钟的含义就是像时钟的指针一样,一遍一遍的遍历所有的缓存描述符。这个指针在代码的名称是nextVictimBuffer,也称为clock hand。它指向上一次受害者的下一位描述符。当后台进程需要将页面从磁盘读取到内存中,而且缓存描述符列表又没有空槽是,时钟就开始了,步骤如下:

  • 获取nextVictimBuffer指针指向的候选缓存区描述符
  • 缓存描述符为unpin:如果usage_count 为0 则该缓存区描述符对应的缓存池槽就是”受害者“,否则,usage_count 减一,并迭代至下一缓缓存区描述符
  • 缓存描述符为pin:迭代至下一缓存区描述符,并重复检查下一描述符的pin状态,直至找到受害者

usage count 初始为0,当页面第一次加入到缓存池时,对应的缓存描述符的 usage count 加一。再次被访问(查询或修改),再加一。为了避免指针循环次数太多,usage count最大值为5 。

缓存管理器将页面从磁盘加载到缓存池受害者槽步骤如下:

(1)查找缓存表没有找到

(2)时钟算法扫描缓存区描述符,找到受害者缓存区描述符,从受害者缓存表获取buffer_id,并在缓存池中将其PIN住。

(3)如果受害者是脏页,将其数据写入到磁盘

(4)以排他模式获取缓存表旧表项所在分区的BufMappingLock,

(5) 获取新表项所在分区的将新表项BufMappingLock,插入到此缓存表的链表

(6)将旧表项从缓存表链表中删除,释放旧表项的BufMappingLock。

(7) 将目标页数据从存储加载至受害者槽位,用受害者槽位的buffer_id 更新描述符的标识位,将脏位设置为0,。

(8) 释放缓存表上新表项所在分区的BufMappingLock

(9) 访问对应的缓存区槽位

二、缓存区的写出

当脏页被选定为受害者时,需要将缓存区的脏页写入存储,写出步骤如下:

  • 获取对应缓存描述符上的共享内容锁,并buffer IO标识位设为1。
  • 根据具体情况,调用XlogFlush()函数,将WAL缓存区上的WAL数据写入到当前的WAL段文件,
  • 将脏页数据写到磁盘中
  • 更新buffer IO 标识位为0,并释放缓存描述符上的内容锁

标签:缓存,HASH,03PG,写入,buffer,描述符,受害者,页面
From: https://blog.csdn.net/qq_29431123/article/details/140901485

相关文章

  • redis缓存雪崩
    Redis缓存雪崩是指在短时间内大量缓存数据同时失效,导致原本应该由缓存承担的请求流量突然涌向后端数据库或其他数据源,从而给后端系统带来巨大压力,可能导致数据库超负荷甚至崩溃的现象。 缓存雪崩通常发生在以下几种情况:-大量数据设置了相同的过期时间,在同一时刻失效。-......
  • 缓存击穿和缓存穿透
    缓存击穿和缓存穿透都是缓存系统中可能出现的问题,但它们的原因和解决方法有所不同。 ###缓存击穿缓存击穿通常发生在高并发场景下,当某个热门数据的缓存刚好过期时,大量请求同时到达,发现缓存中没有数据,因此这些请求会直接落到后端数据库,导致数据库瞬间压力激增,这种情况称为......
  • 如何理解先删除缓存还是先修改数据库。
        针对这个问题,其实反过来更好理解,即“先删除缓存还是先修改数据库能保证数据一致”变为“数据不一致的条件是什么”,好,现在就经过第一步转换了,接下来就解决这个问题。    数据不一致其实就是在经过缓存删除和数据库修改变化后缓存中是旧数据,数据库是新数据。更新......
  • 攻破工程级复杂缓存难题--企业实战
    缓存技术在现代分布式系统中至关重要,不仅提升了系统性能,还减轻了后端数据库的压力。然而,缓存系统也面临着诸多挑战,如缓存穿透、缓存雪崩、缓存击穿和热点key问题。通过多种策略的综合应用,包括本地缓存、双缓存方案、多级缓存、多副本、热点key拆分和动态分散等,可以有效应对这些......
  • 攻破工程级复杂缓存难题--企业实战
    缓存技术在现代分布式系统中至关重要,不仅提升了系统性能,还减轻了后端数据库的压力。然而,缓存系统也面临着诸多挑战,如缓存穿透、缓存雪崩、缓存击穿和热点key问题。通过多种策略的综合应用,包括本地缓存、双缓存方案、多级缓存、多副本、热点key拆分和动态分散等,可以有效应对这些......
  • Linux: 利用缓存优化Docker镜像构建
    在现代软件开发中,容器化应用程序已成为标准做法之一。Docker作为容器技术的领军者,其高效的构建和部署能力深受开发者喜爱。然而,随着项目复杂度的增加,构建Docker镜像的时间也在不断增加。合理利用Docker的缓存机制,可以大大提高镜像构建的速度。接下来将详细介绍如何通过优化......
  • 在flask缓存中指定每个函数的缓存阈值(执行函数时清除旧值)
    使用flask-caching时memoize的默认行为是如果指定的超时已到则执行该函数,然后返回并保存新结果。但是,现在过时的(之前缓存的)结果不会被删除。这对于大多数应用程序来说没问题,但如果缓存结果很大,就会出现问题。flask-caching暴露CACHE_THRESHOLD......
  • 基于STC8H系列单片机的串口1打印片内EEPROM擦除数据、读取数据、写入数据功能调试
    基于STC8H系列单片机的串口1打印片内EEPROM擦除数据、读取数据、写入数据功能调试STC8H4K64TL单片机介绍STC8H4K64TL单片机管脚图(48个引脚)STC8H4K64TL单片机串口仿真与串口通信STC8H4K64TL单片机管脚图(32个引脚)STC8H4K64TL单片机管脚图(20个引脚)STC8H系列单片机管脚......
  • 全面击破工程级复杂缓存难题
    目录一、走进业务中的缓存(一)本地缓存(二)分布式缓存二、缓存更新模式分析(一)CacheAsidePattern(旁路缓存模式)读操作流程写操作流程流程问题思考问题1:为什么不是先删缓存,再更新数据库?问题2:为什么更新操作是将cache失效,而不是更新?CacheAsidePattern模式不一致问题的发......
  • Spring学习(四)三级缓存
    至此,总结一下三级缓存:singletonObjects:缓存经过了完整生命周期的beanearlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入early......