diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 4c5ed1e6d6..3fec491bbe 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -2552,6 +2552,14 @@ The commands accepted in walsender mode are: with pgsql_tmp. + + + Unlogged relations, except for the init fork which is required to + recreate the (empty) unlogged relation on recovery. Unlogged tables + created during a backup may be copied, but will be excluded from the + next backup. + + pg_wal, including subdirectories. If the backup is run diff --git a/src/backend/replication/basebackup.c b/src/backend/replication/basebackup.c index dd7ad64862..1a5c91de17 100644 --- a/src/backend/replication/basebackup.c +++ b/src/backend/replication/basebackup.c @@ -26,6 +26,7 @@ #include "nodes/pg_list.h" #include "pgtar.h" #include "pgstat.h" +#include "port.h" #include "postmaster/syslogger.h" #include "replication/basebackup.h" #include "replication/walsender.h" @@ -33,6 +34,7 @@ #include "storage/dsm_impl.h" #include "storage/fd.h" #include "storage/ipc.h" +#include "storage/reinit.h" #include "utils/builtins.h" #include "utils/elog.h" #include "utils/ps_status.h" @@ -959,6 +961,36 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, char pathbuf[MAXPGPATH * 2]; struct stat statbuf; int64 size = 0; + HTAB *unloggedHash = NULL; /* Unlogged tables in this path. */ + const char *unloggedDelim; /* Split this path from parent path. */ + + /* + * Find any unlogged relations in this path and store them in a hash. All + * unlogged relation forks except init will be excluded from the backup. + * + * Start by finding the location of the delimiter between the parent + * path and the current path. + */ + unloggedDelim = last_dir_separator(path); + + /* Does this path look like a database path (i.e. all digits)? */ + if (unloggedDelim != NULL && + strspn(unloggedDelim + 1, "0123456789") == strlen(unloggedDelim + 1)) + { + /* Part of path that contains the parent directory. */ + int parentPathLen = unloggedDelim - path; + + /* + * Build the unlogged relation hash if the parent path is either + * $PGDATA/base or a tablespace version path. + */ + if (strncmp(path, "./base", parentPathLen) == 0 || + (parentPathLen >= (sizeof(TABLESPACE_VERSION_DIRECTORY) - 1) && + strncmp(unloggedDelim - (sizeof(TABLESPACE_VERSION_DIRECTORY) - 1), + TABLESPACE_VERSION_DIRECTORY, + sizeof(TABLESPACE_VERSION_DIRECTORY) - 1) == 0)) + unloggedHash = ResetUnloggedRelationsHash(path); + } dir = AllocateDir(path); while ((de = ReadDir(dir, path)) != NULL) @@ -1008,6 +1040,15 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, if (excludeFound) continue; + /* Exclude all forks for unlogged tables except the init fork. */ + if (unloggedHash && ResetUnloggedRelationsMatch( + unloggedHash, de->d_name) == unloggedOther) + { + elog(DEBUG2, "unlogged relation 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 */ @@ -1177,6 +1218,11 @@ sendDir(const char *path, int basepathlen, bool sizeonly, List *tablespaces, (errmsg("skipping special file \"%s\"", pathbuf))); } FreeDir(dir); + + /* Free the hash containing unlogged tables, if it was created */ + if (unloggedHash != NULL) + hash_destroy(unloggedHash); + return size; } diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index cdf4f5be37..455c7fca0d 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -4,7 +4,7 @@ use Cwd; use Config; use PostgresNode; use TestLib; -use Test::More tests => 79; +use Test::More tests => 87; program_help_ok('pg_basebackup'); program_version_ok('pg_basebackup'); @@ -66,6 +66,16 @@ foreach my $filename ( # positive. $node->safe_psql('postgres', 'SELECT 1;'); +# Create an unlogged table to test that forks other than init are not copied. +$node->safe_psql('postgres', 'CREATE UNLOGGED TABLE base_unlogged (id int)'); + +my $baseUnloggedPath = $node->safe_psql('postgres', + q{select pg_relation_filepath('base_unlogged')}); + +# Make sure main and init forks exist +ok(-f "$pgdata/${baseUnloggedPath}_init", 'unlogged init fork in base'); +ok(-f "$pgdata/$baseUnloggedPath", 'unlogged main fork in base'); + $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup", '-X', 'none' ], 'pg_basebackup runs'); ok(-f "$tempdir/backup/PG_VERSION", 'backup was created'); @@ -96,6 +106,12 @@ foreach my $filename ( ok(!-f "$tempdir/backup/$filename", "$filename not copied"); } +# Unlogged relation forks other than init should not be copied +ok(-f "$tempdir/backup/${baseUnloggedPath}_init", + 'unlogged init fork in backup'); +ok(!-f "$tempdir/backup/$baseUnloggedPath", + 'unlogged main fork not in backup'); + # Make sure existing backup_label was ignored. isnt(slurp_file("$tempdir/backup/backup_label"), 'DONOTCOPY', 'existing backup_label not copied'); @@ -147,7 +163,7 @@ unlink "$pgdata/$superlongname"; # skip on Windows. SKIP: { - skip "symlinks not supported on Windows", 11 if ($windows_os); + skip "symlinks not supported on Windows", 15 if ($windows_os); # Move pg_replslot out of $pgdata and create a symlink to it. $node->stop; @@ -177,6 +193,19 @@ SKIP: my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar"; is(scalar(@tblspc_tars), 1, 'one tablespace tar was created'); + # Create an unlogged table to test that forks other than init are not copied. + $node->safe_psql('postgres', + 'CREATE UNLOGGED TABLE tblspc1_unlogged (id int) TABLESPACE tblspc1;'); + + my $tblspc1UnloggedPath = $node->safe_psql( + 'postgres', q{select pg_relation_filepath('tblspc1_unlogged')}); + + # Make sure main and init forks exist + ok(-f "$pgdata/${tblspc1UnloggedPath}_init", + 'unlogged init fork in tablespace'); + ok(-f "$pgdata/$tblspc1UnloggedPath", + 'unlogged main fork in tablespace'); + $node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup1", '-Fp' ], 'plain format with tablespaces fails without tablespace mapping'); @@ -195,11 +224,20 @@ SKIP: "tablespace symlink was updated"); closedir $dh; + # Unlogged relation forks other than init should not be copied + my ($tblspc1UnloggedBackupPath) = $tblspc1UnloggedPath =~ /[^\/]*\/[^\/]*\/[^\/]*$/g; + + ok(-f "$tempdir/tbackup/tblspc1/${tblspc1UnloggedBackupPath}_init", + 'unlogged init fork in tablespace backup'); + ok(!-f "$tempdir/tbackup/tblspc1/$tblspc1UnloggedBackupPath", + 'unlogged main fork not in tablespace backup'); + ok( -d "$tempdir/backup1/pg_replslot", 'pg_replslot symlink copied as directory'); mkdir "$tempdir/tbl=spc2"; $node->safe_psql('postgres', "DROP TABLE test1;"); + $node->safe_psql('postgres', "DROP TABLE tblspc1_unlogged;"); $node->safe_psql('postgres', "DROP TABLESPACE tblspc1;"); $node->safe_psql('postgres', "CREATE TABLESPACE tblspc2 LOCATION '$shorter_tempdir/tbl=spc2';");