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
优化策略
- 减少 checkpoint 频率:增加
checkpoint_timeout
和max_wal_size
- 平滑 checkpoint I/O:提高
checkpoint_completion_target
- 平衡恢复时间:确保
max_wal_size
不会导致恢复时间过长 - 监控调整:根据
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)