老杨用AI做了一个玩具,可以随时随地通过SQL打印PG进程堆栈或记录至日志。分享给大家。
先看下这几个场景:
我相信最简单的办法肯定是观察等待事件,抓取执行堆栈来分析。
抓取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版本
1、请全面检查下插件功能是否已按照需求实现
2、代码和注释是否符合PostgreSQL代码风格
3、是否还存在风险,重点排查性能隐患,安全隐患,稳定性隐患,内存泄露,非法内存访问,进程死锁,和其他插件冲突等等
4、是否还存在事实错误,表述不准确等
5、在PG14 - 19版本编译,并进行回归、TAP、压测可靠性测试等各种场景验证
6、 是否可提交至官方contrib
最终AI采用了ptrace + libunwind的方案实现了扩展,经过N轮验证基本稳定可靠。
版本 | 编译标志 | 警告 | .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 哈希互不相同,证明独立编译。
版本 | 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 |
输入 | 行为 |
|---|---|
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 第一,符合规范 |
风险点 | 状态 |
|---|---|
_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+0x25
postgres=#
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+0x25
2026-05-12 00:26:06.671 CST [3139292] STATEMENT: select pg_log_backtrace(373776);
更多详情请参考:https://github.com/Nickyoung0/pg_stat_backtrace.git
本文分享自 PostgreSQL运维之道 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!