ITPub博客

首页 > 数据库 > PostgreSQL > pg_stat_backtrace:让stack抓取更easy

pg_stat_backtrace:让stack抓取更easy

原创 PostgreSQL 作者:nickyoung 时间:2026-05-19 15:24:00 0 删除 编辑
老杨用AI做了一个玩具,可以随时随地通过SQL打印PG进程堆栈或记录至日志。分享给大家。


背景介绍

先看下这几个场景:
  • 这个后端 state=active、wait_event=NULL、CPU 100% 跑了 30 分钟 — 它到底在干嘛?
  • 逻辑复制 apply worker 不再推进 — 是卡在 apply_handle_insert、触发器、还是 heap_update?
  • autovacuum worker 在某张表上跑了 6 小时 — heap 扫描、索引清理、还是表截断 ?
  • WAL sender 不发包 — 卡在 pq_putmessage、WalSndLoop、还是 XLogRead?
  • checkpointer 慢 — BufferSync、mdsync、还是别的地方?
我相信最简单的办法肯定是观察等待事件,抓取执行堆栈来分析。
抓取stack,基本一看函数调用链路就能知道进程目前的逻辑和行为。
特别是我们当前做DBA agent,分析一些性能问题或者“进程死锁”等问题时stack这个信息源显得尤为重要, 有了stack后agent就可以结合代码精准分析。
但是,不是所有人有权限登录机器来抓取stack,并且只能临时记录到屏显无持久化日志。 当前PG自身仅支持error类型的backtrace记录到日志,且需要事先配置guc backtrace_functions。
因此我想写一个扩展,可以随时通过SQL查看PG进程stack,或者记录stack到serverlog。
依然是奴役压榨AI,以下是我给AI的需求和要求:

需求:

生成一个扩展pg_stat_backtrace,实现两个函数,pg_get_backtrace(pid)查看进程stack,pg_log_backtrace(pid)打印stack到serverlog

要求:

1、切记你是一个顶级PostgreSQL内核专家
2、一切功能在扩展中实现,不要污染主干代码
3、不要使用hook,shared memory,signal handle的方案,不要影响其他正常功能
4、要做pid入参对账和权限管理。pid一定要是本实例的,superuser可打印所有backend及辅助进程stack,普通用户仅可以打印自身或同member内进程stack
5、代码和注释完全符合PostgreSQL风格
6、不存在性能隐患、安全隐患、稳定性隐患、内存泄露、非法内存访问、进程死锁等,不和其他插件冲突
7、一定遵循事实,不要存在事实错误,表述不准确的情况
8、进行回归、TAP、压测、可靠性测试等各种场景验证确保扩展高质量
9、绝对满足官方contrib要求
10、适配PG14 - 19版本

设置定时任务,AI自行多轮验证

1、请全面检查下插件功能是否已按照需求实现
2、代码和注释是否符合PostgreSQL代码风格
3、是否还存在风险,重点排查性能隐患,安全隐患,稳定性隐患,内存泄露,非法内存访问,进程死锁,和其他插件冲突等等
4、是否还存在事实错误,表述不准确等
5、在PG14 - 19版本编译,并进行回归、TAP、压测可靠性测试等各种场景验证
6、 是否可提交至官方contrib

方案效果

最终AI采用了ptrace + libunwind的方案实现了扩展,经过N轮验证基本稳定可靠。

测试报告


二、PG14-19 全矩阵编译

版本
编译标志
警告
.so 哈希
pg14
-Wall -Wextra -Wshadow -Wstrict-prototypes -Werror
0
a438594b
pg15
同上
0
6000d0ed
pg16
同上
0
85b21464
pg17
同上
0
a68ab5df
pg18
同上
0
3ec14d7f
pg19
同上
0
0f14931f
6 份 .so 哈希互不相同,证明独立编译。

三、回归 + TAP 测试

版本
regress
TAP(5 文件 / 87 子测试)
trace 字节数
pg14
✅ All 1 passed
✅ 87/87 PASS
784
pg15
✅
✅ 87/87
783
pg16
✅
✅ 87/87
792
pg17
✅
✅ 87/87
893
pg18
✅
✅ 87/87
892
pg19
✅
✅ 87/87
892
总计 522 个 TAP 断言+ 6 个回归套件 全部通过。trace 字节数随 PG 版本调用栈深度变化,是预期。

四、内存检查

测试
结果
target 端连续 25000 次抓栈
RSS 增长80 kB(一个 page,glibc arena 抖动)
caller 端 18000 次抓栈
12000 次后0 kB 增长,libunwind 缓存预热后稳定
valgrind --leak-check=full(10 次抓栈)
0 个泄漏来自插件,1 个definitely lost来自 PG 自身save_ps_display_args(与插件无关)

五、压测可靠性

测试
结果
单机串行 10000 次(含冷启动)
平均2.682 ms/call
单线程 100 次(含 pg_stat_statements)
3.04 ms/call
8 路并发 × 200 = 1600 次抓栈
0 失败,120 s 完成

六、异常路径验证

场景
行为
statement_timeout=100超时
正确 ERROR、ptrace 已 detach
caller 中途pg_cancel_backend
正确 ERROR、target 仍 active 不受影响
cancel 后再次抓栈同一 target
✅ 正常返回 892 字节 trace

七、边界值(10 个用例全部正确)

输入
行为
NULL
NULL ✅
0-1INT32_MIN
WARNING "invalid PID" + NULL ✅
INT32_MAX / 999999999
WARNING "not a PostgreSQL server process" + NULL ✅
自身 PID
ERROR "cannot ptrace self" + hint ✅
postmaster PID
ERROR "cannot ptrace the postmaster" + hint ✅
普通用户 → 超级用户 backend
ERROR "permission denied to target backend" + DETAIL ✅

八、代码风格

维度
结果
制表符缩进
604 行 tab,0 行空格缩进
行尾空白
0
行长度 ≤ 100
100% 符合 PostgreSQL
危险函数
0处 strcpy/strcat/sprintf/gets
ereport / errcode
13 ERROR 级别 100% 带 errcode;2 WARNING 不带(与 postgres_fdw 等官方扩展做法一致,41% 官方 contrib 也如此)
PG_TRY/CATCH/END_TRY
2/2/2 完美平衡
#include 顺序
postgres.h 第一,符合规范

九、ABI 兼容性 / 插件冲突

风险点
状态
_PG_init
不存在
任何 hook 注册
0 个
GUC(DefineCustom*)
0 个
共享内存(RequestAddinShmemSpace)
0 个
PG_MODULE_MAGIC/_EXT
通过#ifdef兼容 PG14-19
ProcArrayLock 用法
LWLockAcquire(LW_SHARED) / LWLockRelease微秒级,全版本稳定 API
BackendPidGetProcWithLock
PG14-19 全版本兼容,未触碰 PG18 ProcNumber 改造影响面
结论:本插件 结构上不可能与任何其他扩展冲突—— 没有全局状态、没有 hook、没有共享内存。

结果验证

pg_get_backtrace:执行sql实时获取stack
postgres=# select *,pg_get_backtrace(pid) from pg_stat_activity where backend_type in ('client backend','checkpointer','background writer') and pid <> pg_backend_pid();-[ RECORD 1 ]----+-------------------------------------------------------------datid            | 5
datname          | postgres
pid              | 3133583
leader_pid       | 
usesysid         | 10
usename          | postgres
application_name | psql
client_addr      | 
client_hostname  | 
client_port      | -1
backend_start    | 2026-05-12 00:16:04.383869+08
xact_start       | 
query_start      | 2026-05-12 00:16:13.353197+08
state_change     | 2026-05-12 00:16:13.353285+08
wait_event_type  | Client
wait_event       | ClientRead
state            | idle
backend_xid      | 
backend_xmin     | 
query_id         | 
query            | select1;
backend_type     | client backend
pg_get_backtrace | #0   0x00007f48d004ff57 in epoll_wait+0x17                  +
                 | #1   0x000000000091416b in WaitEventSetWait+0x12b           +
                 | #2   0x00000000007882e4 in secure_read+0xd4                 +
                 | #3   0x00000000007930ce in pq_recvbuf.lto_priv.0+0x8e       +
                 | #4   0x00000000009319e5 in PostgresMain+0x14c5              +
                 | #5   0x0000000000932ec5 in BackendMain+0x45                 +
                 | #6   0x000000000088434d in postmaster_child_launch+0x12d    +
                 | #7   0x000000000088f2ad in ServerLoop.lto_priv.0+0x52d      +
                 | #8   0x0000000000885d19 in PostmasterMain+0xce9             +
                 | #9   0x000000000052fa80 in main+0x1d0                       +
                 | #10  0x00007f48cff612a0 in __libc_start_call_main+0x80      +
                 | #11  0x00007f48cff61359 in __libc_start_main+0x89           +
                 | #12  0x0000000000530075 in _start+0x25-[ RECORD 2 ]----+-------------------------------------------------------------datid            | 
datname          | 
pid              | 373780
leader_pid       | 
usesysid         | 
usename          | 
application_name | 
client_addr      | 
client_hostname  | 
client_port      | 
backend_start    | 2026-05-08 11:04:19.226274+08
xact_start       | 
query_start      | 
state_change     | 
wait_event_type  | Activity
wait_event       | BgwriterHibernate
state            | 
backend_xid      | 
backend_xmin     | 
query_id         | 
query            | 
backend_type     | background writer
pg_get_backtrace | #0   0x00007f48d004ff57 in epoll_wait+0x17                  +
                 | #1   0x000000000091416b in WaitEventSetWait+0x12b           +
                 | #2   0x000000000090b6a5 in WaitLatch+0x75                   +
                 | #3   0x000000000088310d in BackgroundWriterMain+0x2ed       +
                 | #4   0x000000000088434d in postmaster_child_launch+0x12d    +
                 | #5   0x000000000088d12f in StartChildProcess.lto_priv.0+0x2f+
                 | #6   0x0000000000885dfa in PostmasterMain+0xdca             +
                 | #7   0x000000000052fa80 in main+0x1d0                       +
                 | #8   0x00007f48cff612a0 in __libc_start_call_main+0x80      +
                 | #9   0x00007f48cff61359 in __libc_start_main+0x89           +
                 | #10  0x0000000000530075 in _start+0x25-[ RECORD 3 ]----+-------------------------------------------------------------datid            | 
datname          | 
pid              | 373779
leader_pid       | 
usesysid         | 
usename          | 
application_name | 
client_addr      | 
client_hostname  | 
client_port      | 
backend_start    | 2026-05-08 11:04:19.225799+08
xact_start       | 
query_start      | 
state_change     | 
wait_event_type  | Activity
wait_event       | CheckpointerMain
state            | 
backend_xid      | 
backend_xmin     | 
query_id         | 
query            | 
backend_type     | checkpointer
pg_get_backtrace | #0   0x00007f48d004ff57 in epoll_wait+0x17                  +
                 | #1   0x000000000091416b in WaitEventSetWait+0x12b           +
                 | #2   0x000000000090b6a5 in WaitLatch+0x75                   +
                 | #3   0x0000000000883d03 in CheckpointerMain+0x503           +
                 | #4   0x000000000088434d in postmaster_child_launch+0x12d    +
                 | #5   0x000000000088d12f in StartChildProcess.lto_priv.0+0x2f+
                 | #6   0x0000000000885e10 in PostmasterMain+0xde0             +
                 | #7   0x000000000052fa80 in main+0x1d0                       +
                 | #8   0x00007f48cff612a0 in __libc_start_call_main+0x80      +
                 | #9   0x00007f48cff61359 in __libc_start_main+0x89           +
                 | #10  0x0000000000530075 in _start+0x25postgres=#
pg_log_backtrace:记录stack到serverlog
2026-05-12 00:26:06.671 CST [3139292] LOG:  backtrace of PID 373776
2026-05-12 00:26:06.671 CST [3139292] DETAIL:  #0   0x00007f48d004ff57 in epoll_wait+0x17
        #1   0x000000000091416b in WaitEventSetWait+0x12b
        #2   0x000000000090b6a5 in WaitLatch+0x75
        #3   0x00000000008ed787 in IoWorkerMain+0x4c7
        #4   0x000000000088434d in postmaster_child_launch+0x12d
        #5   0x000000000088d12f in StartChildProcess.lto_priv.0+0x2f
        #6   0x000000000088d2fb in maybe_adjust_io_workers.lto_priv.0+0x8b
        #7   0x0000000000885cd8 in PostmasterMain+0xca8
        #8   0x000000000052fa80 in main+0x1d0
        #9   0x00007f48cff612a0 in __libc_start_call_main+0x80
        #10  0x00007f48cff61359 in __libc_start_main+0x89
        #11  0x0000000000530075 in _start+0x252026-05-12 00:26:06.671 CST [3139292] STATEMENT:  select pg_log_backtrace(373776);

更多详情请参考:https://github.com/Nickyoung0/pg_stat_backtrace.git



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70033867/viewspace-3171629/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
PostgreSQL深度爱好者,PostgreSQL ACE,PG分会西安用户组负责人,墨天轮MVP, 公众号「PostgreSQL运维之道」主理人。

注册时间:2023-09-26

  • 博文量
    17
  • 访问量
    19033