shard_buffers 共享缓冲区

默认值:128MB

设置数据库服务使用的共享内存缓冲区数量。

注意,这里说的是数量,是因为缓冲区大小=数量*块大小。默认的块大小,block_size是8192字节,就是8KB。shared_buffers的默认值是16384,也就是2的14次方。

shared_buffers 是 PostgreSQL 最重要的内存配置参数之一,它决定了 PostgreSQL 使用多少内存来缓存数据库表和索引数据。这些缓存数据可以显著提高查询性能,因为 PostgreSQL 不需要每次查询都从磁盘读取数据。

延申解读:

  1. wal日志:
  • WAL(Write-Ahead Logging,预写式日志):这是 PostgreSQL 确保数据持久性和一致性的核心机制。任何数据文件的修改,都必须先被记录到 WAL 日志中,然后才能写回数据文件。这样即使数据库崩溃,也能用 WAL 日志来重放操作,恢复数据。
  1. 检查点

    • 检查点(Checkpoint)

      :一个定期发生的事件,主要做两件事:

      1. shared_buffers 中所有累积的脏页**刷写(Flush)**到磁盘的数据文件中,确保数据持久化。
      2. 在 WAL 日志中创建一个标记点,表示在这个点之前的所有数据修改都已经安全落盘。这个点之前的 WAL 日志就可以被回收或覆盖了。
  2. shard_buffers设置过大,导致wal日志增长过快

    好的,这是一个 PostgreSQL 中非常重要且典型的性能调优问题。我们来分步解释这个现象。

    核心解释

    “过大的 shared_buffers 会导致 WAL 日志增长过快” 这一现象的核心原因在于:
    shared_buffers 太大,导致大量脏页在内存中堆积,当检查点发生时,这些脏页必须在短时间内集中刷写到磁盘,这会触发大量的“全页写入”进入 WAL 日志,从而造成 WAL 日志量暴增。

    下面我们来拆解这个逻辑链条:


    1. 基本概念

    • shared_buffers:PostgreSQL 最主要的内存缓存区域,用于缓存数据页。所有数据的读写都首先在这里进行。一个被修改过的数据页称为“脏页”。
    • WAL(Write-Ahead Logging,预写式日志):这是 PostgreSQL 确保数据持久性和一致性的核心机制。任何数据文件的修改,都必须先被记录到 WAL 日志中,然后才能写回数据文件。这样即使数据库崩溃,也能用 WAL 日志来重放操作,恢复数据。
    • 检查点(Checkpoint):一个定期发生的事件,主要做两件事:
      1. shared_buffers 中所有累积的脏页**刷写(Flush)**到磁盘的数据文件中,确保数据持久化。
      2. 在 WAL 日志中创建一个标记点,表示在这个点之前的所有数据修改都已经安全落盘。这个点之前的 WAL 日志就可以被回收或覆盖了。

    2. 关键机制:“全页写入”

    这是理解整个问题的钥匙。PostgreSQL 有一个重要的安全特性:在每次检查点之后,一个数据页第一次被修改时,整个数据页(通常是 8KB)的完整映像会被写入 WAL 日志。

    • 为什么需要这个? 为了防止“部分页面写入”。例如,操作系统可能正在将一个 8KB 的页面写入磁盘时发生停电,导致只写入了 4KB。这个页面就损坏了。下次恢复时,WAL 日志里如果只记录了这个页面的增量修改(如“把A字段从1改成2”),是无法修复一个已损坏的基础页面的。而有了整个页面的完整拷贝,就可以在恢复时直接覆盖该页面,保证其完整性。
    • full_page_writes:控制这个行为的参数,默认为 on

    3. 链条推演:过大的 shared_buffers 如何导致 WAL 暴涨

    现在,我们把以上概念串联起来:

    1. 设置过大的 shared_buffers

      • 假设你设置了远超常用数据量的 shared_buffers(比如,数据库热点数据只有 10GB,你却设置了 16GB 的 shared_buffers)。
      • 这导致大量数据页可以长期驻留在内存中。在一个更新频繁的系统中,这些驻留的页面会被反复修改,产生海量的脏页
    2. 检查点触发

      • 根据配置(checkpoint_timeoutmax_wal_size),检查点会定期启动。
      • 在检查点期间,PostgreSQL 需要将所有脏页刷写到磁盘。由于 shared_buffers 过大,累积的脏页数量极为庞大。
    3. “全页写入”的雪崩效应

      • 在检查点之后,所有在上个检查点周期内被修改过的页面,都会在下一次修改时触发一次“全页写入”。
      • 由于大量的脏页在检查点时刻被集中刷盘,这意味着检查点刚结束时,几乎 shared_buffers 中的所有活跃页都刚刚被清理为“干净页”。
      • 检查点一过,业务继续进行,这些页面又开始被修改。因为它们都位于“上一个检查点之后”,所以对这些页面的第一次修改,每一个都会产生一个完整的 8KB 页面映像写入 WAL
      • 想象一下,如果检查点瞬间刷了 10GB 的脏页,那么在检查点后的很短时间内,对这 10GB 数据页的任何修改,都会产生 10GB 的 WAL 日志(并且这部分 WAL 是“全页写入”产生的,体积巨大),而不是通常的仅有少量增量数据的 WAL 记录。
    4. 最终结果

      • 这导致了 WAL 日志生成速率在检查点后出现一个巨大的尖峰
      • WAL 日志文件会迅速被填满,需要频繁地进行归档或清理。
      • 如果归档速度跟不上,可能会导致磁盘空间被撑满,数据库挂起。
    flowchart TD
        A[设置过大的 shared_buffers] --> B[内存中可驻留大量数据页]
        B --> C[高负载下产生海量脏页]
        C --> D[检查点触发_集中刷写所有脏页]
        D --> E[检查点完成后<br>所有活跃页都变为'干净页']
        E --> F[检查点后业务继续<br>对这些页的首次修改]
        F --> G{触发全页写入机制}
        G --> H[将整个页面的完整映像<br>写入 WAL 日志]
        H --> I[WAL 日志量产生尖峰式暴增]
        I --> J[磁盘I/O压力剧增<br>WAL归档/清理压力大<br>有磁盘爆满风险]
    

    4. 连带问题

    • I/O 风暴:不仅 WAL 写入暴增,检查点本身集中刷脏页也会造成巨大的磁盘 I/O 压力,可能导致业务查询性能骤降(“检查点抖动”)。
    • 恢复时间变长:如果发生故障,需要重放如此庞大的 WAL 日志,恢复时间会很长。

    5. 如何避免和调优

    1. 合理设置 shared_buffers

      • 通常建议设置为系统内存的 25%。对于专用数据库服务器,可以提高到 30-40%,但很少需要超过总内存的一半。
      • 更重要的是,它的值应该与你的活跃数据集大小相匹配。如果活跃数据只有 5GB,那么 16GB 的 shared_buffers 就是巨大的浪费和隐患。
    2. 调整检查点相关参数

      • max_wal_size:增加此值,可以让检查点间隔更长,平滑写入压力,但会延长恢复时间。
      • checkpoint_timeout:同样,增加超时时间可以拉长检查点周期。
      • checkpoint_completion_target:设置为 0.9 左右,让检查点的刷盘工作更均匀地分布在整个检查点周期内,避免在周期末的“紧急冲刺”。
    3. 评估 full_page_writes

      • 在具有断电保护功能的电池后备写缓存(BBWC)的 RAID 控制器或存储设备上,在充分理解风险后,可以考虑将 full_page_writes 设置为 off,这能直接消除“全页写入”带来的 WAL 膨胀。但这会降低数据的安全性,需谨慎。

    总结

    简单来说:shared_buffers 不是越大越好。过大的 shared_buffers 会像一个大水库,在检查点这个“开闸泄洪”的时刻,导致下游的 WAL 河道瞬间流量激增。 正确的做法是根据业务负载和硬件资源,平衡地设置内存参数和检查点参数,让数据写入能够平稳、持续地进行。

huge_pages 大数据页

默认值:try

建议:直接设置为off。如果你知道这个参数含义的话,可以调整为on,不建议使用try。

  1. 对于 PostgreSQL 的 huge_pages 参数,它的内涵是:

    1. 一种内存分配模式:它让 PostgreSQL 在启动时,尝试使用操作系统提供的“大页”机制来分配共享内存(主要是 shared_buffers 使用的区域),而不是默认的标准 4KB 内存页。

本质是操作系统(这里是Linux和Windows)提供了虚拟内存和物理内存映射表(TLB),由于默认数据页比较小(4KB),数据多的话,这个映射表就会比较大,整个性能就会被拖累比较差。这里的改进思路就是提供HugePage,2MB的数据页,映射表就变小,效率提高。

定义

想象一下,你的电脑内存就像一个大仓库,里面堆满了各种箱子(数据)。操作系统(仓库管理员)平时用小推车(默认的 4KB 内存页)来搬运这些箱子。每次搬一点,虽然灵活,但搬得次数多了,管理员(CPU)就得频繁地记录和查找这些小推车放在了哪里(管理页表),非常累人。

huge_pages 就是 PostgreSQL 里的一个配置选项,它告诉数据库:“嘿,别用那些小推车了,咱们换个大卡车来搬箱子吧!” 这个大卡车,就是“大页”(Huge Page),通常指 2MB 或 1GB 大小的内存块。

它的历史来源是为了解决 Translation Lookaside Buffer (TLB) 命中的性能问题。TLB 是 CPU 里的一个“地址速查表”,用来快速把程序看到的“虚拟地址”转换成实际的“物理地址”。如果内存页很小(4KB),要管理同样大小的内存,就需要更多的页表项,TLB 就很容易“查不过来”(TLB Miss),导致 CPU 要去更慢的地方找地址,拖慢速度。使用大页后,同样大小的内存,页表项大大减少,TLB 的命中率就提高了,CPU 干活就更顺畅了。

一个生活化的比喻: 你是一个图书管理员(CPU),负责从巨大的书库(内存)里按读者(PostgreSQL 进程)的要求找书。如果书库的目录(页表)是按每一页纸(4KB页)来编目的,找一本300页的书你可能要翻300条目录。但如果目录是按“章”(2MB大页)来编的,找同样一本书,你只需要翻一两条目录就行了,效率天差地别。huge_pages 就是决定你是否启用这个“按章编目”系统的开关。

PG中如果期望使用,需要有以下前提或者同步设置

  1. 明确shared_buffers设置值

  2. 查询当前系统的大页Size,比如Linux

    grep Hugepage /proc/meminfo
  3. 计算所需页数:比如2MB,shared_buffers/2MB

  4. /etc/sysctl.conf 文件并设置 vm.nr_hugepages。数量应略大于您的 shared_buffers 所需的总内存量

  5. 系统配置生效,sysctl -p

  6. pg配置生效,修改为on

  7. 重启pg

操作系统内存管理
|
|--- 虚拟内存与分页机制
| |
| |--- 标准页 (通常 4KB) ---> [高灵活性,高管理开销]
| |
| |--- 大页 (Huge Pages, 如 2MB/1GB) ---> [高TLB命中率,低管理开销,需预分配]
| |
| |--- 配置 (vm.nr_hugepages)
| |
| |--- 挂载 (hugetlbfs)
|
数据库内存架构 (PostgreSQL)
|
|--- 共享内存 (Shared Memory)
| |
| |--- shared_buffers (缓存池) <--- [huge_pages 主要作用于此]
| |
| |--- wal_buffers
| |
| |--- ... 其他共享区
|
|--- 本地内存 (Local Memory)
| |
| |--- work_mem
| |
| |--- maintenance_work_mem
| |
| |--- ... 每个后端进程私有
|
v
性能表现
|
|--- 事务吞吐量 (可能提升)
|
|--- 查询延迟 (可能降低)
|
|--- 系统整体稳定性 (需确保配置正确)

参考:https://zhuanlan.zhihu.com/p/269756045