shard_buffers 共享缓冲区
默认值:128MB
设置数据库服务使用的共享内存缓冲区数量。
注意,这里说的是数量,是因为缓冲区大小=数量*块大小。默认的块大小,block_size是8192字节,就是8KB。shared_buffers的默认值是16384,也就是2的14次方。
shared_buffers是 PostgreSQL 最重要的内存配置参数之一,它决定了 PostgreSQL 使用多少内存来缓存数据库表和索引数据。这些缓存数据可以显著提高查询性能,因为 PostgreSQL 不需要每次查询都从磁盘读取数据。
-
只能通过重启生效。
-
合理设置值,是服务器总内存的25%。
-
如果设置过大:
- 内存浪费:
- 设置过高会导致大量内存被 PostgreSQL 占用,而未被充分利用
- 可能导致系统整体性能下降,因为其他进程可用内存减少
- 检查点延迟:
- 过大的
shared_buffers会导致 WAL 日志增长过快 - 可能增加检查点操作的频率和延迟
- 过大的
- 内存碎片:
- 大量连续内存分配可能导致内存碎片
- 影响操作系统的内存管理效率
- 启动时间增加:
- 更多的缓冲区需要更长时间加载到内存
- 内存浪费:
-
如果设置过低
- 缓存命中率低:
- 导致大量磁盘 I/O,严重影响性能
- 查询响应时间变长
- 缓冲区不足:
- 在高并发环境下可能导致缓冲区不足
- 需要频繁清理缓存,导致性能抖动
- 索引效率降低:
- 索引数据无法完全缓存在内存中
- 索引扫描效率下降
- 临时文件增加:
- 复杂查询可能需要更多临时工作内存
- 导致更多临时文件 I/O
- 缓存命中率低:
延申解读:
- wal日志:
- WAL(Write-Ahead Logging,预写式日志):这是 PostgreSQL 确保数据持久性和一致性的核心机制。任何数据文件的修改,都必须先被记录到 WAL 日志中,然后才能写回数据文件。这样即使数据库崩溃,也能用 WAL 日志来重放操作,恢复数据。
-
检查点
-
检查点(Checkpoint)
:一个定期发生的事件,主要做两件事:
- 将
shared_buffers中所有累积的脏页**刷写(Flush)**到磁盘的数据文件中,确保数据持久化。 - 在 WAL 日志中创建一个标记点,表示在这个点之前的所有数据修改都已经安全落盘。这个点之前的 WAL 日志就可以被回收或覆盖了。
- 将
-
-
shard_buffers设置过大,导致wal日志增长过快
好的,这是一个 PostgreSQL 中非常重要且典型的性能调优问题。我们来分步解释这个现象。
核心解释
“过大的
shared_buffers会导致 WAL 日志增长过快” 这一现象的核心原因在于:
shared_buffers太大,导致大量脏页在内存中堆积,当检查点发生时,这些脏页必须在短时间内集中刷写到磁盘,这会触发大量的“全页写入”进入 WAL 日志,从而造成 WAL 日志量暴增。下面我们来拆解这个逻辑链条:
1. 基本概念
shared_buffers:PostgreSQL 最主要的内存缓存区域,用于缓存数据页。所有数据的读写都首先在这里进行。一个被修改过的数据页称为“脏页”。- WAL(Write-Ahead Logging,预写式日志):这是 PostgreSQL 确保数据持久性和一致性的核心机制。任何数据文件的修改,都必须先被记录到 WAL 日志中,然后才能写回数据文件。这样即使数据库崩溃,也能用 WAL 日志来重放操作,恢复数据。
- 检查点(Checkpoint):一个定期发生的事件,主要做两件事:
- 将
shared_buffers中所有累积的脏页**刷写(Flush)**到磁盘的数据文件中,确保数据持久化。 - 在 WAL 日志中创建一个标记点,表示在这个点之前的所有数据修改都已经安全落盘。这个点之前的 WAL 日志就可以被回收或覆盖了。
- 将
2. 关键机制:“全页写入”
这是理解整个问题的钥匙。PostgreSQL 有一个重要的安全特性:在每次检查点之后,一个数据页第一次被修改时,整个数据页(通常是 8KB)的完整映像会被写入 WAL 日志。
- 为什么需要这个? 为了防止“部分页面写入”。例如,操作系统可能正在将一个 8KB 的页面写入磁盘时发生停电,导致只写入了 4KB。这个页面就损坏了。下次恢复时,WAL 日志里如果只记录了这个页面的增量修改(如“把A字段从1改成2”),是无法修复一个已损坏的基础页面的。而有了整个页面的完整拷贝,就可以在恢复时直接覆盖该页面,保证其完整性。
full_page_writes:控制这个行为的参数,默认为on。
3. 链条推演:过大的
shared_buffers如何导致 WAL 暴涨现在,我们把以上概念串联起来:
-
设置过大的
shared_buffers:- 假设你设置了远超常用数据量的
shared_buffers(比如,数据库热点数据只有 10GB,你却设置了 16GB 的shared_buffers)。 - 这导致大量数据页可以长期驻留在内存中。在一个更新频繁的系统中,这些驻留的页面会被反复修改,产生海量的脏页。
- 假设你设置了远超常用数据量的
-
检查点触发:
- 根据配置(
checkpoint_timeout或max_wal_size),检查点会定期启动。 - 在检查点期间,PostgreSQL 需要将所有脏页刷写到磁盘。由于
shared_buffers过大,累积的脏页数量极为庞大。
- 根据配置(
-
“全页写入”的雪崩效应:
- 在检查点之后,所有在上个检查点周期内被修改过的页面,都会在下一次修改时触发一次“全页写入”。
- 由于大量的脏页在检查点时刻被集中刷盘,这意味着检查点刚结束时,几乎
shared_buffers中的所有活跃页都刚刚被清理为“干净页”。 - 检查点一过,业务继续进行,这些页面又开始被修改。因为它们都位于“上一个检查点之后”,所以对这些页面的第一次修改,每一个都会产生一个完整的 8KB 页面映像写入 WAL!
- 想象一下,如果检查点瞬间刷了 10GB 的脏页,那么在检查点后的很短时间内,对这 10GB 数据页的任何修改,都会产生 10GB 的 WAL 日志(并且这部分 WAL 是“全页写入”产生的,体积巨大),而不是通常的仅有少量增量数据的 WAL 记录。
-
最终结果:
- 这导致了 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. 如何避免和调优
-
合理设置
shared_buffers:- 通常建议设置为系统内存的 25%。对于专用数据库服务器,可以提高到 30-40%,但很少需要超过总内存的一半。
- 更重要的是,它的值应该与你的活跃数据集大小相匹配。如果活跃数据只有 5GB,那么 16GB 的
shared_buffers就是巨大的浪费和隐患。
-
调整检查点相关参数:
max_wal_size:增加此值,可以让检查点间隔更长,平滑写入压力,但会延长恢复时间。checkpoint_timeout:同样,增加超时时间可以拉长检查点周期。checkpoint_completion_target:设置为0.9左右,让检查点的刷盘工作更均匀地分布在整个检查点周期内,避免在周期末的“紧急冲刺”。
-
评估
full_page_writes:- 在具有断电保护功能的电池后备写缓存(BBWC)的 RAID 控制器或存储设备上,在充分理解风险后,可以考虑将
full_page_writes设置为off,这能直接消除“全页写入”带来的 WAL 膨胀。但这会降低数据的安全性,需谨慎。
- 在具有断电保护功能的电池后备写缓存(BBWC)的 RAID 控制器或存储设备上,在充分理解风险后,可以考虑将
总结
简单来说:
shared_buffers不是越大越好。过大的shared_buffers会像一个大水库,在检查点这个“开闸泄洪”的时刻,导致下游的 WAL 河道瞬间流量激增。 正确的做法是根据业务负载和硬件资源,平衡地设置内存参数和检查点参数,让数据写入能够平稳、持续地进行。
huge_pages 大数据页
默认值:try
建议:直接设置为off。如果你知道这个参数含义的话,可以调整为on,不建议使用try。
对于 PostgreSQL 的
huge_pages参数,它的内涵是:
- 一种内存分配模式:它让 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中如果期望使用,需要有以下前提或者同步设置
-
明确shared_buffers设置值
-
查询当前系统的大页Size,比如Linux
grep Hugepage /proc/meminfo
-
计算所需页数:比如2MB,shared_buffers/2MB
-
/etc/sysctl.conf文件并设置vm.nr_hugepages。数量应略大于您的shared_buffers所需的总内存量 -
系统配置生效,
sysctl -p -
pg配置生效,修改为on
-
重启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
性能表现
|
|--- 事务吞吐量 (可能提升)
|
|--- 查询延迟 (可能降低)
|
|--- 系统整体稳定性 (需确保配置正确)