On 2020-01-15 01:40, Masahiko Sawada wrote:
Could you rebase the main patch that adds base backup client as auxiliary backend process since the previous version patch (v3) conflicts with the current HEAD?
attached -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From 892ba431956c7d936555f758efc874f58b3679e8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <pe...@eisentraut.org> Date: Wed, 15 Jan 2020 16:15:06 +0100 Subject: [PATCH v4] Base backup client as auxiliary backend process Discussion: https://www.postgresql.org/message-id/flat/61b8d18d-c922-ac99-b990-a31ba63cd...@2ndquadrant.com --- doc/src/sgml/protocol.sgml | 12 +- doc/src/sgml/ref/initdb.sgml | 17 + src/backend/access/transam/xlog.c | 102 +++-- src/backend/bootstrap/bootstrap.c | 9 + src/backend/postmaster/pgstat.c | 6 + src/backend/postmaster/postmaster.c | 114 ++++- src/backend/replication/basebackup.c | 70 +++ .../libpqwalreceiver/libpqwalreceiver.c | 400 ++++++++++++++++++ src/backend/replication/repl_gram.y | 9 +- src/backend/replication/repl_scanner.l | 1 + src/backend/storage/file/fd.c | 36 +- src/bin/initdb/initdb.c | 39 +- src/bin/pg_resetwal/pg_resetwal.c | 6 +- src/include/access/xlog.h | 8 +- src/include/miscadmin.h | 2 + src/include/pgstat.h | 1 + src/include/replication/basebackup.h | 2 + src/include/replication/walreceiver.h | 4 + src/include/storage/fd.h | 2 +- src/include/utils/guc.h | 2 +- src/test/recovery/t/018_basebackup.pl | 29 ++ 21 files changed, 783 insertions(+), 88 deletions(-) create mode 100644 src/test/recovery/t/018_basebackup.pl diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 80275215e0..f54b820edf 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -2466,7 +2466,7 @@ <title>Streaming Replication Protocol</title> </varlistentry> <varlistentry> - <term><literal>BASE_BACKUP</literal> [ <literal>LABEL</literal> <replaceable>'label'</replaceable> ] [ <literal>PROGRESS</literal> ] [ <literal>FAST</literal> ] [ <literal>WAL</literal> ] [ <literal>NOWAIT</literal> ] [ <literal>MAX_RATE</literal> <replaceable>rate</replaceable> ] [ <literal>TABLESPACE_MAP</literal> ] [ <literal>NOVERIFY_CHECKSUMS</literal> ] + <term><literal>BASE_BACKUP</literal> [ <literal>LABEL</literal> <replaceable>'label'</replaceable> ] [ <literal>PROGRESS</literal> ] [ <literal>FAST</literal> ] [ <literal>WAL</literal> ] [ <literal>NOWAIT</literal> ] [ <literal>MAX_RATE</literal> <replaceable>rate</replaceable> ] [ <literal>TABLESPACE_MAP</literal> ] [ <literal>NOVERIFY_CHECKSUMS</literal> ] [ <literal>EXCLUDE_CONF</literal> ] <indexterm><primary>BASE_BACKUP</primary></indexterm> </term> <listitem> @@ -2576,6 +2576,16 @@ <title>Streaming Replication Protocol</title> </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>EXCLUDE_CONF</literal></term> + <listitem> + <para> + Do not copy configuration files, that is, files that end in + <filename>.conf</filename>. + </para> + </listitem> + </varlistentry> </variablelist> </para> <para> diff --git a/doc/src/sgml/ref/initdb.sgml b/doc/src/sgml/ref/initdb.sgml index da5c8f5307..1261e02d59 100644 --- a/doc/src/sgml/ref/initdb.sgml +++ b/doc/src/sgml/ref/initdb.sgml @@ -286,6 +286,23 @@ <title>Options</title> </listitem> </varlistentry> + <varlistentry> + <term><option>-r</option></term> + <term><option>--replica</option></term> + <listitem> + <para> + Initialize a data directory for a physical replication replica. The + data directory will not be initialized with a full database system, + but will instead only contain a minimal set of files. A server that + is started on this data directory will first fetch a base backup and + then switch to standby mode. The connection information for the base + backup has to be configured by setting <xref + linkend="guc-primary-conninfo"/>, and other parameters as desired, + before the server is started. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>-S</option></term> <term><option>--sync-only</option></term> diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 7f4f784c0e..36c6cdef82 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -903,8 +903,6 @@ static void CheckRecoveryConsistency(void); static XLogRecord *ReadCheckpointRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int whichChkpt, bool report); static bool rescanLatestTimeLine(void); -static void WriteControlFile(void); -static void ReadControlFile(void); static char *str_time(pg_time_t tnow); static bool CheckForStandbyTrigger(void); @@ -4494,7 +4492,7 @@ rescanLatestTimeLine(void) * ReadControlFile() verifies they are correct. We could split out the * I/O and compatibility-check functions, but there seems no need currently. */ -static void +void WriteControlFile(void) { int fd; @@ -4585,7 +4583,7 @@ WriteControlFile(void) XLOG_CONTROL_FILE))); } -static void +void ReadControlFile(void) { pg_crc32c crc; @@ -5075,6 +5073,41 @@ XLOGShmemInit(void) InitSharedLatch(&XLogCtl->recoveryWakeupLatch); } +void +InitControlFile(uint64 sysidentifier) +{ + char mock_auth_nonce[MOCK_AUTH_NONCE_LEN]; + + /* + * Generate a random nonce. This is used for authentication requests that + * will fail because the user does not exist. The nonce is used to create + * a genuine-looking password challenge for the non-existent user, in lieu + * of an actual stored password. + */ + if (!pg_strong_random(mock_auth_nonce, MOCK_AUTH_NONCE_LEN)) + ereport(PANIC, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate secret authorization token"))); + + memset(ControlFile, 0, sizeof(ControlFileData)); + /* Initialize pg_control status fields */ + ControlFile->system_identifier = sysidentifier; + memcpy(ControlFile->mock_authentication_nonce, mock_auth_nonce, MOCK_AUTH_NONCE_LEN); + ControlFile->state = DB_SHUTDOWNED; + ControlFile->unloggedLSN = FirstNormalUnloggedLSN; + + /* Set important parameter values for use when replaying WAL */ + ControlFile->MaxConnections = MaxConnections; + ControlFile->max_worker_processes = max_worker_processes; + ControlFile->max_wal_senders = max_wal_senders; + ControlFile->max_prepared_xacts = max_prepared_xacts; + ControlFile->max_locks_per_xact = max_locks_per_xact; + ControlFile->wal_level = wal_level; + ControlFile->wal_log_hints = wal_log_hints; + ControlFile->track_commit_timestamp = track_commit_timestamp; + ControlFile->data_checksum_version = bootstrap_data_checksum_version; +} + /* * This func must be called ONCE on system install. It creates pg_control * and the initial XLOG segment. @@ -5090,7 +5123,6 @@ BootStrapXLOG(void) char *recptr; bool use_existent; uint64 sysidentifier; - char mock_auth_nonce[MOCK_AUTH_NONCE_LEN]; struct timeval tv; pg_crc32c crc; @@ -5111,17 +5143,6 @@ BootStrapXLOG(void) sysidentifier |= ((uint64) tv.tv_usec) << 12; sysidentifier |= getpid() & 0xFFF; - /* - * Generate a random nonce. This is used for authentication requests that - * will fail because the user does not exist. The nonce is used to create - * a genuine-looking password challenge for the non-existent user, in lieu - * of an actual stored password. - */ - if (!pg_strong_random(mock_auth_nonce, MOCK_AUTH_NONCE_LEN)) - ereport(PANIC, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("could not generate secret authorization token"))); - /* First timeline ID is always 1 */ ThisTimeLineID = 1; @@ -5229,30 +5250,12 @@ BootStrapXLOG(void) openLogFile = -1; /* Now create pg_control */ - - memset(ControlFile, 0, sizeof(ControlFileData)); - /* Initialize pg_control status fields */ - ControlFile->system_identifier = sysidentifier; - memcpy(ControlFile->mock_authentication_nonce, mock_auth_nonce, MOCK_AUTH_NONCE_LEN); - ControlFile->state = DB_SHUTDOWNED; + InitControlFile(sysidentifier); ControlFile->time = checkPoint.time; ControlFile->checkPoint = checkPoint.redo; ControlFile->checkPointCopy = checkPoint; - ControlFile->unloggedLSN = FirstNormalUnloggedLSN; - - /* Set important parameter values for use when replaying WAL */ - ControlFile->MaxConnections = MaxConnections; - ControlFile->max_worker_processes = max_worker_processes; - ControlFile->max_wal_senders = max_wal_senders; - ControlFile->max_prepared_xacts = max_prepared_xacts; - ControlFile->max_locks_per_xact = max_locks_per_xact; - ControlFile->wal_level = wal_level; - ControlFile->wal_log_hints = wal_log_hints; - ControlFile->track_commit_timestamp = track_commit_timestamp; - ControlFile->data_checksum_version = bootstrap_data_checksum_version; /* some additional ControlFile fields are set in WriteControlFile() */ - WriteControlFile(); /* Bootstrap the commit log, too */ @@ -6297,24 +6300,31 @@ StartupXLOG(void) */ ValidateXLOGDirectoryStructure(); - /*---------- + /* * If we previously crashed, perform a couple of actions: + * * - The pg_wal directory may still include some temporary WAL segments - * used when creating a new segment, so perform some clean up to not - * bloat this path. This is done first as there is no point to sync this - * temporary data. - * - There might be data which we had written, intending to fsync it, - * but which we had not actually fsync'd yet. Therefore, a power failure - * in the near future might cause earlier unflushed writes to be lost, - * even though more recent data written to disk from here on would be - * persisted. To avoid that, fsync the entire data directory. - *--------- + * used when creating a new segment, so perform some clean up to not + * bloat this path. This is done first as there is no point to sync + * this temporary data. + * + * - There might be data which we had written, intending to fsync it, but + * which we had not actually fsync'd yet. Therefore, a power failure + * in the near future might cause earlier unflushed writes to be lost, + * even though more recent data written to disk from here on would be + * persisted. To avoid that, fsync the entire data directory. Errors + * are logged but not considered fatal. Aborting on error would result + * in failure to start for harmless cases such as read-only files in + * the data directory, and that's not good either. + * + * Note that if we previously crashed due to a PANIC on fsync(), we'll + * be rewriting all changes again during recovery. */ if (ControlFile->state != DB_SHUTDOWNED && ControlFile->state != DB_SHUTDOWNED_IN_RECOVERY) { RemoveTempXlogFiles(); - SyncDataDirectory(); + SyncDataDirectory(true, LOG); } /* diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index bfc629c753..c4d2bff0b3 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -36,6 +36,7 @@ #include "postmaster/bgwriter.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" +#include "replication/basebackup.h" #include "replication/walreceiver.h" #include "storage/bufmgr.h" #include "storage/bufpage.h" @@ -326,6 +327,9 @@ AuxiliaryProcessMain(int argc, char *argv[]) case StartupProcess: statmsg = pgstat_get_backend_desc(B_STARTUP); break; + case BaseBackupProcess: + statmsg = pgstat_get_backend_desc(B_BASE_BACKUP); + break; case BgWriterProcess: statmsg = pgstat_get_backend_desc(B_BG_WRITER); break; @@ -451,6 +455,11 @@ AuxiliaryProcessMain(int argc, char *argv[]) StartupProcessMain(); proc_exit(1); /* should never return */ + case BaseBackupProcess: + /* don't set signals, basebackup has its own agenda */ + BaseBackupMain(); + proc_exit(1); /* should never return */ + case BgWriterProcess: /* don't set signals, bgwriter has its own agenda */ BackgroundWriterMain(); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 51c486bebd..f4bb4192b7 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -2927,6 +2927,9 @@ pgstat_bestart(void) case StartupProcess: lbeentry.st_backendType = B_STARTUP; break; + case BaseBackupProcess: + lbeentry.st_backendType = B_BASE_BACKUP; + break; case BgWriterProcess: lbeentry.st_backendType = B_BG_WRITER; break; @@ -4285,6 +4288,9 @@ pgstat_get_backend_desc(BackendType backendType) case B_BG_WORKER: backendDesc = "background worker"; break; + case B_BASE_BACKUP: + backendDesc = "base backup"; + break; case B_BG_WRITER: backendDesc = "background writer"; break; diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 7a92dac525..07555a55d7 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -116,6 +116,7 @@ #include "postmaster/postmaster.h" #include "postmaster/syslogger.h" #include "replication/logicallauncher.h" +#include "replication/walreceiver.h" #include "replication/walsender.h" #include "storage/fd.h" #include "storage/ipc.h" @@ -248,6 +249,7 @@ bool restart_after_crash = true; /* PIDs of special child processes; 0 when not running */ static pid_t StartupPID = 0, + BaseBackupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, WalWriterPID = 0, @@ -539,6 +541,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #endif /* EXEC_BACKEND */ #define StartupDataBase() StartChildProcess(StartupProcess) +#define StartBaseBackup() StartChildProcess(BaseBackupProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) @@ -572,6 +575,8 @@ PostmasterMain(int argc, char *argv[]) bool listen_addr_saved = false; int i; char *output_config_variable = NULL; + struct stat stat_buf; + bool basebackup_signal_file_found = false; InitProcessGlobals(); @@ -886,12 +891,27 @@ PostmasterMain(int argc, char *argv[]) /* Verify that DataDir looks reasonable */ checkDataDir(); - /* Check that pg_control exists */ - checkControlFile(); - /* And switch working directory into it */ ChangeToDataDir(); + if (stat(BASEBACKUP_SIGNAL_FILE, &stat_buf) == 0) + { + int fd; + + fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY, + S_IRUSR | S_IWUSR); + if (fd >= 0) + { + (void) pg_fsync(fd); + close(fd); + } + basebackup_signal_file_found = true; + } + + /* Check that pg_control exists */ + if (!basebackup_signal_file_found) + checkControlFile(); + /* * Check for invalid combinations of GUC settings. */ @@ -970,7 +990,8 @@ PostmasterMain(int argc, char *argv[]) * processes will inherit the correct function pointer and not need to * repeat the test. */ - LocalProcessControlFile(false); + if (!basebackup_signal_file_found) + LocalProcessControlFile(false); /* * Initialize SSL library, if specified. @@ -1386,6 +1407,39 @@ PostmasterMain(int argc, char *argv[]) */ AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING); + if (basebackup_signal_file_found) + { + BaseBackupPID = StartBaseBackup(); + + /* + * Wait until done. Start WAL receiver in the meantime, once base + * backup has received the starting position. + */ + while (BaseBackupPID != 0) + { + PG_SETMASK(&UnBlockSig); + pg_usleep(1000000L); + PG_SETMASK(&BlockSig); + MaybeStartWalReceiver(); + } + + /* + * XXX Shut down WAL receiver. It will be restarted later in xlog.c, + * and that will complain if it's already running. + */ + ShutdownWalRcv(); + + /* + * Base backup done, now signal standby mode. + */ + durable_rename(BASEBACKUP_SIGNAL_FILE, STANDBY_SIGNAL_FILE, FATAL); + + /* + * Reread the control file that came in with the base backup. + */ + ReadControlFile(); + } + /* * We're ready to rock and roll... */ @@ -2665,6 +2719,8 @@ SIGHUP_handler(SIGNAL_ARGS) SignalChildren(SIGHUP); if (StartupPID != 0) signal_child(StartupPID, SIGHUP); + if (BaseBackupPID != 0) + signal_child(BaseBackupPID, SIGHUP); if (BgWriterPID != 0) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) @@ -2824,6 +2880,8 @@ pmdie(SIGNAL_ARGS) if (StartupPID != 0) signal_child(StartupPID, SIGTERM); + if (BaseBackupPID != 0) + signal_child(BaseBackupPID, SIGTERM); if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); if (WalReceiverPID != 0) @@ -3062,6 +3120,23 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the base backup process? + */ + if (pid == BaseBackupPID) + { + BaseBackupPID = 0; + if (EXIT_STATUS_0(exitstatus)) + ; + else if (EXIT_STATUS_1(exitstatus)) + ereport(FATAL, + (errmsg("base backup failed"))); + else + HandleChildCrash(pid, exitstatus, + _("base backup process")); + continue; + } + /* * Was it the bgwriter? Normal exit can be ignored; we'll start a new * one at the next iteration of the postmaster's main loop, if @@ -3583,6 +3658,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) StartupStatus = STARTUP_SIGNALED; } + /* Take care of the base backup process too */ + if (pid == BaseBackupPID) + BaseBackupPID = 0; + else if (BaseBackupPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) BaseBackupPID))); + signal_child(BaseBackupPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the bgwriter too */ if (pid == BgWriterPID) BgWriterPID = 0; @@ -3817,6 +3904,7 @@ PostmasterStateMachine(void) if (CountChildren(BACKEND_TYPE_NORMAL | BACKEND_TYPE_WORKER) == 0 && StartupPID == 0 && WalReceiverPID == 0 && + BaseBackupPID == 0 && BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && @@ -3911,6 +3999,7 @@ PostmasterStateMachine(void) /* These other guys should be dead already */ Assert(StartupPID == 0); Assert(WalReceiverPID == 0); + Assert(BaseBackupPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); Assert(WalWriterPID == 0); @@ -4094,6 +4183,8 @@ TerminateChildren(int signal) if (signal == SIGQUIT || signal == SIGKILL) StartupStatus = STARTUP_SIGNALED; } + if (BaseBackupPID != 0) + signal_child(BgWriterPID, signal); if (BgWriterPID != 0) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) @@ -4919,6 +5010,7 @@ SubPostmasterMain(int argc, char *argv[]) strcmp(argv[1], "--forkavlauncher") == 0 || strcmp(argv[1], "--forkavworker") == 0 || strcmp(argv[1], "--forkboot") == 0 || + strcmp(argv[1], "--forkbasebackup") == 0 || strncmp(argv[1], "--forkbgworker=", 15) == 0) PGSharedMemoryReAttach(); else @@ -4958,7 +5050,8 @@ SubPostmasterMain(int argc, char *argv[]) * (re-)read control file, as it contains config. The postmaster will * already have read this, but this process doesn't know about that. */ - LocalProcessControlFile(false); + if (strcmp(argv[1], "--forkbasebackup") != 0) + LocalProcessControlFile(false); /* * Reload any libraries that were preloaded by the postmaster. Since we @@ -5019,7 +5112,8 @@ SubPostmasterMain(int argc, char *argv[]) /* And run the backend */ BackendRun(&port); /* does not return */ } - if (strcmp(argv[1], "--forkboot") == 0) + if (strcmp(argv[1], "--forkboot") == 0 || + strcmp(argv[1], "--forkbasebackup") == 0) { /* Restore basic shared memory pointers */ InitShmemAccess(UsedShmemSegAddr); @@ -5431,7 +5525,7 @@ StartChildProcess(AuxProcType type) av[ac++] = "postgres"; #ifdef EXEC_BACKEND - av[ac++] = "--forkboot"; + av[ac++] = (type == BaseBackupProcess) ? "--forkbasebackup" : "--forkboot"; av[ac++] = NULL; /* filled in by postmaster_forkexec */ #endif @@ -5475,6 +5569,10 @@ StartChildProcess(AuxProcType type) ereport(LOG, (errmsg("could not fork startup process: %m"))); break; + case BaseBackupProcess: + ereport(LOG, + (errmsg("could not fork base backup process: %m"))); + break; case BgWriterProcess: ereport(LOG, (errmsg("could not fork background writer process: %m"))); @@ -5616,7 +5714,7 @@ static void MaybeStartWalReceiver(void) { if (WalReceiverPID == 0 && - (pmState == PM_STARTUP || pmState == PM_RECOVERY || + (pmState == PM_INIT || pmState == PM_STARTUP || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_WAIT_READONLY) && Shutdown == NoShutdown) { diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index dea8aab45e..e2f88a2f11 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -29,6 +29,7 @@ #include "port.h" #include "postmaster/syslogger.h" #include "replication/basebackup.h" +#include "replication/walreceiver.h" #include "replication/walsender.h" #include "replication/walsender_private.h" #include "storage/bufpage.h" @@ -38,6 +39,7 @@ #include "storage/ipc.h" #include "storage/reinit.h" #include "utils/builtins.h" +#include "utils/guc.h" #include "utils/ps_status.h" #include "utils/relcache.h" #include "utils/timestamp.h" @@ -121,6 +123,9 @@ static long long int total_checksum_failures; /* Do not verify checksums. */ static bool noverify_checksums = false; +/* Do not copy config files. */ +static bool exclude_conf = false; + /* * The contents of these directories are removed or recreated during server * start so they are not included in backups. The directories themselves are @@ -639,6 +644,7 @@ parse_basebackup_options(List *options, basebackup_options *opt) bool o_maxrate = false; bool o_tablespace_map = false; bool o_noverify_checksums = false; + bool o_exclude_conf = false; MemSet(opt, 0, sizeof(*opt)); foreach(lopt, options) @@ -727,6 +733,15 @@ parse_basebackup_options(List *options, basebackup_options *opt) noverify_checksums = true; o_noverify_checksums = true; } + else if (strcmp(defel->defname, "exclude_conf") == 0) + { + if (o_exclude_conf) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("duplicate option \"%s\"", defel->defname))); + exclude_conf = true; + o_exclude_conf = true; + } else elog(ERROR, "option \"%s\" not recognized", defel->defname); @@ -1136,6 +1151,18 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, continue; } + if (exclude_conf) + { + char *dot = strrchr(de->d_name, '.'); + if (dot && strcmp(dot, ".conf") == 0) + { + elog(DEBUG2, + "configuration file \"%s\" excluded from backup", + de->d_name); + continue; + } + } + snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, de->d_name); /* Skip pg_control here to back up it last */ @@ -1730,3 +1757,46 @@ throttle(size_t increment) */ throttled_last = GetCurrentTimestamp(); } + + +/* + * base backup worker process (client) main function + */ +void +BaseBackupMain(void) +{ + WalReceiverConn *wrconn = NULL; + char *err; + TimeLineID primaryTLI; + uint64 primary_sysid; + + /* Load the libpq-specific functions */ + load_file("libpqwalreceiver", false); + if (WalReceiverFunctions == NULL) + elog(ERROR, "libpqwalreceiver didn't initialize correctly"); + + /* Establish the connection to the primary */ + wrconn = walrcv_connect(PrimaryConnInfo, false, cluster_name[0] ? cluster_name : "basebackup", &err); + if (!wrconn) + ereport(ERROR, + (errmsg("could not connect to the primary server: %s", err))); + + /* + * Get the remote sysid and stick it into the local control file, so that + * the walreceiver is happy. The control file will later be overwritten + * by the base backup. + */ + primary_sysid = strtoull(walrcv_identify_system(wrconn, &primaryTLI), NULL, 10); + InitControlFile(primary_sysid); + WriteControlFile(); + + walrcv_base_backup(wrconn); + + walrcv_disconnect(wrconn); + + SyncDataDirectory(false, ERROR); + + ereport(LOG, + (errmsg("base backup completed"))); + proc_exit(0); +} diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c index e4fd1f9bb6..52819d504c 100644 --- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c @@ -17,20 +17,29 @@ #include "postgres.h" #include <unistd.h> +#include <sys/stat.h> #include <sys/time.h> +#ifdef USE_SYSTEMD +#include <systemd/sd-daemon.h> +#endif + #include "access/xlog.h" #include "catalog/pg_type.h" +#include "common/string.h" #include "funcapi.h" #include "libpq-fe.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "pgstat.h" +#include "pgtar.h" #include "pqexpbuffer.h" #include "replication/walreceiver.h" #include "utils/builtins.h" +#include "utils/guc.h" #include "utils/memutils.h" #include "utils/pg_lsn.h" +#include "utils/ps_status.h" #include "utils/tuplestore.h" PG_MODULE_MAGIC; @@ -61,6 +70,7 @@ static int libpqrcv_server_version(WalReceiverConn *conn); static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn, TimeLineID tli, char **filename, char **content, int *len); +static void libpqrcv_base_backup(WalReceiverConn *conn); static bool libpqrcv_startstreaming(WalReceiverConn *conn, const WalRcvStreamOptions *options); static void libpqrcv_endstreaming(WalReceiverConn *conn, @@ -89,6 +99,7 @@ static WalReceiverFunctionsType PQWalReceiverFunctions = { libpqrcv_identify_system, libpqrcv_server_version, libpqrcv_readtimelinehistoryfile, + libpqrcv_base_backup, libpqrcv_startstreaming, libpqrcv_endstreaming, libpqrcv_receive, @@ -358,6 +369,395 @@ libpqrcv_server_version(WalReceiverConn *conn) return PQserverVersion(conn->streamConn); } +/* + * XXX copied from pg_basebackup.c + */ + +unsigned long long totaldone; +unsigned long long totalsize_kb; +int tablespacenum; +int tablespacecount; + +static void +base_backup_report_progress(void) +{ + int percent; + char *progress; + + percent = totalsize_kb ? (int) ((totaldone / 1024) * 100 / totalsize_kb) : 0; + + /* + * Avoid overflowing past 100% or the full size. This may make the total + * size number change as we approach the end of the backup (the estimate + * will always be wrong if WAL is included), but that's better than having + * the done column be bigger than the total. + */ + if (percent > 100) + percent = 100; + if (totaldone / 1024 > totalsize_kb) + totalsize_kb = totaldone / 1024; + + /* Note: no translation of ps status */ + progress = psprintf((tablespacecount == 1 ? + "%llu/%llu kB (%d%%), %d/%d tablespace" : + "%llu/%llu kB (%d%%), %d/%d tablespaces"), + totaldone / 1024, + totalsize_kb, + percent, + tablespacenum, + tablespacecount); + + set_ps_display(progress, false); +#ifdef USE_SYSTEMD + sd_pid_notifyf(PostmasterPid, 0, "STATUS=base backup %s", progress); +#endif + + pfree(progress); +} + +static void +ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res) +{ + char current_path[MAXPGPATH]; + char filename[MAXPGPATH]; + pgoff_t current_len_left = 0; + int current_padding = 0; + char *copybuf = NULL; + FILE *file = NULL; + off_t flush_offset; + + strlcpy(current_path, DataDir, sizeof(current_path)); + + /* + * Get the COPY data + */ + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_COPY_OUT) + ereport(ERROR, + (errmsg("could not get COPY data stream: %s", + PQerrorMessage(conn)))); + + while (1) + { + int r; + + if (copybuf != NULL) + { + PQfreemem(copybuf); + copybuf = NULL; + } + + r = PQgetCopyData(conn, ©buf, 0); + + if (r == -1) + { + /* + * End of chunk + */ + if (file) + fclose(file); + + break; + } + else if (r == -2) + { + ereport(ERROR, + (errmsg("could not read COPY data: %s", + PQerrorMessage(conn)))); + } + + if (file == NULL) + { + int filemode; + + /* + * No current file, so this must be the header for a new file + */ + if (r != 512) + ereport(ERROR, + (errmsg("invalid tar block header size: %d", r))); + + current_len_left = read_tar_number(©buf[124], 12); + + /* Set permissions on the file */ + filemode = read_tar_number(©buf[100], 8); + + /* + * All files are padded up to 512 bytes + */ + current_padding = + ((current_len_left + 511) & ~511) - current_len_left; + + /* + * First part of header is zero terminated filename + */ + snprintf(filename, sizeof(filename), "%s/%s", current_path, + copybuf); + if (filename[strlen(filename) - 1] == '/') + { + /* + * Ends in a slash means directory or symlink to directory + */ + if (copybuf[156] == '5') + { + /* + * Directory + */ + filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */ + if (MakePGDirectory(filename) != 0) + { + if (errno != EEXIST) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create directory \"%s\": %m", + filename))); + } +#ifndef WIN32 + if (chmod(filename, (mode_t) filemode)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not set permissions on directory \"%s\": %m", + filename))); +#endif + } + /* + * Symbolic link + */ + else if (copybuf[156] == '2') + { + /* TODO: tablespace mapping */ + const char *tblspc_path = ©buf[157]; + + filename[strlen(filename) - 1] = '\0'; /* Remove trailing slash */ + + if (symlink(tblspc_path, filename) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not create symbolic link from \"%s\" to \"%s\": %m", + filename, tblspc_path))); + } + else + { + ereport(ERROR, + (errmsg("unrecognized link indicator \"%c\"", + copybuf[156]))); + } + continue; /* directory or link handled */ + } + + /* + * regular file + */ + file = fopen(filename, "wb"); + if (!file) + ereport(ERROR, + (errcode_for_file_access(), + (errmsg("could not create file \"%s\": %m", filename)))); + + flush_offset = 0; + +#ifndef WIN32 + if (chmod(filename, (mode_t) filemode)) + ereport(ERROR, + (errcode_for_file_access(), + (errmsg("could not set permissions on file \"%s\": %m", + filename)))); +#endif + + if (current_len_left == 0) + { + /* + * Done with this file, next one will be a new tar header + */ + fclose(file); + file = NULL; + continue; + } + } /* new file */ + else + { + /* + * Continuing blocks in existing file + */ + if (current_len_left == 0 && r == current_padding) + { + /* + * Received the padding block for this file, ignore it and + * close the file, then move on to the next tar header. + */ + fclose(file); + file = NULL; + continue; + } + + if (fwrite(copybuf, r, 1, file) != 1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", filename))); + + pg_flush_data(fileno(file), flush_offset, r); + flush_offset += r; + totaldone += r; + base_backup_report_progress(); + + current_len_left -= r; + if (current_len_left == 0 && current_padding == 0) + { + /* + * Received the last block, and there is no padding to be + * expected. Close the file and move on to the next tar + * header. + */ + fclose(file); + file = NULL; + continue; + } + } /* continuing data in existing file */ + } /* loop over all data blocks */ + base_backup_report_progress(); + + if (file != NULL) + ereport(ERROR, + (errmsg("COPY stream ended before last file was finished"))); + + if (copybuf != NULL) + PQfreemem(copybuf); +} + +/* + * Make base backup from remote and write to local disk. + */ +static void +libpqrcv_base_backup(WalReceiverConn *conn) +{ + StringInfoData stmt; + PGresult *res; + char xlogstart[64]; + TimeLineID starttli; + XLogRecPtr recptr; + bool error; + + ereport(LOG, + (errmsg("initiating base backup, waiting for remote checkpoint to complete"))); + set_ps_display("waiting for checkpoint", false); + + initStringInfo(&stmt); + appendStringInfo(&stmt, "BASE_BACKUP PROGRESS NOWAIT EXCLUDE_CONF"); + if (cluster_name && cluster_name[0]) + appendStringInfo(&stmt, " LABEL %s", quote_literal_cstr(cluster_name)); + + if (PQsendQuery(conn->streamConn, stmt.data) == 0) + ereport(ERROR, + (errmsg("could not start base backup on remote server: %s", + pchomp(PQerrorMessage(conn->streamConn))))); + + /* + * First result set: WAL start position and timeline ID + */ + res = PQgetResult(conn->streamConn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + PQclear(res); + ereport(ERROR, + (errmsg("could not start base backup on remote server: %s", + pchomp(PQerrorMessage(conn->streamConn))))); + } + if (PQntuples(res) != 1) + { + PQclear(res); + ereport(ERROR, + (errmsg("server returned unexpected response to BASE_BACKUP command; got %d rows and %d fields, expected %d rows and %d fields", + PQntuples(res), PQnfields(res), 1, 2))); + } + + ereport(LOG, + (errmsg("remote checkpoint completed"))); + + strlcpy(xlogstart, PQgetvalue(res, 0, 0), sizeof(xlogstart)); + starttli = atoi(PQgetvalue(res, 0, 1)); + PQclear(res); + elog(DEBUG1, "write-ahead log start point: %s on timeline %u", + xlogstart, starttli); + recptr = pg_lsn_in_internal(xlogstart, &error); + if (error) + elog(ERROR, "invalid LSN received: %s", xlogstart); + + /* + * Second result set: tablespace information + */ + res = PQgetResult(conn->streamConn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + PQclear(res); + ereport(ERROR, + (errmsg("could not get backup header: %s", + pchomp(PQerrorMessage(conn->streamConn))))); + } + if (PQntuples(res) < 1) + { + PQclear(res); + ereport(ERROR, + (errmsg("no data returned from server"))); + } + + totalsize_kb = totaldone = 0; + tablespacecount = PQntuples(res); + for (int i = 0; i < PQntuples(res); i++) + { + totalsize_kb += atol(PQgetvalue(res, i, 2)); + } + + RequestXLogStreaming(starttli, recptr, PrimaryConnInfo, PrimarySlotName); + + /* + * Start receiving chunks + */ + for (int i = 0; i < PQntuples(res); i++) + { + tablespacenum = i; + ReceiveAndUnpackTarFile(conn->streamConn, res); + } + tablespacenum++; + base_backup_report_progress(); + + PQclear(res); + + /* + * Final result set: WAL end position and timeline ID + */ + res = PQgetResult(conn->streamConn); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + PQclear(res); + ereport(ERROR, + (errmsg("could not get write-ahead log end position from server: %s", + pchomp(PQerrorMessage(conn->streamConn))))); + } + if (PQntuples(res) != 1) + { + PQclear(res); + ereport(ERROR, + (errmsg("no write-ahead log end position returned from server"))); + } + PQclear(res); + + res = PQgetResult(conn->streamConn); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + const char *sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + + if (sqlstate && + strcmp(sqlstate, "XX001" /*ERRCODE_DATA_CORRUPTED*/) == 0) + ereport(ERROR, + (errmsg("checksum error occurred"))); + else + ereport(ERROR, + (errmsg("final receive failed: %s", + pchomp(PQerrorMessage(conn->streamConn))))); + } + PQclear(res); +} + /* * Start streaming WAL data from given streaming options. * diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y index 2d96567409..0a679ea0ee 100644 --- a/src/backend/replication/repl_gram.y +++ b/src/backend/replication/repl_gram.y @@ -78,6 +78,7 @@ static SQLCmd *make_sqlcmd(void); %token K_WAL %token K_TABLESPACE_MAP %token K_NOVERIFY_CHECKSUMS +%token K_EXCLUDE_CONF %token K_TIMELINE %token K_PHYSICAL %token K_LOGICAL @@ -154,8 +155,7 @@ var_name: IDENT { $$ = $1; } ; /* - * BASE_BACKUP [LABEL '<label>'] [PROGRESS] [FAST] [WAL] [NOWAIT] - * [MAX_RATE %d] [TABLESPACE_MAP] [NOVERIFY_CHECKSUMS] + * BASE_BACKUP [option]... */ base_backup: K_BASE_BACKUP base_backup_opt_list @@ -214,6 +214,11 @@ base_backup_opt: $$ = makeDefElem("noverify_checksums", (Node *)makeInteger(true), -1); } + | K_EXCLUDE_CONF + { + $$ = makeDefElem("exclude_conf", + (Node *)makeInteger(true), -1); + } ; create_replication_slot: diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l index 14c9a1e798..c2e2aced7d 100644 --- a/src/backend/replication/repl_scanner.l +++ b/src/backend/replication/repl_scanner.l @@ -93,6 +93,7 @@ MAX_RATE { return K_MAX_RATE; } WAL { return K_WAL; } TABLESPACE_MAP { return K_TABLESPACE_MAP; } NOVERIFY_CHECKSUMS { return K_NOVERIFY_CHECKSUMS; } +EXCLUDE_CONF { return K_EXCLUDE_CONF; } TIMELINE { return K_TIMELINE; } START_REPLICATION { return K_START_REPLICATION; } CREATE_REPLICATION_SLOT { return K_CREATE_REPLICATION_SLOT; } diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index fa79b45f63..6dd7e2f938 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -3154,21 +3154,14 @@ looks_like_temp_rel_name(const char *name) * Other symlinks are presumed to point at files we're not responsible * for fsyncing, and might not have privileges to write at all. * - * Errors are logged but not considered fatal; that's because this is used - * only during database startup, to deal with the possibility that there are - * issued-but-unsynced writes pending against the data directory. We want to - * ensure that such writes reach disk before anything that's done in the new - * run. However, aborting on error would result in failure to start for - * harmless cases such as read-only files in the data directory, and that's - * not good either. - * - * Note that if we previously crashed due to a PANIC on fsync(), we'll be - * rewriting all changes again during recovery. + * If pre_sync is true, issue flush requests to the kernel before starting the + * actual fsync calls. This can be skipped if the caller has already done it + * itself. * * Note we assume we're chdir'd into PGDATA to begin with. */ void -SyncDataDirectory(void) +SyncDataDirectory(bool pre_sync, int loglevel) { bool xlog_is_symlink; @@ -3187,7 +3180,7 @@ SyncDataDirectory(void) struct stat st; if (lstat("pg_wal", &st) < 0) - ereport(LOG, + ereport(loglevel, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", "pg_wal"))); @@ -3201,15 +3194,18 @@ SyncDataDirectory(void) /* * If possible, hint to the kernel that we're soon going to fsync the data - * directory and its contents. Errors in this step are even less + * directory and its contents. Errors in this step are less * interesting than normal, so log them only at DEBUG1. */ + if (pre_sync) + { #ifdef PG_FLUSH_DATA_WORKS - walkdir(".", pre_sync_fname, false, DEBUG1); - if (xlog_is_symlink) - walkdir("pg_wal", pre_sync_fname, false, DEBUG1); - walkdir("pg_tblspc", pre_sync_fname, true, DEBUG1); + walkdir(".", pre_sync_fname, false, DEBUG1); + if (xlog_is_symlink) + walkdir("pg_wal", pre_sync_fname, false, DEBUG1); + walkdir("pg_tblspc", pre_sync_fname, true, DEBUG1); #endif + } /* * Now we do the fsync()s in the same order. @@ -3220,10 +3216,10 @@ SyncDataDirectory(void) * in pg_tblspc, they'll get fsync'd twice. That's not an expected case * so we don't worry about optimizing it. */ - walkdir(".", datadir_fsync_fname, false, LOG); + walkdir(".", datadir_fsync_fname, false, loglevel); if (xlog_is_symlink) - walkdir("pg_wal", datadir_fsync_fname, false, LOG); - walkdir("pg_tblspc", datadir_fsync_fname, true, LOG); + walkdir("pg_wal", datadir_fsync_fname, false, loglevel); + walkdir("pg_tblspc", datadir_fsync_fname, true, loglevel); } /* diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index ec6d0bdf8e..fc8ca5a65b 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -136,6 +136,7 @@ static char *pwfilename = NULL; static char *superuser_password = NULL; static const char *authmethodhost = NULL; static const char *authmethodlocal = NULL; +static bool replica = false; static bool debug = false; static bool noclean = false; static bool do_sync = true; @@ -2935,6 +2936,22 @@ initialize_data_directory(void) /* Now create all the text config files */ setup_config(); + /* + * If data directory for replica requested, write basebackup.signal, and + * then we are done here. + */ + if (replica) + { + char *path; + char *lines[1] = {NULL}; + + path = psprintf("%s/basebackup.signal", pg_data); + writefile(path, lines); + free(path); + + return; + } + /* Bootstrap template1 */ bootstrap_template1(); @@ -3026,6 +3043,7 @@ main(int argc, char *argv[]) {"wal-segsize", required_argument, NULL, 12}, {"data-checksums", no_argument, NULL, 'k'}, {"allow-group-access", no_argument, NULL, 'g'}, + {"replica", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0} }; @@ -3067,7 +3085,7 @@ main(int argc, char *argv[]) /* process command-line options */ - while ((c = getopt_long(argc, argv, "dD:E:kL:nNU:WA:sST:X:g", long_options, &option_index)) != -1) + while ((c = getopt_long(argc, argv, "dD:E:kL:nNrU:WA:sST:X:g", long_options, &option_index)) != -1) { switch (c) { @@ -3113,6 +3131,9 @@ main(int argc, char *argv[]) case 'N': do_sync = false; break; + case 'r': + replica = true; + break; case 'S': sync_only = true; break; @@ -3334,9 +3355,19 @@ main(int argc, char *argv[]) /* translator: This is a placeholder in a shell command. */ appendPQExpBuffer(start_db_cmd, " -l %s start", _("logfile")); - printf(_("\nSuccess. You can now start the database server using:\n\n" - " %s\n\n"), - start_db_cmd->data); + if (!replica) + { + printf(_("\nSuccess. You can now start the database server using:\n\n" + " %s\n\n"), + start_db_cmd->data); + } + else + { + printf(_("\nSo far so good. Now configure the replication connection in\n" + "postgresql.conf, and then start the database server using:\n\n" + " %s\n\n"), + start_db_cmd->data); + } destroyPQExpBuffer(start_db_cmd); diff --git a/src/bin/pg_resetwal/pg_resetwal.c b/src/bin/pg_resetwal/pg_resetwal.c index f9cfeae264..c9edeb54d3 100644 --- a/src/bin/pg_resetwal/pg_resetwal.c +++ b/src/bin/pg_resetwal/pg_resetwal.c @@ -76,7 +76,7 @@ static int WalSegSz; static int set_wal_segsize; static void CheckDataVersion(void); -static bool ReadControlFile(void); +static bool read_controlfile(void); static void GuessControlValues(void); static void PrintControlValues(bool guessed); static void PrintNewControlValues(void); @@ -393,7 +393,7 @@ main(int argc, char *argv[]) /* * Attempt to read the existing pg_control file */ - if (!ReadControlFile()) + if (!read_controlfile()) GuessControlValues(); /* @@ -578,7 +578,7 @@ CheckDataVersion(void) * to the current format. (Currently we don't do anything of the sort.) */ static bool -ReadControlFile(void) +read_controlfile(void) { int fd; int len; diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 98b033fc20..e56d85a96d 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -127,8 +127,8 @@ extern char *archiveCleanupCommand; extern bool recoveryTargetInclusive; extern int recoveryTargetAction; extern int recovery_min_apply_delay; -extern char *PrimaryConnInfo; -extern char *PrimarySlotName; +extern PGDLLIMPORT char *PrimaryConnInfo; +extern PGDLLIMPORT char *PrimarySlotName; /* indirectly set via GUC system */ extern TransactionId recoveryTargetXid; @@ -298,6 +298,9 @@ extern Size XLOGShmemSize(void); extern void XLOGShmemInit(void); extern void BootStrapXLOG(void); extern void LocalProcessControlFile(bool reset); +extern void InitControlFile(uint64 sysidentifier); +extern void WriteControlFile(void); +extern void ReadControlFile(void); extern void StartupXLOG(void); extern void ShutdownXLOG(int code, Datum arg); extern void InitXLOGAccess(void); @@ -354,6 +357,7 @@ extern void register_persistent_abort_backup_handler(void); extern SessionBackupState get_backup_status(void); /* File path names (all relative to $PGDATA) */ +#define BASEBACKUP_SIGNAL_FILE "basebackup.signal" #define RECOVERY_SIGNAL_FILE "recovery.signal" #define STANDBY_SIGNAL_FILE "standby.signal" #define BACKUP_LABEL_FILE "backup_label" diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 62d64aa0a1..38311b05a1 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -396,6 +396,7 @@ typedef enum CheckerProcess = 0, BootstrapProcess, StartupProcess, + BaseBackupProcess, BgWriterProcess, CheckpointerProcess, WalWriterProcess, @@ -408,6 +409,7 @@ extern AuxProcType MyAuxProcType; #define AmBootstrapProcess() (MyAuxProcType == BootstrapProcess) #define AmStartupProcess() (MyAuxProcType == StartupProcess) +#define AmBaseBackupProcess() (MyAuxProcType == BaseBackupProcess) #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 36b530bc27..2f87ef63a6 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -721,6 +721,7 @@ typedef enum BackendType B_AUTOVAC_LAUNCHER, B_AUTOVAC_WORKER, B_BACKEND, + B_BASE_BACKUP, B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, diff --git a/src/include/replication/basebackup.h b/src/include/replication/basebackup.h index 07ed281bd6..0764a7b67b 100644 --- a/src/include/replication/basebackup.h +++ b/src/include/replication/basebackup.h @@ -33,4 +33,6 @@ extern void SendBaseBackup(BaseBackupCmd *cmd); extern int64 sendTablespace(char *path, bool sizeonly); +extern void BaseBackupMain(void); + #endif /* _BASEBACKUP_H */ diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h index e08afc6548..83fc4b3fb0 100644 --- a/src/include/replication/walreceiver.h +++ b/src/include/replication/walreceiver.h @@ -221,6 +221,7 @@ typedef void (*walrcv_readtimelinehistoryfile_fn) (WalReceiverConn *conn, TimeLineID tli, char **filename, char **content, int *size); +typedef void (*walrcv_base_backup_fn) (WalReceiverConn *conn); typedef bool (*walrcv_startstreaming_fn) (WalReceiverConn *conn, const WalRcvStreamOptions *options); typedef void (*walrcv_endstreaming_fn) (WalReceiverConn *conn, @@ -249,6 +250,7 @@ typedef struct WalReceiverFunctionsType walrcv_identify_system_fn walrcv_identify_system; walrcv_server_version_fn walrcv_server_version; walrcv_readtimelinehistoryfile_fn walrcv_readtimelinehistoryfile; + walrcv_base_backup_fn walrcv_base_backup; walrcv_startstreaming_fn walrcv_startstreaming; walrcv_endstreaming_fn walrcv_endstreaming; walrcv_receive_fn walrcv_receive; @@ -275,6 +277,8 @@ extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions; WalReceiverFunctions->walrcv_server_version(conn) #define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \ WalReceiverFunctions->walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) +#define walrcv_base_backup(conn) \ + WalReceiverFunctions->walrcv_base_backup(conn) #define walrcv_startstreaming(conn, options) \ WalReceiverFunctions->walrcv_startstreaming(conn, options) #define walrcv_endstreaming(conn, next_tli) \ diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h index c6ce7eacf2..5b3153455c 100644 --- a/src/include/storage/fd.h +++ b/src/include/storage/fd.h @@ -148,7 +148,7 @@ extern void fsync_fname(const char *fname, bool isdir); extern int durable_rename(const char *oldfile, const char *newfile, int loglevel); extern int durable_unlink(const char *fname, int loglevel); extern int durable_link_or_rename(const char *oldfile, const char *newfile, int loglevel); -extern void SyncDataDirectory(void); +extern void SyncDataDirectory(bool pre_sync, int loglevel); extern int data_sync_elevel(int elevel); /* Filename components */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ce93ace76c..c087c51dbe 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -264,7 +264,7 @@ extern int temp_file_limit; extern int num_temp_buffers; -extern char *cluster_name; +extern PGDLLIMPORT char *cluster_name; extern PGDLLIMPORT char *ConfigFileName; extern char *HbaFileName; extern char *IdentFileName; diff --git a/src/test/recovery/t/018_basebackup.pl b/src/test/recovery/t/018_basebackup.pl new file mode 100644 index 0000000000..99731fc388 --- /dev/null +++ b/src/test/recovery/t/018_basebackup.pl @@ -0,0 +1,29 @@ +# Test basebackup worker functionality +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +my $node1 = get_new_node('node1'); +$node1->init(allows_streaming => 1); +$node1->start; + +$node1->safe_psql('postgres', + "CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a"); + +my $node2 = get_new_node('node2'); +$node2->init(allows_streaming => 1, extra => [ '--replica' ]); +$node2->append_conf('postgresql.conf', "primary_conninfo = '" . $node1->connstr . "'"); +my $old_mtime = (stat($node2->data_dir . '/postgresql.conf'))[9]; +$node2->start; + +$node1->wait_for_catchup($node2, 'replay', $node1->lsn('insert')); + +is($node2->safe_psql('postgres', "SELECT count(*) FROM tab_int"), + qq(1000), + 'check content of standby'); + +my $new_mtime = (stat($node2->data_dir . '/postgresql.conf'))[9]; +is($new_mtime, $old_mtime, + 'configuration files were not copied'); base-commit: a166d408eb0b35023c169e765f4664c3b114b52e -- 2.24.1