当 PostgreSQL 慢在数据库之外:用 bpftrace 打通 SQL 与内核观测
本页目录
前言
排查 PostgreSQL 问题时,我们通常会先看这些东西:
pg_stat_activitypg_stat_statementspg_locks- 数据库日志
- 系统 CPU、内存、I/O 指标
大多数问题,到这里基本能有个方向。
但有些场景会比较尴尬:
- SQL 看起来很慢,但不知道慢在 CPU、锁、I/O,还是系统调用;
shared_blks_read很高,但不确定是否真的打到了内核 I/O;wait_event看到等待,但不知道 OS 层面发生了什么;- 数据库指标看起来正常,但延迟就是在抖;
- 怀疑是网络、调度、文件系统或虚拟化层问题,但 PostgreSQL 自己的视图解释不了。
这时候,bpftrace 就很有价值。
一句话概括:
bpftrace 可以帮助我们从操作系统和进程行为层面,看见 PostgreSQL 后端进程到底在做什么。
它不是替代 EXPLAIN、pg_stat_activity、pg_stat_statements 的工具,而是当数据库内部视角不够时,继续往下看系统调用、内核 tracepoint、用户态函数、调度、I/O、网络等行为。
bpftrace 是什么
bpftrace 可以理解为 Linux 上的“动态观测脚本工具”。
它基于 eBPF,可以在不改代码、不重启进程的情况下,临时观察内核、系统调用、用户态函数、调度、I/O、网络等行为。
官方对它的定位是:
bpftrace 是 Linux 的高级 tracing 语言,利用 eBPF 提供动态观测能力。
它的语法有点像 awk + C。你可以把一段小脚本挂到某个事件上,当事件发生时执行统计、打印、聚合等动作。
比如统计 PostgreSQL 进程退出 read() 系统调用的次数:
sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read
/comm == "postgres"/
{
@[pid] = count();
}'
这个例子很简单,但它说明了 bpftrace 的核心价值:
临时挂上去,观察一段时间,拿到证据,然后摘下来。
用完就走,不留感情包袱。
bpftrace 和 strace / perf / gdb 有什么区别?
| 工具 | 更适合做什么 | 特点 |
|---|---|---|
strace |
看系统调用明细 | 直观,但输出量大,开销可能明显 |
perf |
CPU 采样、火焰图、热点函数 | 适合性能剖析,但脚本化表达能力不如 bpftrace |
gdb |
调试进程、断点、查看变量 | 强大但侵入性更高,线上慎用 |
bpftrace |
临时观测、聚合统计、延迟分布、Top N | 可在内核侧聚合,适合短时间定向分析 |
bpftrace 的一个优势是:很多数据可以直接在内核侧聚合,最后只把统计结果输出到用户态。
所以它很适合做:
- 按秒统计
- 延迟直方图
- Top N 调用栈
- 某类系统调用次数
- 某类等待分布
- 某个进程的 I/O 行为
bpftrace 能观察什么?
bpftrace 支持多类 probe:
| Probe 类型 | 说明 | PostgreSQL 场景 |
|---|---|---|
tracepoint |
内核预定义稳定事件 | 系统调用、调度、块设备 I/O、网络 |
kprobe/kretprobe |
内核函数入口/返回 | 深入观察内核路径 |
uprobe/uretprobe |
用户态函数入口/返回 | 跟踪 PostgreSQL 内部函数 |
usdt |
用户态静态探针 | PostgreSQL Dynamic Tracing 探针 |
profile/interval |
采样或定时事件 | CPU 采样、周期性输出 |
PostgreSQL 自身也支持 Dynamic Tracing。官方文档中提到,PostgreSQL 可以在构建时开启 DTrace/USDT 相关探针,这样就能观察 query、transaction、lock、lwlock、buffer 等内部事件。
这意味着,如果 PostgreSQL 编译时带了相关探针,我们可以直接从 bpftrace 里观察数据库内部行为。
用 bpftrace 分析 PostgreSQL,能做什么?
1. 看 PostgreSQL 后端进程在做哪些系统调用
比如一个 backend 慢,到底是不是频繁 read()、write()、fsync()、openat()?
可以统计 PostgreSQL 进程的系统调用:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_*
/comm == "postgres"/
{
@[probe] = count();
}'
这类分析适合判断:
- SQL 慢是不是卡在文件 I/O;
- checkpoint 或 commit 是否触发大量 fsync;
- 是否存在异常频繁的小文件访问;
- backend 是否在频繁 futex 等待。
2. 分析 fsync / fdatasync 延迟
PostgreSQL 的事务提交、checkpoint、WAL flush 都可能涉及同步刷盘。
如果怀疑慢在存储层,可以看 fsync() 延迟分布:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_fsync
/comm == "postgres"/
{
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_fsync
/@start[tid]/
{
@fsync_latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
它可以回答一个很关键的问题:
PostgreSQL 慢,是不是因为 fsync 延迟突然变高?
如果直方图里大量 fsync 落在几十毫秒甚至几百毫秒,就应该继续向存储、文件系统、云盘、RAID、虚拟化层排查。
3. 看 read/write I/O 延迟
例如观察 PostgreSQL 进程的 read() 延迟:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_read
/comm == "postgres"/
{
@start[tid] = nsecs;
}
tracepoint:syscalls:sys_exit_read
/@start[tid]/
{
@read_latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
这可以用于判断:
- 查询慢是不是因为大量读磁盘;
- shared buffers 命中率之外,真实 OS read 行为如何;
- 某个 backend 是否发生异常 I/O 等待;
- 表面上 CPU 不高,但实际大量时间耗在 I/O 上。
4. 看 PostgreSQL 是否被调度出去
有时 SQL 慢不是数据库内部问题,而是 OS 调度、CPU 争抢、容器 cgroup 限制、虚拟化抖动导致 PostgreSQL 后端拿不到 CPU。
可以观察 postgres 进程的调度切换:
sudo bpftrace -e '
tracepoint:sched:sched_switch
/args->prev_comm == "postgres"/
{
@[args->prev_state] = count();
}'
这类分析适合排查:
- CPU steal;
- run queue 过长;
- cgroup 限制;
- backend 被频繁调度出去;
- 查询延迟抖动但数据库内部指标不明显。
5. 看锁等待 / futex 等待
PostgreSQL 内部很多等待最终可能表现为 futex 等系统调用,尤其是 latch、进程间同步、锁等待相关场景。
可以粗略观察:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_futex
/comm == "postgres"/
{
@[pid, comm] = count();
}'
如果 futex 调用异常频繁,可以结合 PostgreSQL 侧继续看:
select pid, wait_event_type, wait_event, query
from pg_stat_activity
where wait_event is not null;
这时 bpftrace 负责从 OS 层证明“确实在等”,PostgreSQL 视图负责告诉你“等的是什么”。
6. 分析 TCP / 网络问题
对于 PostgreSQL 主从复制、客户端连接、连接池、Greenplum interconnect、YMatrix 分布式通信,网络问题经常不是 SQL 层能直接解释的。
bpftrace 可以观察:
- TCP 重传;
- 连接建立;
- send/recv 延迟;
- socket buffer 行为;
- 网络系统调用频率。
比如统计 postgres 的 sendto():
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_sendto
/comm == "postgres"/
{
@[pid] = count();
}'
对于 Greenplum/YMatrix 这类分布式数据库,还可以结合 segment 进程观察是否某些 segment 网络发送异常频繁,或者某类进程出现网络阻塞。
7. 分析用户态 PostgreSQL 函数
如果 PostgreSQL 二进制没有 strip,或者有符号信息,可以用 uprobe 跟踪 PostgreSQL 内部函数。
例如查看某个函数是否被频繁调用:
sudo bpftrace -e '
uprobe:/usr/pgsql-16/bin/postgres:ExecProcNode
{
@[comm] = count();
}'
也可以观察函数耗时:
sudo bpftrace -e '
uprobe:/usr/pgsql-16/bin/postgres:some_function
{
@start[tid] = nsecs;
}
uretprobe:/usr/pgsql-16/bin/postgres:some_function
/@start[tid]/
{
@latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
这里的难点是:你需要知道 PostgreSQL 内部函数名,并且二进制要保留符号。
如果 PostgreSQL 被 strip,或者没有 debuginfo,uprobe 能看到的东西就会少很多。这种情况下,可能更适合用 perf,或者在带 debuginfo/debug build 的环境里分析。
PostgreSQL 排障中,bpftrace 应该放在哪一层?
我会把 bpftrace 放在这个位置:
SQL 层现象
↓
pg_stat_activity / pg_stat_statements / wait_event
↓
确认是 CPU、I/O、锁、网络、调度、文件系统哪一类问题
↓
bpftrace 从 OS / 内核 / 进程行为层面验证
↓
再决定是调 SQL、调参数、调存储、调网络、调 OS,还是改架构
举几个典型问题。
问题 1:SQL 偶发慢,但执行计划没变
可以看:
- 是否大量
read(); - 是否
fsync()抖动; - backend 是否被频繁调度出去;
- 是否有 TCP 重传或网络阻塞;
- 是否有大量 futex 等待。
问题 2:commit 延迟突然变高
重点看:
fsync/fdatasync延迟;- WAL 文件写入;
- block I/O 延迟;
- 存储设备是否出现尾延迟。
问题 3:CPU 很高,但不知道 PostgreSQL 在忙什么
可以用:
profile做采样;ustack()打用户态调用栈;- 结合 perf 生成火焰图。
问题 4:数据库看起来空闲,但查询还是慢
可以查:
- 进程是否被 cgroup 限制;
- 是否在 off-CPU;
- 是否被调度器频繁切换;
- 是否卡在内核 I/O 或 futex。
一个实用的排查链路
当 PostgreSQL 慢时,不要一上来就改参数。
可以按这个链路看:
1. SQL 层
pg_stat_activity / wait_event / pg_stat_statements
2. 进程层
top / pidstat / perf / bpftrace
3. 系统调用层
read / write / fsync / futex / sendto / recvfrom
4. 内核层
block I/O / scheduler / TCP / page cache
5. 根因定位
SQL、锁、存储、网络、CPU 调度、内存、cgroup、文件系统
bpftrace 主要补的是第 2 到第 4 层。
它不是告诉你 SQL 应该怎么改,而是告诉你 PostgreSQL 后端进程在操作系统层面到底发生了什么。
使用 bpftrace 的注意事项
第一,bpftrace 通常需要 root 权限,或者需要具备 BPF 相关 capability。
第二,生产环境使用时一定要控制范围,尽量加过滤条件:
/comm == "postgres"/
/pid == 12345/
不要一上来全系统无差别抓取。
第三,优先使用稳定探针。tracepoint 通常比 kprobe 更稳定,因为 kprobe 依赖内核函数名,跨内核版本可能变化。
第四,用户态函数分析依赖符号信息。PostgreSQL 如果被 strip,或者没有 debuginfo,uprobe 能看到的东西会少很多。
第五,少在高频路径里疯狂 printf()。更推荐用 count()、sum()、hist() 做聚合,最后统一输出结果。
第六,bpftrace 适合“短时间、针对性、统计型”的线上观测,不适合长期替代 Prometheus、pg_stat_statements、日志系统这类常规监控。
总结
对 PostgreSQL 来说,bpftrace 不是替代 EXPLAIN、pg_stat_activity、pg_stat_statements 的工具,而是它们的补充。
它最适合回答这类问题:
PostgreSQL 现在到底是在 CPU 上跑,在等磁盘,在等锁,在等网络,在被调度器切走,还是卡在某个内核路径里?
所以,在数据库专家手里,bpftrace 的价值不是“会写几个命令”,而是把数据库现象和操作系统行为打通:
- 从 SQL 慢,追到 backend 进程;
- 从 backend 进程,追到系统调用;
- 从系统调用,追到 I/O、调度、网络和内核路径。
这才是 bpftrace 在 PostgreSQL 排障里的真正价值。