diff --git a/build/lib/pgBackRestBuild/Config/Data.pm b/build/lib/pgBackRestBuild/Config/Data.pm index dc98d8813..3568d6195 100644 --- a/build/lib/pgBackRestBuild/Config/Data.pm +++ b/build/lib/pgBackRestBuild/Config/Data.pm @@ -281,6 +281,8 @@ use constant CFGOPT_REPO_S3_VERIFY_SSL => CFGDEF_RE #----------------------------------------------------------------------------------------------------------------------------------- use constant CFGOPT_ARCHIVE_ASYNC => 'archive-async'; push @EXPORT, qw(CFGOPT_ARCHIVE_ASYNC); +use constant CFGOPT_ARCHIVE_GET_QUEUE_MAX => 'archive-get-queue-max'; + push @EXPORT, qw(CFGOPT_ARCHIVE_GET_QUEUE_MAX); use constant CFGOPT_ARCHIVE_PUSH_QUEUE_MAX => 'archive-push-queue-max'; push @EXPORT, qw(CFGOPT_ARCHIVE_PUSH_QUEUE_MAX); @@ -558,6 +560,7 @@ my $rhCommandDefine = &CFGCMD_ARCHIVE_GET => { &CFGDEF_LOG_FILE => false, + &CFGDEF_LOCK_TYPE => CFGDEF_LOCK_TYPE_ARCHIVE, }, &CFGCMD_ARCHIVE_PUSH => @@ -1048,6 +1051,7 @@ my %hConfigDefine = &CFGDEF_ALLOW_RANGE => [WAIT_TIME_MINIMUM, 86400], &CFGDEF_COMMAND => { + &CFGCMD_ARCHIVE_GET => {}, &CFGCMD_ARCHIVE_PUSH => {}, &CFGCMD_BACKUP => {}, &CFGCMD_CHECK => {}, @@ -1756,6 +1760,7 @@ my %hConfigDefine = &CFGDEF_DEFAULT => '/var/spool/' . BACKREST_EXE, &CFGDEF_COMMAND => { + &CFGCMD_ARCHIVE_GET => {}, &CFGCMD_ARCHIVE_PUSH => {}, }, &CFGDEF_DEPEND => @@ -1773,6 +1778,7 @@ my %hConfigDefine = &CFGDEF_ALLOW_RANGE => [1, 96], &CFGDEF_COMMAND => { + &CFGCMD_ARCHIVE_GET => {}, &CFGCMD_ARCHIVE_PUSH => {}, &CFGCMD_BACKUP => {}, &CFGCMD_RESTORE => {}, @@ -1864,6 +1870,7 @@ my %hConfigDefine = &CFGDEF_DEFAULT => false, &CFGDEF_COMMAND => { + &CFGCMD_ARCHIVE_GET => {}, &CFGCMD_ARCHIVE_PUSH => {}, } }, @@ -1884,6 +1891,18 @@ my %hConfigDefine = }, }, + &CFGOPT_ARCHIVE_GET_QUEUE_MAX => + { + &CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL, + &CFGDEF_TYPE => CFGDEF_TYPE_INTEGER, + &CFGDEF_DEFAULT => 128 * 1024 * 1024, # 128MB + &CFGDEF_ALLOW_RANGE => [0, 4 * 1024 * 1024 * 1024 * 1024 * 1024], # 0-4PB + &CFGDEF_COMMAND => + { + &CFGCMD_ARCHIVE_GET => {}, + }, + }, + # Backup options #------------------------------------------------------------------------------------------------------------------------------- &CFGOPT_ARCHIVE_CHECK => diff --git a/doc/xml/reference.xml b/doc/xml/reference.xml index 39aacea91..93677b94f 100644 --- a/doc/xml/reference.xml +++ b/doc/xml/reference.xml @@ -177,9 +177,15 @@ Path where transient data is stored. - This path is used to store acknowledgements from the asynchronous archive-push process. These files are generally very small (zero to a few hundred bytes) so not much space is required. + This path is used to store data for the asynchronous archive-push and archive-get command. - The data stored in the spool path is not strictly temporary since it can and should survive a reboot. However, loss of the data in the spool path is not a problem. will simply recheck each WAL segment to ensure it is safely archived. + The asynchronous archive-push command writes acknowledgements into the spool path when it has successfully stored WAL in the archive (and errors on failure) so the foreground process can quickly notify . Acknowledgement files are very small (zero on success and a few hundred bytes on error). + + The asynchronous archive-push process queues WAL in the spool path so it can be provided very quickly when requests it. Moving files to is most efficient when the spool path is on the same filesystem as pg_xlog/pg_wal. + + The data stored in the spool path is not strictly temporary since it can and should survive a reboot. However, loss of the data in the spool path is not a problem. will simply recheck each WAL segment to ensure it is safely archived for archive-push and rebuild the queue for archive-get. + + The spool path is intended to be located on a local Posix-compatible filesystem, not a remote filesystem such as NFS or CIFS. /backup/db/spool @@ -514,7 +520,7 @@ Manifest save threshold during backup. - Defines how often the manifest will be saved during a backup (in bytes). Saving the manifest is important because it stores the checksums and allows the resume function to work efficiently. The actual threshold used is 1% of the backup size or manifest-save-threshold, whichever is greater. + Defines how often the manifest will be saved during a backup. Saving the manifest is important because it stores the checksums and allows the resume function to work efficiently. The actual threshold used is 1% of the backup size or manifest-save-threshold, whichever is greater. Size can be entered in bytes (default) or KB, MB, GB, TB, or PB where the multiplier is a power of 1024. @@ -558,21 +564,34 @@ - The archive section defines parameters when doing async archiving. This means that the archive files will be stored locally, then a background process will pick them and move them to the backup. + The archive section defines options for the archive-push and archive-get commands. - Archive WAL segments asynchronously. + Push/get WAL segments asynchronously. - WAL segments will be copied to the local repo, then a process will be forked to compress the segment and transfer it to the remote repo if configured. Control will be returned to as soon as the WAL segment is copied locally. + Enables asynchronous operation for the archive-push and archive-get commands. + + Asynchronous operation is more efficient because it can reuse connections and take advantage of parallelism. See the spool-path, archive-get-queue-max, and archive-push-queue-max options for more information. y + + + Maximum size of the archive-get queue. + + Specifies the maximum size of the archive-get queue when archive-async is enabled. The queue is stored in the spool-path and is used to speed providing WAL to . + + Size can be entered in bytes (default) or KB, MB, GB, TB, or PB where the multiplier is a power of 1024. + + 1073741824 + + - Limit size (in bytes) of the archive queue. + Maximum size of the archive queue. After the limit is reached, the following will happen:
    diff --git a/doc/xml/release.xml b/doc/xml/release.xml index 62998de49..140dfa788 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -25,6 +25,10 @@ + +

    Add asynchronous, parallel archive-get. This feature maintains a queue of WAL segments to help reduce latency when requests a WAL segment with restore_command.

    +
    + diff --git a/doc/xml/user-guide.xml b/doc/xml/user-guide.xml index 4f66f4052..6134fc7b6 100644 --- a/doc/xml/user-guide.xml +++ b/doc/xml/user-guide.xml @@ -1970,75 +1970,6 @@ - - -
    - Asynchronous Archiving - -

    The archive-async option offloads WAL archiving to a separate process (or processes) to improve throughput. It works by looking ahead to see which WAL segments are ready to be archived beyond the request that is currently making via the archive_command. WAL segments are transferred to the archive directly from the pg_xlog/pg_wal directory and success is only returned by the archive_command when the WAL segment has been safely stored in the archive.

    - -

    The spool directory is created to hold the current status of WAL archiving. Status files written into the spool directory are typically zero length and should consume a minimal amount of space (a few MB at most) and very little IO. All the information in this directory can be recreated so it is not necessary to preserve the spool directory if the cluster is moved to new hardware.

    - -

    NOTE: In the original implementation of asynchronous archiving, WAL segments were copied to the spool directory before compression and transfer. The new implementation copies WAL directly from the pg_xlog directory. If asynchronous archiving was utilized in v1.12 or prior, read the v1.13 release notes carefully before upgrading.

    - - - Create the spool directory - - - mkdir -m 750 {[spool-path]} - - - chown postgres:postgres {[spool-path]} - - - -

    The spool path must be configured and asynchronous archiving enabled. Asynchronous archiving automatically confers some benefit by reducing the number of ssh connections made to the backup server, but setting process-max can drastically improve performance. Be sure not to set process-max so high that it affects normal database operations.

    - - - Configure the spool path and asynchronous archiving - - {[spool-path]} - y - 2 - - -

    The [stanza]-archive-push-async.log file can be used to monitor the activity of the asynchronous process. A good way to test this is to quickly push a number of WAL segments.

    - - - Test parallel asynchronous archiving - - - rm -f /var/log/pgbackrest/demo-archive-push-async.log - - - - - psql -c " - select pg_create_restore_point('test async push'); select pg_switch_xlog(); - select pg_create_restore_point('test async push'); select pg_switch_xlog(); - select pg_create_restore_point('test async push'); select pg_switch_xlog(); - select pg_create_restore_point('test async push'); select pg_switch_xlog(); - select pg_create_restore_point('test async push'); select pg_switch_xlog();" - - - - - {[project-exe]} {[dash]}-stanza={[postgres-cluster-demo]} {[dash]}-log-level-console=info check - WAL segment - - - -

    Now the log file will contain parallel, asynchronous activity.

    - - - Check results in the log - - - cat /var/log/pgbackrest/demo-archive-push-async.log - WAL file\(s\) to archive|pushed WAL file 0000000 - - -
    @@ -2222,6 +2153,8 @@ standby_mode=on + detail + off n @@ -2518,6 +2451,158 @@ + +
    + Asynchronous Archiving + +

    Asynchronous archiving is enabled with the archive-async option. This option enables asynchronous operation for both the archive-push and archive-get commands.

    + +

    A spool path is required. The commands will store transient data here but each command works quite a bit differently so spool path usage is described in detail in each section.

    + + + Create the spool directory + + + mkdir -m 750 {[spool-path]} + + + chown postgres:postgres {[spool-path]} + + + + + Create the spool directory + + + mkdir -m 750 {[spool-path]} + + + chown postgres:postgres {[spool-path]} + + + +

    The spool path must be configured and asynchronous archiving enabled. Asynchronous archiving automatically confers some benefit by reducing the number of connections made to remote storage, but setting process-max can drastically improve performance by parallelizing operations. Be sure not to set process-max so high that it affects normal database operations.

    + + + Configure the spool path and asynchronous archiving + + {[spool-path]} + y + 2 + 2 + + + + Configure the spool path and asynchronous archiving + + {[spool-path]} + y + 2 + 2 + + +

    Note that process-max is configured using command sections so that the option is not used by backup and restore. This also allows different values for archive-push and archive-get.

    + +

    For demonstration purposes streaming replication will be broken to force to get WAL using the restore_command.

    + + + Break streaming replication by changing the replication password + + + + psql -c "alter user replicator password 'bogus'" + + + + + + Restart standby to break connection + + + {[pg-cluster-restart]} + + + +
    + Archive Push + +

    The asynchronous archive-push command offloads WAL archiving to a separate process (or processes) to improve throughput. It works by looking ahead to see which WAL segments are ready to be archived beyond the request that is currently making via the archive_command. WAL segments are transferred to the archive directly from the pg_xlog/pg_wal directory and success is only returned by the archive_command when the WAL segment has been safely stored in the archive.

    + +

    The spool path holds the current status of WAL archiving. Status files written into the spool directory are typically zero length and should consume a minimal amount of space (a few MB at most) and very little IO. All the information in this directory can be recreated so it is not necessary to preserve the spool directory if the cluster is moved to new hardware.

    + +

    NOTE: In the original implementation of asynchronous archiving, WAL segments were copied to the spool directory before compression and transfer. The new implementation copies WAL directly from the pg_xlog directory. If asynchronous archiving was utilized in v1.12 or prior, read the v1.13 release notes carefully before upgrading.

    + +

    The [stanza]-archive-push-async.log file can be used to monitor the activity of the asynchronous process. A good way to test this is to quickly push a number of WAL segments.

    + + + Test parallel asynchronous archiving + + + rm -f /var/log/pgbackrest/demo-archive-push-async.log + + + + + psql -c " + select pg_create_restore_point('test async push'); select pg_switch_xlog(); + select pg_create_restore_point('test async push'); select pg_switch_xlog(); + select pg_create_restore_point('test async push'); select pg_switch_xlog(); + select pg_create_restore_point('test async push'); select pg_switch_xlog(); + select pg_create_restore_point('test async push'); select pg_switch_xlog();" + + + + + {[project-exe]} {[dash]}-stanza={[postgres-cluster-demo]} {[dash]}-log-level-console=info check + WAL segment + + + +

    Now the log file will contain parallel, asynchronous activity.

    + + + Check results in the log + + + cat /var/log/pgbackrest/demo-archive-push-async.log + WAL file\(s\) to archive|pushed WAL file 0000000 + + +
    + +
    + Archive Get + +

    The asynchronous archive-get command maintains a local queue of WAL to improve throughput. If a WAL segment is not found in the queue it is fetched from the repository along with enough consecutive WAL to fill the queue. The maximum size of the queue is defined by archive-get-queue-max. Whenever the queue is less than half full more WAL will be fetched to fill it.

    + +

    Asynchronous operation is most useful in environments that generate a lot of WAL or have a high latency connection to the repository storage (i.e., S3). In the case of a high latency connection it may be a good idea to increase process-max.

    + +

    The [stanza]-archive-get-async.log file can be used to monitor the activity of the asynchronous process.

    + + + Check results in the log + + + sleep 5 + + + cat /var/log/pgbackrest/demo-archive-get-async.log + got WAL file [0-F]{24} from archive + + +
    + + + Fix streaming replication by changing the replication password + + + + psql -c "alter user replicator password 'jw8s0F4'" + + + +
    +
    Backup from a Standby diff --git a/lib/pgBackRest/Archive/Get/Async.pm b/lib/pgBackRest/Archive/Get/Async.pm new file mode 100644 index 000000000..036de01bd --- /dev/null +++ b/lib/pgBackRest/Archive/Get/Async.pm @@ -0,0 +1,199 @@ +#################################################################################################################################### +# ARCHIVE GET ASYNC MODULE +#################################################################################################################################### +package pgBackRest::Archive::Get::Async; +use parent 'pgBackRest::Archive::Get::Get'; + +use strict; +use warnings FATAL => qw(all); +use Carp qw(confess); +use English '-no_match_vars'; + +use pgBackRest::Common::Exception; +use pgBackRest::Common::Lock; +use pgBackRest::Common::Log; +use pgBackRest::Archive::Common; +use pgBackRest::Archive::Info; +use pgBackRest::Common::String; +use pgBackRest::Common::Wait; +use pgBackRest::Config::Config; +use pgBackRest::Db; +use pgBackRest::DbVersion; +use pgBackRest::LibC qw(:lock); +use pgBackRest::Protocol::Local::Process; +use pgBackRest::Protocol::Helper; +use pgBackRest::Storage::Helper; +use pgBackRest::Version; + +#################################################################################################################################### +# constructor +#################################################################################################################################### +sub new +{ + my $class = shift; # Class name + + # Init object + my $self = $class->SUPER::new(); + bless $self, $class; + + # Assign function parameters, defaults, and log debug info + ( + my $strOperation, + $self->{strSpoolPath}, + $self->{strBackRestBin}, + $self->{rstryWal}, + ) = + logDebugParam + ( + __PACKAGE__ . '->new', \@_, + {name => 'strSpoolPath'}, + {name => 'strBackRestBin', default => backrestBin()}, + {name => 'rstryWal'}, + ); + + # Return from function and log return values if any + return logDebugReturn + ( + $strOperation, + {name => 'self', value => $self} + ); +} + +#################################################################################################################################### +# Create the spool directory and initialize the archive process. +#################################################################################################################################### +sub initServer +{ + my $self = shift; + + # Assign function parameters, defaults, and log debug info + my ($strOperation) = logDebugParam(__PACKAGE__ . '->initServer'); + + # Initialize the archive process + $self->{oArchiveProcess} = new pgBackRest::Protocol::Local::Process( + CFGOPTVAL_LOCAL_TYPE_BACKUP, cfgOption(CFGOPT_PROTOCOL_TIMEOUT) < 60 ? cfgOption(CFGOPT_PROTOCOL_TIMEOUT) / 2 : 30, + $self->{strBackRestBin}, false); + $self->{oArchiveProcess}->hostAdd(1, cfgOption(CFGOPT_PROCESS_MAX)); + + # Return from function and log return values if any + return logDebugReturn($strOperation); +} + +#################################################################################################################################### +# Setup the server and process the queue. This function is separate from processQueue() for testing purposes. +#################################################################################################################################### +sub process +{ + my $self = shift; + + # Assign function parameters, defaults, and log debug info + my ($strOperation) = logDebugParam(__PACKAGE__ . '->process'); + + # Open the log file + logFileSet(storageLocal(), cfgOption(CFGOPT_LOG_PATH) . '/' . cfgOption(CFGOPT_STANZA) . '-archive-get-async'); + + # There is no loop here because it seems wise to let the async process exit periodically. As the queue grows each async + # execution will naturally run longer. This behavior is also far easier to test. + $self->initServer(); + $self->processQueue(); + + # Return from function and log return values if any + return logDebugReturn($strOperation); +} + +#################################################################################################################################### +# Get WAL from archive +#################################################################################################################################### +sub processQueue +{ + my $self = shift; + + # Assign function parameters, defaults, and log debug info + my ($strOperation) = logDebugParam(__PACKAGE__ . '->processQueue'); + + # Queue the jobs + foreach my $strWalFile (@{$self->{rstryWal}}) + { + $self->{oArchiveProcess}->queueJob( + 1, 'default', $strWalFile, OP_ARCHIVE_GET_FILE, [$strWalFile, "$self->{strSpoolPath}/${strWalFile}", true]); + } + + # Process jobs + my $iFoundTotal = 0; + my $iMissingTotal = 0; + my $iErrorTotal = 0; + + &log(INFO, + 'get ' . @{$self->{rstryWal}} . ' WAL file(s) from archive: ' . + ${$self->{rstryWal}}[0] . (@{$self->{rstryWal}} > 1 ? "...${$self->{rstryWal}}[-1]" : '')); + + eval + { + # Check for a stop lock + lockStopTest(); + + while (my $hyJob = $self->{oArchiveProcess}->process()) + { + foreach my $hJob (@{$hyJob}) + { + my $strWalFile = @{$hJob->{rParam}}[0]; + my $iResult = @{$hJob->{rResult}}[0]; + + # If error then write out an error file + if (defined($hJob->{oException})) + { + archiveAsyncStatusWrite( + WAL_STATUS_ERROR, $self->{strSpoolPath}, $strWalFile, $hJob->{oException}->code(), + $hJob->{oException}->message()); + + $iErrorTotal++; + + &log(WARN, + "could not get WAL file ${strWalFile} from archive (will be retried): [" . + $hJob->{oException}->code() . "] " . $hJob->{oException}->message()); + } + # Else write a '.ok' file to indicate that the WAL was not found but there was no error + elsif ($iResult == 1) + { + archiveAsyncStatusWrite(WAL_STATUS_OK, $self->{strSpoolPath}, $strWalFile); + + $iMissingTotal++; + + &log(DETAIL, "WAL file ${strWalFile} not found in archive", undef, undef, undef, $hJob->{iProcessId}); + } + # Else success so just output a log message + else + { + $iFoundTotal++; + + &log(DETAIL, "got WAL file ${strWalFile} from archive", undef, undef, undef, $hJob->{iProcessId}); + } + } + } + + return 1; + } + or do + { + # Get error info + my $iCode = exceptionCode($EVAL_ERROR); + my $strMessage = exceptionMessage($EVAL_ERROR); + + # Error all queued jobs + foreach my $strWalFile (@{$self->{rstryWal}}) + { + archiveAsyncStatusWrite(WAL_STATUS_ERROR, $self->{strSpoolPath}, $strWalFile, $iCode, $strMessage); + } + }; + + return logDebugReturn + ( + $strOperation, + {name => 'iNewTotal', value => scalar(@{$self->{rstryWal}})}, + {name => 'iFoundTotal', value => $iFoundTotal}, + {name => 'iMissingTotal', value => $iMissingTotal}, + {name => 'iErrorTotal', value => $iErrorTotal} + ); +} + +1; diff --git a/lib/pgBackRest/Archive/Get/File.pm b/lib/pgBackRest/Archive/Get/File.pm index c0c3b1f95..9526e5ccf 100644 --- a/lib/pgBackRest/Archive/Get/File.pm +++ b/lib/pgBackRest/Archive/Get/File.pm @@ -140,13 +140,15 @@ sub archiveGetFile ( $strOperation, $strSourceArchive, - $strDestinationFile + $strDestinationFile, + $bAtomic, ) = logDebugParam ( __PACKAGE__ . '::archiveGetFile', \@_, {name => 'strSourceArchive'}, - {name => 'strDestinationFile'} + {name => 'strDestinationFile'}, + {name => 'bAtomic'}, ); lockStopTest(); @@ -185,7 +187,7 @@ sub archiveGetFile strCipherPass => defined($strCipherPass) ? $strCipherPass : undef}), storageDb()->openWrite( $strDestinationFile, - {rhyFilter => $bSourceCompressed ? + {bAtomic => $bAtomic, rhyFilter => $bSourceCompressed ? [{strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]}] : undef})); } diff --git a/lib/pgBackRest/Archive/Get/Get.pm b/lib/pgBackRest/Archive/Get/Get.pm index abec4e2e2..9d0d8d851 100644 --- a/lib/pgBackRest/Archive/Get/Get.pm +++ b/lib/pgBackRest/Archive/Get/Get.pm @@ -43,42 +43,60 @@ sub process my ( $strOperation, - $strSourceArchive, - $strDestinationFile + $rstryCommandArg, ) = logDebugParam ( __PACKAGE__ . '->process', \@_, - {name => 'strSourceArchive'}, - {name => 'strDestinationFile'} + {name => 'rstryCommandArg'}, ); + my $iResult = 0; + # Make sure the command happens on the db side if (!isDbLocal()) { confess &log(ERROR, cfgCommandName(CFGCMD_ARCHIVE_GET) . ' operation must run on db host', ERROR_HOST_INVALID); } - # Make sure the archive file is defined - if (!defined($strSourceArchive)) + # Start the async process and wait for WAL to complete + if (cfgOption(CFGOPT_ARCHIVE_ASYNC)) { - confess &log(ERROR, 'WAL segment not provided', ERROR_PARAM_REQUIRED); + # Load module dynamically + require pgBackRest::Archive::Get::Async; + (new pgBackRest::Archive::Get::Async( + storageSpool()->pathGet(STORAGE_SPOOL_ARCHIVE_IN), $self->{strBackRestBin}, $rstryCommandArg))->process(); } - - # Make sure the destination file is defined - if (!defined($strDestinationFile)) + # Else push synchronously + else { - confess &log(ERROR, 'WAL segment destination not provided', ERROR_PARAM_REQUIRED); - } + # Make sure the archive file is defined + my $strSourceArchive = ${$rstryCommandArg}[0]; - # Info for the Postgres log - &log(INFO, 'get WAL segment ' . $strSourceArchive); + if (!defined($strSourceArchive)) + { + confess &log(ERROR, 'WAL segment not provided', ERROR_PARAM_REQUIRED); + } + + # Make sure the destination file is defined + my $strDestinationFile = ${$rstryCommandArg}[1]; + + if (!defined($strDestinationFile)) + { + confess &log(ERROR, 'WAL segment destination not provided', ERROR_PARAM_REQUIRED); + } + + $iResult = archiveGetFile($strSourceArchive, $strDestinationFile, false); + + # Info for the Postgres log + &log(INFO, 'got WAL segment ' . $strSourceArchive); + } # Return from function and log return values if any return logDebugReturn ( $strOperation, - {name => 'iResult', value => archiveGetFile($strSourceArchive, $strDestinationFile), trace => true}, + {name => 'iResult', value => $iResult, trace => true}, ); } diff --git a/lib/pgBackRest/Archive/Push/Async.pm b/lib/pgBackRest/Archive/Push/Async.pm index 20d2ae7f6..4c512a3f5 100644 --- a/lib/pgBackRest/Archive/Push/Async.pm +++ b/lib/pgBackRest/Archive/Push/Async.pm @@ -9,14 +9,6 @@ use warnings FATAL => qw(all); use Carp qw(confess); use English '-no_match_vars'; -use Exporter qw(import); - our @EXPORT = qw(); -use Fcntl qw(SEEK_CUR O_RDONLY O_WRONLY O_CREAT); -use File::Basename qw(dirname basename); -use IO::Socket::UNIX; -use POSIX qw(setsid); -use Scalar::Util qw(blessed); - use pgBackRest::Common::Exception; use pgBackRest::Common::Lock; use pgBackRest::Common::Log; @@ -153,6 +145,9 @@ sub processQueue eval { + # Check for a stop lock + lockStopTest(); + # Hold a lock when the repo is remote to be sure no other process is pushing WAL !isRepoLocal() && protocolGet(CFGOPTVAL_REMOTE_TYPE_BACKUP); @@ -176,7 +171,7 @@ sub processQueue $iErrorTotal++; &log(WARN, - "could not push WAl file ${strWalFile} to archive (will be retried): [" . + "could not push WAL file ${strWalFile} to archive (will be retried): [" . $hJob->{oException}->code() . "] " . $hJob->{oException}->message()); } # Else write success diff --git a/lib/pgBackRest/Archive/Push/Push.pm b/lib/pgBackRest/Archive/Push/Push.pm index 93f6440a9..da0a25b9a 100644 --- a/lib/pgBackRest/Archive/Push/Push.pm +++ b/lib/pgBackRest/Archive/Push/Push.pm @@ -56,9 +56,6 @@ sub process confess &log(ERROR, 'WAL file to push required', ERROR_PARAM_REQUIRED); } - # Check for a stop lock - lockStopTest(); - # Extract WAL path and file my $strWalPath = dirname(walPath($strWalPathFile, cfgOption(CFGOPT_PG_PATH, false), cfgCommandName(cfgCommandGet()))); my $strWalFile = basename($strWalPathFile); @@ -74,6 +71,9 @@ sub process # Else push synchronously else { + # Check for a stop lock + lockStopTest(); + # Load module dynamically require pgBackRest::Archive::Push::File; pgBackRest::Archive::Push::File->import(); diff --git a/lib/pgBackRest/Main.pm b/lib/pgBackRest/Main.pm index 13688651e..1f719aa1c 100644 --- a/lib/pgBackRest/Main.pm +++ b/lib/pgBackRest/Main.pm @@ -86,7 +86,7 @@ sub main require pgBackRest::Archive::Get::Get; pgBackRest::Archive::Get::Get->import(); - $result = new pgBackRest::Archive::Get::Get()->process($stryCommandArg[0], $stryCommandArg[1]); + $result = new pgBackRest::Archive::Get::Get()->process(\@stryCommandArg); } # Process remote command @@ -279,10 +279,16 @@ sub main # are other errors that could be arriving in $EVAL_ERROR. my $oException = defined($EVAL_ERROR) && length($EVAL_ERROR) > 0 ? $EVAL_ERROR : logErrorLast(); - # If a backrest exception then only return the code since the message has already been logged + # If a backrest exception if (isException(\$oException)) { $result = $oException->code(); + + # Only return message if we are in an async process since this will not be logged to the console + if (!$bConfigLoaded && cfgOption(CFGOPT_ARCHIVE_ASYNC)) + { + $message = $oException->message(); + } } # Else a regular Perl exception else diff --git a/lib/pgBackRest/Protocol/Helper.pm b/lib/pgBackRest/Protocol/Helper.pm index fcee6b3af..4ecb006bf 100644 --- a/lib/pgBackRest/Protocol/Helper.pm +++ b/lib/pgBackRest/Protocol/Helper.pm @@ -28,11 +28,9 @@ use constant OP_ARCHIVE_GET_CHECK => 'archiveC use constant OP_ARCHIVE_PUSH_CHECK => 'archivePushCheck'; push @EXPORT, qw(OP_ARCHIVE_PUSH_CHECK); -# Archive Push Async Module -use constant OP_ARCHIVE_PUSH_ASYNC => 'archivePushAsync'; - push @EXPORT, qw(OP_ARCHIVE_PUSH_ASYNC); - # Archive File Module +use constant OP_ARCHIVE_GET_FILE => 'archiveGetFile'; + push @EXPORT, qw(OP_ARCHIVE_GET_FILE); use constant OP_ARCHIVE_PUSH_FILE => 'archivePushFile'; push @EXPORT, qw(OP_ARCHIVE_PUSH_FILE); diff --git a/lib/pgBackRest/Protocol/Local/Minion.pm b/lib/pgBackRest/Protocol/Local/Minion.pm index e22e84838..8d045951b 100644 --- a/lib/pgBackRest/Protocol/Local/Minion.pm +++ b/lib/pgBackRest/Protocol/Local/Minion.pm @@ -8,6 +8,7 @@ use strict; use warnings FATAL => qw(all); use Carp qw(confess); +use pgBackRest::Archive::Get::File; use pgBackRest::Archive::Push::File; use pgBackRest::Backup::File; use pgBackRest::Common::Log; @@ -54,6 +55,7 @@ sub init # Create anonymous subs for each command my $hCommandMap = { + &OP_ARCHIVE_GET_FILE => sub {archiveGetFile(@{shift()})}, &OP_ARCHIVE_PUSH_FILE => sub {archivePushFile(@{shift()})}, &OP_BACKUP_FILE => sub {backupFile(@{shift()})}, &OP_RESTORE_FILE => sub {restoreFile(@{shift()})}, diff --git a/lib/pgBackRest/Storage/Helper.pm b/lib/pgBackRest/Storage/Helper.pm index 39df5a231..bed359754 100644 --- a/lib/pgBackRest/Storage/Helper.pm +++ b/lib/pgBackRest/Storage/Helper.pm @@ -25,6 +25,8 @@ use constant STORAGE_LOCAL => '' use constant STORAGE_SPOOL => ''; push @EXPORT, qw(STORAGE_SPOOL); +use constant STORAGE_SPOOL_ARCHIVE_IN => ''; + push @EXPORT, qw(STORAGE_SPOOL_ARCHIVE_IN); use constant STORAGE_SPOOL_ARCHIVE_OUT => ''; push @EXPORT, qw(STORAGE_SPOOL_ARCHIVE_OUT); @@ -108,6 +110,7 @@ sub storageSpool # Path rules my $hRule = { + &STORAGE_SPOOL_ARCHIVE_IN => "archive/${strStanza}/in", &STORAGE_SPOOL_ARCHIVE_OUT => "archive/${strStanza}/out", }; diff --git a/libc/lib/pgBackRest/LibCAuto.pm b/libc/lib/pgBackRest/LibCAuto.pm index a49366703..13353a85f 100644 --- a/libc/lib/pgBackRest/LibCAuto.pm +++ b/libc/lib/pgBackRest/LibCAuto.pm @@ -118,6 +118,7 @@ sub libcAutoExportTag 'CFGOPT_ARCHIVE_ASYNC', 'CFGOPT_ARCHIVE_CHECK', 'CFGOPT_ARCHIVE_COPY', + 'CFGOPT_ARCHIVE_GET_QUEUE_MAX', 'CFGOPT_ARCHIVE_PUSH_QUEUE_MAX', 'CFGOPT_ARCHIVE_TIMEOUT', 'CFGOPT_BACKUP_STANDBY', diff --git a/libc/xs/config/config.auto.xsh b/libc/xs/config/config.auto.xsh index 2b9cfabe5..6fb85cb03 100644 --- a/libc/xs/config/config.auto.xsh +++ b/libc/xs/config/config.auto.xsh @@ -32,6 +32,7 @@ Option constants #define CFGOPT_ARCHIVE_ASYNC cfgOptArchiveAsync #define CFGOPT_ARCHIVE_CHECK cfgOptArchiveCheck #define CFGOPT_ARCHIVE_COPY cfgOptArchiveCopy +#define CFGOPT_ARCHIVE_GET_QUEUE_MAX cfgOptArchiveGetQueueMax #define CFGOPT_ARCHIVE_PUSH_QUEUE_MAX cfgOptArchivePushQueueMax #define CFGOPT_ARCHIVE_TIMEOUT cfgOptArchiveTimeout #define CFGOPT_BACKUP_STANDBY cfgOptBackupStandby diff --git a/src/Makefile b/src/Makefile index 3edc2c487..b55788e66 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,6 +55,7 @@ DESTDIR = #################################################################################################################################### SRCS = \ command/archive/common.c \ + command/archive/get/get.c \ command/archive/push/push.c \ command/help/help.c \ command/command.c \ @@ -82,6 +83,7 @@ SRCS = \ config/parse.c \ perl/config.c \ perl/exec.c \ + postgres/info.c \ storage/driver/posix/driver.c \ storage/driver/posix/driverFile.c \ storage/driver/posix/driverRead.c \ diff --git a/src/command/archive/common.c b/src/command/archive/common.c index 56d1b76b6..235edb997 100644 --- a/src/command/archive/common.c +++ b/src/command/archive/common.c @@ -17,14 +17,16 @@ Archive Push Command Check for ok/error status files in the spool in/out directory ***********************************************************************************************************************************/ bool -archiveAsyncStatus(const String *walSegment, bool confessOnError) +archiveAsyncStatus(ArchiveMode archiveMode, const String *walSegment, bool confessOnError) { bool result = false; MEM_CONTEXT_TEMP_BEGIN() { + String *spoolQueue = strNew(archiveMode == archiveModeGet ? STORAGE_SPOOL_ARCHIVE_IN : STORAGE_SPOOL_ARCHIVE_OUT); + StringList *fileList = storageListP( - storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_OUT), .expression = strNewFmt("^%s\\.(ok|error)$", strPtr(walSegment))); + storageSpool(), spoolQueue, .expression = strNewFmt("^%s\\.(ok|error)$", strPtr(walSegment))); if (fileList != NULL && strLstSize(fileList) > 0) { @@ -33,14 +35,14 @@ archiveAsyncStatus(const String *walSegment, bool confessOnError) { THROW( AssertError, "multiple status files found in '%s' for WAL segment '%s'", - strPtr(storagePathNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_OUT))), strPtr(walSegment)); + strPtr(storagePath(storageSpool(), spoolQueue)), strPtr(walSegment)); } // Get the status file content const String *statusFile = strLstGet(fileList, 0); String *content = strNewBuf( - storageGetNP(storageNewReadNP(storageSpool(), strNewFmt("%s/%s", STORAGE_SPOOL_ARCHIVE_OUT, strPtr(statusFile))))); + storageGetNP(storageNewReadNP(storageSpool(), strNewFmt("%s/%s", strPtr(spoolQueue), strPtr(statusFile))))); // Get the code and message if the file has content int code = 0; diff --git a/src/command/archive/common.h b/src/command/archive/common.h index 404c6a2a2..cbc8ff16a 100644 --- a/src/command/archive/common.h +++ b/src/command/archive/common.h @@ -6,12 +6,36 @@ Archive Common #include +/*********************************************************************************************************************************** +Archive mode enum + +Used for functions that are common to both archive-push and archive-get so they can tailor their behavior to the command being run. +***********************************************************************************************************************************/ +typedef enum +{ + archiveModePush, + archiveModeGet, +} ArchiveMode; + #include "common/type/stringList.h" +/*********************************************************************************************************************************** +WAL segment constants +***********************************************************************************************************************************/ +// Only match on a WAL segment without checksum appended +#define WAL_SEGMENT_REGEXP "^[0-F]{24}$" + +// Defines the size of standard WAL segment name -- this should never changed +#define WAL_SEGMENT_NAME_SIZE ((uint)24) + +// Default size of a WAL segment +#define WAL_SEGMENT_DEFAULT_SIZE ((size_t)(16 * 1024 * 1024)) + /*********************************************************************************************************************************** Functions ***********************************************************************************************************************************/ -bool archiveAsyncStatus(const String *walSegment, bool confessOnError); +bool archiveAsyncStatus(ArchiveMode archiveMode, const String *walSegment, bool confessOnError); + String *walSegmentNext(const String *walSegment, size_t walSegmentSize, uint pgVersion); StringList *walSegmentRange(const String *walSegmentBegin, size_t walSegmentSize, uint pgVersion, uint range); diff --git a/src/command/archive/get/get.c b/src/command/archive/get/get.c new file mode 100644 index 000000000..a5da5159c --- /dev/null +++ b/src/command/archive/get/get.c @@ -0,0 +1,274 @@ +/*********************************************************************************************************************************** +Archive Get Command +***********************************************************************************************************************************/ +#include +#include + +#include "command/archive/common.h" +#include "command/command.h" +#include "common/fork.h" +#include "common/log.h" +#include "common/memContext.h" +#include "common/regExp.h" +#include "common/wait.h" +#include "config/config.h" +#include "config/load.h" +#include "perl/exec.h" +#include "postgres/info.h" +#include "storage/helper.h" + +/*********************************************************************************************************************************** +Clean the queue and prepare a list of WAL segments that the async process should get +***********************************************************************************************************************************/ +static StringList * +queueNeed(const String *walSegment, bool found, size_t queueSize, size_t walSegmentSize, uint pgVersion) +{ + StringList *result = strLstNew(); + + MEM_CONTEXT_TEMP_BEGIN() + { + // Determine the first WAL segment for the async process to get. If the WAL segment requested by + // PostgreSQL was not found then use that. If the segment was found but the queue is not full then + // start with the next segment. + const String *walSegmentFirst = + found ? walSegmentNext(walSegment, walSegmentSize, pgVersion) : walSegment; + + // Determine how many WAL segments should be in the queue. The queue total must be at least 2 or it doesn't make sense to + // have async turned on at all. + uint walSegmentQueueTotal = (uint)(queueSize / walSegmentSize); + + if (walSegmentQueueTotal < 2) + walSegmentQueueTotal = 2; + + // Build the ideal queue -- the WAL segments we want in the queue after the async process has run + StringList *idealQueue = walSegmentRange(walSegmentFirst, walSegmentSize, pgVersion, walSegmentQueueTotal); + + // Get the list of files actually in the queue + StringList *actualQueue = strLstSort( + storageListP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN), .errorOnMissing = true), sortOrderAsc); + + // Only preserve files that match the ideal queue. '.error'/'.ok' files are deleted so the async process can try again. + RegExp *regExpPreserve = regExpNew(strNewFmt("^(%s)$", strPtr(strLstJoin(idealQueue, "|")))); + + // Build a list of WAL segments that are being kept so we can later make a list of what is needed + StringList *keepQueue = strLstNew(); + + for (uint actualQueueIdx = 0; actualQueueIdx < strLstSize(actualQueue); actualQueueIdx++) + { + // Get file from actual queue + const String *file = strLstGet(actualQueue, actualQueueIdx); + + // Does this match a file we want to preserve? + if (regExpMatch(regExpPreserve, file)) + strLstAdd(keepQueue, file); + + // Else delete it + else + storageRemoveNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(file))); + } + + // Generate a list of the WAL that are needed by removing kept WAL from the ideal queue + for (uint idealQueueIdx = 0; idealQueueIdx < strLstSize(idealQueue); idealQueueIdx++) + { + if (!strLstExists(keepQueue, strLstGet(idealQueue, idealQueueIdx))) + strLstAdd(result, strLstGet(idealQueue, idealQueueIdx)); + } + } + MEM_CONTEXT_TEMP_END(); + + return result; +} + +/*********************************************************************************************************************************** +Push a WAL segment to the repository +***********************************************************************************************************************************/ +int +cmdArchiveGet() +{ + int result = 1; + + MEM_CONTEXT_TEMP_BEGIN() + { + // Check the parameters + const StringList *commandParam = cfgCommandParam(); + + if (strLstSize(commandParam) != 2) + { + if (strLstSize(commandParam) == 0) + THROW(ParamRequiredError, "WAL segment to get required"); + + if (strLstSize(commandParam) == 1) + THROW(ParamRequiredError, "Path to copy WAL segment required"); + + THROW(ParamRequiredError, "extra parameters found"); + } + + // Get the segment name + String *walSegment = strBase(strLstGet(commandParam, 0)); + + // Destination is wherever we were told to move the WAL segment. In some cases the path that PostgreSQL passes will not be + // absolute so prefix pg-path. + const String *walDestination = strLstGet(commandParam, 1); + + if (!strBeginsWithZ(walDestination, "/")) + walDestination = strNewFmt("%s/%s", strPtr(cfgOptionStr(cfgOptPgPath)), strPtr(walDestination)); + + // Async get can only be performed on WAL segments, history or other files must use synchronous mode + if (cfgOptionBool(cfgOptArchiveAsync) && regExpMatchOne(strNew(WAL_SEGMENT_REGEXP), walSegment)) + { + bool found = false; // Has the WAL segment been found yet? + bool queueFull = false; // Is the queue half or more full? + bool forked = false; // Has the async process been forked yet? + bool confessOnError = false; // Should we confess errors? + + // Loop and wait for the WAL segment to be pushed + Wait *wait = waitNew(cfgOptionDbl(cfgOptArchiveTimeout)); + + do + { + // Check for errors or missing files. For archive-get '.ok' indicates that the process succeeded but there is no + // WAL file to download. + if (archiveAsyncStatus(archiveModeGet, walSegment, confessOnError)) + { + storageRemoveP( + storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s.ok", strPtr(walSegment)), .errorOnMissing = true); + + LOG_INFO("unable to find WAL segment %s", strPtr(walSegment)); + break; + } + + // Check if the WAL segment is already in the queue + found = storageExistsNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); + + // If found then move the WAL segment to the destination directory + if (found) + { + // Source is the WAL segment in the spool queue + StorageFileRead *source = storageNewReadNP( + storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); + + // A move will be attempted but if the spool queue and the WAL path are on different file systems then a copy + // will be performed instead. + // + // It looks scary that we are disabling syncs and atomicity (in case we need to copy intead of move) but this + // is safe because if the system crashes Postgres will not try to reuse a restored WAL segment but will instead + // request it again using the restore_command. In the case of a move this hardly matters since path syncs are + // cheap but if a copy is required we could save a lot of writes. + StorageFileWrite *destination = storageNewWriteP( + storageLocalWrite(), walDestination, .noCreatePath = true, .noSyncFile = true, .noSyncPath = true, + .noAtomic = true); + + // Move (or copy if required) the file + storageMoveNP(source, destination); + + // Log success + LOG_INFO("got WAL segment %s asynchronously", strPtr(walSegment)); + result = 0; + + // Get a list of WAL segments left in the queue + StringList *queue = storageListP( + storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN), .expression = strNew(WAL_SEGMENT_REGEXP)); + + if (strLstSize(queue) > 0) + { + // Get size of the WAL segment + size_t walSegmentSize = storageInfoNP(storageLocal(), walDestination).size; + + // Use WAL segment size to estimate queue size and determine if the async process should be launched + queueFull = + strLstSize(queue) * walSegmentSize > (size_t)cfgOptionInt64(cfgOptArchiveGetQueueMax) / 2; + } + } + + // If the WAL segment has not already been found then start the async process to get it. There's no point in + // forking the async process off more than once so track that as well. Use an archive lock to prevent more than + // one async process being launched. + if (!forked && (!found || !queueFull) && + lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 0, false)) + { + // Fork off the async process + if (fork() == 0) + { + // Async process returns 0 unless there is an error + result = 0; + + // Execute async process and catch exceptions + TRY_BEGIN() + { + // Get the version of PostgreSQL + uint pgVersion = pgControlInfo(cfgOptionStr(cfgOptPgPath)).version; + + // Determine WAL segment size -- for now this is the default but for PG11 it will be configurable + uint walSegmentSize = WAL_SEGMENT_DEFAULT_SIZE; + + // Create the queue + storagePathCreateNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN)); + + // Clean the current queue using the list of WAL that we ideally want in the queue. queueNeed() + // will return the list of WAL needed to fill the queue and this will be passed to the async process. + cfgCommandParamSet( + queueNeed( + walSegment, found, (size_t)cfgOptionInt64(cfgOptArchiveGetQueueMax), walSegmentSize, + pgVersion)); + + // The async process should not output on the console at all + cfgOptionSet(cfgOptLogLevelConsole, cfgSourceParam, varNewStrZ("off")); + cfgOptionSet(cfgOptLogLevelStderr, cfgSourceParam, varNewStrZ("off")); + cfgLoadLogSetting(); + + // Open the log file + logFileSet( + strPtr(strNewFmt("%s/%s-%s-async.log", strPtr(cfgOptionStr(cfgOptLogPath)), + strPtr(cfgOptionStr(cfgOptStanza)), cfgCommandName(cfgCommand())))); + + // Log command info since we are starting a new log + cmdBegin(true); + + // Detach from parent process + forkDetach(); + + perlExec(); + } + CATCH_ANY() + { + RETHROW(); + } + FINALLY() + { + // Release the lock (mostly here for testing since it would be freed in exitSafe() anyway) + lockRelease(true); + } + TRY_END(); + + break; + } + // Else mark async process as forked + else + { + lockClear(true); + forked = true; + } + } + + // Exit loop if WAL was found + if (found) + break; + + // Now that the async process has been launched, confess any errors that are found + confessOnError = true; + } + while (waitMore(wait)); + } + else + { + // Disable async if it was enabled + cfgOptionSet(cfgOptArchiveAsync, cfgOptionSource(cfgOptArchiveAsync), varNewBool(false)); + + // Call synchronous get + result = perlExec(); + } + } + MEM_CONTEXT_TEMP_END(); + + return result; +} diff --git a/src/command/archive/get/get.h b/src/command/archive/get/get.h new file mode 100644 index 000000000..d7d746e54 --- /dev/null +++ b/src/command/archive/get/get.h @@ -0,0 +1,12 @@ +/*********************************************************************************************************************************** +Archive Get Command +***********************************************************************************************************************************/ +#ifndef COMMAND_ARCHIVE_GET_GET_H +#define COMMAND_ARCHIVE_GET_GET_H + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +int cmdArchiveGet(); + +#endif diff --git a/src/command/archive/push/push.c b/src/command/archive/push/push.c index df97981ff..3104eeca0 100644 --- a/src/command/archive/push/push.c +++ b/src/command/archive/push/push.c @@ -45,7 +45,7 @@ cmdArchivePush() { // Check if the WAL segment has been pushed. Errors will not be confessed on the first try to allow the async // process a chance to fix them. - pushed = archiveAsyncStatus(walSegment, confessOnError); + pushed = archiveAsyncStatus(archiveModePush, walSegment, confessOnError); // If the WAL segment has not already been pushed then start the async process to push it. There's no point in // forking the async process off more than once so track that as well. Use an archive lock to prevent more than diff --git a/src/config/config.auto.c b/src/config/config.auto.c index 8a36637e5..bc0800ef0 100644 --- a/src/config/config.auto.c +++ b/src/config/config.auto.c @@ -17,7 +17,7 @@ static ConfigCommandData configCommandData[CFG_COMMAND_TOTAL] = CONFIG_COMMAND_L CONFIG_COMMAND_LOG_LEVEL_DEFAULT(logLevelInfo) CONFIG_COMMAND_LOG_LEVEL_STDERR_MAX(logLevelTrace) CONFIG_COMMAND_LOCK_REQUIRED(false) - CONFIG_COMMAND_LOCK_TYPE(lockTypeNone) + CONFIG_COMMAND_LOCK_TYPE(lockTypeArchive) ) CONFIG_COMMAND @@ -215,6 +215,14 @@ static ConfigOptionData configOptionData[CFG_OPTION_TOTAL] = CONFIG_OPTION_LIST CONFIG_OPTION_DEFINE_ID(cfgDefOptArchiveCopy) ) + //------------------------------------------------------------------------------------------------------------------------------ + CONFIG_OPTION + ( + CONFIG_OPTION_NAME("archive-get-queue-max") + CONFIG_OPTION_INDEX(0) + CONFIG_OPTION_DEFINE_ID(cfgDefOptArchiveGetQueueMax) + ) + //------------------------------------------------------------------------------------------------------------------------------ CONFIG_OPTION ( diff --git a/src/config/config.auto.h b/src/config/config.auto.h index 5c8b38c39..71d32165f 100644 --- a/src/config/config.auto.h +++ b/src/config/config.auto.h @@ -14,7 +14,7 @@ Command constants /*********************************************************************************************************************************** Option constants ***********************************************************************************************************************************/ -#define CFG_OPTION_TOTAL 159 +#define CFG_OPTION_TOTAL 160 /*********************************************************************************************************************************** Command enum @@ -48,6 +48,7 @@ typedef enum cfgOptArchiveAsync, cfgOptArchiveCheck, cfgOptArchiveCopy, + cfgOptArchiveGetQueueMax, cfgOptArchivePushQueueMax, cfgOptArchiveTimeout, cfgOptBackupStandby, @@ -80,16 +81,16 @@ typedef enum cfgOptOutput, cfgOptPerlOption, cfgOptPgHost, - cfgOptPgHostCmd = 42, - cfgOptPgHostConfig = 50, - cfgOptPgHostConfigIncludePath = 58, - cfgOptPgHostConfigPath = 66, - cfgOptPgHostPort = 74, - cfgOptPgHostUser = 82, - cfgOptPgPath = 90, - cfgOptPgPort = 98, - cfgOptPgSocketPath = 106, - cfgOptProcess = 114, + cfgOptPgHostCmd = 43, + cfgOptPgHostConfig = 51, + cfgOptPgHostConfigIncludePath = 59, + cfgOptPgHostConfigPath = 67, + cfgOptPgHostPort = 75, + cfgOptPgHostUser = 83, + cfgOptPgPath = 91, + cfgOptPgPort = 99, + cfgOptPgSocketPath = 107, + cfgOptProcess = 115, cfgOptProcessMax, cfgOptProtocolTimeout, cfgOptRecoveryOption, diff --git a/src/config/config.c b/src/config/config.c index bc68f08d4..9a41df7bf 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -263,7 +263,6 @@ cfgCommandParamSet(const StringList *param) paramList = strLstDup(param); } MEM_CONTEXT_END(); - } /*********************************************************************************************************************************** diff --git a/src/config/define.auto.c b/src/config/define.auto.c index d9bb230fe..edca2f911 100644 --- a/src/config/define.auto.c +++ b/src/config/define.auto.c @@ -228,16 +228,18 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_SECURE(false) CFGDEFDATA_OPTION_HELP_SECTION("archive") - CFGDEFDATA_OPTION_HELP_SUMMARY("Archive WAL segments asynchronously.") + CFGDEFDATA_OPTION_HELP_SUMMARY("Push/get WAL segments asynchronously.") CFGDEFDATA_OPTION_HELP_DESCRIPTION ( - "WAL segments will be copied to the local repo, then a process will be forked to compress the segment and transfer it " - "to the remote repo if configured. Control will be returned to PostgreSQL as soon as the WAL segment is copied " - "locally." + "Enables asynchronous operation for the archive-push and archive-get commands.\n" + "\n" + "Asynchronous operation is more efficient because it can reuse connections and take advantage of parallelism. See the " + "spool-path, archive-get-queue-max, and archive-push-queue-max options for more information." ) CFGDEFDATA_OPTION_COMMAND_LIST ( + CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchivePush) ) @@ -335,6 +337,40 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST ) ) + // ----------------------------------------------------------------------------------------------------------------------------- + CFGDEFDATA_OPTION + ( + CFGDEFDATA_OPTION_NAME("archive-get-queue-max") + CFGDEFDATA_OPTION_REQUIRED(true) + CFGDEFDATA_OPTION_SECTION(cfgDefSectionGlobal) + CFGDEFDATA_OPTION_TYPE(cfgDefOptTypeInteger) + CFGDEFDATA_OPTION_INTERNAL(false) + + CFGDEFDATA_OPTION_INDEX_TOTAL(1) + CFGDEFDATA_OPTION_SECURE(false) + + CFGDEFDATA_OPTION_HELP_SECTION("archive") + CFGDEFDATA_OPTION_HELP_SUMMARY("Maximum size of the pgBackRest archive-get queue.") + CFGDEFDATA_OPTION_HELP_DESCRIPTION + ( + "Specifies the maximum size of the archive-get queue when archive-async is enabled. The queue is stored in the " + "spool-path and is used to speed providing WAL to PostgreSQL.\n" + "\n" + "Size can be entered in bytes (default) or KB, MB, GB, TB, or PB where the multiplier is a power of 1024." + ) + + CFGDEFDATA_OPTION_COMMAND_LIST + ( + CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet) + ) + + CFGDEFDATA_OPTION_OPTIONAL_LIST + ( + CFGDEFDATA_OPTION_OPTIONAL_ALLOW_RANGE(0, 4503599627370496) + CFGDEFDATA_OPTION_OPTIONAL_DEFAULT("134217728") + ) + ) + // ----------------------------------------------------------------------------------------------------------------------------- CFGDEFDATA_OPTION ( @@ -348,7 +384,7 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_SECURE(false) CFGDEFDATA_OPTION_HELP_SECTION("archive") - CFGDEFDATA_OPTION_HELP_SUMMARY("Limit size (in bytes) of the PostgreSQL archive queue.") + CFGDEFDATA_OPTION_HELP_SUMMARY("Maximum size of the PostgreSQL archive queue.") CFGDEFDATA_OPTION_HELP_DESCRIPTION ( "After the limit is reached, the following will happen:\n" @@ -403,6 +439,7 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_COMMAND_LIST ( + CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchivePush) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdBackup) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdCheck) @@ -1483,9 +1520,9 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_HELP_SUMMARY("Manifest save threshold during backup.") CFGDEFDATA_OPTION_HELP_DESCRIPTION ( - "Defines how often the manifest will be saved during a backup (in bytes). Saving the manifest is important because it " - "stores the checksums and allows the resume function to work efficiently. The actual threshold used is 1% of the " - "backup size or manifest-save-threshold, whichever is greater.\n" + "Defines how often the manifest will be saved during a backup. Saving the manifest is important because it stores the " + "checksums and allows the resume function to work efficiently. The actual threshold used is 1% of the backup size " + "or manifest-save-threshold, whichever is greater.\n" "\n" "Size can be entered in bytes (default) or KB, MB, GB, TB, or PB where the multiplier is a power of 1024." ) @@ -2223,6 +2260,7 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_COMMAND_LIST ( + CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchivePush) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdBackup) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdRestore) @@ -3576,16 +3614,27 @@ static ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST CFGDEFDATA_OPTION_HELP_SUMMARY("Path where transient data is stored.") CFGDEFDATA_OPTION_HELP_DESCRIPTION ( - "This path is used to store acknowledgements from the asynchronous archive-push process. These files are generally " - "very small (zero to a few hundred bytes) so not much space is required.\n" + "This path is used to store data for the asynchronous archive-push and archive-get command.\n" + "\n" + "The asynchronous archive-push command writes acknowledgements into the spool path when it has successfully stored WAL " + "in the archive (and errors on failure) so the foreground process can quickly notify PostgreSQL. Acknowledgement " + "files are very small (zero on success and a few hundred bytes on error).\n" + "\n" + "The asynchronous archive-push process queues WAL in the spool path so it can be provided very quickly when PostgreSQL " + "requests it. Moving files to PostgreSQL is most efficient when the spool path is on the same filesystem as " + "pg_xlog/pg_wal.\n" "\n" "The data stored in the spool path is not strictly temporary since it can and should survive a reboot. However, loss " "of the data in the spool path is not a problem. pgBackRest will simply recheck each WAL segment to ensure it is " - "safely archived." + "safely archived for archive-push and rebuild the queue for archive-get.\n" + "\n" + "The spool path is intended to be located on a local Posix-compatible filesystem, not a remote filesystem such as NFS " + "or CIFS." ) CFGDEFDATA_OPTION_COMMAND_LIST ( + CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet) CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchivePush) ) diff --git a/src/config/define.auto.h b/src/config/define.auto.h index 45e378c16..f179f2501 100644 --- a/src/config/define.auto.h +++ b/src/config/define.auto.h @@ -51,6 +51,7 @@ typedef enum cfgDefOptArchiveAsync, cfgDefOptArchiveCheck, cfgDefOptArchiveCopy, + cfgDefOptArchiveGetQueueMax, cfgDefOptArchivePushQueueMax, cfgDefOptArchiveTimeout, cfgDefOptBackupStandby, diff --git a/src/config/parse.auto.c b/src/config/parse.auto.c index c2a0daa3c..4aed5f78b 100644 --- a/src/config/parse.auto.c +++ b/src/config/parse.auto.c @@ -54,6 +54,18 @@ static const struct option optionList[] = .val = PARSE_OPTION_FLAG | PARSE_RESET_FLAG | cfgOptArchiveCopy, }, + // archive-get-queue-max option + // ----------------------------------------------------------------------------------------------------------------------------- + { + .name = "archive-get-queue-max", + .has_arg = required_argument, + .val = PARSE_OPTION_FLAG | cfgOptArchiveGetQueueMax, + }, + { + .name = "reset-archive-get-queue-max", + .val = PARSE_OPTION_FLAG | PARSE_RESET_FLAG | cfgOptArchiveGetQueueMax, + }, + // archive-push-queue-max option and deprecations // ----------------------------------------------------------------------------------------------------------------------------- { diff --git a/src/main.c b/src/main.c index 57378db03..a89cc40db 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ Main #include #include +#include "command/archive/get/get.h" #include "command/archive/push/push.h" #include "command/help/help.h" #include "command/command.h" @@ -44,6 +45,13 @@ main(int argListSize, const char *argList[]) fflush(stdout); } + // Archive get command + // ------------------------------------------------------------------------------------------------------------------------- + else if (cfgCommand() == cfgCmdArchiveGet) + { + result = cmdArchiveGet(); + } + // Archive push command. Currently only implements local operations of async archive push. // ------------------------------------------------------------------------------------------------------------------------- else if (cfgCommand() == cfgCmdArchivePush && cfgOptionBool(cfgOptArchiveAsync)) diff --git a/src/storage/helper.c b/src/storage/helper.c index dd77a5d2f..5ea401b96 100644 --- a/src/storage/helper.c +++ b/src/storage/helper.c @@ -90,7 +90,14 @@ storageSpoolPathExpression(const String *expression, const String *path) { String *result = NULL; - if (strcmp(strPtr(expression), STORAGE_SPOOL_ARCHIVE_OUT) == 0) + if (strcmp(strPtr(expression), STORAGE_SPOOL_ARCHIVE_IN) == 0) + { + if (path == NULL) + result = strNewFmt("archive/%s/in", strPtr(storageSpoolStanza)); + else + result = strNewFmt("archive/%s/in/%s", strPtr(storageSpoolStanza), strPtr(path)); + } + else if (strcmp(strPtr(expression), STORAGE_SPOOL_ARCHIVE_OUT) == 0) { if (path == NULL) result = strNewFmt("archive/%s/out", strPtr(storageSpoolStanza)); diff --git a/src/storage/helper.h b/src/storage/helper.h index 2cd0caf30..9381285d7 100644 --- a/src/storage/helper.h +++ b/src/storage/helper.h @@ -9,6 +9,7 @@ Storage Helper /*********************************************************************************************************************************** Spool storage path constants ***********************************************************************************************************************************/ +#define STORAGE_SPOOL_ARCHIVE_IN "" #define STORAGE_SPOOL_ARCHIVE_OUT "" /*********************************************************************************************************************************** diff --git a/test/define.yaml b/test/define.yaml index 7cde400d9..a55b5d443 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -544,11 +544,20 @@ unit: Archive/Info: partial # ---------------------------------------------------------------------------------------------------------------------------- - - name: get-perl + - name: get total: 2 + perlReq: true coverage: - Archive/Base: partial + command/archive/get/get: full + + # ---------------------------------------------------------------------------------------------------------------------------- + - name: get-perl + total: 3 + + coverage: + Archive/Base: full + Archive/Get/Async: full Archive/Get/File: partial Archive/Get/Get: partial diff --git a/test/expect/mock-archive-001.log b/test/expect/mock-archive-001.log index 8104aa81d..3fff7d9a3 100644 --- a/test/expect/mock-archive-001.log +++ b/test/expect/mock-archive-001.log @@ -14,7 +14,6 @@ P00 INFO: archive-push command end: aborted with exception [055] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [055]: unable to open [TEST_PATH]/db-master/repo/archive/db/archive.info or [TEST_PATH]/db-master/repo/archive/db/archive.info.copy P00 ERROR: [055]: archive.info does not exist but is required to push/get WAL segments HINT: is archive_command configured in postgresql.conf? @@ -114,9 +113,8 @@ P00 INFO: archive-push command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --log-level-console=debug 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=debug --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 DEBUG: Archive::Get::Get->process(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 -P00 INFO: get WAL segment 000000010000000100000001 -P00 DEBUG: Archive::Get::File::archiveGetFile(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 +P00 DEBUG: Archive::Get::Get->process(): rstryCommandArg = (000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG) +P00 DEBUG: Archive::Get::File::archiveGetFile(): bAtomic = false, strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 P00 DEBUG: Common::Lock::lockStopTest(): bStanzaStopRequired = P00 DEBUG: Common::Lock::lockStopTest=>: bStopExists = false P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = @@ -147,9 +145,10 @@ P00 DEBUG: Archive::Get::File::archiveGetCheck=>: strArchiveFile = 00000001 P00 DEBUG: Storage::Local->openRead(): bIgnoreMissing = , rhyFilter = [undef], strCipherPass = , xFileExp = /9.4-1/000000010000000100000001-ceb021d9bb41f220511e413b095d2b0d89fec113 P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = P00 DEBUG: Storage::Local->new(): bAllowTemp = , hRule = [undef], lBufferMax = 4194304, oDriver = [object], strCipherPassUser = [undef], strCipherType = [undef], strDefaultFileMode = <0640>, strDefaultPathMode = <0750>, strPathBase = [TEST_PATH]/db-master/db/base, strTempExtension = pgbackrest.tmp -P00 DEBUG: Storage::Local->openWrite(): bAtomic = , bPathCreate = , lTimestamp = [undef], rhyFilter = [undef], strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +P00 DEBUG: Storage::Local->openWrite(): bAtomic = false, bPathCreate = , lTimestamp = [undef], rhyFilter = [undef], strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG P00 DEBUG: Storage::Base->copy(): xDestinationFile = [object], xSourceFile = [object] P00 DEBUG: Archive::Get::File::archiveGetFile=>: iResult = 0 +P00 INFO: got WAL segment 000000010000000100000001 P00 DEBUG: Main::mainCleanup(): iExitCode = 0 P00 DEBUG: Protocol::Helper::protocolDestroy(): bComplete = true, iRemoteIdx = [undef], strRemoteType = [undef] P00 DEBUG: Protocol::Helper::protocolDestroy=>: iExitStatus = 0 @@ -171,13 +170,12 @@ P00 INFO: archive-push command end: aborted with exception [044] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 +P00 INFO: got WAL segment 000000010000000100000001 P00 INFO: archive-get command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [125]: unable to retrieve the archive id for database version '9.4' and system-id '1000000000000000094' P00 INFO: archive-get command end: aborted with exception [125] @@ -191,7 +189,6 @@ P00 INFO: archive-push command end: aborted with exception [044] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [125]: unable to retrieve the archive id for database version '9.4' and system-id '1000000000000000094' P00 INFO: archive-get command end: aborted with exception [125] @@ -210,7 +207,6 @@ P00 INFO: archive-push command end: aborted with exception [062] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [062]: stop file exists for stanza db P00 INFO: archive-get command end: aborted with exception [062] @@ -234,10 +230,10 @@ P00 INFO: archive-push command begin [BACKREST-VERSION]: [[TEST_PATH]/db-maste P00 ERROR: [045]: WAL segment 000000010000000100000002 already exists in the archive P00 INFO: archive-push command end: aborted with exception [045] -> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --archive-async --archive-timeout=5 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ -P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 INFO: get WAL segment 000000010000000100000002 +P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --archive-async --archive-timeout=5 --no-compress --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass= --repo1-cipher-type=aes-256-cbc --repo1-path=[TEST_PATH]/db-master/repo --spool-path=[TEST_PATH]/db-master/spool --stanza=db +P00 INFO: got WAL segment 000000010000000100000002 asynchronously P00 INFO: archive-get command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-push [TEST_PATH]/db-master/db/base/pg_xlog/000000010000000100000002.partial diff --git a/test/expect/mock-archive-002.log b/test/expect/mock-archive-002.log index 0d841fd63..46f96a650 100644 --- a/test/expect/mock-archive-002.log +++ b/test/expect/mock-archive-002.log @@ -13,7 +13,6 @@ P00 INFO: archive-push command end: aborted with exception [055] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [055]: raised from remote process on 'backup': archive.info does not exist but is required to push/get WAL segments HINT: is archive_command configured in postgresql.conf? HINT: has a stanza-create been performed? @@ -98,9 +97,8 @@ P00 INFO: archive-push command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --log-level-console=debug 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=debug --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 DEBUG: Archive::Get::Get->process(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 -P00 INFO: get WAL segment 000000010000000100000001 -P00 DEBUG: Archive::Get::File::archiveGetFile(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 +P00 DEBUG: Archive::Get::Get->process(): rstryCommandArg = (000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG) +P00 DEBUG: Archive::Get::File::archiveGetFile(): bAtomic = false, strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000001 P00 DEBUG: Common::Lock::lockStopTest(): bStanzaStopRequired = P00 DEBUG: Common::Lock::lockStopTest=>: bStopExists = false P00 DEBUG: Protocol::Helper::protocolGet(): bCache = , iProcessIdx = [undef], iRemoteIdx = <1>, strBackRestBin = [undef], strCommand = , strRemoteType = backup @@ -122,9 +120,10 @@ P00 DEBUG: Archive::Get::File::archiveGetCheck=>: strArchiveFile = 00000001 P00 DEBUG: Protocol::Storage::Remote->openRead(): rhParam = [hash], strFileExp = /9.4-1/000000010000000100000001-ceb021d9bb41f220511e413b095d2b0d89fec113 P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = P00 DEBUG: Storage::Local->new(): bAllowTemp = , hRule = [undef], lBufferMax = 4194304, oDriver = [object], strCipherPassUser = [undef], strCipherType = [undef], strDefaultFileMode = <0640>, strDefaultPathMode = <0750>, strPathBase = [TEST_PATH]/db-master/db/base, strTempExtension = pgbackrest.tmp -P00 DEBUG: Storage::Local->openWrite(): bAtomic = , bPathCreate = , lTimestamp = [undef], rhyFilter = [undef], strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +P00 DEBUG: Storage::Local->openWrite(): bAtomic = false, bPathCreate = , lTimestamp = [undef], rhyFilter = [undef], strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG P00 DEBUG: Storage::Base->copy(): xDestinationFile = [object], xSourceFile = [object] P00 DEBUG: Archive::Get::File::archiveGetFile=>: iResult = 0 +P00 INFO: got WAL segment 000000010000000100000001 P00 DEBUG: Main::mainCleanup(): iExitCode = 0 P00 DEBUG: Protocol::Helper::protocolDestroy(): bComplete = true, iRemoteIdx = [undef], strRemoteType = [undef] P00 DEBUG: Protocol::Helper::protocolDestroy: found cached protocol: iRemoteIdx = 1, strRemoteType = backup @@ -148,13 +147,12 @@ P00 INFO: archive-push command end: aborted with exception [044] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 +P00 INFO: got WAL segment 000000010000000100000001 P00 INFO: archive-get command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [125]: raised from remote process on 'backup': unable to retrieve the archive id for database version '9.4' and system-id '1000000000000000094' P00 INFO: archive-get command end: aborted with exception [125] @@ -168,7 +166,6 @@ P00 INFO: archive-push command end: aborted with exception [044] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [125]: raised from remote process on 'backup': unable to retrieve the archive id for database version '9.4' and system-id '1000000000000000094' P00 INFO: archive-get command end: aborted with exception [125] @@ -187,7 +184,6 @@ P00 INFO: archive-push command end: aborted with exception [062] > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000001 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000001, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000001 P00 ERROR: [062]: stop file exists for stanza db P00 INFO: archive-get command end: aborted with exception [062] @@ -211,10 +207,10 @@ P00 INFO: archive-push command begin [BACKREST-VERSION]: [[TEST_PATH]/db-maste P00 ERROR: [045]: WAL segment 000000010000000100000002 already exists in the archive P00 INFO: archive-push command end: aborted with exception [045] -> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --cmd-ssh=/usr/bin/ssh 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --cmd-ssh=/usr/bin/ssh --archive-async --archive-timeout=5 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ -P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --cmd-ssh=/usr/bin/ssh --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --stanza=db -P00 INFO: get WAL segment 000000010000000100000002 +P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --archive-async --archive-timeout=5 --cmd-ssh=/usr/bin/ssh --no-compress --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-1] --spool-path=[TEST_PATH]/db-master/spool --stanza=db +P00 INFO: got WAL segment 000000010000000100000002 asynchronously P00 INFO: archive-get command end: completed successfully > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-push [TEST_PATH]/db-master/db/base/pg_xlog/000000010000000100000002.partial diff --git a/test/expect/mock-archive-003.log b/test/expect/mock-archive-003.log index c0ec3c262..71c7ef5d3 100644 --- a/test/expect/mock-archive-003.log +++ b/test/expect/mock-archive-003.log @@ -108,7 +108,7 @@ P00 WARN: WAL segment 000000010000000100000002 already exists in the archive w ------------------------------------------------------------------------------------------------------------------------------------ P00 ERROR: [045]: WAL segment 000000010000000100000002 already exists in the archive -> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --cmd-ssh=/usr/bin/ssh 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get --cmd-ssh=/usr/bin/ssh --archive-async --archive-timeout=5 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-push [TEST_PATH]/db-master/db/base/pg_xlog/000000010000000100000002.partial diff --git a/test/expect/mock-stanza-001.log b/test/expect/mock-stanza-001.log index 5464b8631..3abcc8f64 100644 --- a/test/expect/mock-stanza-001.log +++ b/test/expect/mock-stanza-001.log @@ -516,9 +516,8 @@ db-version="9.4" > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=debug --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-path=[TEST_PATH]/db-master/repo --stanza=db -P00 DEBUG: Archive::Get::Get->process(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 -P00 INFO: get WAL segment 000000010000000100000002 -P00 DEBUG: Archive::Get::File::archiveGetFile(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 +P00 DEBUG: Archive::Get::Get->process(): rstryCommandArg = (000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG) +P00 DEBUG: Archive::Get::File::archiveGetFile(): bAtomic = false, strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 P00 DEBUG: Common::Lock::lockStopTest(): bStanzaStopRequired = P00 DEBUG: Common::Lock::lockStopTest=>: bStopExists = false P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = @@ -549,9 +548,10 @@ P00 DEBUG: Archive::Get::File::archiveGetCheck=>: strArchiveFile = 00000001 P00 DEBUG: Storage::Local->openRead(): bIgnoreMissing = , rhyFilter = [undef], strCipherPass = [undef], xFileExp = /9.3-1/000000010000000100000002-488ba4b8b98acc510bce86b8f16e3c1ed9886a29.gz P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = P00 DEBUG: Storage::Local->new(): bAllowTemp = , hRule = [undef], lBufferMax = 4194304, oDriver = [object], strCipherPassUser = [undef], strCipherType = [undef], strDefaultFileMode = <0640>, strDefaultPathMode = <0750>, strPathBase = [TEST_PATH]/db-master/db/base, strTempExtension = pgbackrest.tmp -P00 DEBUG: Storage::Local->openWrite(): bAtomic = , bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +P00 DEBUG: Storage::Local->openWrite(): bAtomic = false, bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG P00 DEBUG: Storage::Base->copy(): xDestinationFile = [object], xSourceFile = [object] P00 DEBUG: Archive::Get::File::archiveGetFile=>: iResult = 0 +P00 INFO: got WAL segment 000000010000000100000002 P00 DEBUG: Main::mainCleanup(): iExitCode = 0 P00 DEBUG: Protocol::Helper::protocolDestroy(): bComplete = true, iRemoteIdx = [undef], strRemoteType = [undef] P00 DEBUG: Protocol::Helper::protocolDestroy=>: iExitStatus = 0 diff --git a/test/expect/mock-stanza-002.log b/test/expect/mock-stanza-002.log index c717b5836..434b10ab3 100644 --- a/test/expect/mock-stanza-002.log +++ b/test/expect/mock-stanza-002.log @@ -382,9 +382,8 @@ db-version="9.4" > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=debug --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-host=backup --repo1-host-cmd=[BACKREST-BIN] --repo1-host-config=[TEST_PATH]/backup/pgbackrest.conf --repo1-host-user=[USER-2] --stanza=db -P00 DEBUG: Archive::Get::Get->process(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 -P00 INFO: get WAL segment 000000010000000100000002 -P00 DEBUG: Archive::Get::File::archiveGetFile(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 +P00 DEBUG: Archive::Get::Get->process(): rstryCommandArg = (000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG) +P00 DEBUG: Archive::Get::File::archiveGetFile(): bAtomic = false, strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 P00 DEBUG: Common::Lock::lockStopTest(): bStanzaStopRequired = P00 DEBUG: Common::Lock::lockStopTest=>: bStopExists = false P00 DEBUG: Protocol::Helper::protocolGet(): bCache = , iProcessIdx = [undef], iRemoteIdx = <1>, strBackRestBin = [undef], strCommand = , strRemoteType = backup @@ -406,9 +405,10 @@ P00 DEBUG: Archive::Get::File::archiveGetCheck=>: strArchiveFile = 00000001 P00 DEBUG: Protocol::Storage::Remote->openRead(): rhParam = [hash], strFileExp = /9.3-1/000000010000000100000002-488ba4b8b98acc510bce86b8f16e3c1ed9886a29.gz P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = P00 DEBUG: Storage::Local->new(): bAllowTemp = , hRule = [undef], lBufferMax = 4194304, oDriver = [object], strCipherPassUser = [undef], strCipherType = [undef], strDefaultFileMode = <0640>, strDefaultPathMode = <0750>, strPathBase = [TEST_PATH]/db-master/db/base, strTempExtension = pgbackrest.tmp -P00 DEBUG: Storage::Local->openWrite(): bAtomic = , bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +P00 DEBUG: Storage::Local->openWrite(): bAtomic = false, bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG P00 DEBUG: Storage::Base->copy(): xDestinationFile = [object], xSourceFile = [object] P00 DEBUG: Archive::Get::File::archiveGetFile=>: iResult = 0 +P00 INFO: got WAL segment 000000010000000100000002 P00 DEBUG: Main::mainCleanup(): iExitCode = 0 P00 DEBUG: Protocol::Helper::protocolDestroy(): bComplete = true, iRemoteIdx = [undef], strRemoteType = [undef] P00 DEBUG: Protocol::Helper::protocolDestroy: found cached protocol: iRemoteIdx = 1, strRemoteType = backup diff --git a/test/expect/mock-stanza-003.log b/test/expect/mock-stanza-003.log index b0d5b1a69..2bacf8c53 100644 --- a/test/expect/mock-stanza-003.log +++ b/test/expect/mock-stanza-003.log @@ -491,9 +491,8 @@ db-version="9.4" > [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db archive-get 000000010000000100000002 [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG ------------------------------------------------------------------------------------------------------------------------------------ P00 INFO: archive-get command begin [BACKREST-VERSION]: [000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG] --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/db-master/lock --log-level-console=debug --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key= --repo1-s3-key-secret= --repo1-s3-region=us-east-1 --no-repo1-s3-verify-ssl --repo1-type=s3 --stanza=db -P00 DEBUG: Archive::Get::Get->process(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 -P00 INFO: get WAL segment 000000010000000100000002 -P00 DEBUG: Archive::Get::File::archiveGetFile(): strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 +P00 DEBUG: Archive::Get::Get->process(): rstryCommandArg = (000000010000000100000002, [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG) +P00 DEBUG: Archive::Get::File::archiveGetFile(): bAtomic = false, strDestinationFile = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG, strSourceArchive = 000000010000000100000002 P00 DEBUG: Common::Lock::lockStopTest(): bStanzaStopRequired = P00 DEBUG: Common::Lock::lockStopTest=>: bStopExists = false P00 DEBUG: Storage::S3::Request->new(): bVerifySsl = false, iPort = [undef], lBufferMax = 4194304, strAccessKeyId = , strBucket = pgbackrest-dev, strCaFile = [undef], strCaPath = [undef], strEndPoint = s3.amazonaws.com, strHost = [undef], strRegion = us-east-1, strSecretAccessKey = @@ -524,9 +523,10 @@ P00 DEBUG: Archive::Get::File::archiveGetCheck=>: strArchiveFile = 00000001 P00 DEBUG: Storage::Local->openRead(): bIgnoreMissing = , rhyFilter = [undef], strCipherPass = [undef], xFileExp = /9.3-1/000000010000000100000002-488ba4b8b98acc510bce86b8f16e3c1ed9886a29.gz P00 DEBUG: Storage::Posix::Driver->new(): bFileSync = , bPathSync = P00 DEBUG: Storage::Local->new(): bAllowTemp = , hRule = [undef], lBufferMax = 4194304, oDriver = [object], strCipherPassUser = [undef], strCipherType = [undef], strDefaultFileMode = <0640>, strDefaultPathMode = <0750>, strPathBase = [TEST_PATH]/db-master/db/base, strTempExtension = pgbackrest.tmp -P00 DEBUG: Storage::Local->openWrite(): bAtomic = , bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG +P00 DEBUG: Storage::Local->openWrite(): bAtomic = false, bPathCreate = , lTimestamp = [undef], rhyFilter = ({rxyParam => ({strCompressType => decompress}), strClass => pgBackRest::Storage::Filter::Gzip}), strCipherPass = [undef], strGroup = [undef], strMode = <0640>, strUser = [undef], xFileExp = [TEST_PATH]/db-master/db/base/pg_xlog/RECOVERYXLOG P00 DEBUG: Storage::Base->copy(): xDestinationFile = [object], xSourceFile = [object] P00 DEBUG: Archive::Get::File::archiveGetFile=>: iResult = 0 +P00 INFO: got WAL segment 000000010000000100000002 P00 DEBUG: Main::mainCleanup(): iExitCode = 0 P00 DEBUG: Protocol::Helper::protocolDestroy(): bComplete = true, iRemoteIdx = [undef], strRemoteType = [undef] P00 DEBUG: Protocol::Helper::protocolDestroy=>: iExitStatus = 0 diff --git a/test/lib/pgBackRestTest/Env/HostEnvTest.pm b/test/lib/pgBackRestTest/Env/HostEnvTest.pm index bb6bdf4c7..4cce5576f 100644 --- a/test/lib/pgBackRestTest/Env/HostEnvTest.pm +++ b/test/lib/pgBackRestTest/Env/HostEnvTest.pm @@ -457,13 +457,21 @@ sub walGenerate my $iSourceNo = shift; my $strWalSegment = shift; my $bPartial = shift; + my $bChecksum = shift; + my $bReady = shift; - my $strWalFile = "${strWalPath}/${strWalSegment}" . (defined($bPartial) && $bPartial ? '.partial' : ''); my $rtWalContent = $self->walGenerateContent($strPgVersion, {iSourceNo => $iSourceNo}); + my $strWalFile = + "${strWalPath}/${strWalSegment}" . ($bChecksum ? '-' . sha1_hex($rtWalContent) : '') . + (defined($bPartial) && $bPartial ? '.partial' : ''); # Put the WAL segment and the ready file storageTest()->put($strWalFile, $rtWalContent); - storageTest()->put("${strWalPath}/archive_status/${strWalSegment}.ready"); + + if (!defined($bReady) || $bReady) + { + storageTest()->put("${strWalPath}/archive_status/${strWalSegment}.ready"); + } return $strWalFile; } diff --git a/test/lib/pgBackRestTest/Module/Archive/ArchiveGetPerlTest.pm b/test/lib/pgBackRestTest/Module/Archive/ArchiveGetPerlTest.pm index 8190e38ee..4a291a180 100644 --- a/test/lib/pgBackRestTest/Module/Archive/ArchiveGetPerlTest.pm +++ b/test/lib/pgBackRestTest/Module/Archive/ArchiveGetPerlTest.pm @@ -15,6 +15,7 @@ use Storable qw(dclone); use Digest::SHA qw(sha1_hex); use pgBackRest::Archive::Common; +use pgBackRest::Archive::Get::Async; use pgBackRest::Archive::Get::File; use pgBackRest::Archive::Get::Get; use pgBackRest::Archive::Info; @@ -24,6 +25,7 @@ use pgBackRest::Config::Config; use pgBackRest::DbVersion; use pgBackRest::Manifest; use pgBackRest::Protocol::Storage::Helper; +use pgBackRest::Storage::Helper; use pgBackRestTest::Env::HostEnvTest; use pgBackRestTest::Common::ExecuteTest; @@ -37,9 +39,11 @@ sub initModule my $self = shift; $self->{strDbPath} = $self->testPath() . '/db'; + $self->{strLockPath} = $self->testPath() . '/lock'; $self->{strRepoPath} = $self->testPath() . '/repo'; $self->{strArchivePath} = "$self->{strRepoPath}/archive/" . $self->stanza(); $self->{strBackupPath} = "$self->{strRepoPath}/backup/" . $self->stanza(); + $self->{strSpoolPath} = "$self->{strArchivePath}/in"; } #################################################################################################################################### @@ -57,6 +61,8 @@ sub initTest $self->optionTestSet(CFGOPT_STANZA, $self->stanza()); $self->optionTestSet(CFGOPT_REPO_PATH, $self->testPath() . '/repo'); $self->optionTestSet(CFGOPT_PG_PATH, $self->{strDbPath}); + $self->optionTestSet(CFGOPT_LOG_PATH, $self->testPath()); + $self->optionTestSet(CFGOPT_LOCK_PATH, $self->{strLockPath}); $self->configTestLoad(CFGCMD_ARCHIVE_GET); # Create archive info path @@ -65,6 +71,9 @@ sub initTest # Create backup info path storageTest()->pathCreate($self->{strBackupPath}, {bIgnoreExists => true, bCreateParent => true}); + # Create spool path + storageTest()->pathCreate($self->{strSpoolPath}, {bIgnoreExists => true, bCreateParent => true}); + # Create pg_control path storageTest()->pathCreate($self->{strDbPath} . '/' . DB_PATH_GLOBAL, {bCreateParent => true}); @@ -90,7 +99,7 @@ sub run my $strArchivePath; ################################################################################################################################ - if ($self->begin("Archive::Base::getCheck()")) + if ($self->begin("Archive::Common::archiveGetCheck()")) { # Create and save archive.info file my $oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false, @@ -184,11 +193,11 @@ sub run } ################################################################################################################################ - if ($self->begin("Archive::Get::Get::get()")) + if ($self->begin("Archive::Get::Get::get() sync")) { # archive.info missing #--------------------------------------------------------------------------------------------------------------------------- - $self->testException(sub {archiveGetFile($strWalSegment, $strDestinationFile)}, + $self->testException(sub {archiveGetFile($strWalSegment, $strDestinationFile, false)}, ERROR_FILE_MISSING, ARCHIVE_INFO_FILE . " does not exist but is required to push/get WAL segments\n" . "HINT: is archive_command configured in postgresql.conf?\n" . @@ -205,7 +214,7 @@ sub run # file not found #--------------------------------------------------------------------------------------------------------------------------- - $self->testResult(sub {archiveGetFile($strWalSegment, $strDestinationFile)}, 1, + $self->testResult(sub {archiveGetFile($strWalSegment, $strDestinationFile, false)}, 1, "unable to find ${strWalSegment} in the archive"); # file found but is not a WAL segment @@ -219,14 +228,14 @@ sub run # Create path to copy file storageRepo()->pathCreate($strDestinationPath); - $self->testResult(sub {archiveGetFile(BOGUS, $strDestinationFile)}, 0, + $self->testResult(sub {archiveGetFile(BOGUS, $strDestinationFile, false)}, 0, "non-WAL segment copied"); # Confirm the correct file is copied $self->testResult(sub {sha1_hex(${storageRepo()->get($strDestinationFile)})}, $strBogusHash, ' check correct non-WAL copied from older archiveId'); - # create same WAL segment in same DB but different archives and different has values. Confirm latest one copied. + # create same WAL segment in same DB but different archives and different hash values. Confirm latest one copied. #--------------------------------------------------------------------------------------------------------------------------- my $strWalMajorPath = "${strArchivePath}/" . substr($strWalSegment, 0, 16); my $strWalSegmentName = "${strWalSegment}-${strFileHash}"; @@ -244,7 +253,7 @@ sub run storageRepo()->pathCreate($strWalMajorPath, {bCreateParent => true}); storageRepo()->put("${strWalMajorPath}/${strWalSegmentName}", $strFileContent); - $self->testResult(sub {archiveGetFile($strWalSegmentName, $strDestinationFile)}, 0, + $self->testResult(sub {archiveGetFile($strWalSegmentName, $strDestinationFile, false)}, 0, "WAL segment copied"); # Confirm the correct file is copied @@ -271,13 +280,102 @@ sub run # Overwrite current pg_control file with older version $self->controlGenerate($self->{strDbPath}, PG_VERSION_93); - $self->testResult(sub {archiveGetFile($strWalSegmentName, $strDestinationFile)}, 0, + $self->testResult(sub {archiveGetFile($strWalSegmentName, $strDestinationFile, false)}, 0, "WAL segment copied from older db backupset to same version older db"); # Confirm the correct file is copied $self->testResult(sub {sha1_hex(${storageRepo()->get($strDestinationFile)})}, $strWalHash, ' check correct WAL copied from older db'); } + + ################################################################################################################################ + if ($self->begin("Archive::Get::Get::get() async")) + { + # Test error in local process when stanza has not been created + #--------------------------------------------------------------------------------------------------------------------------- + my @stryWal = ('000000010000000A0000000A', '000000010000000A0000000B'); + + my $oGetAsync = new pgBackRest::Archive::Get::Async( + $self->{strSpoolPath}, $self->backrestExe(), \@stryWal); + + $self->optionTestSetBool(CFGOPT_ARCHIVE_ASYNC, true); + $self->optionTestSet(CFGOPT_SPOOL_PATH, $self->{strRepoPath}); + $self->configTestLoad(CFGCMD_ARCHIVE_GET); + + $oGetAsync->process(); + + my $strErrorMessage = + "55\n" . + "raised from local-1 process: archive.info does not exist but is required to push/get WAL segments\n" . + "HINT: is archive_command configured in postgresql.conf?\n" . + "HINT: has a stanza-create been performed?\n" . + "HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme."; + + $self->testResult( + sub {storageSpool()->list(STORAGE_SPOOL_ARCHIVE_IN)}, + "(000000010000000A0000000A.error, 000000010000000A0000000B.error)", 'error files created'); + + $self->testResult( + ${storageSpool()->get(STORAGE_SPOOL_ARCHIVE_IN . "/000000010000000A0000000A.error")}, $strErrorMessage, + "check error file contents"); + storageSpool()->remove(STORAGE_SPOOL_ARCHIVE_IN . "/000000010000000A0000000A.error"); + $self->testResult( + ${storageSpool()->get(STORAGE_SPOOL_ARCHIVE_IN . "/000000010000000A0000000B.error")}, $strErrorMessage, + "check error file contents"); + storageSpool()->remove(STORAGE_SPOOL_ARCHIVE_IN . "/000000010000000A0000000B.error"); + + # Create archive info file + #--------------------------------------------------------------------------------------------------------------------------- + my $oArchiveInfo = new pgBackRest::Archive::Info($self->{strArchivePath}, false, {bIgnoreMissing => true}); + $oArchiveInfo->create(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), true); + + my $strArchiveId = $oArchiveInfo->archiveId(); + + # Transfer first file + #--------------------------------------------------------------------------------------------------------------------------- + my $strWalPath = "$self->{strRepoPath}/archive/db/${strArchiveId}/000000010000000A"; + storageRepo()->pathCreate($strWalPath, {bCreateParent => true}); + + $self->walGenerate($strWalPath, PG_VERSION_94, 1, "000000010000000A0000000A", false, true, false); + $oGetAsync->processQueue(); + + $self->testResult( + sub {storageSpool()->list(STORAGE_SPOOL_ARCHIVE_IN)}, + "(000000010000000A0000000A, 000000010000000A0000000B.ok)", 'WAL and OK file'); + + # Transfer second file + #--------------------------------------------------------------------------------------------------------------------------- + @stryWal = ('000000010000000A0000000B'); + + storageSpool()->remove(STORAGE_SPOOL_ARCHIVE_IN . "/000000010000000A0000000B.ok"); + + $self->walGenerate($strWalPath, PG_VERSION_94, 1, "000000010000000A0000000B", false, true, false); + $oGetAsync->processQueue(); + + $self->testResult( + sub {storageSpool()->list(STORAGE_SPOOL_ARCHIVE_IN)}, + "(000000010000000A0000000A, 000000010000000A0000000B)", 'WAL files'); + + # Error on main process + #--------------------------------------------------------------------------------------------------------------------------- + @stryWal = ('000000010000000A0000000C'); + + storageTest()->put(storageTest()->openWrite($self->{strLockPath} . "/db.stop", {bPathCreate => true}), undef); + + $oGetAsync->processQueue(); + + $self->testResult( + sub {storageSpool()->list(STORAGE_SPOOL_ARCHIVE_IN)}, + "(000000010000000A0000000A, 000000010000000A0000000B, 000000010000000A0000000C.error)", 'WAL files and error file'); + + # Set protocol timeout low + #--------------------------------------------------------------------------------------------------------------------------- + $self->optionTestSet(CFGOPT_PROTOCOL_TIMEOUT, 30); + $self->optionTestSet(CFGOPT_DB_TIMEOUT, 29); + $self->configTestLoad(CFGCMD_ARCHIVE_GET); + + $oGetAsync->process(); + } } 1; diff --git a/test/lib/pgBackRestTest/Module/Mock/MockArchiveTest.pm b/test/lib/pgBackRestTest/Module/Mock/MockArchiveTest.pm index 0f7b21edc..583486427 100644 --- a/test/lib/pgBackRestTest/Module/Mock/MockArchiveTest.pm +++ b/test/lib/pgBackRestTest/Module/Mock/MockArchiveTest.pm @@ -159,12 +159,12 @@ sub run # Test that the WAL was pushed $self->archiveCheck($strSourceFile, $strArchiveChecksum, false); + # Remove WAL + storageTest()->remove("${strWalPath}/${strSourceFile}", {bIgnoreMissing => false}); + #--------------------------------------------------------------------------------------------------------------------------- &log(INFO, ' get first WAL'); - # Remove WAL so it can be recovered - storageTest()->remove("${strWalPath}/${strSourceFile}", {bIgnoreMissing => false}); - $oHostDbMaster->executeSimple( $strCommandGet . " ${strLogDebug} ${strSourceFile} ${strWalPath}/RECOVERYXLOG", {oLogTest => $self->expect()}); @@ -325,6 +325,8 @@ sub run $oHostDbMaster->start({strStanza => $oHostDbMaster->stanza()}); + storageTest->remove("${strWalPath}/RECOVERYXLOG", {bIgnoreMissing => false}); + #--------------------------------------------------------------------------------------------------------------------------- &log(INFO, ' WAL duplicate ok'); @@ -339,14 +341,15 @@ sub run $strCommandPush . " ${strWalPath}/${strSourceFile}", {iExpectedExitStatus => ERROR_ARCHIVE_DUPLICATE, oLogTest => $self->expect()}); - #--------------------------------------------------------------------------------------------------------------------------- - &log(INFO, ' get second WAL'); - - # Remove WAL so it can be recovered + # Remove WAL storageTest()->remove("${strWalPath}/${strSourceFile}", {bIgnoreMissing => false}); + #--------------------------------------------------------------------------------------------------------------------------- + &log(INFO, " get second WAL (${strSourceFile})"); + $oHostDbMaster->executeSimple( - $strCommandGet . ($bRemote ? ' --cmd-ssh=/usr/bin/ssh' : '') . " ${strSourceFile} ${strWalPath}/RECOVERYXLOG", + $strCommandGet . ($bRemote ? ' --cmd-ssh=/usr/bin/ssh' : '') . + " --archive-async --archive-timeout=5 ${strSourceFile} ${strWalPath}/RECOVERYXLOG", {oLogTest => $self->expect()}); # Check that the destination file exists diff --git a/test/src/common/harnessConfig.c b/test/src/common/harnessConfig.c index 2fdfdb038..2c5d271f5 100644 --- a/test/src/common/harnessConfig.c +++ b/test/src/common/harnessConfig.c @@ -16,6 +16,6 @@ void harnessCfgLoad(unsigned int argListSize, const char *argList[]) { configParse(argListSize, argList); - logInit(logLevelInfo, logLevelOff, logLevelOff, false); + logInit(logLevelInfo, logLevelOff, logLevelDebug, false); cfgLoadUpdateOption(); } diff --git a/test/src/module/archive/commonTest.c b/test/src/module/archive/commonTest.c index effdfdc47..afb796cf3 100644 --- a/test/src/module/archive/commonTest.c +++ b/test/src/module/archive/commonTest.c @@ -26,42 +26,48 @@ testRun() // ------------------------------------------------------------------------------------------------------------------------- String *segment = strNew("000000010000000100000001"); - TEST_RESULT_BOOL(archiveAsyncStatus(segment, false), false, "directory and status file not present"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), false, "directory and status file not present"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModeGet, segment, false), false, "directory and status file not present"); // ------------------------------------------------------------------------------------------------------------------------- mkdir(strPtr(strNewFmt("%s/archive", testPath())), 0750); mkdir(strPtr(strNewFmt("%s/archive/db", testPath())), 0750); mkdir(strPtr(strNewFmt("%s/archive/db/out", testPath())), 0750); - TEST_RESULT_BOOL(archiveAsyncStatus(segment, false), false, "status file not present"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), false, "status file not present"); // ------------------------------------------------------------------------------------------------------------------------- storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), bufNewStr(strNew(BOGUS_STR))); TEST_ERROR( - archiveAsyncStatus(segment, false), FormatError, "000000010000000100000001.ok content must have at least two lines"); + archiveAsyncStatus(archiveModePush, segment, false), FormatError, + "000000010000000100000001.ok content must have at least two lines"); storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), bufNewStr(strNew(BOGUS_STR "\n"))); - TEST_ERROR(archiveAsyncStatus(segment, false), FormatError, "000000010000000100000001.ok message must be > 0"); + TEST_ERROR( + archiveAsyncStatus(archiveModePush, segment, false), FormatError, "000000010000000100000001.ok message must be > 0"); storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), bufNewStr(strNew(BOGUS_STR "\nmessage"))); - TEST_ERROR(archiveAsyncStatus(segment, false), FormatError, "unable to convert str 'BOGUS' to int"); + TEST_ERROR(archiveAsyncStatus(archiveModePush, segment, false), FormatError, "unable to convert str 'BOGUS' to int"); + + storagePutNP(storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), NULL); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), true, "ok file"); storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), bufNewStr(strNew("0\nwarning"))); - TEST_RESULT_BOOL(archiveAsyncStatus(segment, false), true, "ok file with warning"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), true, "ok file with warning"); testLogResult("P00 WARN: warning"); storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))), bufNewStr(strNew("25\nerror"))); - TEST_RESULT_BOOL(archiveAsyncStatus(segment, false), true, "error status renamed to ok"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), true, "error status renamed to ok"); testLogResult( "P00 WARN: WAL segment '000000010000000100000001' was not pushed due to error [25] and was manually skipped: error"); @@ -70,20 +76,22 @@ testRun() storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.error", strPtr(segment))), bufNewStr(strNew(""))); TEST_ERROR( - archiveAsyncStatus(segment, false), AssertError, + archiveAsyncStatus(archiveModePush, segment, false), AssertError, strPtr( strNewFmt( "multiple status files found in '%s/archive/db/out' for WAL segment '000000010000000100000001'", testPath()))); unlink(strPtr(storagePathNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.ok", strPtr(segment))))); - TEST_ERROR(archiveAsyncStatus(segment, true), AssertError, "status file '000000010000000100000001.error' has no content"); + TEST_ERROR( + archiveAsyncStatus(archiveModePush, segment, true), AssertError, + "status file '000000010000000100000001.error' has no content"); storagePutNP( storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.error", strPtr(segment))), bufNewStr(strNew("25\nmessage"))); - TEST_ERROR(archiveAsyncStatus(segment, true), AssertError, "message"); + TEST_ERROR(archiveAsyncStatus(archiveModePush, segment, true), AssertError, "message"); - TEST_RESULT_BOOL(archiveAsyncStatus(segment, false), false, "suppress error"); + TEST_RESULT_BOOL(archiveAsyncStatus(archiveModePush, segment, false), false, "suppress error"); unlink(strPtr(storagePathNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_OUT "/%s.error", strPtr(segment))))); } diff --git a/test/src/module/archive/getTest.c b/test/src/module/archive/getTest.c new file mode 100644 index 000000000..04ad4daa3 --- /dev/null +++ b/test/src/module/archive/getTest.c @@ -0,0 +1,262 @@ +/*********************************************************************************************************************************** +Test Archive Get Command +***********************************************************************************************************************************/ +#include + +#include "common/harnessConfig.h" +#include "postgres/type.h" +#include "postgres/version.h" + +/*********************************************************************************************************************************** +Test Run +***********************************************************************************************************************************/ +void +testRun() +{ + Storage *storageTest = storageNewP(strNew(testPath()), .write = true); + + // ***************************************************************************************************************************** + if (testBegin("queueNeed()")) + { + StringList *argList = strLstNew(); + strLstAddZ(argList, "pgbackrest"); + strLstAddZ(argList, "--stanza=db"); + strLstAddZ(argList, "--archive-async"); + strLstAdd(argList, strNewFmt("--spool-path=%s/spool", testPath())); + strLstAddZ(argList, "archive-get"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + size_t queueSize = WAL_SEGMENT_DEFAULT_SIZE; + size_t walSegmentSize = WAL_SEGMENT_DEFAULT_SIZE; + + TEST_ERROR_FMT( + queueNeed(strNew("000000010000000100000001"), false, queueSize, walSegmentSize, PG_VERSION_92), + PathOpenError, "unable to open path '%s/spool/archive/db/in' for read: [2] No such file or directory", testPath()); + + // ------------------------------------------------------------------------------------------------------------------------- + storagePathCreateNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN)); + + TEST_RESULT_STR( + strPtr(strLstJoin(queueNeed(strNew("000000010000000100000001"), false, queueSize, walSegmentSize, PG_VERSION_92), "|")), + "000000010000000100000001|000000010000000100000002", "queue size smaller than min"); + + // ------------------------------------------------------------------------------------------------------------------------- + queueSize = WAL_SEGMENT_DEFAULT_SIZE * 3; + + TEST_RESULT_STR( + strPtr(strLstJoin(queueNeed(strNew("000000010000000100000001"), false, queueSize, walSegmentSize, PG_VERSION_92), "|")), + "000000010000000100000001|000000010000000100000002|000000010000000100000003", "empty queue"); + + // ------------------------------------------------------------------------------------------------------------------------- + storagePutNP( + storageNewWriteNP( + storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/0000000100000001000000FE")), bufNew(walSegmentSize)); + storagePutNP( + storageNewWriteNP( + storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/0000000100000001000000FF")), bufNew(walSegmentSize)); + + TEST_RESULT_STR( + strPtr(strLstJoin(queueNeed(strNew("0000000100000001000000FE"), false, queueSize, walSegmentSize, PG_VERSION_92), "|")), + "000000010000000200000000|000000010000000200000001", "queue has wal < 9.3"); + + TEST_RESULT_STR( + strPtr(strLstJoin(storageListNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN)), "|")), + "0000000100000001000000FE", "check queue"); + + // ------------------------------------------------------------------------------------------------------------------------- + walSegmentSize = 1024 * 1024; + queueSize = walSegmentSize * 5; + + storagePutNP(storageNewWriteNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/junk")), bufNew(16)); + storagePutNP( + storageNewWriteNP( + storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/000000010000000A00000FFE")), bufNew(walSegmentSize)); + storagePutNP( + storageNewWriteNP( + storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/000000010000000A00000FFF")), bufNew(walSegmentSize)); + + TEST_RESULT_STR( + strPtr(strLstJoin(queueNeed(strNew("000000010000000A00000FFD"), true, queueSize, walSegmentSize, PG_VERSION_11), "|")), + "000000010000000B00000000|000000010000000B00000001|000000010000000B00000002", "queue has wal >= 9.3"); + + TEST_RESULT_STR( + strPtr(strLstJoin(strLstSort(storageListNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN)), sortOrderAsc), "|")), + "000000010000000A00000FFE|000000010000000A00000FFF", "check queue"); + + storagePathRemoveP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN), .recurse = true); + } + + // ***************************************************************************************************************************** + if (testBegin("cmdArchiveGet()")) + { + StringList *argList = strLstNew(); + strLstAddZ(argList, "pgbackrest"); + strLstAddZ(argList, "--archive-timeout=1"); + strLstAdd(argList, strNewFmt("--log-path=%s", testPath())); + strLstAdd(argList, strNewFmt("--log-level-file=debug")); + strLstAddZ(argList, "--stanza=db"); + strLstAddZ(argList, "archive-get"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + TEST_ERROR(cmdArchiveGet(), ParamRequiredError, "WAL segment to get required"); + + // ------------------------------------------------------------------------------------------------------------------------- + StringList *argListTemp = strLstDup(argList); + String *walSegment = strNew("000000010000000100000001"); + strLstAdd(argListTemp, walSegment); + harnessCfgLoad(strLstSize(argListTemp), strLstPtr(argListTemp)); + + TEST_ERROR(cmdArchiveGet(), ParamRequiredError, "Path to copy WAL segment required"); + + // ------------------------------------------------------------------------------------------------------------------------- + String *controlFile = strNew("db/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL); + PgControlFile control = {.systemId = 0xFACEFACE, .controlVersion = 1002, .catalogVersion = 201707211}; + storagePutNP(storageNewWriteNP(storageTest, controlFile), bufNewC(sizeof(PgControlFile), &control)); + + storagePathCreateNP(storageTest, strNewFmt("%s/db/pg_wal", testPath())); + + String *walFile = strNewFmt("%s/db/pg_wal/RECOVERYXLOG", testPath()); + strLstAdd(argListTemp, walFile); + strLstAdd(argListTemp, strNewFmt("--pg1-path=%s/db", testPath())); + harnessCfgLoad(strLstSize(argListTemp), strLstPtr(argListTemp)); + + int processId = fork(); + + // Test this in a fork so we can use different Perl options in later tests + if (processId == 0) + { + TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!"); + exit(0); + } + else + { + int processStatus; + + if (waitpid(processId, &processStatus, 0) != processId) // {uncoverable - fork() does not fail} + THROW_SYS_ERROR(AssertError, "unable to find child process"); // {uncoverable+} + + if (WEXITSTATUS(processStatus) != 0) + THROW(AssertError, "perl exited with error %d", WEXITSTATUS(processStatus)); + } + + // ------------------------------------------------------------------------------------------------------------------------- + argListTemp = strLstDup(argList); + strLstAdd(argListTemp, strNewFmt("--pg1-path=%s/db", testPath())); + strLstAddZ(argListTemp, "00000001.history"); + strLstAdd(argListTemp, walFile); + strLstAddZ(argListTemp, "--archive-async"); + harnessCfgLoad(strLstSize(argListTemp), strLstPtr(argListTemp)); + processId = fork(); + + // Test this in a fork so we can use different Perl options in later tests + if (processId == 0) + { + TEST_ERROR(cmdArchiveGet(), FileMissingError, "!!!EMBEDDEDPERLERROR!!!"); + exit(0); + } + else + { + int processStatus; + + if (waitpid(processId, &processStatus, 0) != processId) // {uncoverable - fork() does not fail} + THROW_SYS_ERROR(AssertError, "unable to find child process"); // {uncoverable+} + + if (WEXITSTATUS(processStatus) != 0) + THROW(AssertError, "perl exited with error %d", WEXITSTATUS(processStatus)); + } + + // Make sure the process times out when there is nothing to get + // ------------------------------------------------------------------------------------------------------------------------- + strLstAdd(argList, strNewFmt("--spool-path=%s/spool", testPath())); + strLstAddZ(argList, "--archive-async"); + strLstAdd(argList, walSegment); + strLstAddZ(argList, "pg_wal/RECOVERYXLOG"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + TEST_RESULT_INT(cmdArchiveGet(), 1, "timeout getting WAL segment"); + + // Write out a bogus .error file to make sure it is ignored on the first loop + // ------------------------------------------------------------------------------------------------------------------------- + // String *errorFile = storagePathNP(storageSpool(), strNew(STORAGE_SPOOL_ARCHIVE_IN "/000000010000000100000001.error")); + // storagePutNP(storageNewWriteNP(storageSpool(), errorFile), bufNewStr(strNew("25\n" BOGUS_STR))); + // + // TEST_ERROR(cmdArchiveGet(), AssertError, BOGUS_STR); + // unlink(strPtr(errorFile)); + // + // // Wait for the lock to be released + // lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true); + // lockRelease(true); + + // Check for missing WAL + // ------------------------------------------------------------------------------------------------------------------------- + strLstAdd(argList, strNewFmt("--pg1-path=%s/db", testPath())); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + storagePutNP( + storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s.ok", strPtr(walSegment))), NULL); + + TEST_RESULT_VOID(cmdArchiveGet(), "successful get of missing WAL"); + testLogResult("P00 INFO: unable to find WAL segment 000000010000000100000001"); + + TEST_RESULT_BOOL( + storageExistsNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s.ok", strPtr(walSegment))), false, + "check OK file was removed"); + + // Wait for the lock to be released + lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true); + lockRelease(true); + + // Write out a WAL segment for success + // ------------------------------------------------------------------------------------------------------------------------- + storagePutNP( + storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))), + bufNewStr(strNew("SHOULD-BE-A-REAL-WAL-FILE"))); + + TEST_RESULT_VOID(cmdArchiveGet(), "successful get"); + testLogResult("P00 INFO: got WAL segment 000000010000000100000001 asynchronously"); + + TEST_RESULT_BOOL(storageExistsNP(storageTest, walFile), true, "check WAL segment was moved"); + storageRemoveP(storageTest, walFile, .errorOnMissing = true); + + // Wait for the lock to be released + lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true); + lockRelease(true); + + // Write more WAL segments (in this case queue should be full) + // ------------------------------------------------------------------------------------------------------------------------- + strLstAddZ(argList, "--archive-get-queue-max=48"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + String *walSegment2 = strNew("000000010000000100000002"); + + storagePutNP( + storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))), + bufNewStr(strNew("SHOULD-BE-A-REAL-WAL-FILE"))); + storagePutNP( + storageNewWriteNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment2))), + bufNewStr(strNew("SHOULD-BE-A-REAL-WAL-FILE"))); + + TEST_RESULT_VOID(cmdArchiveGet(), "successful get"); + testLogResult("P00 INFO: got WAL segment 000000010000000100000001 asynchronously"); + + TEST_RESULT_BOOL(storageExistsNP(storageTest, walFile), true, "check WAL segment was moved"); + + // Wait for the lock to be released + lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true); + lockRelease(true); + + // Make sure the process times out when it can't get a lock + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID( + lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 30, true), "acquire lock"); + TEST_RESULT_VOID(lockClear(true), "clear lock"); + + TEST_RESULT_INT(cmdArchiveGet(), 1, "timeout waiting for lock"); + + // ------------------------------------------------------------------------------------------------------------------------- + strLstAddZ(argList, BOGUS_STR); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + TEST_ERROR(cmdArchiveGet(), ParamRequiredError, "extra parameters found"); + } +} diff --git a/test/src/module/help/helpTest.c b/test/src/module/help/helpTest.c index 61d2d8574..4ee977d9f 100644 --- a/test/src/module/help/helpTest.c +++ b/test/src/module/help/helpTest.c @@ -105,10 +105,9 @@ testRun() "\n" "Command Options:\n" "\n" - " --archive-async archive WAL segments asynchronously\n" + " --archive-async push/get WAL segments asynchronously\n" " [default=n]\n" - " --archive-push-queue-max limit size (in bytes) of the PostgreSQL\n" - " archive queue\n" + " --archive-push-queue-max maximum size of the PostgreSQL archive queue\n" " --archive-timeout archive timeout [default=60]\n" "\n" "General Options:\n" diff --git a/test/src/module/storage/helperTest.c b/test/src/module/storage/helperTest.c index 188098f22..ffd9bbba4 100644 --- a/test/src/module/storage/helperTest.c +++ b/test/src/module/storage/helperTest.c @@ -61,7 +61,14 @@ testRun() "check spool out path"); TEST_RESULT_STR( strPtr(storagePathNP(storage, strNewFmt("%s/%s", STORAGE_SPOOL_ARCHIVE_OUT, "file.ext"))), - strPtr(strNewFmt("%s/archive/db/out/file.ext", testPath())), "check spool out path"); + strPtr(strNewFmt("%s/archive/db/out/file.ext", testPath())), "check spool out file"); + + TEST_RESULT_STR( + strPtr(storagePathNP(storage, strNew(STORAGE_SPOOL_ARCHIVE_IN))), strPtr(strNewFmt("%s/archive/db/in", testPath())), + "check spool in path"); + TEST_RESULT_STR( + strPtr(storagePathNP(storage, strNewFmt("%s/%s", STORAGE_SPOOL_ARCHIVE_IN, "file.ext"))), + strPtr(strNewFmt("%s/archive/db/in/file.ext", testPath())), "check spool in file"); TEST_ERROR(storagePathNP(storage, strNew("<" BOGUS_STR ">")), AssertError, "invalid expression ''");