首页 » PostgreSQL/GaussDB » PostgreSQL %SYS CPU newfstatat() high 调优一例

PostgreSQL %SYS CPU newfstatat() high 调优一例

最这我们一个客户从oracle迁移到postgresql系的某国产数据库后,CPU一直接近100%, 但是再仔细分析,发现%system CPU占到60%左右,当然这是一种不正常的现象,之前我写过《如何在 Linux 上诊断高 %Sys CPU》, 使用pidstat 确认%sys cpu进程大部分为postgresql进程,pstack 查看发现call, PostgreSQL 的线程大部分时间都在调用 newfstatat(),这 不是正常现象,并且通常意味着数据库运行中存在 频繁的文件状态检查(stat)操作,严重时可能导致性能瓶颈。

什么是 newfstatat()

newfstatat() 是 Linux 的系统调用,用于检查文件状态(类似 stat()lstat() 等)。PostgreSQL 在以下场景中可能频繁调用 newfstatat()

  • 判断 WAL 文件是否存在或过期
  • 检查 relation 文件(如表、索引文件)
  • 目录扫描(如 pg_tblspc, pg_wal, pg_stat_tmp 等)
  • 检查文件是否存在或大小变化(特别是在归档、WAL回放、恢复或重启点期间)
  • 后台进程(如 checkpointer, walwriter, autovacuum, archiver)可能周期性遍历文件

特别是在 WAL 写入或 checkpoint 过程中,PostgreSQL 会频繁检查 pg_wal 目录中的文件状态。频繁调用它 通常表示 PostgreSQL 正在不断访问某些文件或目录的元信息.

如何分析排查?

sys% CPU高存在2种情况,常见是系统级配置,还有一种是局部会话级。当看到CPU 高,部分人是想着赶紧优化SQL,  但是进数据库发现活动用户进程并非多高并发,其次一些较差的应用使用DB总有优化不完的TOP SQL,  如果没有成本可以让你的DBA或乙方厂家在优化SQL上面折腾,或找应用厂家自查,但都效果微乎其微。首先应该定位CPU 使用类型,与触发点。

  • vmstat 或top 查看sys/user CPU占比
  • 明确范围,使用pidstat查找进程,pstack调用堆栈, strace 跟踪函数的调用位置
  • perf 做系统级负载分析

如本案例分析到是newfsatat()函数,能猜到是FS文件系统相关,再查看文件系统负载

使用iostat查看负载,发现数据盘繁忙近100%,根据之前的负载压测基线数据,大概可以判断是否达到了硬件磁盘的IOPS或吞吐量上限,使用iotop找I/O 高的进程。

优化调整

通过上面的排查,发现是I/O 方面问题,并且主要进程为checkpoint进程,下面可以在系统级做一些调优,postgresql本地文件文件确实较多,但inode使用不到10%,之前我记录过<Linux最佳实践for Postgresql/openGauss>在文件系统级有提到,调整文件系统的noatime nodirtime  禁用访问时间 ,可以在线调整,我们调整完后%SYS CPU有明显降低,但是I/O 繁忙率依旧比较高。

Checkpoint 是 PostgreSQL 中重要的后台进程,负责将共享缓冲区中的脏页写入磁盘,并确保事务日志(WAL)的一致性。优化参数主要有

  • checkpoint_timeout 增加此值可减少 checkpoint 频率
  • max_wal_size 控制两次 checkpoint 之间允许的 WAL 最大大小
  • min_wal_size WAL 文件回收时的最小保留大小,应与 max_wal_size 配合调整
  • checkpoint_completion_target 控制在 checkpoint_timeout 内完成 checkpoint 的目标比例

监控 checkpoint 性能

SELECT * FROM pg_stat_bgwriter;

SELECT
  checkpoints_timed, 
  checkpoints_req,
  100.0 * checkpoints_req / (checkpoints_timed + checkpoints_req) AS req_checkpoint_ratio,
  buffers_checkpoint,
  buffers_clean
FROM pg_stat_bgwriter;

关注以下指标:

  • checkpoints_timed – 定时触发的 checkpoint
  • checkpoints_req – 因 WAL 增长触发的 checkpoint
  • buffers_checkpoint – checkpoint 写入的缓冲区数量
  • buffers_clean- 后台写入器清理的缓冲区数量
  • req_checkpoint_ratio < 10% (请求式checkpoint占比低), checkpoint 应由超时触发(checkpoints_timed),而非 WAL 写满触发,尤其是>30%应该增加WAL

优化策略

  1. 减少 checkpoint 频率:增加 checkpoint_timeout 和 max_wal_size
  2. 平滑 checkpoint I/O:提高 checkpoint_completion_target
  3. 平衡恢复时间:确保 max_wal_size 不会导致恢复时间过长
  4. 监控调整:根据 pg_stat_bgwriter 结果持续优化
-- 增加checkpoint间隔(默认5min,可增至15-30min)
ALTER SYSTEM SET checkpoint_timeout = '30min';

-- 允许更多WAL积累(默认1GB,根据磁盘空间调整)
ALTER SYSTEM SET max_wal_size = '8GB';

-- 使checkpoint写入更分散(默认0.5,建议0.7-0.9)
ALTER SYSTEM SET checkpoint_completion_target = 0.9;

查看 WAL 文件统计

  COUNT(*) AS total_wal_files,
  SUM(size) / 1024 / 1024 AS total_size_mb,
  (SELECT setting FROM pg_settings WHERE name = 'max_wal_size') AS max_wal_size
FROM pg_ls_waldir();

检查 WAL 生成速率

SELECT 
  pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') / 1024 / 1024 AS total_wal_mb,
  (SELECT (sum(blks_hit)+sum(blks_read)) FROM pg_stat_database) AS total_io,
  (SELECT extract(epoch from now() - pg_postmaster_start_time()) / 3600 AS hours_up);

优化高 WAL 生成的查询

SELECT query, wal_bytes FROM pg_stat_statements ORDER BY wal_bytes DESC LIMIT 10;

如果太多 WAL 文件/频繁 checkpoint, 提高 max_wal_size,降低 checkpoint_timeout,提高 checkpoint_completion_targetj.  我们把max_wal_size 从20G 调到400G后,明显磁盘的使用率降了下来。

检查relation 文件

除了WAL清理外,还有可能是检查relation 文件是否存在时触发newfsatat, 下面测试

-- session 1
select pg_backend_pid();

-- session 2 
strace -p xxx -o s2.o

-- session 1
select big query....

创建了一个大分区表,确认有多个relation文件, 然后在执行此表的关连查询,另一个会话使用strace 跟踪,后面 查看newfsatat函数。发现newfsatat调用次数与表数据的文件个数并不成正比,而当join时有调用newfsatat.

# awk -F"(" '{print $1}' s2.o|sort|uniq -c|sort -nk 1
      1 fallocate
      1 ftruncate
      1 mmap
      1 munmap
      2 epoll_pwait
      2 kill
      2 newfstatat
      2 rt_sigprocmask
      3 recvfrom
      3 --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=2024915, si_uid=1000} ---
      3 --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=2024916, si_uid=1000} ---
      3 unlinkat
      4 --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=394857, si_uid=1000} ---
     10 rt_sigreturn
    521 brk
    628 sendto
    761 pread64
    763 pwrite64
  17138 openat
  17139 close
  18042 lseek

# grep newfstatat s2.o
newfstatat(AT_FDCWD, "base/pgsql_tmp/pgsql_tmp1981697.8", {st_mode=S_IFREG|0600, st_size=2211840, ...}, 0) = 0
newfstatat(AT_FDCWD, "base/pgsql_tmp/pgsql_tmp1981697.7", {st_mode=S_IFREG|0600, st_size=1810432, ...}, 0) = 0

NOTE:
使用newfstatat函数检查的是查询过程中的pgsql_tmp临时文件, 接下来可以考虑优化SQL减少temp 或调DB 参数。

总结

出现 newfstatat() 占用大量调用栈,不是 bug,而是 PostgreSQL 或操作系统层面在频繁获取文件元信息。但它很可能是以下问题的表现症状

  • WAL 写入压力大
  • Checkpoint 频繁
  • 归档问题
  • 文件系统性能差
  • 查询产生大量的中间temp文件
打赏

, ,

目前这篇文章还没有评论(Rss)

我要评论