
就一般系统而言,总的服务进程停止时,都要做一些清理善后的工作。PostgreSQL也不例外。
近期就有同学问:做PITR的恢复时,前期归档的archive wal数量够不够?因为使用archive wal日志进行restore时,旧的PG进程需要stop。因则这个问题可以拆解来看。
首先,replay wal的过程,先要尽量从archive wal中restore, 量不够,就要从以前的pg_wal下边已经写入但是来不及写到archive中的wal进行restore。
问题来了,到底archive进程会不会彻底完成呢?
我们先看看文档:https://www.postgresql.org/docs/15/app-pg-ctl.html
pg_ctl — initialize, start, stop, or control a PostgreSQL server
pg_ctl stop [-D datadir] [-m s[mart] | f[ast] | i[mmediate] ] [-W] [-t seconds] [-s]
stopmode shuts down the server that is running in the specified data directory. Three different shutdown methods can be selected with the-moption.“Smart” mode disallows new connections, then waits for all existing clients to disconnect. If the server is in hot standby, recovery and streaming replication will be terminated once all clients have disconnected.
“Fast” mode (the default) does not wait for clients to disconnect. All active transactions are rolled back and clients are forcibly disconnected, then the server is shut down. 这个模式是默认模式
“Immediate” mode will abort all server processes immediately, without a clean shutdown. This choice will lead to a crash-recovery cycle during the next server start.
kill mode sends a signal to a specified process. 这是另外一种停止进程的方式:
而直接向数据库主进程发送的的signal信号主要有以下三种:
SIGTERM: (terminate) : 相当于 -m smart
SIGINT: (interrupt): 相当于 -m fast
SIGQUIT: (quit): 相当于-m immediate
1- `SIGTERM`(信号15):这是一个终止信号,通知进程正常关闭。PostgreSQL 会在接收到此信号后尽快完成当前事务,然后安全地关闭服务器。这是推荐的关闭 PostgreSQL 服务器的方法。
2- `SIGINT`(信号2):这是一个中断信号,通常用于停止正在运行的程序。在 PostgreSQL 中,接收到此信号的服务器会尝试中断当前事务并快速关闭。这可能会导致数据不一致,但在某些情况下(例如,需要紧急关闭服务器以解决问题)可能是合适的。
3- `SIGQUIT`(信号3):这是一个退出信号,用于立即终止进程。在 PostgreSQL 中,接收到此信号的服务器会立即关闭,不等待当前事务完成。这可能导致数据损坏,仅在非常紧急的情况下使用。
1我们也要留意下边的用法: (PID为postgres主进程ID)
2pg_ctl kill SIGNALNAME PID
3
4Allowed signal names for kill:
5 ABRT HUP INT KILL QUIT TERM USR1 USR2
6
7pg_ctl kill QUIT 1724 就相当于pg_ctl stop -mi (1724为postgres主进程PID)
我们直接看看源码中是怎么说的:(src/bin/pg_ctl.c)
1static void
2do_stop(void)
3{
4 int cnt;
5 pgpid_t pid;
6 struct stat statbuf;
7
8 pid = get_pgpid(false);
9
10 if (pid == 0) /* no pid file */
11 {
12 write_stderr(_("%s: PID file \"%s\" does not exist\n"), progname, pid_file);
13 write_stderr(_("Is server running?\n"));
14 exit(1);
15 }
16 else if (pid < 0) /* standalone backend, not postmaster */
17 {
18 pid = -pid;
19 write_stderr(_("%s: cannot stop server; "
20 "single-user server is running (PID: %ld)\n"),
21 progname, pid);
22 exit(1);
23 }
24
25 if (kill((pid_t) pid, sig) != 0)
26 {
27 write_stderr(_("%s: could not send stop signal (PID: %ld): %s\n"), progname, pid,
28 strerror(errno));
29 exit(1);
30 }
31
32 if (!do_wait)
33 {
34 print_msg(_("server shutting down\n"));
35 return;
36 }
37 else
38 {
39 /*
40 * If backup_label exists, an online backup is running. Warn the user
41 * that smart shutdown will wait for it to finish. However, if the
42 * server is in archive recovery, we're recovering from an online
43 * backup instead of performing one.
44 */
45 if (shutdown_mode == SMART_MODE &&
46 stat(backup_file, &statbuf) == 0 &&
47 get_control_dbstate() != DB_IN_ARCHIVE_RECOVERY)
48 {
49 print_msg(_("WARNING: online backup mode is active\n"
50 "Shutdown will not complete until pg_stop_backup() is called.\n\n"));
51 }
52
53 print_msg(_("waiting for server to shut down..."));
54
55 for (cnt = 0; cnt < wait_seconds * WAITS_PER_SEC; cnt++)
56 {
57 if ((pid = get_pgpid(false)) != 0)
58 {
59 if (cnt % WAITS_PER_SEC == 0)
60 print_msg(".");
61 pg_usleep(USEC_PER_SEC / WAITS_PER_SEC);
62 }
63 else
64 break;
65 }
66
67 if (pid != 0) /* pid file still exists */
68 {
69 print_msg(_(" failed\n"));
70
71 write_stderr(_("%s: server does not shut down\n"), progname);
72 if (shutdown_mode == SMART_MODE)
73 write_stderr(_("HINT: The \"-m fast\" option immediately disconnects sessions rather than\n"
74 "waiting for session-initiated disconnection.\n"));
75 exit(1);
76 }
77 print_msg(_(" done\n"));
78
79 print_msg(_("server stopped\n"));
80 }
81}
其中,if (kill((pid_t) pid, sig) != 0), 是发送默认的 sig: SIGINT信号,先让进程停止。
然后后续的等待过程中,如果是smart方式,如果期间有online backup,会一直到backup结束。
在postmaster.c的mainloop中有下边的代码:
1 /* If we have lost the archiver, try to start a new one. */
2 if (PgArchPID == 0 && PgArchStartupAllowed())
3 PgArchPID = StartArchiver();
就是说它在运行期间能自动重启archiver进程。
我们再看看archiver进程本身的逻辑:backend/postmaster/pgarch.v
1/*
2 * pgarch_ArchiverCopyLoop
3 *
4 * Archives all outstanding xlogs then returns
5 */
6static void
7pgarch_ArchiverCopyLoop(void)
8{
9 char xlog[MAX_XFN_CHARS + 1];
10
11 /*
12 * loop through all xlogs with archive_status of .ready and archive
13 * them...mostly we expect this to be a single file, though it is possible
14 * some backend will add files onto the list of those that need archiving
15 * while we are still copying earlier archives
16 */
17 while (pgarch_readyXlog(xlog))
18 {
19 int failures = 0;
20 int failures_orphan = 0;
21
22 for (;;)
23 {
24 struct stat stat_buf;
25 char pathname[MAXPGPATH];
26
27 /*
28 * Do not initiate any more archive commands after receiving
29 * SIGTERM, nor after the postmaster has died unexpectedly. The
30 * first condition is to try to keep from having init SIGKILL the
31 * command, and the second is to avoid conflicts with another
32 * archiver spawned by a newer postmaster.
33 */
34 if (ShutdownRequestPending || !PostmasterIsAlive())
35 return;
36
37 /*
38 * Check for config update. This is so that we'll adopt a new
39 * setting for archive_command as soon as possible, even if there
40 * is a backlog of files to be archived.
41 */
42 if (ConfigReloadPending)
43 {
44 ConfigReloadPending = false;
45 ProcessConfigFile(PGC_SIGHUP);
46 }
47
48 /* can't do anything if no command ... */
49 if (!XLogArchiveCommandSet())
50 {
51 ereport(WARNING,
52 (errmsg("archive_mode enabled, yet archive_command is not set")));
53 return;
54 }
我们就看上边的那一小段就好:
1 /*
2 * Do not initiate any more archive commands after receiving
3 * SIGTERM, nor after the postmaster has died unexpectedly. The
4 * first condition is to try to keep from having init SIGKILL the
5 * command, and the second is to avoid conflicts with another
6 * archiver spawned by a newer postmaster.
7 */
8 if (ShutdownRequestPending || !PostmasterIsAlive())
9 return;
如果收到的是SIGTERM信号或者postmaster意外终止,相当于-m smart,则不会再触发新的archive命令了。当前要archive的东西继续往前做。如果收到的是SIGKILL,意味着-m i,那就会立即返回。
我们再看看这个变量:ShutdownRequestPending, 它的触发调用位于postmaster/interrupt.c
1/*
2 * Simple signal handler for triggering a long-running background process to
3 * shut down and exit.
4 *
5 * Typically, this handler would be used for SIGTERM, but some processes use
6 * other signals. In particular, the checkpointer exits on SIGUSR2, the
7 * stats collector on SIGQUIT, and the WAL writer exits on either SIGINT
8 * or SIGTERM.
9 *
10 * ShutdownRequestPending should be checked at a convenient place within the
11 * main loop, or else the main loop should call HandleMainLoopInterrupts.
12 */
13void
14SignalHandlerForShutdownRequest(SIGNAL_ARGS)
15{
16 int save_errno = errno;
17
18 ShutdownRequestPending = true;
19 SetLatch(MyLatch);
20
21 errno = save_errno;
22}
archiver进程注册的就是:*pqsignal*(SIGTERM, *SignalHandlerForShutdownRequest*) 意味着它在收到正常结束命令以后不再处理新的archive。
pgarch_mainLoop:
1static void
2pgarch_MainLoop(void)
3.........
4 do
5 {
6 ResetLatch(MyLatch);
7
8 /* When we get SIGUSR2, we do one more archive cycle, then exit */
9 time_to_stop = ready_to_stop;
10
11 /*
12 * If we've gotten SIGTERM, we normally just sit and do nothing until
13 * SIGUSR2 arrives. However, that means a random SIGTERM would
14 * disable archiving indefinitely, which doesn't seem like a good
15 * idea. If more than 60 seconds pass since SIGTERM, exit anyway, so
16 * that the postmaster can start a new archiver if needed.
17 */
18 if (ShutdownRequestPending)
19 {
20 time_t curtime = time(NULL);
21
22 if (last_sigterm_time == 0)
23 last_sigterm_time = curtime;
24 else if ((unsigned int) (curtime - last_sigterm_time) >=
25 (unsigned int) 60)
26 break;
27 }
28 /*
29 * The archiver quits either when the postmaster dies (not expected)
30 * or after completing one more archiving cycle after receiving
31 * SIGUSR2.
32 */
33 } while (!time_to_stop);
看到这块逻辑,应该基本上就明了了。如果是SIGTERM信号,-m start,它还是会尽量完成应该有的archive工作。
初步结论是:-m smart方式基本上能保证所有的有效的wal log在完全停止之前,全部完成归档。而默认的-m fast不一定能保证(虽然大多数情况下,它是没什么问题的)。至于-m i (SIGQUIT), 则更不能保证了。
为了进一步确认,也直接问了下chatGPT:
Q: pg_ctl stop -m fast 能保证archive进程完成所有归档吗?
A:
1`pg_ctl stop -m fast` 命令会快速地停止 PostgreSQL 服务器。这个命令会尽量让所有活跃的客户端连接断开,然后在关闭数据库之前,会尝试完成所有活动的事务。然而,这个命令并不能保证归档进程完成所有的归档操作。
2
3在某些情况下,如果归档进程在数据库关闭之前未能完成所有归档操作,那么在下次启动数据库时,归档进程会继续完成剩余的归档操作。
4
5如果您希望在关闭数据库之前确保归档进程完成所有归档操作,您可以使用 `pg_ctl stop -m smart` 命令。这个命令会等待所有活动连接断开、事务完成以及归档进程完成所有归档操作,然后才会关闭数据库。这样会更加安全,但可能需要更长的时间。
6
如果有兴趣,可以自己搭个小环境,模拟一下。

原文作者:数据库杂记;如有侵权,请联系管理员删除。