针对 PG 停库 hang 住的问题,本文将针对以下两种可能的问题解法进行分析:
- 使用 pg_ctl stop -m immediate 模式进行停库;
- kill -9 死锁会话进程;
行为分析
pg_ctl stop 模式
pg_ctl 停库模式主要有三种:
- smart:不允许新连接,等待现有的会话退出
- fast(默认):不会等待客户端断开连接并且将终止进行中的在线备份。所有活动事务都被回滚并且客户端被强制断开连接,然后服务器被关闭。
- immediate:将立刻中止所有服务器进程,而不是做一次干净的关闭,下一次重启时进行一次崩溃恢复。
fast 模式
向各子进程发送 SIGTERM 信号,子进程收到 SIGTERM 信号,用注册的信号处理函数进行处理,然后主动完成清理操作并退出。
这种模式下可能会有 stop 一直卡住的问题。一种典型的极端情况就是:正在运行的这部分是第三方插件代码,一直在死循环,它收到信号后并没有去做清理退出操作,而是继续死循环,因此就一直卡住。
除此之外,一些死锁也会导致类似问题。
immediate 模式
向各子进程发送 SIGQUIT 信号,子进程收到 SIGQUIT 信号,用注册的信号处理函数进行处理,直接暂停所有在做的事情并退出。这里内核的信号处理函数会直接退出进程,不存在上面的死循环/死锁的问题。
kill -9 分析
还有一种方法是用 kill -9 杀死 postmaster 等待的进程。
kill -9 发送的是 SIGKILL 信号,该信号不能被捕捉或忽略,如果 kill -9 任意一个子进程的话,其他子进程会一并退出,等待 postmaster 进程重新拉起来。
在 stop 的场景下,此时其余正常的进程都已经退出,kill -9 一个进程看起来并没有太大的风险。
实验
我们在使用 pgbench 压测的同时,kill -9 postmaster 进程,测试实例重启 recovery 需要的时间。
测试脚本
export PORT=5432
psql -p $PORT -U postgres -d postgres -c "create table t(a int, b int);"
psql -p $PORT -U postgres -d postgres -c "show max_connections;"
echo "insert into t select generate_series(1,100),1;">/data/user_00/script.sql
pgbench --no-vacuum -p $PORT --client=500 -U postgres -d postgres -T 500 --file /data/user_00/script.sql
在上述脚本运行一分钟后,使用 kill -9 杀掉 postmaster 进程,同时 rm postmaster.pid 文件。
测试结果
选取 PG 12、13 版本测试,如果并发数 < 1000 都能在 10s 内恢复。如果是大并发的话,时间可能会更长。
总结
在目前的场景下, stop -m immediate 和 kill -9 阻塞的进程都可以成功。
根据查询到的资料和询问 Linux 内核的同学,目前没有构造出 stop -m immediate 无法退出的场景(除非改动内核对应 SIGQUIT 部分的信号处理代码)。
参考文档
[1] http://postgres.cn/docs/13/app-pg-ctl.html
[2] https://www.ibm.com/docs/en/aix/7.2?topic=management-process-termination
[3] https://komodor.com/learn/what-is-sigkill-signal-9-fast-termination-of-linux-containers/
[4] https://zhuanlan.zhihu.com/p/463100471