PostgreSQL码农集散地

PostgreSQL 18 preview - 高效驱逐unpinned page

PostgreSQL 18 preview - pg_buffercache 新增高效驱逐shared buffer unpinned page的函数接口pg_buffercache_evict_{relation,all}

PostgreSQL 18 pg_buffercache 插件 ( https://www.postgresql.org/docs/devel/pgbuffercache.html ) 新增 pg_buffercache_evict_{relation,all} 函数接口, 用于快速从shared buffer驱逐未使用的buffer.  常用于测试场景.

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=dcf7e1697ba75ce7883ad7c6bc26ba24422eb892

Add pg_buffercache_evict_{relation,all} functions master github/master  
author Andres Freund <[email protected]>   
Tue, 8 Apr 2025 06:16:51 +0000 (02:16 -0400)  
committer Andres Freund <[email protected]>   
Tue, 8 Apr 2025 06:19:32 +0000 (02:19 -0400)  
commit dcf7e1697ba75ce7883ad7c6bc26ba24422eb892  
tree 6aeadaaafac3658ce47f4cdb88df31b0f9add807 tree  
parent d69d45a5a956e930dc91b3ca09a0188bf9fe2176 commit | diff  
Add pg_buffercache_evict_{relation,all} functions

In addition to the added functions, the pg_buffercache_evict() function now  
shows whether the buffer was flushed.  

pg_buffercache_evict_relation(): Evicts all shared buffers in a  
relation at once.  
pg_buffercache_evict_all(): Evicts all shared buffers at once.  

Both functions provide mechanism to evict multiple shared buffers at  
once. They are designed to address the inefficiency of repeatedly calling  
pg_buffercache_evict() for each individual buffer, which can be time-consuming  
when dealing with large shared buffer pools. (e.g., ~477ms vs. ~2576ms for
16GB of fully populated shared buffers).  

These functions are intended for developer testing and debugging  
purposes and are available to superusers only.  

Minimal tests for the new functions are included. Also, there was no testfor
pg_buffercache_evict(), testfor this added too.  

No new extension version is needed, as it was already increased this release  
by ba2a3c2302f.  

Author: Nazir Bilal Yavuz <[email protected]>  
Reviewed-by: Andres Freund <[email protected]>  
Reviewed-by: Aidar Imamov <[email protected]>  
Reviewed-by: Joseph Koshakow <[email protected]>  
Discussion: https://postgr.es/m/CAN55FZ0h_YoSqqutxV6DES1RW8ig6wcA8CR9rJk358YRMxZFmw%40mail.gmail.com  

AI 解读

好的,结合您提供的更详细的函数说明,我们来重新解读一下这个 PostgreSQL 补丁。

代码作用概述 (Patch Summary)

这个补丁显著增强了 pg_buffercache 扩展的功能,旨在提高开发者在测试和调试场景下操作共享缓冲区(Shared Buffers)的效率和信息透明度。它主要做了三件事:

  1. 新增 pg_buffercache_evict_relation 函数: 允许用户一次性尝试驱逐(evict)属于特定关系(包括其所有 fork,如主数据、FSM、VM)的所有缓冲区。
  2. 新增 pg_buffercache_evict_all 函数: 允许用户一次性尝试驱逐整个共享缓冲区池中的所有缓冲区。
  3. 增强 pg_buffercache_evict 函数: 修改了原有的单缓冲区驱逐函数,使其返回更详细的信息,包括是否成功驱逐以及缓冲区是否被写回(flush)。

这些功能解决了通过反复调用旧版 pg_buffercache_evict 来清空大量缓冲区效率低下的问题,并提供了关于操作结果的更精确反馈。

详细解读 (Detailed Interpretation)

  1. 增强的 pg_buffercache_evict(bufferid int):

  • buffer_evicted (boolean):
  • buffer_flushed (boolean):
  • 该 bufferid 对应的缓冲区当前无效(可能已经被驱逐或从未被使用)。
  • 缓冲区被锁定(pinned),意味着有进程正在活跃地使用它,不能被驱逐。
  • 尝试将脏缓冲区写回磁盘后,该缓冲区再次变脏(可能因为并发写入),导致驱逐尝试失败。
  • true: 表示缓冲区成功被驱逐。
  • false: 表示驱逐失败。失败的原因可能是:
  • true: 表示该缓冲区的内容(如果是脏页)已被写回磁盘。
  • 重要 caveat: 这不一定是本次 pg_buffercache_evict 调用触发的写回。它可能是在此调用之前或期间由其他后台进程(如 checkpointer 或 background writer)或并发操作完成的。它只表明在函数检查时,该缓冲区不再是脏页。
  • 目的: 尝试驱逐由 bufferid 标识的单个共享缓冲区。
  • 返回值: 返回一个包含两列的记录:
  • 时效性: 函数返回的结果立即就可能过时,因为并发活动可能随时重新加载或修改该缓冲区。
  • 用途: 仅供开发者测试使用。
  • 新增的 pg_buffercache_evict_relation(relation regclass):

    • buffers_evicted (bigint): 成功驱逐的缓冲区数量。
    • buffers_flushed (bigint): 被写回磁盘的缓冲区数量。同样,这不保证是由此函数调用触发的写回。
    • buffers_not_evicted (bigint): 未能成功驱逐的缓冲区数量(例如因为被 pinned)。
    • 目的: 接收一个关系标识符(表或索引的名称或 OID),尝试驱逐该关系所有 fork(主数据、空闲空间映射 FSM、可见性映射 VM)在共享缓冲区中的所有页面。
    • 返回值: 返回一个包含三列的记录:
    • 效率: 这是对旧方法(循环调用 pg_buffercache_evict)的主要改进,通过一次调用处理整个关系,减少了函数调用开销和内部锁竞争。
    • 时效性: 结果同样会立即过时,因为缓冲区可能被并发地读回。
    • 用途: 仅供开发者测试使用。
  • 新增的 pg_buffercache_evict_all():

    • buffers_evicted (bigint): 成功驱逐的缓冲区数量。
    • buffers_flushed (bigint): 被写回磁盘的缓冲区数量(同样不保证是此调用触发)。
    • buffers_not_evicted (bigint): 未能成功驱逐的缓冲区数量。
    • 目的: 不接收参数,尝试驱逐整个共享缓冲区池中的所有缓冲区。
    • 返回值: 与 pg_buffercache_evict_relation 类似,返回三列:
    • 效率: 提供了一种高效清空整个缓存的方法,适用于需要模拟完全冷缓存启动的测试场景。
    • 时效性: 结果同样会立即过时。
    • 用途: 仅供开发者测试使用。

    核心优势与动机 (Core Advantage & Motivation)

    • 效率提升: 新增的批量驱逐函数 (_relation, _all) 显著优于对大量缓冲区反复调用单次驱逐函数,大大缩短了清空缓存所需的时间,特别是在大型共享缓冲区环境中。
    • 更丰富的反馈: 增强后的 _evict 和新的批量函数提供了关于驱逐成功与否、写回状态以及无法驱逐数量的明确信息,有助于开发者理解缓存操作的实际效果和遇到的限制(如 pinned buffers)。

    重要注意事项 (Important Considerations)

    • 开发者工具: 这些函数被明确设计为仅供开发者测试和调试使用,不应用于生产环境的常规操作。
    • 超级用户权限: 执行这些函数需要超级用户权限,因为它们直接干预底层的缓存管理,可能对性能产生显著(通常是负面)影响。
    • 结果时效性: 所有这些函数返回的结果都具有瞬时性,在高并发系统中可能在返回后立即失效。它们反映的是函数执行检查那个时间点的状态快照。

    总结 (Conclusion)

    这个补丁通过提供高效的批量缓存驱逐接口 (pg_buffercache_evict_relation, pg_buffercache_evict_all) 并增强现有单缓冲区驱逐函数 (pg_buffercache_evict) 的反馈信息,极大地改善了 pg_buffercache 扩展在开发者测试和调试场景下的可用性。它解决了旧方法效率低下的问题,并提供了更精确的操作结果视图,但用户必须清楚这些工具的用途限制和权限要求。


    详细解释一下 PostgreSQL 共享缓冲区(Shared Buffers)上下文中 “unpinned” 的含义。 参考: https://www.interdb.jp/pg/pgsql08.html

    理解 "unpinned" 的关键在于先理解它的反面:“pinned”(钉住,固定的)。

    Pinned (钉住/引用计数 > 0)

    1. 含义: 当一个后端进程(backend process,即处理用户查询或执行内部任务的进程)需要读取或修改共享缓冲区中的某个页面(buffer)时,它必须首先确保这个缓冲区在它操作期间不会被意外地“偷走”或重用。为了做到这一点,进程会对该缓冲区进行“pin”操作。
    2. 实现机制: 这通常是通过增加缓冲区头部(Buffer Header)中的一个引用计数(reference count,通常称为 pin count 或 refcount)来实现的。每当一个进程需要访问缓冲区时,它会增加这个计数;当它完成访问时,它会减少这个计数。
    3. 作用:
    • 防止被驱逐 (Eviction Prevention): 只要一个缓冲区的引用计数大于 0(即它是 "pinned" 状态),缓冲区的替换策略(如 CLOCK-sweep 算法)或显式的驱逐函数(如 pg_buffercache_evict*)就不能选择这个缓冲区作为“牺牲品”来为新的页面腾出空间。这是最重要的作用——保证正在被使用的内存不会被回收。
    • 协调并发访问: Pinning 也作为一种基础机制,与其他锁(如 content lock)配合,确保在读取或修改缓冲区内容时的基本安全。

    Unpinned (未钉住/引用计数 = 0)

    1. 含义: 当一个缓冲区的引用计数(pin count)为 0 时,它就处于 "unpinned" 状态。
    2. 状态解读: 这意味着当前没有任何后端进程持有对该缓冲区的有效引用(pin)。换句话说,在这一时刻,没有进程正在直接、活跃地读取或写入这个缓冲区的数据(至少不是通过持有 pin 的方式)。
    3. 重要意义(尤其对驱逐而言):
    • 成为驱逐候选者:只有处于 unpinned 状态的缓冲区,才能被缓存替换算法或 pg_buffercache_evict* 函数考虑作为驱逐的对象。 如果一个缓冲区是 pinned 状态(pin count > 0),pg_buffercache_evict 函数尝试驱逐它时就会失败,并在返回结果中将 buffer_evicted 标记为 false,或者在批量驱逐函数中增加 buffers_not_evicted 的计数。
    • 不代表未使用或无效: 一个 unpinned 的缓冲区不一定是空的或包含无效数据。它可能仍然包含着有用的、最近从磁盘读取的数据,只是暂时没有进程在操作它。它随时可能被其他进程再次 pin 住并使用。
    • 不代表是干净的 (Clean): 一个 unpinned 的缓冲区也可能是“脏”的(dirty),即其内容已被修改但尚未写回磁盘。如果要驱逐一个 unpinned 但 dirty 的缓冲区,系统必须先将其内容写回(flush)到磁盘。

    与 Patch 的关联

    在解读 pg_buffercache_evict* 函数的行为时,"unpinned" 是一个核心概念:

    • 这些函数尝试驱逐缓冲区时,首要的检查之一就是目标缓冲区是否 unpinned。
    • 如果缓冲区是 pinned 状态,驱逐操作会失败,因为它正被活跃使用。这就是为什么函数返回值需要区分“成功驱逐”和“未能驱逐”的情况。
    • 一个缓冲区可能因为各种原因无法被驱逐,但“被 pinned”是最常见和最主要的原因之一。

    简单类比:

    想象一下图书馆的书架(共享缓冲区)和读者(后端进程)。

    • Pinned: 一个读者正在拿着一本书阅读。这本书被“钉”在了这个读者手上,图书管理员(缓存管理器)不能把它放回书库深处或借给别人。这本书的 pin count > 0。
    • Unpinned: 一本书静静地放在书架上。当前没有读者在阅读它。这本书的 pin count = 0。图书管理员现在可以考虑把它移走(驱逐)以腾出空间放新书。但这本书本身可能还是很有价值的,随时可能有下一个读者来取走(再次 pin 住)。

    总结:

    在 PostgreSQL 共享缓冲区管理中,“unpinned”状态意味着一个缓冲区的引用计数为零,表示当前没有进程持有对它的活跃引用。这是该缓冲区能够被缓存替换算法或显式驱逐函数(如 pg_buffercache_evict*)选为驱逐对象的先决条件。如果一个缓冲区被 "pinned"(引用计数 > 0),则无法被驱逐。