diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c index 5914b15017..821e4b0c81 100644 --- a/src/bin/pg_rewind/libpq_fetch.c +++ b/src/bin/pg_rewind/libpq_fetch.c @@ -337,7 +337,18 @@ receiveFileChunks(const char *sql) pg_log(PG_DEBUG, "received chunk for file \"%s\", offset %s, size %d\n", filename, chunkoff_str, chunksize); - open_target_file(filename, false); + /* + * Truncate new files which are fully copied. We are sure to not + * truncate a file whose data has been partially fetched for two + * reasons: + * 1) The set of file ranges is ordered so as the chunks of each + * file is processed sequentially. + * 2) Only files fully copied register a chunk with an offset of + * 0, which means that the beginning of a file is received, so it + * should be truncated first. Note that the truncation is lossy, + * and may be tried on a file which does not exist locally. + */ + open_target_file(filename, chunkoff == 0); write_target_range(chunk, chunkoff, chunksize); @@ -464,8 +475,6 @@ libpq_executeFileMap(filemap_t *map) break; case FILE_ACTION_COPY: - /* Truncate the old file out of the way, if any */ - open_target_file(entry->path, true); fetch_file_range(entry->path, 0, entry->newsize); break; @@ -501,12 +510,17 @@ libpq_executeFileMap(filemap_t *map) /* * We've now copied the list of file ranges that we need to fetch to the - * temporary table. Now, actually fetch all of those ranges. + * temporary table. Now, actually fetch all of those ranges. The elements + * fetched are ordered so as the truncation of files fully copied from the + * source server is done after receiving their first chunk. This also + * gives a better sequential performance to the operations, especially + * when working on large files. */ sql = "SELECT path, begin,\n" " pg_read_binary_file(path, begin, len, true) AS chunk\n" - "FROM fetchchunks\n"; + "FROM fetchchunks\n" + "ORDER BY path, begin\n"; receiveFileChunks(sql); } diff --git a/src/bin/pg_rewind/t/006_readonly.pl b/src/bin/pg_rewind/t/006_readonly.pl new file mode 100644 index 0000000000..51db93a9a0 --- /dev/null +++ b/src/bin/pg_rewind/t/006_readonly.pl @@ -0,0 +1,85 @@ +# Test how pg_rewind reacts to read-only files in the data dirs. +# All such files should be ignored in the process. + +use strict; +use warnings; +use TestLib; +use Test::More tests => 2; + +use File::Copy; +use File::Find; + +use RewindTest; + +my $test_mode = "remote"; + +RewindTest::setup_cluster($test_mode); +RewindTest::start_master(); +RewindTest::create_standby($test_mode); + +# Create the same read-only file in standby and master +my $test_master_datadir = $node_master->data_dir; +my $test_standby_datadir = $node_standby->data_dir; +my $readonly_master = "$test_master_datadir/readonly_file"; +my $readonly_standby = "$test_standby_datadir/readonly_file"; + +append_to_file($readonly_master, "in master"); +append_to_file($readonly_standby, "in standby"); +chmod 0400, $readonly_master, $readonly_standby; + +RewindTest::promote_standby(); + +# Stop the master and run pg_rewind. +$node_master->stop; + +my $master_pgdata = $node_master->data_dir; +my $standby_pgdata = $node_standby->data_dir; +my $standby_connstr = $node_standby->connstr('postgres'); +my $tmp_folder = TestLib::tempdir; + +# Keep a temporary postgresql.conf for master node or it would be +# overwritten during the rewind. +copy( + "$master_pgdata/postgresql.conf", + "$tmp_folder/master-postgresql.conf.tmp"); + +# Including read-only data in the source and the target will +# cause pg_rewind to fail. +my $rewind_command = [ 'pg_rewind', "--debug", + "--source-server", $standby_connstr, + "--target-pgdata=$master_pgdata" ]; + +command_fails($rewind_command, 'pg_rewind fails with read-only'); + +# Need to put the server back into a clean state for next rewind. +# Move back postgresql.conf with old settings to allow the node to +# start and stop, allowing the follow-up rewind to work properly. +move("$tmp_folder/master-postgresql.conf.tmp", + "$master_pgdata/postgresql.conf"); +$node_master->start; +$node_master->stop; + +# Now remove the read-only data on both sides, the data folder +# from the previous attempt should still be able to work. +unlink($readonly_master); +unlink($readonly_standby); +command_ok($rewind_command, 'pg_rewind passes without read-only'); + +# Now move back postgresql.conf with old settings +move("$tmp_folder/master-postgresql.conf.tmp", + "$master_pgdata/postgresql.conf"); + +# Plug-in rewound node to the now-promoted standby node +my $port_standby = $node_standby->port; +$node_master->append_conf('recovery.conf', qq( +primary_conninfo='port=$port_standby' +standby_mode=on +recovery_target_timeline='latest' +)); + +# Restart the master to check that rewind went correctly. +$node_master->start; + +RewindTest::clean_rewind_test(); + +exit(0);