2012-05-18 08:54:36 +00:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
2014-01-24 20:37:13 +09:00
|
|
|
* backup.c: backup DB cluster, archived WAL
|
2012-05-18 08:54:36 +00:00
|
|
|
*
|
2017-03-01 16:50:07 +03:00
|
|
|
* Portions Copyright (c) 2009-2013, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
2018-11-02 15:08:33 +03:00
|
|
|
* Portions Copyright (c) 2015-2018, Postgres Professional
|
2012-05-18 08:54:36 +00:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2018-10-18 15:43:30 +03:00
|
|
|
#include "pg_probackup.h"
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-10-15 17:26:06 +03:00
|
|
|
#if PG_VERSION_NUM < 110000
|
|
|
|
#include "catalog/catalog.h"
|
|
|
|
#endif
|
2017-09-29 20:25:11 +03:00
|
|
|
#include "catalog/pg_tablespace.h"
|
2018-06-07 19:13:11 +03:00
|
|
|
#include "pgtar.h"
|
2016-05-26 15:56:32 +03:00
|
|
|
#include "receivelog.h"
|
2017-08-07 16:23:37 +03:00
|
|
|
#include "streamutil.h"
|
2018-10-15 15:43:20 +03:00
|
|
|
|
|
|
|
#include <sys/stat.h>
|
2018-11-15 18:37:13 +03:00
|
|
|
#include <time.h>
|
2018-10-15 15:43:20 +03:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2018-06-07 19:13:11 +03:00
|
|
|
#include "utils/thread.h"
|
2018-11-02 20:24:09 +03:00
|
|
|
#include "utils/file.h"
|
2012-05-18 08:54:36 +00:00
|
|
|
|
|
|
|
|
2018-10-15 15:43:20 +03:00
|
|
|
/*
|
|
|
|
* Macro needed to parse ptrack.
|
|
|
|
* NOTE Keep those values syncronised with definitions in ptrack.h
|
|
|
|
*/
|
|
|
|
#define PTRACK_BITS_PER_HEAPBLOCK 1
|
|
|
|
#define HEAPBLOCKS_PER_BYTE (BITS_PER_BYTE / PTRACK_BITS_PER_HEAPBLOCK)
|
|
|
|
|
2016-05-26 15:56:32 +03:00
|
|
|
static int standby_message_timeout = 10 * 1000; /* 10 sec = default */
|
|
|
|
static XLogRecPtr stop_backup_lsn = InvalidXLogRecPtr;
|
2018-04-04 17:06:50 +03:00
|
|
|
static XLogRecPtr stop_stream_lsn = InvalidXLogRecPtr;
|
2017-05-29 18:53:48 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* How long we should wait for streaming end in seconds.
|
|
|
|
* Retreived as checkpoint_timeout + checkpoint_timeout * 0.1
|
|
|
|
*/
|
|
|
|
static uint32 stream_stop_timeout = 0;
|
|
|
|
/* Time in which we started to wait for streaming end */
|
|
|
|
static time_t stream_stop_begin = 0;
|
|
|
|
|
2016-11-16 20:34:21 +03:00
|
|
|
const char *progname = "pg_probackup";
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2016-01-15 23:47:38 +09:00
|
|
|
/* list of files contained in backup */
|
2017-02-25 15:12:07 +03:00
|
|
|
static parray *backup_files_list = NULL;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2018-05-29 18:44:08 +03:00
|
|
|
/* We need critical section for datapagemap_add() in case of using threads */
|
|
|
|
static pthread_mutex_t backup_pagemap_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
2017-05-25 14:05:48 +03:00
|
|
|
/*
|
|
|
|
* We need to wait end of WAL streaming before execute pg_stop_backup().
|
|
|
|
*/
|
2018-04-05 18:58:40 +03:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
const char *basedir;
|
2018-04-10 19:02:00 +03:00
|
|
|
PGconn *conn;
|
|
|
|
|
2018-04-05 18:58:40 +03:00
|
|
|
/*
|
|
|
|
* Return value from the thread.
|
|
|
|
* 0 means there is no error, 1 - there is an error.
|
|
|
|
*/
|
|
|
|
int ret;
|
|
|
|
} StreamThreadArg;
|
|
|
|
|
2017-05-25 14:05:48 +03:00
|
|
|
static pthread_t stream_thread;
|
2019-01-27 01:11:52 +03:00
|
|
|
static StreamThreadArg stream_thread_arg = {"", NULL, 1};
|
2016-01-15 23:47:38 +09:00
|
|
|
|
2017-04-05 19:48:55 +03:00
|
|
|
static int is_ptrack_enable = false;
|
2018-01-18 04:55:10 +03:00
|
|
|
bool is_ptrack_support = false;
|
2018-01-15 17:58:44 +03:00
|
|
|
bool is_checksum_enabled = false;
|
2018-02-06 22:15:41 +03:00
|
|
|
bool exclusive_backup = false;
|
2017-04-05 19:48:55 +03:00
|
|
|
|
2017-06-07 16:50:33 +03:00
|
|
|
/* Backup connections */
|
2017-03-21 11:54:49 +03:00
|
|
|
static PGconn *backup_conn = NULL;
|
2017-06-07 16:50:33 +03:00
|
|
|
static PGconn *master_conn = NULL;
|
2017-09-28 17:40:24 +03:00
|
|
|
static PGconn *backup_conn_replication = NULL;
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-05-22 14:22:20 +03:00
|
|
|
/* PostgreSQL server version from "backup_conn" */
|
|
|
|
static int server_version = 0;
|
2017-11-09 19:14:39 +03:00
|
|
|
static char server_version_str[100] = "";
|
2017-05-22 14:22:20 +03:00
|
|
|
|
|
|
|
/* Is pg_start_backup() was executed */
|
|
|
|
static bool backup_in_progress = false;
|
2017-11-01 12:29:28 +03:00
|
|
|
/* Is pg_stop_backup() was sent */
|
|
|
|
static bool pg_stop_backup_is_sent = false;
|
2017-05-22 14:22:20 +03:00
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
/*
|
|
|
|
* Backup routines
|
|
|
|
*/
|
|
|
|
static void backup_cleanup(bool fatal, void *userdata);
|
2017-03-21 11:54:49 +03:00
|
|
|
static void backup_disconnect(bool fatal, void *userdata);
|
|
|
|
|
2018-06-07 19:13:11 +03:00
|
|
|
static void *backup_files(void *arg);
|
|
|
|
static void *remote_backup_files(void *arg);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
2017-09-28 14:39:21 +03:00
|
|
|
static void do_backup_instance(void);
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
static void pg_start_backup(const char *label, bool smooth, pgBackup *backup);
|
2017-07-13 16:16:05 +03:00
|
|
|
static void pg_switch_wal(PGconn *conn);
|
2012-05-18 08:54:36 +00:00
|
|
|
static void pg_stop_backup(pgBackup *backup);
|
2017-05-29 18:53:48 +03:00
|
|
|
static int checkpoint_timeout(void);
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
//static void backup_list_file(parray *files, const char *root, )
|
2017-09-29 20:25:11 +03:00
|
|
|
static void parse_backup_filelist_filenames(parray *files, const char *root);
|
2018-11-22 14:44:57 +03:00
|
|
|
static XLogRecPtr wait_wal_lsn(XLogRecPtr lsn, bool is_start_lsn,
|
2018-11-23 12:03:29 +03:00
|
|
|
bool wait_prev_segment);
|
2017-07-11 17:41:52 +03:00
|
|
|
static void wait_replica_wal_lsn(XLogRecPtr lsn, bool is_start_backup);
|
2017-03-21 11:54:49 +03:00
|
|
|
static void make_pagemap_from_ptrack(parray *files);
|
2018-06-09 15:45:03 +03:00
|
|
|
static void *StreamLog(void *arg);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
static void get_remote_pgdata_filelist(parray *files);
|
|
|
|
static void ReceiveFileList(parray* files, PGconn *conn, PGresult *res, int rownum);
|
|
|
|
static void remote_copy_file(PGconn *conn, pgFile* file);
|
|
|
|
|
2017-03-21 11:54:49 +03:00
|
|
|
/* Ptrack functions */
|
2016-02-27 21:07:55 +03:00
|
|
|
static void pg_ptrack_clear(void);
|
2016-10-18 16:25:13 +03:00
|
|
|
static bool pg_ptrack_support(void);
|
2016-11-22 16:07:54 +03:00
|
|
|
static bool pg_ptrack_enable(void);
|
2018-01-15 17:58:44 +03:00
|
|
|
static bool pg_checksum_enable(void);
|
2016-10-25 14:38:51 +03:00
|
|
|
static bool pg_is_in_recovery(void);
|
2017-09-29 20:25:11 +03:00
|
|
|
static bool pg_ptrack_get_and_clear_db(Oid dbOid, Oid tblspcOid);
|
2016-05-11 19:35:14 +03:00
|
|
|
static char *pg_ptrack_get_and_clear(Oid tablespace_oid,
|
|
|
|
Oid db_oid,
|
|
|
|
Oid rel_oid,
|
|
|
|
size_t *result_size);
|
2017-10-02 18:31:46 +03:00
|
|
|
static XLogRecPtr get_last_ptrack_lsn(void);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
|
|
|
/* Check functions */
|
|
|
|
static void check_server_version(void);
|
2017-04-18 11:41:02 +03:00
|
|
|
static void check_system_identifiers(void);
|
2017-03-21 11:54:49 +03:00
|
|
|
static void confirm_block_size(const char *name, int blcksz);
|
2017-10-19 13:33:31 +03:00
|
|
|
static void set_cfs_datafiles(parray *files, const char *root, char *relative, size_t i);
|
2016-05-26 15:56:32 +03:00
|
|
|
|
|
|
|
#define disconnect_and_exit(code) \
|
|
|
|
{ \
|
|
|
|
if (conn != NULL) PQfinish(conn); \
|
|
|
|
exit(code); \
|
|
|
|
}
|
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
/* Fill "files" with data about all the files to backup */
|
|
|
|
static void
|
|
|
|
get_remote_pgdata_filelist(parray *files)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
int resultStatus;
|
|
|
|
int i;
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
backup_conn_replication = pgut_connect_replication(instance_config.pghost,
|
|
|
|
instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
if (PQsendQuery(backup_conn_replication, "FILE_BACKUP FILELIST") == 0)
|
|
|
|
elog(ERROR,"%s: could not send replication command \"%s\": %s",
|
|
|
|
PROGRAM_NAME, "FILE_BACKUP", PQerrorMessage(backup_conn_replication));
|
|
|
|
|
|
|
|
res = PQgetResult(backup_conn_replication);
|
|
|
|
|
|
|
|
if (PQresultStatus(res) != PGRES_TUPLES_OK)
|
|
|
|
{
|
|
|
|
resultStatus = PQresultStatus(res);
|
|
|
|
PQclear(res);
|
|
|
|
elog(ERROR, "cannot start getting FILE_BACKUP filelist: %s, result_status %d",
|
|
|
|
PQerrorMessage(backup_conn_replication), resultStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PQntuples(res) < 1)
|
|
|
|
elog(ERROR, "%s: no data returned from server", PROGRAM_NAME);
|
|
|
|
|
|
|
|
for (i = 0; i < PQntuples(res); i++)
|
|
|
|
{
|
|
|
|
ReceiveFileList(files, backup_conn_replication, res, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = PQgetResult(backup_conn_replication);
|
|
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
|
|
{
|
|
|
|
elog(ERROR, "%s: final receive failed: %s",
|
|
|
|
PROGRAM_NAME, PQerrorMessage(backup_conn_replication));
|
|
|
|
}
|
|
|
|
|
|
|
|
PQfinish(backup_conn_replication);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* workhorse for get_remote_pgdata_filelist().
|
|
|
|
* Parse received message into pgFile structure.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ReceiveFileList(parray* files, PGconn *conn, PGresult *res, int rownum)
|
|
|
|
{
|
|
|
|
char filename[MAXPGPATH];
|
|
|
|
pgoff_t current_len_left = 0;
|
|
|
|
bool basetablespace;
|
|
|
|
char *copybuf = NULL;
|
|
|
|
pgFile *pgfile;
|
|
|
|
|
|
|
|
/* What for do we need this basetablespace field?? */
|
|
|
|
basetablespace = PQgetisnull(res, rownum, 0);
|
|
|
|
if (basetablespace)
|
|
|
|
elog(LOG,"basetablespace");
|
|
|
|
else
|
|
|
|
elog(LOG, "basetablespace %s", PQgetvalue(res, rownum, 1));
|
|
|
|
|
|
|
|
res = PQgetResult(conn);
|
|
|
|
|
|
|
|
if (PQresultStatus(res) != PGRES_COPY_OUT)
|
|
|
|
elog(ERROR, "Could not get COPY data stream: %s", PQerrorMessage(conn));
|
|
|
|
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
int filemode;
|
|
|
|
|
|
|
|
if (copybuf != NULL)
|
|
|
|
{
|
|
|
|
PQfreemem(copybuf);
|
|
|
|
copybuf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = PQgetCopyData(conn, ©buf, 0);
|
|
|
|
|
|
|
|
if (r == -2)
|
|
|
|
elog(ERROR, "Could not read COPY data: %s", PQerrorMessage(conn));
|
|
|
|
|
|
|
|
/* end of copy */
|
|
|
|
if (r == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* This must be the header for a new file */
|
|
|
|
if (r != 512)
|
|
|
|
elog(ERROR, "Invalid tar block header size: %d\n", r);
|
|
|
|
|
|
|
|
current_len_left = read_tar_number(©buf[124], 12);
|
|
|
|
|
|
|
|
/* Set permissions on the file */
|
|
|
|
filemode = read_tar_number(©buf[100], 8);
|
|
|
|
|
|
|
|
/* First part of header is zero terminated filename */
|
|
|
|
snprintf(filename, sizeof(filename), "%s", copybuf);
|
|
|
|
|
|
|
|
pgfile = pgFileInit(filename);
|
|
|
|
pgfile->size = current_len_left;
|
|
|
|
pgfile->mode |= filemode;
|
|
|
|
|
|
|
|
if (filename[strlen(filename) - 1] == '/')
|
|
|
|
{
|
|
|
|
/* Symbolic link or directory has size zero */
|
|
|
|
Assert (pgfile->size == 0);
|
|
|
|
/* Ends in a slash means directory or symlink to directory */
|
|
|
|
if (copybuf[156] == '5')
|
|
|
|
{
|
|
|
|
/* Directory */
|
2017-10-11 16:43:09 +03:00
|
|
|
pgfile->mode |= S_IFDIR;
|
2017-09-28 17:40:24 +03:00
|
|
|
}
|
|
|
|
else if (copybuf[156] == '2')
|
|
|
|
{
|
|
|
|
/* Symlink */
|
2018-06-07 19:13:11 +03:00
|
|
|
#ifndef WIN32
|
2017-10-11 16:43:09 +03:00
|
|
|
pgfile->mode |= S_IFLNK;
|
2018-06-07 19:13:11 +03:00
|
|
|
#else
|
|
|
|
pgfile->mode |= S_IFDIR;
|
|
|
|
#endif
|
2017-09-28 17:40:24 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
elog(ERROR, "Unrecognized link indicator \"%c\"\n",
|
|
|
|
copybuf[156]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* regular file */
|
2017-10-11 16:43:09 +03:00
|
|
|
pgfile->mode |= S_IFREG;
|
2017-09-28 17:40:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
parray_append(files, pgfile);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (copybuf != NULL)
|
|
|
|
PQfreemem(copybuf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read one file via replication protocol
|
|
|
|
* and write it to the destination subdir in 'backup_path' */
|
2018-04-24 17:45:30 +03:00
|
|
|
static void
|
|
|
|
remote_copy_file(PGconn *conn, pgFile* file)
|
2017-09-28 17:40:24 +03:00
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
char *copybuf = NULL;
|
|
|
|
char buf[32768];
|
|
|
|
FILE *out;
|
|
|
|
char database_path[MAXPGPATH];
|
|
|
|
char to_path[MAXPGPATH];
|
|
|
|
bool skip_padding = false;
|
|
|
|
|
|
|
|
pgBackupGetPath(¤t, database_path, lengthof(database_path),
|
|
|
|
DATABASE_DIR);
|
|
|
|
join_path_components(to_path, database_path, file->path);
|
|
|
|
|
2018-06-07 19:13:11 +03:00
|
|
|
out = fopen(to_path, PG_BINARY_W);
|
2017-09-28 17:40:24 +03:00
|
|
|
if (out == NULL)
|
|
|
|
{
|
|
|
|
int errno_tmp = errno;
|
|
|
|
elog(ERROR, "cannot open destination file \"%s\": %s",
|
|
|
|
to_path, strerror(errno_tmp));
|
|
|
|
}
|
|
|
|
|
2018-11-26 14:17:46 +03:00
|
|
|
INIT_FILE_CRC32(true, file->crc);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
/* read from stream and write to backup file */
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
int row_length;
|
|
|
|
int errno_tmp;
|
|
|
|
int write_buffer_size = 0;
|
|
|
|
if (copybuf != NULL)
|
|
|
|
{
|
|
|
|
PQfreemem(copybuf);
|
|
|
|
copybuf = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
row_length = PQgetCopyData(conn, ©buf, 0);
|
|
|
|
|
|
|
|
if (row_length == -2)
|
|
|
|
elog(ERROR, "Could not read COPY data: %s", PQerrorMessage(conn));
|
|
|
|
|
|
|
|
if (row_length == -1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!skip_padding)
|
|
|
|
{
|
|
|
|
write_buffer_size = Min(row_length, sizeof(buf));
|
|
|
|
memcpy(buf, copybuf, write_buffer_size);
|
2018-11-26 14:17:46 +03:00
|
|
|
COMP_FILE_CRC32(true, file->crc, buf, write_buffer_size);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
/* TODO calc checksum*/
|
|
|
|
if (fwrite(buf, 1, write_buffer_size, out) != write_buffer_size)
|
|
|
|
{
|
|
|
|
errno_tmp = errno;
|
|
|
|
/* oops */
|
2018-11-26 14:17:46 +03:00
|
|
|
FIN_FILE_CRC32(true, file->crc);
|
2017-09-28 17:40:24 +03:00
|
|
|
fclose(out);
|
|
|
|
PQfinish(conn);
|
|
|
|
elog(ERROR, "cannot write to \"%s\": %s", to_path,
|
|
|
|
strerror(errno_tmp));
|
|
|
|
}
|
|
|
|
|
|
|
|
file->read_size += write_buffer_size;
|
|
|
|
}
|
|
|
|
if (file->read_size >= file->size)
|
|
|
|
{
|
|
|
|
skip_padding = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res = PQgetResult(conn);
|
|
|
|
|
|
|
|
/* File is not found. That's normal. */
|
|
|
|
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
|
|
|
{
|
|
|
|
elog(ERROR, "final receive failed: status %d ; %s",PQresultStatus(res), PQerrorMessage(conn));
|
|
|
|
}
|
|
|
|
|
2018-06-09 15:14:44 +03:00
|
|
|
file->write_size = (int64) file->read_size;
|
2018-11-26 14:17:46 +03:00
|
|
|
FIN_FILE_CRC32(true, file->crc);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
fclose(out);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Take a remote backup of the PGDATA at a file level.
|
|
|
|
* Copy all directories and files listed in backup_files_list.
|
|
|
|
*/
|
2018-06-07 19:13:11 +03:00
|
|
|
static void *
|
2017-09-28 17:40:24 +03:00
|
|
|
remote_backup_files(void *arg)
|
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
int i;
|
|
|
|
backup_files_arg *arguments = (backup_files_arg *) arg;
|
|
|
|
int n_backup_files_list = parray_num(arguments->files_list);
|
|
|
|
PGconn *file_backup_conn = NULL;
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
for (i = 0; i < n_backup_files_list; i++)
|
|
|
|
{
|
|
|
|
char *query_str;
|
|
|
|
PGresult *res;
|
|
|
|
char *copybuf = NULL;
|
|
|
|
pgFile *file;
|
|
|
|
int row_length;
|
|
|
|
|
2018-05-30 13:43:52 +03:00
|
|
|
file = (pgFile *) parray_get(arguments->files_list, i);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
/* We have already copied all directories */
|
|
|
|
if (S_ISDIR(file->mode))
|
|
|
|
continue;
|
|
|
|
|
2018-06-07 19:13:11 +03:00
|
|
|
if (!pg_atomic_test_set_flag(&file->lock))
|
2017-09-28 17:40:24 +03:00
|
|
|
continue;
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
file_backup_conn = pgut_connect_replication(instance_config.pghost,
|
|
|
|
instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
/* check for interrupt */
|
|
|
|
if (interrupted)
|
|
|
|
elog(ERROR, "interrupted during backup");
|
|
|
|
|
|
|
|
query_str = psprintf("FILE_BACKUP FILEPATH '%s'",file->path);
|
|
|
|
|
|
|
|
if (PQsendQuery(file_backup_conn, query_str) == 0)
|
|
|
|
elog(ERROR,"%s: could not send replication command \"%s\": %s",
|
|
|
|
PROGRAM_NAME, query_str, PQerrorMessage(file_backup_conn));
|
|
|
|
|
|
|
|
res = PQgetResult(file_backup_conn);
|
|
|
|
|
|
|
|
/* File is not found. That's normal. */
|
|
|
|
if (PQresultStatus(res) == PGRES_COMMAND_OK)
|
|
|
|
{
|
|
|
|
PQclear(res);
|
|
|
|
PQfinish(file_backup_conn);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PQresultStatus(res) != PGRES_COPY_OUT)
|
|
|
|
{
|
|
|
|
PQclear(res);
|
|
|
|
PQfinish(file_backup_conn);
|
|
|
|
elog(ERROR, "Could not get COPY data stream: %s", PQerrorMessage(file_backup_conn));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read the header of the file */
|
|
|
|
row_length = PQgetCopyData(file_backup_conn, ©buf, 0);
|
|
|
|
|
|
|
|
if (row_length == -2)
|
|
|
|
elog(ERROR, "Could not read COPY data: %s", PQerrorMessage(file_backup_conn));
|
|
|
|
|
|
|
|
/* end of copy TODO handle it */
|
|
|
|
if (row_length == -1)
|
|
|
|
elog(ERROR, "Unexpected end of COPY data");
|
|
|
|
|
|
|
|
if(row_length != 512)
|
|
|
|
elog(ERROR, "Invalid tar block header size: %d\n", row_length);
|
|
|
|
file->size = read_tar_number(©buf[124], 12);
|
|
|
|
|
|
|
|
/* receive the data from stream and write to backup file */
|
|
|
|
remote_copy_file(file_backup_conn, file);
|
|
|
|
|
2018-06-09 15:14:44 +03:00
|
|
|
elog(VERBOSE, "File \"%s\". Copied " INT64_FORMAT " bytes",
|
2018-06-09 12:32:40 +03:00
|
|
|
file->path, file->write_size);
|
2017-09-28 17:40:24 +03:00
|
|
|
PQfinish(file_backup_conn);
|
|
|
|
}
|
2018-04-05 18:58:40 +03:00
|
|
|
|
|
|
|
/* Data files transferring is successful */
|
|
|
|
arguments->ret = 0;
|
2018-06-07 19:13:11 +03:00
|
|
|
|
|
|
|
return NULL;
|
2017-09-28 17:40:24 +03:00
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
|
|
|
|
/*
|
2017-09-28 14:39:21 +03:00
|
|
|
* Take a backup of a single postgresql instance.
|
|
|
|
* Move files from 'pgdata' to a subdirectory in 'backup_path'.
|
2012-05-18 08:54:36 +00:00
|
|
|
*/
|
2017-04-18 11:41:02 +03:00
|
|
|
static void
|
2017-09-28 14:39:21 +03:00
|
|
|
do_backup_instance(void)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2017-09-28 17:40:24 +03:00
|
|
|
int i;
|
2017-02-25 15:12:07 +03:00
|
|
|
char database_path[MAXPGPATH];
|
2016-05-26 15:56:32 +03:00
|
|
|
char dst_backup_path[MAXPGPATH];
|
2012-05-18 08:54:36 +00:00
|
|
|
char label[1024];
|
2017-04-20 15:08:07 +03:00
|
|
|
XLogRecPtr prev_backup_start_lsn = InvalidXLogRecPtr;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2018-06-07 19:13:11 +03:00
|
|
|
/* arrays with meta info for multi threaded backup */
|
2018-05-30 13:43:52 +03:00
|
|
|
pthread_t *threads;
|
|
|
|
backup_files_arg *threads_args;
|
2018-04-05 18:58:40 +03:00
|
|
|
bool backup_isok = true;
|
2013-09-09 09:00:13 +00:00
|
|
|
|
2016-01-15 23:47:38 +09:00
|
|
|
pgBackup *prev_backup = NULL;
|
2017-04-18 11:41:02 +03:00
|
|
|
parray *prev_backup_filelist = NULL;
|
2018-11-12 15:44:22 +03:00
|
|
|
parray *backup_list = NULL;
|
2018-10-31 09:47:53 +03:00
|
|
|
pgFile *pg_control = NULL;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
elog(LOG, "Database backup start");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2013-12-15 23:41:42 +09:00
|
|
|
/* Initialize size summary */
|
2014-01-10 04:11:27 +09:00
|
|
|
current.data_bytes = 0;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
/* Obtain current timeline */
|
2019-01-22 20:41:17 +03:00
|
|
|
if (IsReplicationProtocol())
|
2017-09-28 17:40:24 +03:00
|
|
|
{
|
|
|
|
char *sysidentifier;
|
|
|
|
TimeLineID starttli;
|
|
|
|
XLogRecPtr startpos;
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
backup_conn_replication = pgut_connect_replication(instance_config.pghost,
|
|
|
|
instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
|
|
|
/* Check replication prorocol connection */
|
|
|
|
if (!RunIdentifySystem(backup_conn_replication, &sysidentifier, &starttli, &startpos, NULL))
|
|
|
|
elog(ERROR, "Failed to send command for remote backup");
|
|
|
|
|
|
|
|
// TODO implement the check
|
2018-11-01 19:10:20 +03:00
|
|
|
// if (&sysidentifier != instance_config.system_identifier)
|
2017-09-28 17:40:24 +03:00
|
|
|
// elog(ERROR, "Backup data directory was initialized for system id %ld, but target backup directory system id is %ld",
|
2018-11-01 19:10:20 +03:00
|
|
|
// instance_config.system_identifier, sysidentifier);
|
2013-12-13 03:55:39 +09:00
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
current.tli = starttli;
|
|
|
|
|
|
|
|
PQfinish(backup_conn_replication);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
current.tli = get_current_timeline(false);
|
2018-05-25 18:56:35 +03:00
|
|
|
|
2013-12-25 05:27:25 +09:00
|
|
|
/*
|
2017-04-18 11:41:02 +03:00
|
|
|
* In incremental backup mode ensure that already-validated
|
2017-09-28 14:39:21 +03:00
|
|
|
* backup on current timeline exists and get its filelist.
|
2013-12-25 05:27:25 +09:00
|
|
|
*/
|
2016-02-27 21:07:55 +03:00
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE ||
|
2018-02-26 17:53:29 +03:00
|
|
|
current.backup_mode == BACKUP_MODE_DIFF_PTRACK ||
|
|
|
|
current.backup_mode == BACKUP_MODE_DIFF_DELTA)
|
2013-12-25 05:27:25 +09:00
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
char prev_backup_filelist_path[MAXPGPATH];
|
2018-05-25 18:56:35 +03:00
|
|
|
|
2017-09-28 14:39:21 +03:00
|
|
|
/* get list of backups already taken */
|
|
|
|
backup_list = catalog_get_backup_list(INVALID_BACKUP_ID);
|
|
|
|
|
2013-12-25 05:27:25 +09:00
|
|
|
prev_backup = catalog_get_last_data_backup(backup_list, current.tli);
|
|
|
|
if (prev_backup == NULL)
|
2017-04-20 15:01:29 +03:00
|
|
|
elog(ERROR, "Valid backup on current timeline is not found. "
|
2017-04-18 11:41:02 +03:00
|
|
|
"Create new FULL backup before an incremental one.");
|
2017-09-28 14:39:21 +03:00
|
|
|
|
2018-05-30 13:43:52 +03:00
|
|
|
pgBackupGetPath(prev_backup, prev_backup_filelist_path,
|
|
|
|
lengthof(prev_backup_filelist_path), DATABASE_FILE_LIST);
|
2018-03-06 16:10:58 +03:00
|
|
|
/* Files of previous backup needed by DELTA backup */
|
2019-01-18 00:21:42 +03:00
|
|
|
prev_backup_filelist = dir_read_file_list(NULL, prev_backup_filelist_path, FIO_BACKUP_HOST);
|
2017-09-28 14:39:21 +03:00
|
|
|
|
|
|
|
/* If lsn is not NULL, only pages with higher lsn will be copied. */
|
|
|
|
prev_backup_start_lsn = prev_backup->start_lsn;
|
|
|
|
current.parent_backup = prev_backup->start_time;
|
|
|
|
|
2018-10-15 19:14:29 +03:00
|
|
|
write_backup(¤t);
|
2013-12-25 05:27:25 +09:00
|
|
|
}
|
|
|
|
|
2017-12-08 16:22:06 +03:00
|
|
|
/*
|
2018-10-29 19:19:08 +03:00
|
|
|
* It`s illegal to take PTRACK backup if LSN from ptrack_control() is not
|
|
|
|
* equal to stop_lsn of previous backup.
|
2017-12-08 16:22:06 +03:00
|
|
|
*/
|
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
|
|
|
|
{
|
|
|
|
XLogRecPtr ptrack_lsn = get_last_ptrack_lsn();
|
|
|
|
|
|
|
|
if (ptrack_lsn > prev_backup->stop_lsn || ptrack_lsn == InvalidXLogRecPtr)
|
|
|
|
{
|
2018-08-08 17:30:48 +03:00
|
|
|
elog(ERROR, "LSN from ptrack_control %X/%X differs from STOP LSN of previous backup %X/%X.\n"
|
2017-12-08 16:22:06 +03:00
|
|
|
"Create new full backup before an incremental one.",
|
2018-08-08 17:30:48 +03:00
|
|
|
(uint32) (ptrack_lsn >> 32), (uint32) (ptrack_lsn),
|
|
|
|
(uint32) (prev_backup->stop_lsn >> 32),
|
|
|
|
(uint32) (prev_backup->stop_lsn));
|
2017-12-08 16:22:06 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Clear ptrack files for FULL and PAGE backup */
|
2017-04-05 19:48:55 +03:00
|
|
|
if (current.backup_mode != BACKUP_MODE_DIFF_PTRACK && is_ptrack_enable)
|
2016-05-11 21:49:26 +03:00
|
|
|
pg_ptrack_clear();
|
|
|
|
|
2016-10-13 17:25:53 +03:00
|
|
|
/* notify start of backup to PostgreSQL server */
|
|
|
|
time2iso(label, lengthof(label), current.start_time);
|
2018-05-16 10:12:16 +03:00
|
|
|
strncat(label, " with pg_probackup", lengthof(label) -
|
|
|
|
strlen(" with pg_probackup"));
|
2016-10-13 17:25:53 +03:00
|
|
|
pg_start_backup(label, smooth_checkpoint, ¤t);
|
|
|
|
|
2019-02-04 16:48:14 +03:00
|
|
|
/* Update running backup meta with START LSN */
|
|
|
|
write_backup(¤t);
|
|
|
|
|
2017-05-25 14:05:48 +03:00
|
|
|
pgBackupGetPath(¤t, database_path, lengthof(database_path),
|
|
|
|
DATABASE_DIR);
|
|
|
|
|
|
|
|
/* start stream replication */
|
|
|
|
if (stream_wal)
|
|
|
|
{
|
|
|
|
join_path_components(dst_backup_path, database_path, PG_XLOG_DIR);
|
2018-11-02 20:24:09 +03:00
|
|
|
fio_mkdir(dst_backup_path, DIR_PERMISSION, FIO_BACKUP_HOST);
|
2017-05-25 14:05:48 +03:00
|
|
|
|
2018-04-05 18:58:40 +03:00
|
|
|
stream_thread_arg.basedir = dst_backup_path;
|
2018-04-10 19:02:00 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect in replication mode to the server.
|
|
|
|
*/
|
2018-11-01 19:10:20 +03:00
|
|
|
stream_thread_arg.conn = pgut_connect_replication(instance_config.pghost,
|
|
|
|
instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2018-04-10 19:02:00 +03:00
|
|
|
|
|
|
|
if (!CheckServerVersionForStreaming(stream_thread_arg.conn))
|
|
|
|
{
|
|
|
|
PQfinish(stream_thread_arg.conn);
|
|
|
|
/*
|
|
|
|
* Error message already written in CheckServerVersionForStreaming().
|
|
|
|
* There's no hope of recovering from a version mismatch, so don't
|
|
|
|
* retry.
|
|
|
|
*/
|
|
|
|
elog(ERROR, "Cannot continue backup because stream connect has failed.");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Identify server, obtaining start LSN position and current timeline ID
|
|
|
|
* at the same time, necessary if not valid data can be found in the
|
|
|
|
* existing output directory.
|
|
|
|
*/
|
|
|
|
if (!RunIdentifySystem(stream_thread_arg.conn, NULL, NULL, NULL, NULL))
|
|
|
|
{
|
|
|
|
PQfinish(stream_thread_arg.conn);
|
2017-05-25 14:05:48 +03:00
|
|
|
elog(ERROR, "Cannot continue backup because stream connect has failed.");
|
2018-04-10 19:02:00 +03:00
|
|
|
}
|
|
|
|
|
2018-11-05 15:23:46 +03:00
|
|
|
/* By default there are some error */
|
2018-04-05 18:58:40 +03:00
|
|
|
stream_thread_arg.ret = 1;
|
2017-05-25 14:05:48 +03:00
|
|
|
|
2018-06-09 15:45:03 +03:00
|
|
|
pthread_create(&stream_thread, NULL, StreamLog, &stream_thread_arg);
|
2017-05-25 14:05:48 +03:00
|
|
|
}
|
|
|
|
|
2016-01-15 23:47:38 +09:00
|
|
|
/* initialize backup list */
|
|
|
|
backup_files_list = parray_new();
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2016-01-15 15:26:05 +09:00
|
|
|
/* list files with the logical path. omit $PGDATA */
|
2019-01-22 20:41:17 +03:00
|
|
|
|
|
|
|
if (IsReplicationProtocol())
|
2017-09-28 17:40:24 +03:00
|
|
|
get_remote_pgdata_filelist(backup_files_list);
|
|
|
|
else
|
2018-11-01 19:10:20 +03:00
|
|
|
dir_list_file(backup_files_list, instance_config.pgdata,
|
2019-01-26 17:14:25 +03:00
|
|
|
true, true, false, FIO_DB_HOST);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
2019-02-25 17:12:02 +03:00
|
|
|
/* Sanity check for backup_files_list, thank you, Windows:
|
|
|
|
* https://github.com/postgrespro/pg_probackup/issues/48
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (parray_num(backup_files_list) == 0)
|
2019-02-25 17:13:10 +03:00
|
|
|
elog(ERROR, "PGDATA is empty. Either it was concurrently deleted or "
|
2019-02-25 17:12:02 +03:00
|
|
|
"pg_probackup do not possess sufficient permissions to list PGDATA content");
|
|
|
|
|
2018-05-28 15:19:03 +03:00
|
|
|
/*
|
|
|
|
* Sort pathname ascending. It is necessary to create intermediate
|
|
|
|
* directories sequentially.
|
|
|
|
*
|
|
|
|
* For example:
|
|
|
|
* 1 - create 'base'
|
|
|
|
* 2 - create 'base/1'
|
|
|
|
*
|
|
|
|
* Sorted array is used at least in parse_backup_filelist_filenames(),
|
|
|
|
* extractPageMap(), make_pagemap_from_ptrack().
|
|
|
|
*/
|
|
|
|
parray_qsort(backup_files_list, pgFileComparePath);
|
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
/* Extract information about files in backup_list parsing their names:*/
|
2018-11-01 19:10:20 +03:00
|
|
|
parse_backup_filelist_filenames(backup_files_list, instance_config.pgdata);
|
2016-01-15 23:47:38 +09:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
if (current.backup_mode != BACKUP_MODE_FULL)
|
2016-01-15 23:47:38 +09:00
|
|
|
{
|
|
|
|
elog(LOG, "current_tli:%X", current.tli);
|
|
|
|
elog(LOG, "prev_backup->start_lsn: %X/%X",
|
2017-04-18 11:41:02 +03:00
|
|
|
(uint32) (prev_backup->start_lsn >> 32), (uint32) (prev_backup->start_lsn));
|
2016-01-15 23:47:38 +09:00
|
|
|
elog(LOG, "current.start_lsn: %X/%X",
|
2017-04-18 11:41:02 +03:00
|
|
|
(uint32) (current.start_lsn >> 32), (uint32) (current.start_lsn));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build page mapping in incremental mode.
|
|
|
|
*/
|
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Build the page map. Obtain information about changed pages
|
|
|
|
* reading WAL segments present in archives up to the point
|
|
|
|
* where this backup has started.
|
|
|
|
*/
|
2018-11-01 19:10:20 +03:00
|
|
|
extractPageMap(arclog_path, current.tli, instance_config.xlog_seg_size,
|
2018-10-09 12:51:41 +03:00
|
|
|
prev_backup->start_lsn, current.start_lsn,
|
|
|
|
backup_files_list);
|
2016-01-15 23:47:38 +09:00
|
|
|
}
|
2018-05-28 15:19:03 +03:00
|
|
|
else if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
|
2016-02-27 21:07:55 +03:00
|
|
|
{
|
2018-05-28 15:19:03 +03:00
|
|
|
/*
|
|
|
|
* Build the page map from ptrack information.
|
|
|
|
*/
|
2016-02-27 21:07:55 +03:00
|
|
|
make_pagemap_from_ptrack(backup_files_list);
|
|
|
|
}
|
|
|
|
|
2017-04-21 14:54:33 +03:00
|
|
|
/*
|
2018-05-28 15:19:03 +03:00
|
|
|
* Make directories before backup and setup threads at the same time
|
2017-04-18 11:41:02 +03:00
|
|
|
*/
|
2016-02-29 20:23:48 +03:00
|
|
|
for (i = 0; i < parray_num(backup_files_list); i++)
|
|
|
|
{
|
2017-02-25 15:12:07 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(backup_files_list, i);
|
2016-02-29 20:23:48 +03:00
|
|
|
|
|
|
|
/* if the entry was a directory, create it in the backup */
|
2017-02-25 15:12:07 +03:00
|
|
|
if (S_ISDIR(file->mode))
|
2016-02-29 20:23:48 +03:00
|
|
|
{
|
2017-02-25 15:12:07 +03:00
|
|
|
char dirpath[MAXPGPATH];
|
2017-09-28 17:40:24 +03:00
|
|
|
char *dir_name;
|
|
|
|
char database_path[MAXPGPATH];
|
|
|
|
|
2019-01-22 20:41:17 +03:00
|
|
|
if (!IsReplicationProtocol())
|
2018-11-01 19:10:20 +03:00
|
|
|
dir_name = GetRelativePath(file->path, instance_config.pgdata);
|
2017-09-28 17:40:24 +03:00
|
|
|
else
|
|
|
|
dir_name = file->path;
|
2017-02-25 15:12:07 +03:00
|
|
|
|
2018-01-24 05:00:49 +03:00
|
|
|
elog(VERBOSE, "Create directory \"%s\"", dir_name);
|
2017-09-28 17:40:24 +03:00
|
|
|
pgBackupGetPath(¤t, database_path, lengthof(database_path),
|
|
|
|
DATABASE_DIR);
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2017-02-25 15:12:07 +03:00
|
|
|
join_path_components(dirpath, database_path, dir_name);
|
2018-11-02 20:24:09 +03:00
|
|
|
fio_mkdir(dirpath, DIR_PERMISSION, FIO_BACKUP_HOST);
|
2016-02-29 20:23:48 +03:00
|
|
|
}
|
2016-09-02 20:38:39 +03:00
|
|
|
|
2017-09-28 15:12:12 +03:00
|
|
|
/* setup threads */
|
2018-06-07 19:13:11 +03:00
|
|
|
pg_atomic_clear_flag(&file->lock);
|
2016-02-29 20:23:48 +03:00
|
|
|
}
|
|
|
|
|
2018-08-02 11:57:39 +03:00
|
|
|
/* Sort by size for load balancing */
|
2016-09-02 19:31:49 +03:00
|
|
|
parray_qsort(backup_files_list, pgFileCompareSize);
|
2018-08-02 11:57:39 +03:00
|
|
|
/* Sort the array for binary search */
|
|
|
|
if (prev_backup_filelist)
|
|
|
|
parray_qsort(prev_backup_filelist, pgFileComparePath);
|
2016-09-02 19:31:49 +03:00
|
|
|
|
|
|
|
/* init thread args with own file lists */
|
2018-05-30 13:43:52 +03:00
|
|
|
threads = (pthread_t *) palloc(sizeof(pthread_t) * num_threads);
|
|
|
|
threads_args = (backup_files_arg *) palloc(sizeof(backup_files_arg)*num_threads);
|
2018-06-07 19:13:11 +03:00
|
|
|
|
2016-02-29 20:23:48 +03:00
|
|
|
for (i = 0; i < num_threads; i++)
|
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
backup_files_arg *arg = &(threads_args[i]);
|
2017-02-25 15:12:07 +03:00
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
arg->from_root = instance_config.pgdata;
|
2017-02-25 15:12:07 +03:00
|
|
|
arg->to_root = database_path;
|
2018-05-30 13:43:52 +03:00
|
|
|
arg->files_list = backup_files_list;
|
|
|
|
arg->prev_filelist = prev_backup_filelist;
|
|
|
|
arg->prev_start_lsn = prev_backup_start_lsn;
|
|
|
|
arg->backup_conn = NULL;
|
|
|
|
arg->cancel_conn = NULL;
|
2018-04-05 18:58:40 +03:00
|
|
|
/* By default there are some error */
|
|
|
|
arg->ret = 1;
|
2016-09-02 19:31:49 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Run threads */
|
2018-09-17 18:27:20 +03:00
|
|
|
elog(INFO, "Start transfering data files");
|
2016-09-02 19:31:49 +03:00
|
|
|
for (i = 0; i < num_threads; i++)
|
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
backup_files_arg *arg = &(threads_args[i]);
|
|
|
|
|
2018-01-23 13:43:31 +03:00
|
|
|
elog(VERBOSE, "Start thread num: %i", i);
|
2017-09-28 17:40:24 +03:00
|
|
|
|
2019-01-22 20:41:17 +03:00
|
|
|
if (!IsReplicationProtocol())
|
2018-05-30 13:43:52 +03:00
|
|
|
pthread_create(&threads[i], NULL, backup_files, arg);
|
2017-09-28 17:40:24 +03:00
|
|
|
else
|
2018-05-30 13:43:52 +03:00
|
|
|
pthread_create(&threads[i], NULL, remote_backup_files, arg);
|
2016-02-29 20:23:48 +03:00
|
|
|
}
|
2018-07-18 19:44:11 +03:00
|
|
|
|
2017-05-25 14:05:48 +03:00
|
|
|
/* Wait threads */
|
2016-02-29 20:23:48 +03:00
|
|
|
for (i = 0; i < num_threads; i++)
|
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
pthread_join(threads[i], NULL);
|
|
|
|
if (threads_args[i].ret == 1)
|
2018-04-05 18:58:40 +03:00
|
|
|
backup_isok = false;
|
2016-02-29 20:23:48 +03:00
|
|
|
}
|
2018-04-05 18:58:40 +03:00
|
|
|
if (backup_isok)
|
2018-09-17 18:27:20 +03:00
|
|
|
elog(INFO, "Data files are transfered");
|
2018-04-05 18:58:40 +03:00
|
|
|
else
|
|
|
|
elog(ERROR, "Data files transferring failed");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2019-02-09 12:18:07 +03:00
|
|
|
/* Remove disappeared during backup files from backup_list */
|
|
|
|
for (i = 0; i < parray_num(backup_files_list); i++)
|
|
|
|
{
|
|
|
|
pgFile *tmp_file = (pgFile *) parray_get(backup_files_list, i);
|
|
|
|
|
|
|
|
if (tmp_file->write_size == FILE_NOT_FOUND)
|
|
|
|
{
|
|
|
|
pg_atomic_clear_flag(&tmp_file->lock);
|
|
|
|
pgFileFree(tmp_file);
|
|
|
|
parray_remove(backup_files_list, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-25 15:12:07 +03:00
|
|
|
/* clean previous backup file list */
|
2017-04-18 11:41:02 +03:00
|
|
|
if (prev_backup_filelist)
|
2017-02-25 15:12:07 +03:00
|
|
|
{
|
2017-04-18 11:41:02 +03:00
|
|
|
parray_walk(prev_backup_filelist, pgFileFree);
|
|
|
|
parray_free(prev_backup_filelist);
|
2017-02-25 15:12:07 +03:00
|
|
|
}
|
|
|
|
|
2018-11-07 04:21:56 +03:00
|
|
|
/* In case of backup from replica >= 9.6 we must fix minRecPoint,
|
|
|
|
* First we must find pg_control in backup_files_list.
|
|
|
|
*/
|
2018-10-31 09:47:53 +03:00
|
|
|
if (current.from_replica && !exclusive_backup)
|
|
|
|
{
|
2018-11-07 04:21:56 +03:00
|
|
|
char pg_control_path[MAXPGPATH];
|
|
|
|
|
2018-11-15 15:22:42 +03:00
|
|
|
snprintf(pg_control_path, sizeof(pg_control_path), "%s/%s",
|
|
|
|
instance_config.pgdata, "global/pg_control");
|
2018-11-07 04:21:56 +03:00
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
for (i = 0; i < parray_num(backup_files_list); i++)
|
|
|
|
{
|
|
|
|
pgFile *tmp_file = (pgFile *) parray_get(backup_files_list, i);
|
|
|
|
|
2018-11-07 04:21:56 +03:00
|
|
|
if (strcmp(tmp_file->path, pg_control_path) == 0)
|
2018-10-31 09:47:53 +03:00
|
|
|
{
|
|
|
|
pg_control = tmp_file;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-27 21:07:55 +03:00
|
|
|
/* Notify end of backup */
|
2016-01-15 15:26:05 +09:00
|
|
|
pg_stop_backup(¤t);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
if (current.from_replica && !exclusive_backup)
|
|
|
|
set_min_recovery_point(pg_control, database_path, current.stop_lsn);
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Add archived xlog files into the list of files of this backup */
|
2016-05-26 15:56:32 +03:00
|
|
|
if (stream_wal)
|
|
|
|
{
|
2019-01-27 01:11:52 +03:00
|
|
|
parray *xlog_files_list;
|
|
|
|
char pg_xlog_path[MAXPGPATH];
|
|
|
|
|
|
|
|
/* Scan backup PG_XLOG_DIR */
|
|
|
|
xlog_files_list = parray_new();
|
|
|
|
join_path_components(pg_xlog_path, database_path, PG_XLOG_DIR);
|
|
|
|
dir_list_file(xlog_files_list, pg_xlog_path, false, true, false, FIO_BACKUP_HOST);
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
for (i = 0; i < parray_num(xlog_files_list); i++)
|
2016-05-26 15:56:32 +03:00
|
|
|
{
|
2017-04-18 11:41:02 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(xlog_files_list, i);
|
2017-09-28 17:40:24 +03:00
|
|
|
if (S_ISREG(file->mode))
|
2019-01-18 00:37:18 +03:00
|
|
|
calc_file_checksum(file, FIO_BACKUP_HOST);
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Remove file path root prefix*/
|
2017-02-25 15:12:07 +03:00
|
|
|
if (strstr(file->path, database_path) == file->path)
|
2016-05-26 15:56:32 +03:00
|
|
|
{
|
2017-02-25 15:12:07 +03:00
|
|
|
char *ptr = file->path;
|
2018-05-28 15:19:03 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
file->path = pstrdup(GetRelativePath(ptr, database_path));
|
2016-05-26 15:56:32 +03:00
|
|
|
free(ptr);
|
|
|
|
}
|
|
|
|
}
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Add xlog files into the list of backed up files */
|
|
|
|
parray_concat(backup_files_list, xlog_files_list);
|
|
|
|
parray_free(xlog_files_list);
|
2016-05-26 15:56:32 +03:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Print the list of files to backup catalog */
|
2018-11-15 15:22:42 +03:00
|
|
|
write_backup_filelist(¤t, backup_files_list, instance_config.pgdata);
|
2013-12-10 03:21:07 +09:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Compute summary of size of regular files in the backup */
|
2016-01-15 23:47:38 +09:00
|
|
|
for (i = 0; i < parray_num(backup_files_list); i++)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2017-02-25 15:12:07 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(backup_files_list, i);
|
|
|
|
|
2017-06-16 12:52:27 +03:00
|
|
|
if (S_ISDIR(file->mode))
|
|
|
|
current.data_bytes += 4096;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Count the amount of the data actually copied */
|
2017-06-16 12:52:27 +03:00
|
|
|
if (S_ISREG(file->mode))
|
|
|
|
current.data_bytes += file->write_size;
|
2017-04-18 11:41:02 +03:00
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-11-12 15:44:22 +03:00
|
|
|
/* Cleanup */
|
|
|
|
if (backup_list)
|
|
|
|
{
|
|
|
|
parray_walk(backup_list, pgBackupFree);
|
|
|
|
parray_free(backup_list);
|
|
|
|
}
|
|
|
|
|
2017-07-11 13:18:23 +03:00
|
|
|
parray_walk(backup_files_list, pgFileFree);
|
2017-04-18 11:41:02 +03:00
|
|
|
parray_free(backup_files_list);
|
2017-07-11 13:18:23 +03:00
|
|
|
backup_files_list = NULL;
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
|
|
|
* Entry point of pg_probackup BACKUP subcommand.
|
|
|
|
*/
|
2012-05-18 08:54:36 +00:00
|
|
|
int
|
2017-11-15 19:21:30 +03:00
|
|
|
do_backup(time_t start_time)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
|
|
|
/* PGDATA and BACKUP_MODE are always required */
|
2018-11-01 19:10:20 +03:00
|
|
|
if (instance_config.pgdata == NULL)
|
2017-04-05 19:48:55 +03:00
|
|
|
elog(ERROR, "required parameter not specified: PGDATA "
|
2016-01-14 16:36:39 +09:00
|
|
|
"(-D, --pgdata)");
|
2012-05-18 08:54:36 +00:00
|
|
|
if (current.backup_mode == BACKUP_MODE_INVALID)
|
2017-04-05 19:48:55 +03:00
|
|
|
elog(ERROR, "required parameter not specified: BACKUP_MODE "
|
2016-01-14 16:36:39 +09:00
|
|
|
"(-b, --backup-mode)");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-03-21 11:54:49 +03:00
|
|
|
/* Create connection for PostgreSQL */
|
2018-11-01 19:10:20 +03:00
|
|
|
backup_conn = pgut_connect(instance_config.pghost, instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2017-03-21 11:54:49 +03:00
|
|
|
pgut_atexit_push(backup_disconnect, NULL);
|
|
|
|
|
2018-03-20 15:49:43 +03:00
|
|
|
current.primary_conninfo = pgut_get_conninfo_string(backup_conn);
|
2018-05-21 19:06:12 +03:00
|
|
|
|
2018-10-09 12:51:41 +03:00
|
|
|
#if PG_VERSION_NUM >= 110000
|
|
|
|
if (!RetrieveWalSegSize(backup_conn))
|
|
|
|
elog(ERROR, "Failed to retreive wal_segment_size");
|
|
|
|
#endif
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
current.compress_alg = instance_config.compress_alg;
|
|
|
|
current.compress_level = instance_config.compress_level;
|
2018-05-21 19:06:12 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Confirm data block size and xlog block size are compatible */
|
|
|
|
confirm_block_size("block_size", BLCKSZ);
|
|
|
|
confirm_block_size("wal_block_size", XLOG_BLCKSZ);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-05-21 19:06:12 +03:00
|
|
|
current.from_replica = pg_is_in_recovery();
|
2017-05-22 14:22:20 +03:00
|
|
|
|
|
|
|
/* Confirm that this server version is supported */
|
|
|
|
check_server_version();
|
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
/* TODO fix it for remote backup*/
|
2019-01-22 20:41:17 +03:00
|
|
|
if (!IsReplicationProtocol())
|
2017-09-28 17:40:24 +03:00
|
|
|
current.checksum_version = get_data_checksum_version(true);
|
2018-01-15 17:58:44 +03:00
|
|
|
|
|
|
|
is_checksum_enabled = pg_checksum_enable();
|
2018-02-06 20:37:45 +03:00
|
|
|
|
|
|
|
if (is_checksum_enabled)
|
2018-03-26 19:50:49 +03:00
|
|
|
elog(LOG, "This PostgreSQL instance was initialized with data block checksums. "
|
2018-02-06 20:37:45 +03:00
|
|
|
"Data block corruption will be detected");
|
|
|
|
else
|
2018-03-26 19:50:49 +03:00
|
|
|
elog(WARNING, "This PostgreSQL instance was initialized without data block checksums. "
|
2018-02-06 20:37:45 +03:00
|
|
|
"pg_probackup have no way to detect data block corruption without them. "
|
|
|
|
"Reinitialize PGDATA with option '--data-checksums'.");
|
2018-07-18 19:44:11 +03:00
|
|
|
|
2017-11-09 19:14:39 +03:00
|
|
|
StrNCpy(current.server_version, server_version_str,
|
|
|
|
sizeof(current.server_version));
|
2017-04-18 11:41:02 +03:00
|
|
|
current.stream = stream_wal;
|
2017-03-13 16:22:48 +03:00
|
|
|
|
2017-10-02 21:05:24 +03:00
|
|
|
is_ptrack_support = pg_ptrack_support();
|
2017-10-05 12:03:42 +03:00
|
|
|
if (is_ptrack_support)
|
|
|
|
{
|
|
|
|
is_ptrack_enable = pg_ptrack_enable();
|
|
|
|
}
|
2017-09-28 14:39:21 +03:00
|
|
|
|
2017-10-03 17:57:48 +03:00
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PTRACK)
|
2017-10-02 21:05:24 +03:00
|
|
|
{
|
2017-10-03 17:57:48 +03:00
|
|
|
if (!is_ptrack_support)
|
|
|
|
elog(ERROR, "This PostgreSQL instance does not support ptrack");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if(!is_ptrack_enable)
|
|
|
|
elog(ERROR, "Ptrack is disabled");
|
|
|
|
}
|
2017-04-11 17:56:46 +03:00
|
|
|
}
|
2017-04-05 19:48:55 +03:00
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
if (current.from_replica && exclusive_backup)
|
2017-06-07 16:50:33 +03:00
|
|
|
{
|
|
|
|
/* Check master connection options */
|
2018-11-01 19:10:20 +03:00
|
|
|
if (instance_config.master_host == NULL)
|
2017-06-07 16:50:33 +03:00
|
|
|
elog(ERROR, "Options for connection to master must be provided to perform backup from replica");
|
|
|
|
|
|
|
|
/* Create connection to master server */
|
2018-11-01 19:10:20 +03:00
|
|
|
master_conn = pgut_connect(instance_config.master_host,
|
|
|
|
instance_config.master_port,
|
|
|
|
instance_config.master_db,
|
|
|
|
instance_config.master_user);
|
2017-06-07 16:50:33 +03:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
|
|
|
* Ensure that backup directory was initialized for the same PostgreSQL
|
|
|
|
* instance we opened connection to. And that target backup database PGDATA
|
|
|
|
* belogns to the same instance.
|
|
|
|
*/
|
2017-09-28 17:40:24 +03:00
|
|
|
/* TODO fix it for remote backup */
|
2019-01-22 20:41:17 +03:00
|
|
|
if (!IsReplicationProtocol())
|
2017-09-28 17:40:24 +03:00
|
|
|
check_system_identifiers();
|
2017-04-05 19:48:55 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Start backup. Update backup status. */
|
2012-05-18 08:54:36 +00:00
|
|
|
current.status = BACKUP_STATUS_RUNNING;
|
2017-11-15 19:21:30 +03:00
|
|
|
current.start_time = start_time;
|
2018-10-16 18:28:00 +03:00
|
|
|
StrNCpy(current.program_version, PROGRAM_VERSION,
|
|
|
|
sizeof(current.program_version));
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-19 12:01:10 +03:00
|
|
|
/* Create backup directory and BACKUP_CONTROL_FILE */
|
2017-04-12 17:39:20 +03:00
|
|
|
if (pgBackupCreateDir(¤t))
|
2019-02-18 16:55:46 +03:00
|
|
|
elog(ERROR, "Cannot create backup directory");
|
|
|
|
if (!lock_backup(¤t))
|
2019-02-20 13:29:16 +03:00
|
|
|
elog(ERROR, "Cannot lock backup %s directory",
|
|
|
|
base36enc(current.start_time));
|
2018-10-15 19:14:29 +03:00
|
|
|
write_backup(¤t);
|
2017-04-12 17:39:20 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
elog(LOG, "Backup destination is initialized");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-20 14:14:14 +03:00
|
|
|
/* set the error processing function for the backup process */
|
|
|
|
pgut_atexit_push(backup_cleanup, NULL);
|
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
/* backup data */
|
2017-09-28 14:39:21 +03:00
|
|
|
do_backup_instance();
|
2012-05-18 08:54:36 +00:00
|
|
|
pgut_atexit_pop(backup_cleanup, NULL);
|
2013-12-10 03:21:07 +09:00
|
|
|
|
2017-06-16 12:52:27 +03:00
|
|
|
/* compute size of wal files of this backup stored in the archive */
|
|
|
|
if (!current.stream)
|
|
|
|
{
|
2018-11-01 19:10:20 +03:00
|
|
|
current.wal_bytes = instance_config.xlog_seg_size *
|
|
|
|
(current.stop_lsn / instance_config.xlog_seg_size -
|
|
|
|
current.start_lsn / instance_config.xlog_seg_size + 1);
|
2017-06-16 12:52:27 +03:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Backup is done. Update backup status */
|
2012-05-18 08:54:36 +00:00
|
|
|
current.end_time = time(NULL);
|
|
|
|
current.status = BACKUP_STATUS_DONE;
|
2018-10-15 19:14:29 +03:00
|
|
|
write_backup(¤t);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-11-22 17:04:46 +03:00
|
|
|
//elog(LOG, "Backup completed. Total bytes : " INT64_FORMAT "",
|
|
|
|
// current.data_bytes);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-14 12:51:05 +03:00
|
|
|
pgBackupValidate(¤t);
|
2017-02-25 15:12:07 +03:00
|
|
|
|
2017-04-25 17:33:52 +03:00
|
|
|
elog(INFO, "Backup %s completed", base36enc(current.start_time));
|
|
|
|
|
2017-04-20 15:22:18 +03:00
|
|
|
/*
|
|
|
|
* After successfil backup completion remove backups
|
|
|
|
* which are expired according to retention policies
|
|
|
|
*/
|
2018-01-24 04:37:47 +03:00
|
|
|
if (delete_expired || delete_wal)
|
2017-04-20 15:22:18 +03:00
|
|
|
do_retention_purge();
|
2019-01-25 20:12:46 +03:00
|
|
|
|
|
|
|
return 0;
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2017-04-18 11:41:02 +03:00
|
|
|
* Confirm that this server version is supported
|
2012-05-18 08:54:36 +00:00
|
|
|
*/
|
2017-03-21 11:54:49 +03:00
|
|
|
static void
|
2013-12-12 22:20:08 +09:00
|
|
|
check_server_version(void)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2018-05-16 10:00:49 +03:00
|
|
|
PGresult *res;
|
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
/* confirm server version */
|
2017-03-21 11:54:49 +03:00
|
|
|
server_version = PQserverVersion(backup_conn);
|
|
|
|
|
2018-02-09 13:22:01 +03:00
|
|
|
if (server_version == 0)
|
|
|
|
elog(ERROR, "Unknown server version %d", server_version);
|
|
|
|
|
|
|
|
if (server_version < 100000)
|
|
|
|
sprintf(server_version_str, "%d.%d",
|
|
|
|
server_version / 10000,
|
|
|
|
(server_version / 100) % 100);
|
|
|
|
else
|
|
|
|
sprintf(server_version_str, "%d",
|
|
|
|
server_version / 10000);
|
|
|
|
|
2016-03-02 12:11:25 +03:00
|
|
|
if (server_version < 90500)
|
2016-01-19 12:41:30 +09:00
|
|
|
elog(ERROR,
|
2018-02-09 13:22:01 +03:00
|
|
|
"server version is %s, must be %s or higher",
|
|
|
|
server_version_str, "9.5");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-05-21 19:06:12 +03:00
|
|
|
if (current.from_replica && server_version < 90600)
|
2016-09-29 17:33:21 +03:00
|
|
|
elog(ERROR,
|
2018-02-09 13:22:01 +03:00
|
|
|
"server version is %s, must be %s or higher for backup from replica",
|
|
|
|
server_version_str, "9.6");
|
2017-05-22 14:22:20 +03:00
|
|
|
|
2018-05-16 10:00:49 +03:00
|
|
|
res = pgut_execute_extended(backup_conn, "SELECT pgpro_edition()",
|
|
|
|
0, NULL, true, true);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check major version of connected PostgreSQL and major version of
|
|
|
|
* compiled PostgreSQL.
|
|
|
|
*/
|
|
|
|
#ifdef PGPRO_VERSION
|
|
|
|
if (PQresultStatus(res) == PGRES_FATAL_ERROR)
|
|
|
|
/* It seems we connected to PostgreSQL (not Postgres Pro) */
|
|
|
|
elog(ERROR, "%s was built with Postgres Pro %s %s, "
|
2018-08-02 13:48:29 +03:00
|
|
|
"but connection is made with PostgreSQL %s",
|
2018-05-16 10:00:49 +03:00
|
|
|
PROGRAM_NAME, PG_MAJORVERSION, PGPRO_EDITION, server_version_str);
|
|
|
|
else if (strcmp(server_version_str, PG_MAJORVERSION) != 0 &&
|
|
|
|
strcmp(PQgetvalue(res, 0, 0), PGPRO_EDITION) != 0)
|
|
|
|
elog(ERROR, "%s was built with Postgres Pro %s %s, "
|
2018-08-02 13:48:29 +03:00
|
|
|
"but connection is made with Postgres Pro %s %s",
|
2018-05-16 10:00:49 +03:00
|
|
|
PROGRAM_NAME, PG_MAJORVERSION, PGPRO_EDITION,
|
|
|
|
server_version_str, PQgetvalue(res, 0, 0));
|
|
|
|
#else
|
|
|
|
if (PQresultStatus(res) != PGRES_FATAL_ERROR)
|
|
|
|
/* It seems we connected to Postgres Pro (not PostgreSQL) */
|
|
|
|
elog(ERROR, "%s was built with PostgreSQL %s, "
|
2018-08-02 13:48:29 +03:00
|
|
|
"but connection is made with Postgres Pro %s %s",
|
2018-05-16 10:00:49 +03:00
|
|
|
PROGRAM_NAME, PG_MAJORVERSION,
|
|
|
|
server_version_str, PQgetvalue(res, 0, 0));
|
|
|
|
else if (strcmp(server_version_str, PG_MAJORVERSION) != 0)
|
2018-08-02 13:48:29 +03:00
|
|
|
elog(ERROR, "%s was built with PostgreSQL %s, but connection is made with %s",
|
2018-05-16 10:00:49 +03:00
|
|
|
PROGRAM_NAME, PG_MAJORVERSION, server_version_str);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
PQclear(res);
|
|
|
|
|
2017-05-22 14:22:20 +03:00
|
|
|
/* Do exclusive backup only for PostgreSQL 9.5 */
|
2017-11-21 15:51:45 +03:00
|
|
|
exclusive_backup = server_version < 90600 ||
|
|
|
|
current.backup_mode == BACKUP_MODE_DIFF_PTRACK;
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
2017-04-05 19:48:55 +03:00
|
|
|
/*
|
2017-04-18 11:41:02 +03:00
|
|
|
* Ensure that backup directory was initialized for the same PostgreSQL
|
|
|
|
* instance we opened connection to. And that target backup database PGDATA
|
|
|
|
* belogns to the same instance.
|
|
|
|
* All system identifiers must be equal.
|
2017-04-05 19:48:55 +03:00
|
|
|
*/
|
|
|
|
static void
|
2017-04-18 11:41:02 +03:00
|
|
|
check_system_identifiers(void)
|
2017-04-05 19:48:55 +03:00
|
|
|
{
|
2017-04-18 11:41:02 +03:00
|
|
|
uint64 system_id_conn;
|
|
|
|
uint64 system_id_pgdata;
|
2017-04-05 19:48:55 +03:00
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
system_id_pgdata = get_system_identifier(instance_config.pgdata);
|
2017-12-11 15:44:54 +03:00
|
|
|
system_id_conn = get_remote_system_identifier(backup_conn);
|
2018-07-18 19:44:11 +03:00
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
if (system_id_conn != instance_config.system_identifier)
|
2018-08-08 17:30:48 +03:00
|
|
|
elog(ERROR, "Backup data directory was initialized for system id " UINT64_FORMAT ", "
|
|
|
|
"but connected instance system id is " UINT64_FORMAT,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.system_identifier, system_id_conn);
|
|
|
|
if (system_id_pgdata != instance_config.system_identifier)
|
2018-08-08 17:30:48 +03:00
|
|
|
elog(ERROR, "Backup data directory was initialized for system id " UINT64_FORMAT ", "
|
|
|
|
"but target backup directory system id is " UINT64_FORMAT,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.system_identifier, system_id_pgdata);
|
2017-04-05 19:48:55 +03:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
|
|
|
* Ensure that target backup database is initialized with
|
|
|
|
* compatible settings. Currently check BLCKSZ and XLOG_BLCKSZ.
|
|
|
|
*/
|
2012-05-18 08:54:36 +00:00
|
|
|
static void
|
|
|
|
confirm_block_size(const char *name, int blcksz)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
char *endp;
|
|
|
|
int block_size;
|
|
|
|
|
2018-05-16 10:00:49 +03:00
|
|
|
res = pgut_execute(backup_conn, "SELECT pg_catalog.current_setting($1)", 1, &name);
|
2012-05-18 08:54:36 +00:00
|
|
|
if (PQntuples(res) != 1 || PQnfields(res) != 1)
|
2017-03-21 11:54:49 +03:00
|
|
|
elog(ERROR, "cannot get %s: %s", name, PQerrorMessage(backup_conn));
|
2018-07-18 19:44:11 +03:00
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
block_size = strtol(PQgetvalue(res, 0, 0), &endp, 10);
|
|
|
|
if ((endp && *endp) || block_size != blcksz)
|
2016-01-19 12:41:30 +09:00
|
|
|
elog(ERROR,
|
2017-03-21 11:54:49 +03:00
|
|
|
"%s(%d) is not compatible(%d expected)",
|
|
|
|
name, block_size, blcksz);
|
2018-07-18 19:44:11 +03:00
|
|
|
|
2018-06-09 15:45:03 +03:00
|
|
|
PQclear(res);
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Notify start of backup to PostgreSQL server.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
pg_start_backup(const char *label, bool smooth, pgBackup *backup)
|
|
|
|
{
|
2017-03-21 18:30:48 +03:00
|
|
|
PGresult *res;
|
|
|
|
const char *params[2];
|
2018-09-21 14:21:43 +03:00
|
|
|
uint32 lsn_hi;
|
|
|
|
uint32 lsn_lo;
|
2017-07-11 17:41:52 +03:00
|
|
|
PGconn *conn;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
|
|
|
params[0] = label;
|
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
/* For 9.5 replica we call pg_start_backup() on master */
|
|
|
|
if (backup->from_replica && exclusive_backup)
|
|
|
|
conn = master_conn;
|
|
|
|
else
|
|
|
|
conn = backup_conn;
|
2017-07-11 17:41:52 +03:00
|
|
|
|
2013-12-12 22:20:08 +09:00
|
|
|
/* 2nd argument is 'fast'*/
|
|
|
|
params[1] = smooth ? "false" : "true";
|
2017-05-22 14:22:20 +03:00
|
|
|
if (!exclusive_backup)
|
2017-07-11 17:41:52 +03:00
|
|
|
res = pgut_execute(conn,
|
2018-03-02 19:20:40 +03:00
|
|
|
"SELECT pg_catalog.pg_start_backup($1, $2, false)",
|
2016-09-29 17:33:21 +03:00
|
|
|
2,
|
2018-05-16 10:00:49 +03:00
|
|
|
params);
|
2016-09-29 17:33:21 +03:00
|
|
|
else
|
2017-07-11 17:41:52 +03:00
|
|
|
res = pgut_execute(conn,
|
2018-03-02 19:20:40 +03:00
|
|
|
"SELECT pg_catalog.pg_start_backup($1, $2)",
|
2016-09-29 17:33:21 +03:00
|
|
|
2,
|
2018-05-16 10:00:49 +03:00
|
|
|
params);
|
2013-12-12 22:20:08 +09:00
|
|
|
|
2018-06-07 12:22:45 +03:00
|
|
|
/*
|
|
|
|
* Set flag that pg_start_backup() was called. If an error will happen it
|
|
|
|
* is necessary to call pg_stop_backup() in backup_cleanup().
|
|
|
|
*/
|
|
|
|
backup_in_progress = true;
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Extract timeline and LSN from results of pg_start_backup() */
|
2018-09-21 14:21:43 +03:00
|
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
|
2017-03-21 18:30:48 +03:00
|
|
|
/* Calculate LSN */
|
2018-09-21 14:21:43 +03:00
|
|
|
backup->start_lsn = ((uint64) lsn_hi )<< 32 | lsn_lo;
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-04-24 19:25:42 +03:00
|
|
|
PQclear(res);
|
2017-05-18 15:33:03 +03:00
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE &&
|
|
|
|
(!(backup->from_replica && !exclusive_backup)))
|
2017-07-13 16:16:05 +03:00
|
|
|
/*
|
|
|
|
* Switch to a new WAL segment. It is necessary to get archived WAL
|
|
|
|
* segment, which includes start LSN of current backup.
|
2018-10-31 09:47:53 +03:00
|
|
|
* Don`t do this for replica backups unless it`s PG 9.5
|
2017-07-13 16:16:05 +03:00
|
|
|
*/
|
|
|
|
pg_switch_wal(conn);
|
|
|
|
|
2018-09-04 14:08:50 +03:00
|
|
|
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE)
|
2017-07-13 16:16:05 +03:00
|
|
|
/* In PAGE mode wait for current segment... */
|
2018-11-23 12:03:29 +03:00
|
|
|
wait_wal_lsn(backup->start_lsn, true, false);
|
2018-09-04 14:08:50 +03:00
|
|
|
/*
|
|
|
|
* Do not wait start_lsn for stream backup.
|
|
|
|
* Because WAL streaming will start after pg_start_backup() in stream
|
|
|
|
* mode.
|
|
|
|
*/
|
|
|
|
else if (!stream_wal)
|
2017-07-13 16:16:05 +03:00
|
|
|
/* ...for others wait for previous segment */
|
2018-09-04 14:08:50 +03:00
|
|
|
wait_wal_lsn(backup->start_lsn, true, true);
|
2017-07-12 15:33:21 +03:00
|
|
|
|
2018-11-07 04:21:56 +03:00
|
|
|
/* In case of backup from replica for PostgreSQL 9.5
|
|
|
|
* wait for start_lsn to be replayed by replica
|
|
|
|
*/
|
|
|
|
if (backup->from_replica && exclusive_backup)
|
2017-07-12 15:33:21 +03:00
|
|
|
wait_replica_wal_lsn(backup->start_lsn, true);
|
2017-04-24 19:25:42 +03:00
|
|
|
}
|
|
|
|
|
2017-07-13 16:16:05 +03:00
|
|
|
/*
|
|
|
|
* Switch to a new WAL segment. It should be called only for master.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
pg_switch_wal(PGconn *conn)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
|
|
|
|
/* Remove annoying NOTICE messages generated by backend */
|
2018-05-16 10:00:49 +03:00
|
|
|
res = pgut_execute(conn, "SET client_min_messages = warning;", 0, NULL);
|
2017-07-13 16:16:05 +03:00
|
|
|
PQclear(res);
|
|
|
|
|
2018-10-08 16:39:11 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
res = pgut_execute(conn, "SELECT * FROM pg_catalog.pg_switch_wal()", 0, NULL);
|
|
|
|
#else
|
|
|
|
res = pgut_execute(conn, "SELECT * FROM pg_catalog.pg_switch_xlog()", 0, NULL);
|
|
|
|
#endif
|
2017-07-13 16:16:05 +03:00
|
|
|
|
|
|
|
PQclear(res);
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
|
|
|
* Check if the instance supports ptrack
|
2017-06-07 16:28:22 +03:00
|
|
|
* TODO Maybe we should rather check ptrack_version()?
|
2017-04-18 11:41:02 +03:00
|
|
|
*/
|
2016-10-18 16:25:13 +03:00
|
|
|
static bool
|
|
|
|
pg_ptrack_support(void)
|
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGresult *res_db;
|
|
|
|
|
|
|
|
res_db = pgut_execute(backup_conn,
|
2017-10-11 16:10:25 +03:00
|
|
|
"SELECT proname FROM pg_proc WHERE proname='ptrack_version'",
|
2018-05-16 10:00:49 +03:00
|
|
|
0, NULL);
|
2017-10-11 16:10:25 +03:00
|
|
|
if (PQntuples(res_db) == 0)
|
|
|
|
{
|
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
PQclear(res_db);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-10-11 16:10:25 +03:00
|
|
|
res_db = pgut_execute(backup_conn,
|
2018-03-02 19:20:40 +03:00
|
|
|
"SELECT pg_catalog.ptrack_version()",
|
2018-05-16 10:00:49 +03:00
|
|
|
0, NULL);
|
2016-10-18 16:25:13 +03:00
|
|
|
if (PQntuples(res_db) == 0)
|
|
|
|
{
|
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2018-05-21 17:37:59 +03:00
|
|
|
/* Now we support only ptrack versions upper than 1.5 */
|
2018-05-22 14:02:08 +03:00
|
|
|
if (strcmp(PQgetvalue(res_db, 0, 0), "1.5") != 0 &&
|
2018-10-30 16:21:38 +03:00
|
|
|
strcmp(PQgetvalue(res_db, 0, 0), "1.6") != 0 &&
|
|
|
|
strcmp(PQgetvalue(res_db, 0, 0), "1.7") != 0)
|
2017-10-09 18:22:25 +03:00
|
|
|
{
|
2018-05-21 17:37:59 +03:00
|
|
|
elog(WARNING, "Update your ptrack to the version 1.5 or upper. Current version is %s", PQgetvalue(res_db, 0, 0));
|
2017-10-09 18:22:25 +03:00
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
PQclear(res_db);
|
2016-11-22 16:07:54 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Check if ptrack is enabled in target instance */
|
2016-11-22 16:07:54 +03:00
|
|
|
static bool
|
|
|
|
pg_ptrack_enable(void)
|
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGresult *res_db;
|
|
|
|
|
2019-02-27 19:16:25 +03:00
|
|
|
res_db = pgut_execute(backup_conn, "SHOW ptrack_enable", 0, NULL);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2016-11-15 16:23:53 +03:00
|
|
|
if (strcmp(PQgetvalue(res_db, 0, 0), "on") != 0)
|
|
|
|
{
|
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
PQclear(res_db);
|
2016-10-18 16:25:13 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-15 17:58:44 +03:00
|
|
|
/* Check if ptrack is enabled in target instance */
|
|
|
|
static bool
|
|
|
|
pg_checksum_enable(void)
|
|
|
|
{
|
|
|
|
PGresult *res_db;
|
|
|
|
|
2019-02-27 19:16:25 +03:00
|
|
|
res_db = pgut_execute(backup_conn, "SHOW data_checksums", 0, NULL);
|
2018-01-15 17:58:44 +03:00
|
|
|
|
|
|
|
if (strcmp(PQgetvalue(res_db, 0, 0), "on") != 0)
|
|
|
|
{
|
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
PQclear(res_db);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Check if target instance is replica */
|
2016-10-25 14:38:51 +03:00
|
|
|
static bool
|
|
|
|
pg_is_in_recovery(void)
|
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGresult *res_db;
|
|
|
|
|
2018-05-16 10:00:49 +03:00
|
|
|
res_db = pgut_execute(backup_conn, "SELECT pg_catalog.pg_is_in_recovery()", 0, NULL);
|
2016-10-25 14:38:51 +03:00
|
|
|
|
|
|
|
if (PQgetvalue(res_db, 0, 0)[0] == 't')
|
|
|
|
{
|
|
|
|
PQclear(res_db);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
PQclear(res_db);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Clear ptrack files in all databases of the instance we connected to */
|
2016-02-27 21:07:55 +03:00
|
|
|
static void
|
|
|
|
pg_ptrack_clear(void)
|
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGresult *res_db,
|
|
|
|
*res;
|
|
|
|
const char *dbname;
|
|
|
|
int i;
|
2017-09-29 20:25:11 +03:00
|
|
|
Oid dbOid, tblspcOid;
|
|
|
|
char *params[2];
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
params[0] = palloc(64);
|
|
|
|
params[1] = palloc(64);
|
|
|
|
res_db = pgut_execute(backup_conn, "SELECT datname, oid, dattablespace FROM pg_database",
|
2018-05-16 10:00:49 +03:00
|
|
|
0, NULL);
|
2016-02-27 21:07:55 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
for(i = 0; i < PQntuples(res_db); i++)
|
2016-04-15 15:58:58 +03:00
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGconn *tmp_conn;
|
|
|
|
|
|
|
|
dbname = PQgetvalue(res_db, i, 0);
|
2017-10-02 21:05:24 +03:00
|
|
|
if (strcmp(dbname, "template0") == 0)
|
2016-04-15 15:58:58 +03:00
|
|
|
continue;
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
dbOid = atoi(PQgetvalue(res_db, i, 1));
|
|
|
|
tblspcOid = atoi(PQgetvalue(res_db, i, 2));
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
tmp_conn = pgut_connect(instance_config.pghost, instance_config.pgport,
|
2018-11-19 17:08:56 +03:00
|
|
|
dbname,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.pguser);
|
2018-10-29 19:19:08 +03:00
|
|
|
res = pgut_execute(tmp_conn, "SELECT pg_catalog.pg_ptrack_clear()",
|
|
|
|
0, NULL);
|
|
|
|
PQclear(res);
|
2017-09-29 20:25:11 +03:00
|
|
|
|
|
|
|
sprintf(params[0], "%i", dbOid);
|
|
|
|
sprintf(params[1], "%i", tblspcOid);
|
2018-03-02 19:20:40 +03:00
|
|
|
res = pgut_execute(tmp_conn, "SELECT pg_catalog.pg_ptrack_get_and_clear_db($1, $2)",
|
2018-05-16 10:00:49 +03:00
|
|
|
2, (const char **)params);
|
2016-04-15 15:58:58 +03:00
|
|
|
PQclear(res);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
|
|
|
pgut_disconnect(tmp_conn);
|
2016-04-15 15:58:58 +03:00
|
|
|
}
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
pfree(params[0]);
|
|
|
|
pfree(params[1]);
|
2016-04-15 15:58:58 +03:00
|
|
|
PQclear(res_db);
|
2016-02-27 21:07:55 +03:00
|
|
|
}
|
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
static bool
|
|
|
|
pg_ptrack_get_and_clear_db(Oid dbOid, Oid tblspcOid)
|
|
|
|
{
|
2017-11-01 12:47:28 +03:00
|
|
|
char *params[2];
|
|
|
|
char *dbname;
|
|
|
|
PGresult *res_db;
|
|
|
|
PGresult *res;
|
2018-04-28 23:51:51 +03:00
|
|
|
bool result;
|
2017-09-29 20:25:11 +03:00
|
|
|
|
|
|
|
params[0] = palloc(64);
|
|
|
|
params[1] = palloc(64);
|
|
|
|
|
2017-10-03 17:57:48 +03:00
|
|
|
sprintf(params[0], "%i", dbOid);
|
|
|
|
res_db = pgut_execute(backup_conn,
|
|
|
|
"SELECT datname FROM pg_database WHERE oid=$1",
|
2018-05-16 10:00:49 +03:00
|
|
|
1, (const char **) params);
|
2017-10-03 17:57:48 +03:00
|
|
|
/*
|
|
|
|
* If database is not found, it's not an error.
|
|
|
|
* It could have been deleted since previous backup.
|
|
|
|
*/
|
|
|
|
if (PQntuples(res_db) != 1 || PQnfields(res_db) != 1)
|
|
|
|
return false;
|
|
|
|
|
2017-11-01 12:47:28 +03:00
|
|
|
dbname = PQgetvalue(res_db, 0, 0);
|
2017-10-03 17:57:48 +03:00
|
|
|
|
|
|
|
/* Always backup all files from template0 database */
|
|
|
|
if (strcmp(dbname, "template0") == 0)
|
|
|
|
{
|
2017-11-01 12:47:28 +03:00
|
|
|
PQclear(res_db);
|
2017-10-03 17:57:48 +03:00
|
|
|
return true;
|
|
|
|
}
|
2017-11-01 12:47:28 +03:00
|
|
|
PQclear(res_db);
|
2017-10-03 17:57:48 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
sprintf(params[0], "%i", dbOid);
|
|
|
|
sprintf(params[1], "%i", tblspcOid);
|
2018-03-02 19:20:40 +03:00
|
|
|
res = pgut_execute(backup_conn, "SELECT pg_catalog.pg_ptrack_get_and_clear_db($1, $2)",
|
2018-05-16 10:00:49 +03:00
|
|
|
2, (const char **)params);
|
2017-09-29 20:25:11 +03:00
|
|
|
|
|
|
|
if (PQnfields(res) != 1)
|
|
|
|
elog(ERROR, "cannot perform pg_ptrack_get_and_clear_db()");
|
2018-04-28 23:51:51 +03:00
|
|
|
|
|
|
|
if (!parse_bool(PQgetvalue(res, 0, 0), &result))
|
|
|
|
elog(ERROR,
|
|
|
|
"result of pg_ptrack_get_and_clear_db() is invalid: %s",
|
|
|
|
PQgetvalue(res, 0, 0));
|
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
PQclear(res);
|
|
|
|
pfree(params[0]);
|
|
|
|
pfree(params[1]);
|
|
|
|
|
2018-04-28 23:51:51 +03:00
|
|
|
return result;
|
2017-09-29 20:25:11 +03:00
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Read and clear ptrack files of the target relation.
|
|
|
|
* Result is a bytea ptrack map of all segments of the target relation.
|
2017-09-26 20:50:06 +03:00
|
|
|
* case 1: we know a tablespace_oid, db_oid, and rel_filenode
|
|
|
|
* case 2: we know db_oid and rel_filenode (no tablespace_oid, because file in pg_default)
|
|
|
|
* case 3: we know only rel_filenode (because file in pg_global)
|
2017-04-18 11:41:02 +03:00
|
|
|
*/
|
2016-05-11 19:35:14 +03:00
|
|
|
static char *
|
2017-09-26 20:50:06 +03:00
|
|
|
pg_ptrack_get_and_clear(Oid tablespace_oid, Oid db_oid, Oid rel_filenode,
|
2017-03-21 11:54:49 +03:00
|
|
|
size_t *result_size)
|
2016-05-11 19:35:14 +03:00
|
|
|
{
|
2017-03-21 11:54:49 +03:00
|
|
|
PGconn *tmp_conn;
|
|
|
|
PGresult *res_db,
|
|
|
|
*res;
|
|
|
|
char *params[2];
|
|
|
|
char *result;
|
2017-11-09 12:47:37 +03:00
|
|
|
char *val;
|
2016-05-11 19:35:14 +03:00
|
|
|
|
|
|
|
params[0] = palloc(64);
|
|
|
|
params[1] = palloc(64);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-11-01 12:47:28 +03:00
|
|
|
/* regular file (not in directory 'global') */
|
2017-09-26 20:50:06 +03:00
|
|
|
if (db_oid != 0)
|
|
|
|
{
|
2017-11-01 12:47:28 +03:00
|
|
|
char *dbname;
|
|
|
|
|
2017-09-26 20:50:06 +03:00
|
|
|
sprintf(params[0], "%i", db_oid);
|
|
|
|
res_db = pgut_execute(backup_conn,
|
|
|
|
"SELECT datname FROM pg_database WHERE oid=$1",
|
2018-05-16 10:00:49 +03:00
|
|
|
1, (const char **) params);
|
2017-09-26 20:50:06 +03:00
|
|
|
/*
|
|
|
|
* If database is not found, it's not an error.
|
|
|
|
* It could have been deleted since previous backup.
|
|
|
|
*/
|
|
|
|
if (PQntuples(res_db) != 1 || PQnfields(res_db) != 1)
|
|
|
|
return NULL;
|
2017-09-01 13:04:30 +03:00
|
|
|
|
2017-11-01 12:47:28 +03:00
|
|
|
dbname = PQgetvalue(res_db, 0, 0);
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-10-02 00:57:38 +03:00
|
|
|
if (strcmp(dbname, "template0") == 0)
|
|
|
|
{
|
2017-11-01 12:47:28 +03:00
|
|
|
PQclear(res_db);
|
2017-10-02 00:57:38 +03:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
tmp_conn = pgut_connect(instance_config.pghost, instance_config.pgport,
|
2018-11-19 17:08:56 +03:00
|
|
|
dbname,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.pguser);
|
2017-09-26 20:50:06 +03:00
|
|
|
sprintf(params[0], "%i", tablespace_oid);
|
|
|
|
sprintf(params[1], "%i", rel_filenode);
|
2018-03-02 19:20:40 +03:00
|
|
|
res = pgut_execute(tmp_conn, "SELECT pg_catalog.pg_ptrack_get_and_clear($1, $2)",
|
2018-05-16 10:00:49 +03:00
|
|
|
2, (const char **)params);
|
2017-09-26 20:50:06 +03:00
|
|
|
|
|
|
|
if (PQnfields(res) != 1)
|
|
|
|
elog(ERROR, "cannot get ptrack file from database \"%s\" by tablespace oid %u and relation oid %u",
|
2017-11-01 12:47:28 +03:00
|
|
|
dbname, tablespace_oid, rel_filenode);
|
|
|
|
PQclear(res_db);
|
2017-09-26 20:50:06 +03:00
|
|
|
pgut_disconnect(tmp_conn);
|
|
|
|
}
|
|
|
|
/* file in directory 'global' */
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
2017-09-29 20:25:11 +03:00
|
|
|
* execute ptrack_get_and_clear for relation in pg_global
|
|
|
|
* Use backup_conn, cause we can do it from any database.
|
2017-09-26 20:50:06 +03:00
|
|
|
*/
|
2017-09-29 20:25:11 +03:00
|
|
|
sprintf(params[0], "%i", tablespace_oid);
|
2017-09-26 20:50:06 +03:00
|
|
|
sprintf(params[1], "%i", rel_filenode);
|
2018-03-02 19:20:40 +03:00
|
|
|
res = pgut_execute(backup_conn, "SELECT pg_catalog.pg_ptrack_get_and_clear($1, $2)",
|
2018-05-16 10:00:49 +03:00
|
|
|
2, (const char **)params);
|
2017-09-26 20:50:06 +03:00
|
|
|
|
|
|
|
if (PQnfields(res) != 1)
|
2017-11-10 14:13:44 +03:00
|
|
|
elog(ERROR, "cannot get ptrack file from pg_global tablespace and relation oid %u",
|
2017-09-26 20:50:06 +03:00
|
|
|
rel_filenode);
|
|
|
|
}
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-11-09 12:47:37 +03:00
|
|
|
val = PQgetvalue(res, 0, 0);
|
|
|
|
|
2017-11-10 14:13:44 +03:00
|
|
|
/* TODO Now pg_ptrack_get_and_clear() returns bytea ending with \x.
|
|
|
|
* It should be fixed in future ptrack releases, but till then we
|
|
|
|
* can parse it.
|
|
|
|
*/
|
2017-11-09 12:47:37 +03:00
|
|
|
if (strcmp("x", val+1) == 0)
|
|
|
|
{
|
2017-11-10 14:13:44 +03:00
|
|
|
/* Ptrack file is missing */
|
|
|
|
return NULL;
|
2017-11-09 12:47:37 +03:00
|
|
|
}
|
|
|
|
|
2017-03-21 11:54:49 +03:00
|
|
|
result = (char *) PQunescapeBytea((unsigned char *) PQgetvalue(res, 0, 0),
|
|
|
|
result_size);
|
2016-05-11 19:35:14 +03:00
|
|
|
PQclear(res);
|
|
|
|
pfree(params[0]);
|
|
|
|
pfree(params[1]);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
2017-05-29 18:53:48 +03:00
|
|
|
* Wait for target 'lsn'.
|
|
|
|
*
|
|
|
|
* If current backup started in archive mode wait for 'lsn' to be archived in
|
|
|
|
* archive 'wal' directory with WAL segment file.
|
|
|
|
* If current backup started in stream mode wait for 'lsn' to be streamed in
|
2017-08-07 16:23:37 +03:00
|
|
|
* 'pg_wal' directory.
|
2017-07-13 09:39:00 +03:00
|
|
|
*
|
2018-09-04 14:08:50 +03:00
|
|
|
* If 'is_start_lsn' is true and backup mode is PAGE then we wait for 'lsn' to
|
|
|
|
* be archived in archive 'wal' directory regardless stream mode.
|
|
|
|
*
|
2017-07-13 09:39:00 +03:00
|
|
|
* If 'wait_prev_segment' wait for previous segment.
|
2018-11-22 14:44:57 +03:00
|
|
|
*
|
|
|
|
* Returns LSN of last valid record if wait_prev_segment is not true, otherwise
|
|
|
|
* returns InvalidXLogRecPtr.
|
2017-04-18 11:41:02 +03:00
|
|
|
*/
|
2018-11-22 14:44:57 +03:00
|
|
|
static XLogRecPtr
|
2018-09-04 14:08:50 +03:00
|
|
|
wait_wal_lsn(XLogRecPtr lsn, bool is_start_lsn, bool wait_prev_segment)
|
2016-11-25 16:54:24 +03:00
|
|
|
{
|
|
|
|
TimeLineID tli;
|
|
|
|
XLogSegNo targetSegNo;
|
2018-09-04 14:08:50 +03:00
|
|
|
char pg_wal_dir[MAXPGPATH];
|
|
|
|
char wal_segment_path[MAXPGPATH],
|
|
|
|
*wal_segment_dir,
|
|
|
|
wal_segment[MAXFNAMELEN];
|
2017-12-12 15:07:13 +03:00
|
|
|
bool file_exists = false;
|
2017-05-25 14:05:48 +03:00
|
|
|
uint32 try_count = 0,
|
|
|
|
timeout;
|
2016-11-25 16:54:24 +03:00
|
|
|
|
2017-12-12 15:07:13 +03:00
|
|
|
#ifdef HAVE_LIBZ
|
|
|
|
char gz_wal_segment_path[MAXPGPATH];
|
|
|
|
#endif
|
|
|
|
|
2016-11-25 16:54:24 +03:00
|
|
|
tli = get_current_timeline(false);
|
|
|
|
|
2017-04-20 12:38:51 +03:00
|
|
|
/* Compute the name of the WAL file containig requested LSN */
|
2018-11-01 19:10:20 +03:00
|
|
|
GetXLogSegNo(lsn, targetSegNo, instance_config.xlog_seg_size);
|
2017-07-13 09:39:00 +03:00
|
|
|
if (wait_prev_segment)
|
|
|
|
targetSegNo--;
|
2018-11-01 19:10:20 +03:00
|
|
|
GetXLogFileName(wal_segment, tli, targetSegNo,
|
|
|
|
instance_config.xlog_seg_size);
|
2016-11-25 16:54:24 +03:00
|
|
|
|
2018-09-04 14:08:50 +03:00
|
|
|
/*
|
2018-11-07 04:21:56 +03:00
|
|
|
* In pg_start_backup we wait for 'lsn' in 'pg_wal' directory if it is
|
2018-09-04 14:08:50 +03:00
|
|
|
* stream and non-page backup. Page backup needs archived WAL files, so we
|
|
|
|
* wait for 'lsn' in archive 'wal' directory for page backups.
|
|
|
|
*
|
|
|
|
* In pg_stop_backup it depends only on stream_wal.
|
|
|
|
*/
|
|
|
|
if (stream_wal &&
|
|
|
|
(current.backup_mode != BACKUP_MODE_DIFF_PAGE || !is_start_lsn))
|
2017-05-24 15:17:47 +03:00
|
|
|
{
|
2018-09-04 14:08:50 +03:00
|
|
|
pgBackupGetPath2(¤t, pg_wal_dir, lengthof(pg_wal_dir),
|
2017-05-24 15:17:47 +03:00
|
|
|
DATABASE_DIR, PG_XLOG_DIR);
|
2018-09-04 14:08:50 +03:00
|
|
|
join_path_components(wal_segment_path, pg_wal_dir, wal_segment);
|
|
|
|
wal_segment_dir = pg_wal_dir;
|
2017-05-24 15:17:47 +03:00
|
|
|
}
|
|
|
|
else
|
2017-05-25 14:05:48 +03:00
|
|
|
{
|
2017-12-12 15:07:13 +03:00
|
|
|
join_path_components(wal_segment_path, arclog_path, wal_segment);
|
2018-09-04 14:08:50 +03:00
|
|
|
wal_segment_dir = arclog_path;
|
2017-05-25 14:05:48 +03:00
|
|
|
}
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2018-11-23 19:07:19 +03:00
|
|
|
if (instance_config.archive_timeout > 0)
|
|
|
|
timeout = instance_config.archive_timeout;
|
2018-11-23 15:08:04 +03:00
|
|
|
else
|
|
|
|
timeout = ARCHIVE_TIMEOUT_DEFAULT;
|
|
|
|
|
2017-11-22 17:04:46 +03:00
|
|
|
if (wait_prev_segment)
|
|
|
|
elog(LOG, "Looking for segment: %s", wal_segment);
|
|
|
|
else
|
2018-11-23 12:03:29 +03:00
|
|
|
elog(LOG, "Looking for LSN %X/%X in segment: %s",
|
2018-10-29 19:19:08 +03:00
|
|
|
(uint32) (lsn >> 32), (uint32) lsn, wal_segment);
|
2017-11-22 17:04:46 +03:00
|
|
|
|
2017-12-12 15:07:13 +03:00
|
|
|
#ifdef HAVE_LIBZ
|
|
|
|
snprintf(gz_wal_segment_path, sizeof(gz_wal_segment_path), "%s.gz",
|
|
|
|
wal_segment_path);
|
|
|
|
#endif
|
|
|
|
|
2017-05-29 18:53:48 +03:00
|
|
|
/* Wait until target LSN is archived or streamed */
|
2017-05-25 16:38:21 +03:00
|
|
|
while (true)
|
2016-11-25 16:54:24 +03:00
|
|
|
{
|
2017-12-12 15:07:13 +03:00
|
|
|
if (!file_exists)
|
|
|
|
{
|
2018-11-04 23:20:07 +03:00
|
|
|
file_exists = fileExists(wal_segment_path, is_start_lsn ? FIO_DB_HOST : FIO_BACKUP_HOST);
|
2017-12-12 15:07:13 +03:00
|
|
|
|
|
|
|
/* Try to find compressed WAL file */
|
|
|
|
if (!file_exists)
|
|
|
|
{
|
|
|
|
#ifdef HAVE_LIBZ
|
2018-11-04 23:20:07 +03:00
|
|
|
file_exists = fileExists(gz_wal_segment_path, is_start_lsn ? FIO_DB_HOST : FIO_BACKUP_HOST);
|
2017-12-12 15:07:13 +03:00
|
|
|
if (file_exists)
|
|
|
|
elog(LOG, "Found compressed WAL segment: %s", wal_segment_path);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
elog(LOG, "Found WAL segment: %s", wal_segment_path);
|
|
|
|
}
|
2017-05-25 16:38:21 +03:00
|
|
|
|
|
|
|
if (file_exists)
|
|
|
|
{
|
2017-07-13 13:37:44 +03:00
|
|
|
/* Do not check LSN for previous WAL segment */
|
|
|
|
if (wait_prev_segment)
|
2018-11-22 14:44:57 +03:00
|
|
|
return InvalidXLogRecPtr;
|
2017-07-13 13:37:44 +03:00
|
|
|
|
2017-05-25 16:38:21 +03:00
|
|
|
/*
|
2017-05-29 18:53:48 +03:00
|
|
|
* A WAL segment found. Check LSN on it.
|
2017-05-25 16:38:21 +03:00
|
|
|
*/
|
2018-11-01 19:10:20 +03:00
|
|
|
if (wal_contains_lsn(wal_segment_dir, lsn, tli,
|
|
|
|
instance_config.xlog_seg_size))
|
2017-05-25 16:38:21 +03:00
|
|
|
/* Target LSN was found */
|
2017-11-22 17:04:46 +03:00
|
|
|
{
|
|
|
|
elog(LOG, "Found LSN: %X/%X", (uint32) (lsn >> 32), (uint32) lsn);
|
2018-11-23 12:03:29 +03:00
|
|
|
return lsn;
|
2018-11-21 18:30:03 +03:00
|
|
|
}
|
2018-11-23 12:03:29 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we failed to get LSN of valid record in a reasonable time, try
|
|
|
|
* to get LSN of last valid record prior to the target LSN. But only
|
|
|
|
* in case of a backup from a replica.
|
|
|
|
*/
|
2018-11-23 13:20:16 +03:00
|
|
|
if (!exclusive_backup && current.from_replica &&
|
|
|
|
(try_count > timeout / 4))
|
2018-11-21 18:30:03 +03:00
|
|
|
{
|
|
|
|
XLogRecPtr res;
|
|
|
|
|
|
|
|
res = get_last_wal_lsn(wal_segment_dir, current.start_lsn,
|
2018-11-23 19:07:19 +03:00
|
|
|
lsn, tli, false,
|
|
|
|
instance_config.xlog_seg_size);
|
2018-11-21 18:30:03 +03:00
|
|
|
if (!XLogRecPtrIsInvalid(res))
|
|
|
|
{
|
|
|
|
/* LSN of the prior record was found */
|
2018-11-23 12:03:29 +03:00
|
|
|
elog(LOG, "Found prior LSN: %X/%X, it is used as stop LSN",
|
|
|
|
(uint32) (res >> 32), (uint32) res);
|
2018-11-22 14:44:57 +03:00
|
|
|
return res;
|
2018-11-21 18:30:03 +03:00
|
|
|
}
|
2017-11-22 17:04:46 +03:00
|
|
|
}
|
2017-05-25 16:38:21 +03:00
|
|
|
}
|
|
|
|
|
2016-11-25 16:54:24 +03:00
|
|
|
sleep(1);
|
|
|
|
if (interrupted)
|
2017-12-12 15:07:13 +03:00
|
|
|
elog(ERROR, "Interrupted during waiting for WAL archiving");
|
2016-11-25 16:54:24 +03:00
|
|
|
try_count++;
|
2017-03-24 14:07:03 +03:00
|
|
|
|
|
|
|
/* Inform user if WAL segment is absent in first attempt */
|
|
|
|
if (try_count == 1)
|
2017-07-13 13:37:44 +03:00
|
|
|
{
|
|
|
|
if (wait_prev_segment)
|
2017-12-12 15:07:13 +03:00
|
|
|
elog(INFO, "Wait for WAL segment %s to be archived",
|
|
|
|
wal_segment_path);
|
2017-07-13 13:37:44 +03:00
|
|
|
else
|
2017-12-12 15:07:13 +03:00
|
|
|
elog(INFO, "Wait for LSN %X/%X in archived WAL segment %s",
|
|
|
|
(uint32) (lsn >> 32), (uint32) lsn, wal_segment_path);
|
2017-07-13 13:37:44 +03:00
|
|
|
}
|
2017-03-24 14:07:03 +03:00
|
|
|
|
2018-12-03 13:38:45 +03:00
|
|
|
if (!stream_wal && is_start_lsn && try_count == 30)
|
|
|
|
elog(WARNING, "By default pg_probackup assume WAL delivery method to be ARCHIVE. "
|
2018-12-03 13:41:39 +03:00
|
|
|
"If continius archiving is not set up, use '--stream' option to make autonomous backup. "
|
|
|
|
"Otherwise check that continius archiving works correctly.");
|
2018-12-03 13:38:45 +03:00
|
|
|
|
2017-05-25 14:05:48 +03:00
|
|
|
if (timeout > 0 && try_count > timeout)
|
2017-05-25 16:38:21 +03:00
|
|
|
{
|
|
|
|
if (file_exists)
|
|
|
|
elog(ERROR, "WAL segment %s was archived, "
|
|
|
|
"but target LSN %X/%X could not be archived in %d seconds",
|
|
|
|
wal_segment, (uint32) (lsn >> 32), (uint32) lsn, timeout);
|
2017-07-13 13:37:44 +03:00
|
|
|
/* If WAL segment doesn't exist or we wait for previous segment */
|
2017-05-25 16:38:21 +03:00
|
|
|
else
|
|
|
|
elog(ERROR,
|
2017-12-12 15:07:13 +03:00
|
|
|
"Switched WAL segment %s could not be archived in %d seconds",
|
2017-05-25 16:38:21 +03:00
|
|
|
wal_segment, timeout);
|
|
|
|
}
|
2016-11-25 16:54:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:41:52 +03:00
|
|
|
/*
|
2017-07-12 12:08:14 +03:00
|
|
|
* Wait for target 'lsn' on replica instance from master.
|
2017-07-11 17:41:52 +03:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
wait_replica_wal_lsn(XLogRecPtr lsn, bool is_start_backup)
|
|
|
|
{
|
|
|
|
uint32 try_count = 0;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
XLogRecPtr replica_lsn;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For lsn from pg_start_backup() we need it to be replayed on replica's
|
|
|
|
* data.
|
|
|
|
*/
|
|
|
|
if (is_start_backup)
|
2017-08-11 18:53:57 +03:00
|
|
|
{
|
2018-10-08 16:39:11 +03:00
|
|
|
replica_lsn = get_checkpoint_location(backup_conn);
|
2017-08-11 18:53:57 +03:00
|
|
|
}
|
2017-07-11 17:41:52 +03:00
|
|
|
/*
|
|
|
|
* For lsn from pg_stop_backup() we need it only to be received by
|
|
|
|
* replica and fsync()'ed on WAL segment.
|
|
|
|
*/
|
|
|
|
else
|
2017-08-11 18:53:57 +03:00
|
|
|
{
|
2018-10-08 16:39:11 +03:00
|
|
|
PGresult *res;
|
|
|
|
uint32 lsn_hi;
|
|
|
|
uint32 lsn_lo;
|
2017-07-11 17:41:52 +03:00
|
|
|
|
2018-10-08 16:39:11 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
res = pgut_execute(backup_conn, "SELECT pg_catalog.pg_last_wal_receive_lsn()",
|
|
|
|
0, NULL);
|
|
|
|
#else
|
|
|
|
res = pgut_execute(backup_conn, "SELECT pg_catalog.pg_last_xlog_receive_location()",
|
|
|
|
0, NULL);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Extract LSN from result */
|
|
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
|
|
|
|
/* Calculate LSN */
|
|
|
|
replica_lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
|
|
|
|
PQclear(res);
|
|
|
|
}
|
2017-07-11 17:41:52 +03:00
|
|
|
|
|
|
|
/* target lsn was replicated */
|
|
|
|
if (replica_lsn >= lsn)
|
|
|
|
break;
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
if (interrupted)
|
|
|
|
elog(ERROR, "Interrupted during waiting for target LSN");
|
|
|
|
try_count++;
|
|
|
|
|
|
|
|
/* Inform user if target lsn is absent in first attempt */
|
|
|
|
if (try_count == 1)
|
|
|
|
elog(INFO, "Wait for target LSN %X/%X to be received by replica",
|
|
|
|
(uint32) (lsn >> 32), (uint32) lsn);
|
|
|
|
|
2018-11-01 19:10:20 +03:00
|
|
|
if (instance_config.replica_timeout > 0 &&
|
|
|
|
try_count > instance_config.replica_timeout)
|
2017-07-11 17:41:52 +03:00
|
|
|
elog(ERROR, "Target LSN %X/%X could not be recevied by replica "
|
|
|
|
"in %d seconds",
|
|
|
|
(uint32) (lsn >> 32), (uint32) lsn,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.replica_timeout);
|
2017-07-11 17:41:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
/*
|
|
|
|
* Notify end of backup to PostgreSQL server.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
pg_stop_backup(pgBackup *backup)
|
|
|
|
{
|
2017-11-29 12:51:43 +03:00
|
|
|
PGconn *conn;
|
|
|
|
PGresult *res;
|
|
|
|
PGresult *tablespace_map_content = NULL;
|
2018-09-21 14:21:43 +03:00
|
|
|
uint32 lsn_hi;
|
|
|
|
uint32 lsn_lo;
|
2018-10-31 09:47:53 +03:00
|
|
|
//XLogRecPtr restore_lsn = InvalidXLogRecPtr;
|
2018-09-21 14:21:43 +03:00
|
|
|
int pg_stop_backup_timeout = 0;
|
2017-11-29 12:51:43 +03:00
|
|
|
char path[MAXPGPATH];
|
|
|
|
char backup_label[MAXPGPATH];
|
|
|
|
FILE *fp;
|
|
|
|
pgFile *file;
|
|
|
|
size_t len;
|
2018-03-20 17:09:57 +03:00
|
|
|
char *val = NULL;
|
|
|
|
char *stop_backup_query = NULL;
|
2018-11-21 18:30:03 +03:00
|
|
|
bool stop_lsn_exists = false;
|
2017-03-24 17:22:55 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We will use this values if there are no transactions between start_lsn
|
|
|
|
* and stop_lsn.
|
|
|
|
*/
|
2017-03-24 16:58:35 +03:00
|
|
|
time_t recovery_time;
|
|
|
|
TransactionId recovery_xid;
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-05-22 14:22:20 +03:00
|
|
|
if (!backup_in_progress)
|
2018-08-07 13:28:02 +03:00
|
|
|
elog(ERROR, "backup is not in progress");
|
2017-05-22 14:22:20 +03:00
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
/* For 9.5 replica we call pg_stop_backup() on master */
|
|
|
|
if (current.from_replica && exclusive_backup)
|
|
|
|
conn = master_conn;
|
|
|
|
else
|
|
|
|
conn = backup_conn;
|
2017-07-12 12:08:14 +03:00
|
|
|
|
2017-03-21 18:30:48 +03:00
|
|
|
/* Remove annoying NOTICE messages generated by backend */
|
2017-07-12 12:08:14 +03:00
|
|
|
res = pgut_execute(conn, "SET client_min_messages = warning;",
|
2018-05-16 10:00:49 +03:00
|
|
|
0, NULL);
|
2017-03-21 18:30:48 +03:00
|
|
|
PQclear(res);
|
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
/* Create restore point
|
|
|
|
* only if it`s backup from master, or exclusive replica(wich connects to master)
|
|
|
|
*/
|
|
|
|
if (backup != NULL && (!current.from_replica || (current.from_replica && exclusive_backup)))
|
2017-06-07 16:50:33 +03:00
|
|
|
{
|
|
|
|
const char *params[1];
|
|
|
|
char name[1024];
|
|
|
|
|
2018-05-21 19:06:12 +03:00
|
|
|
if (!current.from_replica)
|
2017-06-07 16:50:33 +03:00
|
|
|
snprintf(name, lengthof(name), "pg_probackup, backup_id %s",
|
2017-12-20 17:57:01 +03:00
|
|
|
base36enc(backup->start_time));
|
2017-06-07 16:50:33 +03:00
|
|
|
else
|
|
|
|
snprintf(name, lengthof(name), "pg_probackup, backup_id %s. Replica Backup",
|
2017-12-20 17:57:01 +03:00
|
|
|
base36enc(backup->start_time));
|
2017-07-12 12:08:14 +03:00
|
|
|
params[0] = name;
|
2017-06-07 16:50:33 +03:00
|
|
|
|
2018-03-02 19:20:40 +03:00
|
|
|
res = pgut_execute(conn, "SELECT pg_catalog.pg_create_restore_point($1)",
|
2018-05-16 10:00:49 +03:00
|
|
|
1, params);
|
2018-09-21 14:21:43 +03:00
|
|
|
/* Extract timeline and LSN from the result */
|
|
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
|
|
|
|
/* Calculate LSN */
|
2018-10-31 09:47:53 +03:00
|
|
|
//restore_lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
|
2017-07-12 12:08:14 +03:00
|
|
|
PQclear(res);
|
2017-06-07 16:50:33 +03:00
|
|
|
}
|
|
|
|
|
2017-06-14 19:30:25 +03:00
|
|
|
/*
|
|
|
|
* send pg_stop_backup asynchronously because we could came
|
|
|
|
* here from backup_cleanup() after some error caused by
|
|
|
|
* postgres archive_command problem and in this case we will
|
|
|
|
* wait for pg_stop_backup() forever.
|
|
|
|
*/
|
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
if (!pg_stop_backup_is_sent)
|
|
|
|
{
|
2017-11-30 15:49:48 +03:00
|
|
|
bool sent = false;
|
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
if (!exclusive_backup)
|
2017-11-29 12:51:43 +03:00
|
|
|
{
|
2017-11-01 12:29:28 +03:00
|
|
|
/*
|
|
|
|
* Stop the non-exclusive backup. Besides stop_lsn it returns from
|
|
|
|
* pg_stop_backup(false) copy of the backup label and tablespace map
|
|
|
|
* so they can be written to disk by the caller.
|
2018-11-07 04:21:56 +03:00
|
|
|
* In case of backup from replica >= 9.6 we do not trust minRecPoint
|
|
|
|
* and stop_backup LSN, so we use latest replayed LSN as STOP LSN.
|
2017-11-01 12:29:28 +03:00
|
|
|
*/
|
2018-11-07 04:21:56 +03:00
|
|
|
if (current.from_replica)
|
|
|
|
stop_backup_query = "SELECT"
|
|
|
|
" pg_catalog.txid_snapshot_xmax(pg_catalog.txid_current_snapshot()),"
|
|
|
|
" current_timestamp(0)::timestamptz,"
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
" pg_catalog.pg_last_wal_replay_lsn(),"
|
|
|
|
#else
|
|
|
|
" pg_catalog.pg_last_xlog_replay_location(),"
|
|
|
|
#endif
|
|
|
|
" labelfile,"
|
|
|
|
" spcmapfile"
|
2018-11-16 09:39:32 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
" FROM pg_catalog.pg_stop_backup(false, false)";
|
|
|
|
#else
|
2018-11-07 04:21:56 +03:00
|
|
|
" FROM pg_catalog.pg_stop_backup(false)";
|
2018-11-16 09:39:32 +03:00
|
|
|
#endif
|
2018-11-07 04:21:56 +03:00
|
|
|
else
|
|
|
|
stop_backup_query = "SELECT"
|
|
|
|
" pg_catalog.txid_snapshot_xmax(pg_catalog.txid_current_snapshot()),"
|
|
|
|
" current_timestamp(0)::timestamptz,"
|
|
|
|
" lsn,"
|
|
|
|
" labelfile,"
|
|
|
|
" spcmapfile"
|
2018-11-16 09:39:32 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
" FROM pg_catalog.pg_stop_backup(false, false)";
|
|
|
|
#else
|
2018-11-07 04:21:56 +03:00
|
|
|
" FROM pg_catalog.pg_stop_backup(false)";
|
2018-11-16 09:39:32 +03:00
|
|
|
#endif
|
2018-03-19 20:51:01 +03:00
|
|
|
|
2017-11-29 12:51:43 +03:00
|
|
|
}
|
2017-11-01 12:29:28 +03:00
|
|
|
else
|
2017-11-29 12:51:43 +03:00
|
|
|
{
|
2018-03-19 20:51:01 +03:00
|
|
|
stop_backup_query = "SELECT"
|
2018-03-02 19:20:40 +03:00
|
|
|
" pg_catalog.txid_snapshot_xmax(pg_catalog.txid_current_snapshot()),"
|
2017-11-29 12:51:43 +03:00
|
|
|
" current_timestamp(0)::timestamptz,"
|
2018-03-19 20:51:01 +03:00
|
|
|
" pg_catalog.pg_stop_backup() as lsn";
|
2017-11-29 12:51:43 +03:00
|
|
|
}
|
2018-03-19 20:51:01 +03:00
|
|
|
|
|
|
|
sent = pgut_send(conn, stop_backup_query, 0, NULL, WARNING);
|
2017-11-01 12:29:28 +03:00
|
|
|
pg_stop_backup_is_sent = true;
|
|
|
|
if (!sent)
|
|
|
|
elog(ERROR, "Failed to send pg_stop_backup query");
|
|
|
|
}
|
2017-06-14 19:30:25 +03:00
|
|
|
|
|
|
|
/*
|
2018-11-27 17:31:35 +03:00
|
|
|
* Wait for the result of pg_stop_backup(), but no longer than
|
|
|
|
* archive_timeout seconds
|
2017-06-14 19:30:25 +03:00
|
|
|
*/
|
2017-11-01 12:29:28 +03:00
|
|
|
if (pg_stop_backup_is_sent && !in_cleanup)
|
2017-06-14 19:30:25 +03:00
|
|
|
{
|
2018-09-21 14:21:43 +03:00
|
|
|
res = NULL;
|
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
while (1)
|
2017-06-14 20:36:11 +03:00
|
|
|
{
|
2017-11-01 12:29:28 +03:00
|
|
|
if (!PQconsumeInput(conn) || PQisBusy(conn))
|
2017-07-12 12:08:14 +03:00
|
|
|
{
|
2017-11-01 12:29:28 +03:00
|
|
|
pg_stop_backup_timeout++;
|
|
|
|
sleep(1);
|
|
|
|
|
|
|
|
if (interrupted)
|
|
|
|
{
|
|
|
|
pgut_cancel(conn);
|
|
|
|
elog(ERROR, "interrupted during waiting for pg_stop_backup");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pg_stop_backup_timeout == 1)
|
|
|
|
elog(INFO, "wait for pg_stop_backup()");
|
|
|
|
|
|
|
|
/*
|
2018-11-27 17:31:35 +03:00
|
|
|
* If postgres haven't answered in archive_timeout seconds,
|
2017-11-01 12:29:28 +03:00
|
|
|
* send an interrupt.
|
|
|
|
*/
|
2018-11-28 12:41:14 +03:00
|
|
|
if (pg_stop_backup_timeout > instance_config.archive_timeout)
|
2017-11-01 12:29:28 +03:00
|
|
|
{
|
|
|
|
pgut_cancel(conn);
|
|
|
|
elog(ERROR, "pg_stop_backup doesn't answer in %d seconds, cancel it",
|
2018-11-28 12:41:14 +03:00
|
|
|
instance_config.archive_timeout);
|
2017-11-01 12:29:28 +03:00
|
|
|
}
|
2017-07-12 12:08:14 +03:00
|
|
|
}
|
2017-11-01 12:29:28 +03:00
|
|
|
else
|
2017-07-12 12:08:14 +03:00
|
|
|
{
|
2017-11-01 12:29:28 +03:00
|
|
|
res = PQgetResult(conn);
|
|
|
|
break;
|
2017-07-12 12:08:14 +03:00
|
|
|
}
|
2017-06-14 20:36:11 +03:00
|
|
|
}
|
2018-03-19 20:51:01 +03:00
|
|
|
|
|
|
|
/* Check successfull execution of pg_stop_backup() */
|
2017-11-01 12:29:28 +03:00
|
|
|
if (!res)
|
|
|
|
elog(ERROR, "pg_stop backup() failed");
|
2017-11-22 17:04:46 +03:00
|
|
|
else
|
2018-03-19 20:51:01 +03:00
|
|
|
{
|
|
|
|
switch (PQresultStatus(res))
|
|
|
|
{
|
2018-09-21 14:21:43 +03:00
|
|
|
/*
|
|
|
|
* We should expect only PGRES_TUPLES_OK since pg_stop_backup
|
|
|
|
* returns tuples.
|
|
|
|
*/
|
2018-03-19 20:51:01 +03:00
|
|
|
case PGRES_TUPLES_OK:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "query failed: %s query was: %s",
|
|
|
|
PQerrorMessage(conn), stop_backup_query);
|
|
|
|
}
|
2017-11-22 17:04:46 +03:00
|
|
|
elog(INFO, "pg_stop backup() successfully executed");
|
2018-03-19 20:51:01 +03:00
|
|
|
}
|
2017-06-14 19:30:25 +03:00
|
|
|
|
2017-11-30 15:49:48 +03:00
|
|
|
backup_in_progress = false;
|
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
/* Extract timeline and LSN from results of pg_stop_backup() */
|
2018-09-21 14:21:43 +03:00
|
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 2), &lsn_hi, &lsn_lo);
|
2017-11-01 12:29:28 +03:00
|
|
|
/* Calculate LSN */
|
2018-09-21 14:21:43 +03:00
|
|
|
stop_backup_lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
if (!XRecOffIsValid(stop_backup_lsn))
|
2017-05-31 16:05:14 +03:00
|
|
|
{
|
2018-11-07 04:21:56 +03:00
|
|
|
if (XRecOffIsNull(stop_backup_lsn))
|
2018-11-20 16:33:36 +03:00
|
|
|
{
|
|
|
|
char *xlog_path,
|
|
|
|
stream_xlog_path[MAXPGPATH];
|
|
|
|
|
|
|
|
if (stream_wal)
|
|
|
|
{
|
|
|
|
pgBackupGetPath2(backup, stream_xlog_path,
|
|
|
|
lengthof(stream_xlog_path),
|
|
|
|
DATABASE_DIR, PG_XLOG_DIR);
|
|
|
|
xlog_path = stream_xlog_path;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
xlog_path = arclog_path;
|
|
|
|
|
|
|
|
stop_backup_lsn = get_last_wal_lsn(xlog_path, backup->start_lsn,
|
2018-11-21 18:30:03 +03:00
|
|
|
stop_backup_lsn, backup->tli,
|
2018-11-23 19:07:19 +03:00
|
|
|
true, instance_config.xlog_seg_size);
|
2018-11-21 18:30:03 +03:00
|
|
|
/*
|
|
|
|
* Do not check existance of LSN again below using
|
|
|
|
* wait_wal_lsn().
|
|
|
|
*/
|
|
|
|
stop_lsn_exists = true;
|
2018-11-20 16:33:36 +03:00
|
|
|
}
|
2018-11-07 04:21:56 +03:00
|
|
|
else
|
|
|
|
elog(ERROR, "Invalid stop_backup_lsn value %X/%X",
|
|
|
|
(uint32) (stop_backup_lsn >> 32), (uint32) (stop_backup_lsn));
|
2017-05-31 16:05:14 +03:00
|
|
|
}
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-11-29 12:51:43 +03:00
|
|
|
/* Write backup_label and tablespace_map */
|
2018-02-06 22:15:41 +03:00
|
|
|
if (!exclusive_backup)
|
2016-05-26 15:56:32 +03:00
|
|
|
{
|
2018-02-06 22:15:41 +03:00
|
|
|
Assert(PQnfields(res) >= 4);
|
|
|
|
pgBackupGetPath(¤t, path, lengthof(path), DATABASE_DIR);
|
|
|
|
|
|
|
|
/* Write backup_label */
|
|
|
|
join_path_components(backup_label, path, PG_BACKUP_LABEL_FILE);
|
2018-11-04 10:02:26 +03:00
|
|
|
fp = fio_fopen(backup_label, PG_BINARY_W, FIO_BACKUP_HOST);
|
2018-02-06 22:15:41 +03:00
|
|
|
if (fp == NULL)
|
|
|
|
elog(ERROR, "can't open backup label file \"%s\": %s",
|
|
|
|
backup_label, strerror(errno));
|
|
|
|
|
|
|
|
len = strlen(PQgetvalue(res, 0, 3));
|
2018-11-04 10:02:26 +03:00
|
|
|
if (fio_fwrite(fp, PQgetvalue(res, 0, 3), len) != len ||
|
|
|
|
fio_fflush(fp) != 0 ||
|
|
|
|
fio_fclose(fp))
|
2018-02-06 22:15:41 +03:00
|
|
|
elog(ERROR, "can't write backup label file \"%s\": %s",
|
|
|
|
backup_label, strerror(errno));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It's vital to check if backup_files_list is initialized,
|
|
|
|
* because we could get here because the backup was interrupted
|
|
|
|
*/
|
|
|
|
if (backup_files_list)
|
|
|
|
{
|
2018-11-05 15:23:46 +03:00
|
|
|
file = pgFileNew(backup_label, true, FIO_BACKUP_HOST);
|
2019-01-18 00:37:18 +03:00
|
|
|
calc_file_checksum(file, FIO_BACKUP_HOST);
|
2018-02-06 22:15:41 +03:00
|
|
|
free(file->path);
|
|
|
|
file->path = strdup(PG_BACKUP_LABEL_FILE);
|
|
|
|
parray_append(backup_files_list, file);
|
|
|
|
}
|
2017-11-29 12:51:43 +03:00
|
|
|
}
|
2017-11-01 12:29:28 +03:00
|
|
|
|
2018-02-06 22:15:41 +03:00
|
|
|
if (sscanf(PQgetvalue(res, 0, 0), XID_FMT, &recovery_xid) != 1)
|
2017-11-29 12:51:43 +03:00
|
|
|
elog(ERROR,
|
|
|
|
"result of txid_snapshot_xmax() is invalid: %s",
|
2018-04-28 23:51:51 +03:00
|
|
|
PQgetvalue(res, 0, 0));
|
2018-07-16 15:18:08 +03:00
|
|
|
if (!parse_time(PQgetvalue(res, 0, 1), &recovery_time, true))
|
2017-11-29 12:51:43 +03:00
|
|
|
elog(ERROR,
|
|
|
|
"result of current_timestamp is invalid: %s",
|
2018-04-28 23:51:51 +03:00
|
|
|
PQgetvalue(res, 0, 1));
|
2017-11-29 12:51:43 +03:00
|
|
|
|
2018-02-06 22:15:41 +03:00
|
|
|
/* Get content for tablespace_map from stop_backup results
|
|
|
|
* in case of non-exclusive backup
|
2017-11-29 12:51:43 +03:00
|
|
|
*/
|
2018-02-06 22:15:41 +03:00
|
|
|
if (!exclusive_backup)
|
2017-11-29 12:51:43 +03:00
|
|
|
val = PQgetvalue(res, 0, 4);
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-11-29 12:51:43 +03:00
|
|
|
/* Write tablespace_map */
|
2018-02-06 22:15:41 +03:00
|
|
|
if (!exclusive_backup && val && strlen(val) > 0)
|
2017-11-29 12:51:43 +03:00
|
|
|
{
|
|
|
|
char tablespace_map[MAXPGPATH];
|
2017-11-01 12:29:28 +03:00
|
|
|
|
2017-11-29 12:51:43 +03:00
|
|
|
join_path_components(tablespace_map, path, PG_TABLESPACE_MAP_FILE);
|
2018-11-04 10:02:26 +03:00
|
|
|
fp = fio_fopen(tablespace_map, PG_BINARY_W, FIO_BACKUP_HOST);
|
2017-03-21 18:30:48 +03:00
|
|
|
if (fp == NULL)
|
2017-11-29 12:51:43 +03:00
|
|
|
elog(ERROR, "can't open tablespace map file \"%s\": %s",
|
|
|
|
tablespace_map, strerror(errno));
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-11-29 12:51:43 +03:00
|
|
|
len = strlen(val);
|
2018-11-04 10:02:26 +03:00
|
|
|
if (fio_fwrite(fp, val, len) != len ||
|
|
|
|
fio_fflush(fp) != 0 ||
|
|
|
|
fio_fclose(fp))
|
2017-11-29 12:51:43 +03:00
|
|
|
elog(ERROR, "can't write tablespace map file \"%s\": %s",
|
|
|
|
tablespace_map, strerror(errno));
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-07-11 13:18:23 +03:00
|
|
|
if (backup_files_list)
|
|
|
|
{
|
2018-11-05 15:23:46 +03:00
|
|
|
file = pgFileNew(tablespace_map, true, FIO_BACKUP_HOST);
|
2017-11-29 12:51:43 +03:00
|
|
|
if (S_ISREG(file->mode))
|
2019-01-18 00:37:18 +03:00
|
|
|
calc_file_checksum(file, FIO_BACKUP_HOST);
|
2017-07-11 13:18:23 +03:00
|
|
|
free(file->path);
|
2017-11-29 12:51:43 +03:00
|
|
|
file->path = strdup(PG_TABLESPACE_MAP_FILE);
|
2017-07-11 13:18:23 +03:00
|
|
|
parray_append(backup_files_list, file);
|
|
|
|
}
|
2017-11-01 12:29:28 +03:00
|
|
|
}
|
2017-11-30 15:49:48 +03:00
|
|
|
|
|
|
|
if (tablespace_map_content)
|
|
|
|
PQclear(tablespace_map_content);
|
2017-11-01 12:29:28 +03:00
|
|
|
PQclear(res);
|
2017-11-30 15:49:48 +03:00
|
|
|
|
2017-11-01 12:29:28 +03:00
|
|
|
if (stream_wal)
|
2018-04-05 18:58:40 +03:00
|
|
|
{
|
2017-11-01 12:29:28 +03:00
|
|
|
/* Wait for the completion of stream */
|
|
|
|
pthread_join(stream_thread, NULL);
|
2018-04-05 18:58:40 +03:00
|
|
|
if (stream_thread_arg.ret == 1)
|
|
|
|
elog(ERROR, "WAL streaming failed");
|
|
|
|
}
|
2017-11-01 12:29:28 +03:00
|
|
|
}
|
2017-03-23 17:00:43 +03:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* Fill in fields if that is the correct end of backup. */
|
2017-03-21 18:30:48 +03:00
|
|
|
if (backup != NULL)
|
|
|
|
{
|
2017-03-24 16:58:35 +03:00
|
|
|
char *xlog_path,
|
|
|
|
stream_xlog_path[MAXPGPATH];
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2017-07-12 12:08:14 +03:00
|
|
|
/* Wait for stop_lsn to be received by replica */
|
2018-11-22 14:44:57 +03:00
|
|
|
/* XXX Do we need this? */
|
|
|
|
// if (current.from_replica)
|
|
|
|
// wait_replica_wal_lsn(stop_backup_lsn, false);
|
2017-06-07 16:50:33 +03:00
|
|
|
/*
|
|
|
|
* Wait for stop_lsn to be archived or streamed.
|
|
|
|
* We wait for stop_lsn in stream mode just in case.
|
|
|
|
*/
|
2018-11-21 18:30:03 +03:00
|
|
|
if (!stop_lsn_exists)
|
2018-11-23 12:03:29 +03:00
|
|
|
stop_backup_lsn = wait_wal_lsn(stop_backup_lsn, false, false);
|
2017-06-07 16:50:33 +03:00
|
|
|
|
2017-03-24 16:58:35 +03:00
|
|
|
if (stream_wal)
|
|
|
|
{
|
2017-05-24 15:17:47 +03:00
|
|
|
pgBackupGetPath2(backup, stream_xlog_path,
|
|
|
|
lengthof(stream_xlog_path),
|
|
|
|
DATABASE_DIR, PG_XLOG_DIR);
|
2017-03-24 16:58:35 +03:00
|
|
|
xlog_path = stream_xlog_path;
|
|
|
|
}
|
2016-09-29 17:33:21 +03:00
|
|
|
else
|
2017-03-24 16:58:35 +03:00
|
|
|
xlog_path = arclog_path;
|
2017-03-21 18:30:48 +03:00
|
|
|
|
2017-03-24 16:58:35 +03:00
|
|
|
backup->tli = get_current_timeline(false);
|
|
|
|
backup->stop_lsn = stop_backup_lsn;
|
2017-03-21 11:54:49 +03:00
|
|
|
|
2017-12-08 16:53:00 +03:00
|
|
|
elog(LOG, "Getting the Recovery Time from WAL");
|
|
|
|
|
2018-10-31 09:47:53 +03:00
|
|
|
/* iterate over WAL from stop_backup lsn to start_backup lsn */
|
2018-11-01 19:10:20 +03:00
|
|
|
if (!read_recovery_info(xlog_path, backup->tli,
|
|
|
|
instance_config.xlog_seg_size,
|
2017-03-24 16:58:35 +03:00
|
|
|
backup->start_lsn, backup->stop_lsn,
|
|
|
|
&backup->recovery_time, &backup->recovery_xid))
|
|
|
|
{
|
2018-10-31 09:47:53 +03:00
|
|
|
elog(LOG, "Failed to find Recovery Time in WAL. Forced to trust current_timestamp");
|
2017-03-24 16:58:35 +03:00
|
|
|
backup->recovery_time = recovery_time;
|
|
|
|
backup->recovery_xid = recovery_xid;
|
|
|
|
}
|
2016-05-26 15:56:32 +03:00
|
|
|
}
|
2013-12-26 21:13:48 +09:00
|
|
|
}
|
|
|
|
|
2017-05-29 18:53:48 +03:00
|
|
|
/*
|
|
|
|
* Retreive checkpoint_timeout GUC value in seconds.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
checkpoint_timeout(void)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
|
|
|
const char *val;
|
|
|
|
const char *hintmsg;
|
|
|
|
int val_int;
|
|
|
|
|
2018-05-16 10:00:49 +03:00
|
|
|
res = pgut_execute(backup_conn, "show checkpoint_timeout", 0, NULL);
|
2017-05-29 18:53:48 +03:00
|
|
|
val = PQgetvalue(res, 0, 0);
|
|
|
|
|
|
|
|
if (!parse_int(val, &val_int, OPTION_UNIT_S, &hintmsg))
|
|
|
|
{
|
2017-11-01 12:47:28 +03:00
|
|
|
PQclear(res);
|
2017-05-29 18:53:48 +03:00
|
|
|
if (hintmsg)
|
|
|
|
elog(ERROR, "Invalid value of checkout_timeout %s: %s", val,
|
|
|
|
hintmsg);
|
|
|
|
else
|
|
|
|
elog(ERROR, "Invalid value of checkout_timeout %s", val);
|
|
|
|
}
|
|
|
|
|
2017-11-01 12:47:28 +03:00
|
|
|
PQclear(res);
|
|
|
|
|
2017-05-29 18:53:48 +03:00
|
|
|
return val_int;
|
|
|
|
}
|
|
|
|
|
2012-05-18 08:54:36 +00:00
|
|
|
/*
|
|
|
|
* Notify end of backup to server when "backup_label" is in the root directory
|
|
|
|
* of the DB cluster.
|
|
|
|
* Also update backup status to ERROR when the backup is not finished.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
backup_cleanup(bool fatal, void *userdata)
|
|
|
|
{
|
|
|
|
/*
|
2017-04-19 12:01:10 +03:00
|
|
|
* Update status of backup in BACKUP_CONTROL_FILE to ERROR.
|
2012-05-18 08:54:36 +00:00
|
|
|
* end_time != 0 means backup finished
|
|
|
|
*/
|
|
|
|
if (current.status == BACKUP_STATUS_RUNNING && current.end_time == 0)
|
|
|
|
{
|
2018-03-26 19:50:49 +03:00
|
|
|
elog(WARNING, "Backup %s is running, setting its status to ERROR",
|
2017-12-20 17:57:01 +03:00
|
|
|
base36enc(current.start_time));
|
2012-05-18 08:54:36 +00:00
|
|
|
current.end_time = time(NULL);
|
|
|
|
current.status = BACKUP_STATUS_ERROR;
|
2018-10-15 19:14:29 +03:00
|
|
|
write_backup(¤t);
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
2017-05-19 12:08:16 +03:00
|
|
|
|
|
|
|
/*
|
2017-05-22 14:22:20 +03:00
|
|
|
* If backup is in progress, notify stop of backup to PostgreSQL
|
2017-05-19 12:08:16 +03:00
|
|
|
*/
|
2017-05-22 14:22:20 +03:00
|
|
|
if (backup_in_progress)
|
2017-05-19 12:08:16 +03:00
|
|
|
{
|
2018-03-26 19:50:49 +03:00
|
|
|
elog(WARNING, "backup in progress, stop backup");
|
2017-05-19 12:08:16 +03:00
|
|
|
pg_stop_backup(NULL); /* don't care stop_lsn on error case */
|
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
2017-03-21 11:54:49 +03:00
|
|
|
/*
|
|
|
|
* Disconnect backup connection during quit pg_probackup.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
backup_disconnect(bool fatal, void *userdata)
|
|
|
|
{
|
|
|
|
pgut_disconnect(backup_conn);
|
2017-06-07 16:50:33 +03:00
|
|
|
if (master_conn)
|
|
|
|
pgut_disconnect(master_conn);
|
2017-03-21 11:54:49 +03:00
|
|
|
}
|
|
|
|
|
2014-01-30 15:58:55 +09:00
|
|
|
/*
|
2017-04-18 11:41:02 +03:00
|
|
|
* Take a backup of the PGDATA at a file level.
|
|
|
|
* Copy all directories and files listed in backup_files_list.
|
|
|
|
* If the file is 'datafile' (regular relation's main fork), read it page by page,
|
|
|
|
* verify checksum and copy.
|
|
|
|
* In incremental backup mode, copy only files or datafiles' pages changed after
|
|
|
|
* previous backup.
|
2014-01-30 15:58:55 +09:00
|
|
|
*/
|
2018-06-07 19:13:11 +03:00
|
|
|
static void *
|
2016-02-29 20:23:48 +03:00
|
|
|
backup_files(void *arg)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2018-05-30 13:43:52 +03:00
|
|
|
int i;
|
|
|
|
backup_files_arg *arguments = (backup_files_arg *) arg;
|
|
|
|
int n_backup_files_list = parray_num(arguments->files_list);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-09-28 17:40:24 +03:00
|
|
|
/* backup a file */
|
2017-04-18 11:41:02 +03:00
|
|
|
for (i = 0; i < n_backup_files_list; i++)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct stat buf;
|
2018-05-30 13:43:52 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(arguments->files_list, i);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-07-18 19:44:11 +03:00
|
|
|
if (!pg_atomic_test_set_flag(&file->lock))
|
2016-09-02 20:38:39 +03:00
|
|
|
continue;
|
2018-12-25 16:44:37 +03:00
|
|
|
elog(VERBOSE, "Copying file: \"%s\" ", file->path);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
|
|
|
/* check for interrupt */
|
|
|
|
if (interrupted)
|
2016-01-19 12:41:30 +09:00
|
|
|
elog(ERROR, "interrupted during backup");
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
if (progress)
|
2018-10-31 09:47:53 +03:00
|
|
|
elog(INFO, "Progress: (%d/%d). Process file \"%s\"",
|
2017-04-18 11:41:02 +03:00
|
|
|
i + 1, n_backup_files_list, file->path);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* stat file to check its current state */
|
2019-01-27 01:11:52 +03:00
|
|
|
ret = fio_stat(file->path, &buf, true, FIO_DB_HOST);
|
2012-05-18 08:54:36 +00:00
|
|
|
if (ret == -1)
|
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
{
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
|
|
|
* If file is not found, this is not en error.
|
|
|
|
* It could have been deleted by concurrent postgres transaction.
|
|
|
|
*/
|
2019-02-09 12:18:07 +03:00
|
|
|
file->write_size = FILE_NOT_FOUND;
|
2017-04-18 11:41:02 +03:00
|
|
|
elog(LOG, "File \"%s\" is not found", file->path);
|
2012-05-18 08:54:36 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-01-19 12:41:30 +09:00
|
|
|
elog(ERROR,
|
2017-04-18 11:41:02 +03:00
|
|
|
"can't stat file to backup \"%s\": %s",
|
2012-05-18 08:54:36 +00:00
|
|
|
file->path, strerror(errno));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/* We have already copied all directories */
|
2012-05-18 08:54:36 +00:00
|
|
|
if (S_ISDIR(buf.st_mode))
|
2016-02-29 20:23:48 +03:00
|
|
|
continue;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
|
|
|
if (S_ISREG(buf.st_mode))
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2018-11-15 11:46:42 +03:00
|
|
|
pgFile **prev_file = NULL;
|
2018-10-10 19:13:17 +03:00
|
|
|
|
2018-03-06 16:10:58 +03:00
|
|
|
/* Check that file exist in previous backup */
|
2018-03-16 11:53:43 +03:00
|
|
|
if (current.backup_mode != BACKUP_MODE_FULL)
|
2018-03-06 16:10:58 +03:00
|
|
|
{
|
|
|
|
char *relative;
|
2018-08-02 11:57:39 +03:00
|
|
|
pgFile key;
|
2018-05-30 13:43:52 +03:00
|
|
|
|
2018-03-06 16:10:58 +03:00
|
|
|
relative = GetRelativePath(file->path, arguments->from_root);
|
2018-08-02 11:57:39 +03:00
|
|
|
key.path = relative;
|
2018-05-30 13:43:52 +03:00
|
|
|
|
2018-08-02 11:57:39 +03:00
|
|
|
prev_file = (pgFile **) parray_bsearch(arguments->prev_filelist,
|
|
|
|
&key, pgFileComparePath);
|
|
|
|
if (prev_file)
|
|
|
|
/* File exists in previous backup */
|
|
|
|
file->exists_in_prev = true;
|
2018-03-06 16:10:58 +03:00
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
/* copy the file into backup */
|
2017-10-19 13:33:31 +03:00
|
|
|
if (file->is_datafile && !file->is_cfs)
|
2017-02-13 11:44:53 +03:00
|
|
|
{
|
2018-08-02 11:57:39 +03:00
|
|
|
char to_path[MAXPGPATH];
|
|
|
|
|
|
|
|
join_path_components(to_path, arguments->to_root,
|
|
|
|
file->path + strlen(arguments->from_root) + 1);
|
|
|
|
|
2017-10-19 13:33:31 +03:00
|
|
|
/* backup block by block if datafile AND not compressed by cfs*/
|
2018-08-02 11:57:39 +03:00
|
|
|
if (!backup_data_file(arguments, to_path, file,
|
2018-05-30 13:43:52 +03:00
|
|
|
arguments->prev_start_lsn,
|
2018-08-02 11:57:39 +03:00
|
|
|
current.backup_mode,
|
2018-11-01 19:10:20 +03:00
|
|
|
instance_config.compress_alg,
|
|
|
|
instance_config.compress_level))
|
2017-02-13 11:44:53 +03:00
|
|
|
{
|
2019-02-09 12:18:07 +03:00
|
|
|
/* disappeared file not to be confused with 'not changed' */
|
|
|
|
if (file->write_size != FILE_NOT_FOUND)
|
|
|
|
file->write_size = BYTES_INVALID;
|
2018-02-06 20:37:45 +03:00
|
|
|
elog(VERBOSE, "File \"%s\" was not copied to backup", file->path);
|
2017-02-13 11:44:53 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2018-11-26 14:17:46 +03:00
|
|
|
else if (strcmp(file->name, "pg_control") == 0)
|
|
|
|
copy_pgcontrol_file(arguments->from_root, arguments->to_root,
|
2018-11-29 19:58:40 +03:00
|
|
|
file, FIO_BACKUP_HOST);
|
2018-10-26 17:49:39 +03:00
|
|
|
else
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2018-11-26 14:17:46 +03:00
|
|
|
bool skip = false;
|
2018-10-10 19:13:17 +03:00
|
|
|
|
|
|
|
/* If non-data file has not changed since last backup... */
|
2018-11-15 11:46:42 +03:00
|
|
|
if (prev_file && file->exists_in_prev &&
|
2018-10-10 19:13:17 +03:00
|
|
|
buf.st_mtime < current.parent_backup)
|
|
|
|
{
|
2019-01-18 00:37:18 +03:00
|
|
|
calc_file_checksum(file, FIO_DB_HOST);
|
2018-10-10 19:13:17 +03:00
|
|
|
/* ...and checksum is the same... */
|
2018-10-26 17:49:39 +03:00
|
|
|
if (EQ_TRADITIONAL_CRC32(file->crc, (*prev_file)->crc))
|
2018-10-10 19:13:17 +03:00
|
|
|
skip = true; /* ...skip copying file. */
|
|
|
|
}
|
|
|
|
if (skip ||
|
2019-03-07 11:01:07 +03:00
|
|
|
!copy_file(arguments->from_root, FIO_DB_HOST, arguments->to_root, FIO_BACKUP_HOST, file))
|
2018-10-10 19:13:17 +03:00
|
|
|
{
|
2019-02-09 12:18:07 +03:00
|
|
|
/* disappeared file not to be confused with 'not changed' */
|
|
|
|
if (file->write_size != FILE_NOT_FOUND)
|
|
|
|
file->write_size = BYTES_INVALID;
|
2018-10-10 19:13:17 +03:00
|
|
|
elog(VERBOSE, "File \"%s\" was not copied to backup",
|
|
|
|
file->path);
|
|
|
|
continue;
|
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
2018-06-09 15:14:44 +03:00
|
|
|
elog(VERBOSE, "File \"%s\". Copied "INT64_FORMAT " bytes",
|
2018-06-09 12:32:40 +03:00
|
|
|
file->path, file->write_size);
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
else
|
2018-10-31 09:47:53 +03:00
|
|
|
elog(WARNING, "unexpected file type %d", buf.st_mode);
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
2018-01-23 13:39:49 +03:00
|
|
|
|
|
|
|
/* Close connection */
|
2018-05-30 13:43:52 +03:00
|
|
|
if (arguments->backup_conn)
|
|
|
|
pgut_disconnect(arguments->backup_conn);
|
2018-01-23 13:39:49 +03:00
|
|
|
|
2018-04-05 18:58:40 +03:00
|
|
|
/* Data files transferring is successful */
|
|
|
|
arguments->ret = 0;
|
2018-06-07 19:13:11 +03:00
|
|
|
|
|
|
|
return NULL;
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2017-09-28 15:12:12 +03:00
|
|
|
* Extract information about files in backup_list parsing their names:
|
|
|
|
* - remove temp tables from the list
|
2017-11-13 18:52:09 +03:00
|
|
|
* - remove unlogged tables from the list (leave the _init fork)
|
2017-10-02 00:57:38 +03:00
|
|
|
* - set flags for database directories
|
2017-09-28 15:12:12 +03:00
|
|
|
* - set flags for datafiles
|
2012-05-18 08:54:36 +00:00
|
|
|
*/
|
|
|
|
static void
|
2017-09-29 20:25:11 +03:00
|
|
|
parse_backup_filelist_filenames(parray *files, const char *root)
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2018-04-24 17:45:30 +03:00
|
|
|
size_t i = 0;
|
|
|
|
Oid unlogged_file_reloid = 0;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
while (i < parray_num(files))
|
2012-05-18 08:54:36 +00:00
|
|
|
{
|
2017-02-25 15:12:07 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(files, i);
|
2017-02-16 17:23:43 +03:00
|
|
|
char *relative;
|
2017-09-29 20:25:11 +03:00
|
|
|
int sscanf_result;
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
relative = GetRelativePath(file->path, root);
|
2012-05-18 08:54:36 +00:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
if (S_ISREG(file->mode) &&
|
|
|
|
path_is_prefix_of_path(PG_TBLSPC_DIR, relative))
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2018-04-24 17:45:30 +03:00
|
|
|
/*
|
|
|
|
* Found file in pg_tblspc/tblsOid/TABLESPACE_VERSION_DIRECTORY
|
|
|
|
* Legal only in case of 'pg_compression'
|
|
|
|
*/
|
|
|
|
if (strcmp(file->name, "pg_compression") == 0)
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2018-04-24 17:45:30 +03:00
|
|
|
Oid tblspcOid;
|
|
|
|
Oid dbOid;
|
|
|
|
char tmp_rel_path[MAXPGPATH];
|
|
|
|
/*
|
|
|
|
* Check that the file is located under
|
|
|
|
* TABLESPACE_VERSION_DIRECTORY
|
|
|
|
*/
|
|
|
|
sscanf_result = sscanf(relative, PG_TBLSPC_DIR "/%u/%s/%u",
|
|
|
|
&tblspcOid, tmp_rel_path, &dbOid);
|
2017-10-19 13:33:31 +03:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
/* Yes, it is */
|
|
|
|
if (sscanf_result == 2 &&
|
2018-10-09 17:42:53 +03:00
|
|
|
strncmp(tmp_rel_path, TABLESPACE_VERSION_DIRECTORY,
|
|
|
|
strlen(TABLESPACE_VERSION_DIRECTORY)) == 0)
|
2018-04-24 17:45:30 +03:00
|
|
|
set_cfs_datafiles(files, root, relative, i);
|
2016-02-27 21:07:55 +03:00
|
|
|
}
|
2017-09-29 20:25:11 +03:00
|
|
|
}
|
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
if (S_ISREG(file->mode) && file->tblspcOid != 0 &&
|
|
|
|
file->name && file->name[0])
|
2016-09-06 16:30:54 +03:00
|
|
|
{
|
2018-04-24 17:45:30 +03:00
|
|
|
if (strcmp(file->forkName, "init") == 0)
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2017-10-02 18:31:46 +03:00
|
|
|
/*
|
2018-04-24 17:45:30 +03:00
|
|
|
* Do not backup files of unlogged relations.
|
|
|
|
* scan filelist backward and exclude these files.
|
2017-10-02 18:31:46 +03:00
|
|
|
*/
|
2018-04-24 17:45:30 +03:00
|
|
|
int unlogged_file_num = i - 1;
|
|
|
|
pgFile *unlogged_file = (pgFile *) parray_get(files,
|
|
|
|
unlogged_file_num);
|
2017-02-13 11:44:53 +03:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
unlogged_file_reloid = file->relOid;
|
2017-11-13 18:52:09 +03:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
while (unlogged_file_num >= 0 &&
|
|
|
|
(unlogged_file_reloid != 0) &&
|
|
|
|
(unlogged_file->relOid == unlogged_file_reloid))
|
2017-02-16 17:23:43 +03:00
|
|
|
{
|
2018-04-24 17:45:30 +03:00
|
|
|
pgFileFree(unlogged_file);
|
|
|
|
parray_remove(files, unlogged_file_num);
|
2017-11-13 18:52:09 +03:00
|
|
|
|
2018-04-24 17:45:30 +03:00
|
|
|
unlogged_file_num--;
|
2017-11-13 18:52:09 +03:00
|
|
|
i--;
|
2018-04-24 17:45:30 +03:00
|
|
|
|
|
|
|
unlogged_file = (pgFile *) parray_get(files,
|
|
|
|
unlogged_file_num);
|
2017-11-13 18:52:09 +03:00
|
|
|
}
|
2017-09-29 20:25:11 +03:00
|
|
|
}
|
2017-02-16 17:23:43 +03:00
|
|
|
}
|
2018-04-24 17:45:30 +03:00
|
|
|
|
|
|
|
i++;
|
2017-10-19 13:33:31 +03:00
|
|
|
}
|
2012-05-18 08:54:36 +00:00
|
|
|
}
|
|
|
|
|
2017-10-19 13:33:31 +03:00
|
|
|
/* If file is equal to pg_compression, then we consider this tablespace as
|
|
|
|
* cfs-compressed and should mark every file in this tablespace as cfs-file
|
|
|
|
* Setting is_cfs is done via going back through 'files' set every file
|
|
|
|
* that contain cfs_tablespace in his path as 'is_cfs'
|
|
|
|
* Goings back through array 'files' is valid option possible because of current
|
|
|
|
* sort rules:
|
|
|
|
* tblspcOid/TABLESPACE_VERSION_DIRECTORY
|
|
|
|
* tblspcOid/TABLESPACE_VERSION_DIRECTORY/dboid
|
|
|
|
* tblspcOid/TABLESPACE_VERSION_DIRECTORY/dboid/1
|
|
|
|
* tblspcOid/TABLESPACE_VERSION_DIRECTORY/dboid/1.cfm
|
|
|
|
* tblspcOid/TABLESPACE_VERSION_DIRECTORY/pg_compression
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_cfs_datafiles(parray *files, const char *root, char *relative, size_t i)
|
|
|
|
{
|
2017-11-01 11:50:37 +03:00
|
|
|
int len;
|
|
|
|
int p;
|
|
|
|
pgFile *prev_file;
|
|
|
|
char *cfs_tblspc_path;
|
|
|
|
char *relative_prev_file;
|
2017-10-19 13:33:31 +03:00
|
|
|
|
|
|
|
cfs_tblspc_path = strdup(relative);
|
2018-02-02 17:33:45 +03:00
|
|
|
if(!cfs_tblspc_path)
|
|
|
|
elog(ERROR, "Out of memory");
|
2017-10-19 13:33:31 +03:00
|
|
|
len = strlen("/pg_compression");
|
|
|
|
cfs_tblspc_path[strlen(cfs_tblspc_path) - len] = 0;
|
|
|
|
elog(VERBOSE, "CFS DIRECTORY %s, pg_compression path: %s", cfs_tblspc_path, relative);
|
|
|
|
|
2017-11-01 11:50:37 +03:00
|
|
|
for (p = (int) i; p >= 0; p--)
|
2017-10-19 13:33:31 +03:00
|
|
|
{
|
2017-11-01 11:50:37 +03:00
|
|
|
prev_file = (pgFile *) parray_get(files, (size_t) p);
|
2017-10-19 13:33:31 +03:00
|
|
|
relative_prev_file = GetRelativePath(prev_file->path, root);
|
2017-10-23 10:21:39 +03:00
|
|
|
|
|
|
|
elog(VERBOSE, "Checking file in cfs tablespace %s", relative_prev_file);
|
|
|
|
|
2017-10-19 13:33:31 +03:00
|
|
|
if (strstr(relative_prev_file, cfs_tblspc_path) != NULL)
|
|
|
|
{
|
|
|
|
if (S_ISREG(prev_file->mode) && prev_file->is_datafile)
|
|
|
|
{
|
2017-10-23 10:21:39 +03:00
|
|
|
elog(VERBOSE, "Setting 'is_cfs' on file %s, name %s",
|
|
|
|
relative_prev_file, prev_file->name);
|
2017-10-19 13:33:31 +03:00
|
|
|
prev_file->is_cfs = true;
|
|
|
|
}
|
|
|
|
}
|
2017-10-23 10:21:39 +03:00
|
|
|
else
|
|
|
|
{
|
|
|
|
elog(VERBOSE, "Breaking on %s", relative_prev_file);
|
|
|
|
break;
|
|
|
|
}
|
2017-10-19 13:33:31 +03:00
|
|
|
}
|
2017-10-23 10:21:39 +03:00
|
|
|
free(cfs_tblspc_path);
|
2017-10-19 13:33:31 +03:00
|
|
|
}
|
|
|
|
|
2016-01-15 23:47:38 +09:00
|
|
|
/*
|
2017-04-18 11:41:02 +03:00
|
|
|
* Find pgfile by given rnode in the backup_files_list
|
|
|
|
* and add given blkno to its pagemap.
|
2016-01-15 23:47:38 +09:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
process_block_change(ForkNumber forknum, RelFileNode rnode, BlockNumber blkno)
|
|
|
|
{
|
2018-05-25 18:56:35 +03:00
|
|
|
char *path;
|
|
|
|
char *rel_path;
|
2016-01-15 23:47:38 +09:00
|
|
|
BlockNumber blkno_inseg;
|
|
|
|
int segno;
|
2018-05-28 15:19:03 +03:00
|
|
|
pgFile **file_item;
|
|
|
|
pgFile f;
|
2016-01-15 23:47:38 +09:00
|
|
|
|
|
|
|
segno = blkno / RELSEG_SIZE;
|
|
|
|
blkno_inseg = blkno % RELSEG_SIZE;
|
|
|
|
|
2018-05-28 15:19:03 +03:00
|
|
|
rel_path = relpathperm(rnode, forknum);
|
|
|
|
if (segno > 0)
|
2018-11-01 19:10:20 +03:00
|
|
|
path = psprintf("%s/%s.%u", instance_config.pgdata, rel_path, segno);
|
2018-05-25 18:56:35 +03:00
|
|
|
else
|
2018-11-01 19:10:20 +03:00
|
|
|
path = psprintf("%s/%s", instance_config.pgdata, rel_path);
|
2018-05-25 18:56:35 +03:00
|
|
|
|
2018-05-28 15:19:03 +03:00
|
|
|
pg_free(rel_path);
|
|
|
|
|
|
|
|
f.path = path;
|
|
|
|
/* backup_files_list should be sorted before */
|
|
|
|
file_item = (pgFile **) parray_bsearch(backup_files_list, &f,
|
|
|
|
pgFileComparePath);
|
2016-01-15 23:47:38 +09:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If we don't have any record of this file in the file map, it means
|
|
|
|
* that it's a relation that did not have much activity since the last
|
|
|
|
* backup. We can safely ignore it. If it is a new relation file, the
|
|
|
|
* backup would simply copy it as-is.
|
|
|
|
*/
|
|
|
|
if (file_item)
|
2018-05-29 18:44:08 +03:00
|
|
|
{
|
|
|
|
/* We need critical section only we use more than one threads */
|
|
|
|
if (num_threads > 1)
|
2018-06-18 11:47:29 +03:00
|
|
|
pthread_lock(&backup_pagemap_mutex);
|
2018-05-29 18:44:08 +03:00
|
|
|
|
2018-05-28 15:19:03 +03:00
|
|
|
datapagemap_add(&(*file_item)->pagemap, blkno_inseg);
|
2016-01-15 23:47:38 +09:00
|
|
|
|
2018-05-29 18:44:08 +03:00
|
|
|
if (num_threads > 1)
|
|
|
|
pthread_mutex_unlock(&backup_pagemap_mutex);
|
|
|
|
}
|
|
|
|
|
2016-01-15 23:47:38 +09:00
|
|
|
pg_free(path);
|
|
|
|
}
|
2016-02-27 21:07:55 +03:00
|
|
|
|
2017-06-07 16:28:22 +03:00
|
|
|
/*
|
|
|
|
* Given a list of files in the instance to backup, build a pagemap for each
|
|
|
|
* data file that has ptrack. Result is saved in the pagemap field of pgFile.
|
2017-09-29 20:25:11 +03:00
|
|
|
* NOTE we rely on the fact that provided parray is sorted by file->path.
|
2017-06-07 16:28:22 +03:00
|
|
|
*/
|
2017-02-25 15:12:07 +03:00
|
|
|
static void
|
|
|
|
make_pagemap_from_ptrack(parray *files)
|
2016-02-27 21:07:55 +03:00
|
|
|
{
|
2017-04-24 18:22:34 +03:00
|
|
|
size_t i;
|
2017-09-29 20:25:11 +03:00
|
|
|
Oid dbOid_with_ptrack_init = 0;
|
|
|
|
Oid tblspcOid_with_ptrack_init = 0;
|
2017-11-09 12:47:37 +03:00
|
|
|
char *ptrack_nonparsed = NULL;
|
|
|
|
size_t ptrack_nonparsed_size = 0;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2018-02-06 20:37:45 +03:00
|
|
|
elog(LOG, "Compiling pagemap");
|
2016-02-27 21:07:55 +03:00
|
|
|
for (i = 0; i < parray_num(files); i++)
|
|
|
|
{
|
2017-09-29 20:25:11 +03:00
|
|
|
pgFile *file = (pgFile *) parray_get(files, i);
|
|
|
|
size_t start_addr;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
/*
|
|
|
|
* If there is a ptrack_init file in the database,
|
|
|
|
* we must backup all its files, ignoring ptrack files for relations.
|
|
|
|
*/
|
|
|
|
if (file->is_database)
|
2016-02-27 21:07:55 +03:00
|
|
|
{
|
2017-10-01 18:28:39 +03:00
|
|
|
char *filename = strrchr(file->path, '/');
|
|
|
|
|
|
|
|
Assert(filename != NULL);
|
|
|
|
filename++;
|
|
|
|
|
2017-04-18 11:41:02 +03:00
|
|
|
/*
|
2017-09-29 20:25:11 +03:00
|
|
|
* The function pg_ptrack_get_and_clear_db returns true
|
2017-10-02 00:57:38 +03:00
|
|
|
* if there was a ptrack_init file.
|
2017-10-02 18:31:46 +03:00
|
|
|
* Also ignore ptrack files for global tablespace,
|
|
|
|
* to avoid any possible specific errors.
|
2017-04-18 11:41:02 +03:00
|
|
|
*/
|
2017-10-03 17:57:48 +03:00
|
|
|
if ((file->tblspcOid == GLOBALTABLESPACE_OID) ||
|
2017-10-02 00:57:38 +03:00
|
|
|
pg_ptrack_get_and_clear_db(file->dbOid, file->tblspcOid))
|
2017-04-24 18:22:34 +03:00
|
|
|
{
|
2017-09-29 20:25:11 +03:00
|
|
|
dbOid_with_ptrack_init = file->dbOid;
|
|
|
|
tblspcOid_with_ptrack_init = file->tblspcOid;
|
2017-04-24 18:22:34 +03:00
|
|
|
}
|
2017-09-29 20:25:11 +03:00
|
|
|
}
|
2017-04-24 18:22:34 +03:00
|
|
|
|
2017-10-03 17:57:48 +03:00
|
|
|
if (file->is_datafile)
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2018-06-29 14:01:08 +03:00
|
|
|
if (file->tblspcOid == tblspcOid_with_ptrack_init &&
|
|
|
|
file->dbOid == dbOid_with_ptrack_init)
|
2017-11-10 14:13:44 +03:00
|
|
|
{
|
|
|
|
/* ignore ptrack if ptrack_init exists */
|
|
|
|
elog(VERBOSE, "Ignoring ptrack because of ptrack_init for file: %s", file->path);
|
2018-06-29 14:01:08 +03:00
|
|
|
file->pagemap_isabsent = true;
|
2017-11-10 14:13:44 +03:00
|
|
|
continue;
|
|
|
|
}
|
2017-11-09 12:47:37 +03:00
|
|
|
|
2017-09-29 20:25:11 +03:00
|
|
|
/* get ptrack bitmap once for all segments of the file */
|
|
|
|
if (file->segno == 0)
|
2017-09-26 20:50:06 +03:00
|
|
|
{
|
2017-09-29 20:25:11 +03:00
|
|
|
/* release previous value */
|
2017-11-09 12:47:37 +03:00
|
|
|
pg_free(ptrack_nonparsed);
|
|
|
|
ptrack_nonparsed_size = 0;
|
2017-09-29 20:25:11 +03:00
|
|
|
|
|
|
|
ptrack_nonparsed = pg_ptrack_get_and_clear(file->tblspcOid, file->dbOid,
|
|
|
|
file->relOid, &ptrack_nonparsed_size);
|
2017-09-26 20:50:06 +03:00
|
|
|
}
|
2017-06-07 16:28:22 +03:00
|
|
|
|
2017-09-01 13:04:30 +03:00
|
|
|
if (ptrack_nonparsed != NULL)
|
|
|
|
{
|
2017-11-09 12:47:37 +03:00
|
|
|
/*
|
2017-11-10 14:13:44 +03:00
|
|
|
* pg_ptrack_get_and_clear() returns ptrack with VARHDR cutted out.
|
|
|
|
* Compute the beginning of the ptrack map related to this segment
|
|
|
|
*
|
|
|
|
* HEAPBLOCKS_PER_BYTE. Number of heap pages one ptrack byte can track: 8
|
|
|
|
* RELSEG_SIZE. Number of Pages per segment: 131072
|
|
|
|
* RELSEG_SIZE/HEAPBLOCKS_PER_BYTE. number of bytes in ptrack file needed
|
|
|
|
* to keep track on one relsegment: 16384
|
|
|
|
*/
|
2017-11-09 12:47:37 +03:00
|
|
|
start_addr = (RELSEG_SIZE/HEAPBLOCKS_PER_BYTE)*file->segno;
|
|
|
|
|
2018-07-30 19:22:08 +03:00
|
|
|
/*
|
|
|
|
* If file segment was created after we have read ptrack,
|
|
|
|
* we won't have a bitmap for this segment.
|
|
|
|
*/
|
|
|
|
if (start_addr > ptrack_nonparsed_size)
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2018-07-30 19:22:08 +03:00
|
|
|
elog(VERBOSE, "Ptrack is missing for file: %s", file->path);
|
|
|
|
file->pagemap_isabsent = true;
|
2017-09-29 20:25:11 +03:00
|
|
|
}
|
2017-09-01 13:04:30 +03:00
|
|
|
else
|
2017-09-29 20:25:11 +03:00
|
|
|
{
|
2017-11-09 12:47:37 +03:00
|
|
|
|
2018-07-30 19:22:08 +03:00
|
|
|
if (start_addr + RELSEG_SIZE/HEAPBLOCKS_PER_BYTE > ptrack_nonparsed_size)
|
|
|
|
{
|
|
|
|
file->pagemap.bitmapsize = ptrack_nonparsed_size - start_addr;
|
|
|
|
elog(VERBOSE, "pagemap size: %i", file->pagemap.bitmapsize);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
file->pagemap.bitmapsize = RELSEG_SIZE/HEAPBLOCKS_PER_BYTE;
|
|
|
|
elog(VERBOSE, "pagemap size: %i", file->pagemap.bitmapsize);
|
|
|
|
}
|
|
|
|
|
|
|
|
file->pagemap.bitmap = pg_malloc(file->pagemap.bitmapsize);
|
|
|
|
memcpy(file->pagemap.bitmap, ptrack_nonparsed+start_addr, file->pagemap.bitmapsize);
|
|
|
|
}
|
2017-09-01 13:04:30 +03:00
|
|
|
}
|
2017-11-09 12:47:37 +03:00
|
|
|
else
|
2017-11-10 14:13:44 +03:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If ptrack file is missing, try to copy the entire file.
|
|
|
|
* It can happen in two cases:
|
|
|
|
* - files were created by commands that bypass buffer manager
|
|
|
|
* and, correspondingly, ptrack mechanism.
|
|
|
|
* i.e. CREATE DATABASE
|
|
|
|
* - target relation was deleted.
|
|
|
|
*/
|
|
|
|
elog(VERBOSE, "Ptrack is missing for file: %s", file->path);
|
2018-06-29 14:01:08 +03:00
|
|
|
file->pagemap_isabsent = true;
|
2017-11-10 14:13:44 +03:00
|
|
|
}
|
2016-02-27 21:07:55 +03:00
|
|
|
}
|
|
|
|
}
|
2018-02-06 20:37:45 +03:00
|
|
|
elog(LOG, "Pagemap compiled");
|
|
|
|
// res = pgut_execute(backup_conn, "SET client_min_messages = warning;", 0, NULL, true);
|
|
|
|
// PQclear(pgut_execute(backup_conn, "CHECKPOINT;", 0, NULL, true));
|
2016-02-27 21:07:55 +03:00
|
|
|
}
|
2016-05-26 15:56:32 +03:00
|
|
|
|
|
|
|
|
2017-04-21 14:54:33 +03:00
|
|
|
/*
|
|
|
|
* Stop WAL streaming if current 'xlogpos' exceeds 'stop_backup_lsn', which is
|
|
|
|
* set by pg_stop_backup().
|
|
|
|
*/
|
2016-05-26 15:56:32 +03:00
|
|
|
static bool
|
|
|
|
stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
|
|
|
|
{
|
|
|
|
static uint32 prevtimeline = 0;
|
|
|
|
static XLogRecPtr prevpos = InvalidXLogRecPtr;
|
|
|
|
|
2018-04-26 16:30:52 +03:00
|
|
|
/* check for interrupt */
|
|
|
|
if (interrupted)
|
|
|
|
elog(ERROR, "Interrupted during backup");
|
|
|
|
|
2016-05-26 15:56:32 +03:00
|
|
|
/* we assume that we get called once at the end of each segment */
|
2017-04-26 17:00:06 +03:00
|
|
|
if (segment_finished)
|
2018-04-04 12:40:35 +03:00
|
|
|
elog(VERBOSE, _("finished segment at %X/%X (timeline %u)"),
|
2017-04-26 17:00:06 +03:00
|
|
|
(uint32) (xlogpos >> 32), (uint32) xlogpos, timeline);
|
2016-05-26 15:56:32 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Note that we report the previous, not current, position here. After a
|
|
|
|
* timeline switch, xlogpos points to the beginning of the segment because
|
|
|
|
* that's where we always begin streaming. Reporting the end of previous
|
|
|
|
* timeline isn't totally accurate, because the next timeline can begin
|
|
|
|
* slightly before the end of the WAL that we received on the previous
|
|
|
|
* timeline, but it's close enough for reporting purposes.
|
|
|
|
*/
|
|
|
|
if (prevtimeline != 0 && prevtimeline != timeline)
|
2017-04-26 17:00:06 +03:00
|
|
|
elog(LOG, _("switched to timeline %u at %X/%X\n"),
|
|
|
|
timeline, (uint32) (prevpos >> 32), (uint32) prevpos);
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2017-05-29 18:53:48 +03:00
|
|
|
if (!XLogRecPtrIsInvalid(stop_backup_lsn))
|
|
|
|
{
|
2018-11-22 14:44:57 +03:00
|
|
|
if (xlogpos >= stop_backup_lsn)
|
2018-04-04 12:40:35 +03:00
|
|
|
{
|
2018-04-04 17:06:50 +03:00
|
|
|
stop_stream_lsn = xlogpos;
|
2017-05-29 18:53:48 +03:00
|
|
|
return true;
|
2018-04-04 12:40:35 +03:00
|
|
|
}
|
2017-05-29 18:53:48 +03:00
|
|
|
|
|
|
|
/* pg_stop_backup() was executed, wait for the completion of stream */
|
|
|
|
if (stream_stop_timeout == 0)
|
|
|
|
{
|
|
|
|
elog(INFO, "Wait for LSN %X/%X to be streamed",
|
|
|
|
(uint32) (stop_backup_lsn >> 32), (uint32) stop_backup_lsn);
|
|
|
|
|
|
|
|
stream_stop_timeout = checkpoint_timeout();
|
|
|
|
stream_stop_timeout = stream_stop_timeout + stream_stop_timeout * 0.1;
|
|
|
|
|
|
|
|
stream_stop_begin = time(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (time(NULL) - stream_stop_begin > stream_stop_timeout)
|
|
|
|
elog(ERROR, "Target LSN %X/%X could not be streamed in %d seconds",
|
|
|
|
(uint32) (stop_backup_lsn >> 32), (uint32) stop_backup_lsn,
|
|
|
|
stream_stop_timeout);
|
|
|
|
}
|
2016-05-26 15:56:32 +03:00
|
|
|
|
|
|
|
prevtimeline = timeline;
|
|
|
|
prevpos = xlogpos;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start the log streaming
|
|
|
|
*/
|
2018-06-09 15:45:03 +03:00
|
|
|
static void *
|
2016-05-26 15:56:32 +03:00
|
|
|
StreamLog(void *arg)
|
|
|
|
{
|
|
|
|
XLogRecPtr startpos;
|
|
|
|
TimeLineID starttli;
|
2018-04-05 18:58:40 +03:00
|
|
|
StreamThreadArg *stream_arg = (StreamThreadArg *) arg;
|
2017-04-18 11:41:02 +03:00
|
|
|
|
2016-10-13 17:25:53 +03:00
|
|
|
/*
|
|
|
|
* We must use startpos as start_lsn from start_backup
|
|
|
|
*/
|
|
|
|
startpos = current.start_lsn;
|
2018-04-10 19:02:00 +03:00
|
|
|
starttli = current.tli;
|
2016-05-26 15:56:32 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Always start streaming at the beginning of a segment
|
|
|
|
*/
|
2018-11-01 19:10:20 +03:00
|
|
|
startpos -= startpos % instance_config.xlog_seg_size;
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2017-05-29 18:53:48 +03:00
|
|
|
/* Initialize timeout */
|
|
|
|
stream_stop_timeout = 0;
|
|
|
|
stream_stop_begin = 0;
|
|
|
|
|
2019-03-01 16:09:53 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
/* if slot name was not provided for temp slot, use default slot name */
|
|
|
|
if (!replication_slot && temp_slot)
|
|
|
|
replication_slot = "pg_probackup_slot";
|
|
|
|
#endif
|
|
|
|
|
2019-03-01 18:40:29 +03:00
|
|
|
|
|
|
|
#if PG_VERSION_NUM >= 110000
|
|
|
|
/* Create temp repslot */
|
|
|
|
if (temp_slot)
|
|
|
|
CreateReplicationSlot(stream_arg->conn, replication_slot,
|
|
|
|
NULL, temp_slot, true, true, false);
|
|
|
|
#endif
|
|
|
|
|
2016-05-26 15:56:32 +03:00
|
|
|
/*
|
|
|
|
* Start the replication
|
|
|
|
*/
|
2018-03-26 19:50:49 +03:00
|
|
|
elog(LOG, _("started streaming WAL at %X/%X (timeline %u)"),
|
2017-04-26 17:00:06 +03:00
|
|
|
(uint32) (startpos >> 32), (uint32) startpos, starttli);
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2016-08-30 17:21:02 +03:00
|
|
|
#if PG_VERSION_NUM >= 90600
|
2016-12-20 16:22:08 +03:00
|
|
|
{
|
2017-08-07 16:23:37 +03:00
|
|
|
StreamCtl ctl;
|
|
|
|
|
2017-08-21 16:36:25 +03:00
|
|
|
MemSet(&ctl, 0, sizeof(ctl));
|
|
|
|
|
2016-12-20 16:22:08 +03:00
|
|
|
ctl.startpos = startpos;
|
|
|
|
ctl.timeline = starttli;
|
|
|
|
ctl.sysidentifier = NULL;
|
2017-08-07 16:23:37 +03:00
|
|
|
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
2019-01-27 01:11:52 +03:00
|
|
|
ctl.walmethod = CreateWalDirectoryMethod(stream_arg->basedir, 0, true);
|
2017-08-07 16:23:37 +03:00
|
|
|
ctl.replication_slot = replication_slot;
|
2017-08-21 16:36:25 +03:00
|
|
|
ctl.stop_socket = PGINVALID_SOCKET;
|
2019-03-01 18:40:29 +03:00
|
|
|
#if PG_VERSION_NUM >= 100000 && PG_VERSION_NUM < 110000
|
2019-03-01 16:09:53 +03:00
|
|
|
ctl.temp_slot = temp_slot;
|
2019-03-01 18:40:29 +03:00
|
|
|
#endif
|
2017-08-07 16:23:37 +03:00
|
|
|
#else
|
2018-04-09 00:10:52 +03:00
|
|
|
ctl.basedir = (char *) stream_arg->basedir;
|
2017-08-07 16:23:37 +03:00
|
|
|
#endif
|
|
|
|
|
2016-12-20 16:22:08 +03:00
|
|
|
ctl.stream_stop = stop_streaming;
|
|
|
|
ctl.standby_message_timeout = standby_message_timeout;
|
|
|
|
ctl.partial_suffix = NULL;
|
|
|
|
ctl.synchronous = false;
|
|
|
|
ctl.mark_done = false;
|
2017-08-07 16:23:37 +03:00
|
|
|
|
2018-04-10 19:02:00 +03:00
|
|
|
if(ReceiveXlogStream(stream_arg->conn, &ctl) == false)
|
2017-05-24 14:04:21 +03:00
|
|
|
elog(ERROR, "Problem in receivexlog");
|
2017-08-07 16:23:37 +03:00
|
|
|
|
|
|
|
#if PG_VERSION_NUM >= 100000
|
|
|
|
if (!ctl.walmethod->finish())
|
|
|
|
elog(ERROR, "Could not finish writing WAL files: %s",
|
|
|
|
strerror(errno));
|
|
|
|
#endif
|
2016-12-20 16:22:08 +03:00
|
|
|
}
|
2016-08-30 17:21:02 +03:00
|
|
|
#else
|
2018-04-29 19:48:49 +03:00
|
|
|
if(ReceiveXlogStream(stream_arg->conn, startpos, starttli, NULL,
|
|
|
|
(char *) stream_arg->basedir, stop_streaming,
|
|
|
|
standby_message_timeout, NULL, false, false) == false)
|
2017-05-24 14:04:21 +03:00
|
|
|
elog(ERROR, "Problem in receivexlog");
|
2016-08-30 17:21:02 +03:00
|
|
|
#endif
|
2016-05-26 15:56:32 +03:00
|
|
|
|
2018-04-04 17:06:50 +03:00
|
|
|
elog(LOG, _("finished streaming WAL at %X/%X (timeline %u)"),
|
|
|
|
(uint32) (stop_stream_lsn >> 32), (uint32) stop_stream_lsn, starttli);
|
2018-04-05 18:58:40 +03:00
|
|
|
stream_arg->ret = 0;
|
2018-04-04 17:06:50 +03:00
|
|
|
|
2018-04-10 19:02:00 +03:00
|
|
|
PQfinish(stream_arg->conn);
|
|
|
|
stream_arg->conn = NULL;
|
2018-06-09 15:45:03 +03:00
|
|
|
|
|
|
|
return NULL;
|
2016-05-26 15:56:32 +03:00
|
|
|
}
|
2017-10-02 18:31:46 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Get lsn of the moment when ptrack was enabled the last time.
|
|
|
|
*/
|
|
|
|
static XLogRecPtr
|
|
|
|
get_last_ptrack_lsn(void)
|
|
|
|
|
|
|
|
{
|
|
|
|
PGresult *res;
|
2018-09-21 14:21:43 +03:00
|
|
|
uint32 lsn_hi;
|
|
|
|
uint32 lsn_lo;
|
2017-10-02 18:31:46 +03:00
|
|
|
XLogRecPtr lsn;
|
|
|
|
|
2018-10-29 19:19:08 +03:00
|
|
|
res = pgut_execute(backup_conn, "select pg_catalog.pg_ptrack_control_lsn()",
|
|
|
|
0, NULL);
|
2017-10-02 18:31:46 +03:00
|
|
|
|
2017-10-02 21:05:24 +03:00
|
|
|
/* Extract timeline and LSN from results of pg_start_backup() */
|
2018-09-21 14:21:43 +03:00
|
|
|
XLogDataFromLSN(PQgetvalue(res, 0, 0), &lsn_hi, &lsn_lo);
|
2017-10-02 21:05:24 +03:00
|
|
|
/* Calculate LSN */
|
2018-09-21 14:21:43 +03:00
|
|
|
lsn = ((uint64) lsn_hi) << 32 | lsn_lo;
|
2017-10-02 18:31:46 +03:00
|
|
|
|
|
|
|
PQclear(res);
|
|
|
|
return lsn;
|
|
|
|
}
|
2017-12-14 17:46:30 +03:00
|
|
|
|
|
|
|
char *
|
2018-05-30 13:43:52 +03:00
|
|
|
pg_ptrack_get_block(backup_files_arg *arguments,
|
2018-01-16 14:16:50 +03:00
|
|
|
Oid dbOid,
|
2017-12-28 15:09:10 +03:00
|
|
|
Oid tblsOid,
|
2017-12-27 18:49:30 +03:00
|
|
|
Oid relOid,
|
2017-12-14 17:46:30 +03:00
|
|
|
BlockNumber blknum,
|
|
|
|
size_t *result_size)
|
|
|
|
{
|
|
|
|
PGresult *res;
|
2017-12-28 16:28:38 +03:00
|
|
|
char *params[4];
|
2017-12-14 17:46:30 +03:00
|
|
|
char *result;
|
|
|
|
|
|
|
|
params[0] = palloc(64);
|
|
|
|
params[1] = palloc(64);
|
2017-12-27 18:49:30 +03:00
|
|
|
params[2] = palloc(64);
|
2017-12-28 16:28:38 +03:00
|
|
|
params[3] = palloc(64);
|
|
|
|
|
2017-12-14 17:46:30 +03:00
|
|
|
/*
|
2018-01-16 11:54:15 +03:00
|
|
|
* Use tmp_conn, since we may work in parallel threads.
|
|
|
|
* We can connect to any database.
|
2017-12-14 17:46:30 +03:00
|
|
|
*/
|
2017-12-27 18:49:30 +03:00
|
|
|
sprintf(params[0], "%i", tblsOid);
|
2017-12-28 16:28:38 +03:00
|
|
|
sprintf(params[1], "%i", dbOid);
|
|
|
|
sprintf(params[2], "%i", relOid);
|
|
|
|
sprintf(params[3], "%u", blknum);
|
2017-12-27 18:49:30 +03:00
|
|
|
|
2018-05-30 13:43:52 +03:00
|
|
|
if (arguments->backup_conn == NULL)
|
2018-01-16 15:19:48 +03:00
|
|
|
{
|
2018-11-01 19:10:20 +03:00
|
|
|
arguments->backup_conn = pgut_connect(instance_config.pghost,
|
|
|
|
instance_config.pgport,
|
|
|
|
instance_config.pgdatabase,
|
|
|
|
instance_config.pguser);
|
2018-01-16 15:19:48 +03:00
|
|
|
}
|
2018-01-23 13:39:49 +03:00
|
|
|
|
2018-05-30 13:43:52 +03:00
|
|
|
if (arguments->cancel_conn == NULL)
|
|
|
|
arguments->cancel_conn = PQgetCancel(arguments->backup_conn);
|
2018-01-16 11:54:15 +03:00
|
|
|
|
2017-12-28 16:28:38 +03:00
|
|
|
//elog(LOG, "db %i pg_ptrack_get_block(%i, %i, %u)",dbOid, tblsOid, relOid, blknum);
|
2018-05-30 13:43:52 +03:00
|
|
|
res = pgut_execute_parallel(arguments->backup_conn,
|
|
|
|
arguments->cancel_conn,
|
2018-03-02 19:20:40 +03:00
|
|
|
"SELECT pg_catalog.pg_ptrack_get_block_2($1, $2, $3, $4)",
|
2017-12-28 16:28:38 +03:00
|
|
|
4, (const char **)params, true);
|
2017-12-14 17:46:30 +03:00
|
|
|
|
|
|
|
if (PQnfields(res) != 1)
|
2017-12-28 16:28:38 +03:00
|
|
|
{
|
2018-01-16 11:54:15 +03:00
|
|
|
elog(VERBOSE, "cannot get file block for relation oid %u",
|
2017-12-28 16:28:38 +03:00
|
|
|
relOid);
|
|
|
|
return NULL;
|
|
|
|
}
|
2017-12-14 17:46:30 +03:00
|
|
|
|
2018-01-12 21:11:09 +03:00
|
|
|
if (PQgetisnull(res, 0, 0))
|
|
|
|
{
|
2018-01-16 11:54:15 +03:00
|
|
|
elog(VERBOSE, "cannot get file block for relation oid %u",
|
2018-01-12 21:11:09 +03:00
|
|
|
relOid);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-12-14 17:46:30 +03:00
|
|
|
result = (char *) PQunescapeBytea((unsigned char *) PQgetvalue(res, 0, 0),
|
|
|
|
result_size);
|
2017-12-28 15:09:10 +03:00
|
|
|
|
2017-12-14 17:46:30 +03:00
|
|
|
PQclear(res);
|
2018-01-16 11:54:15 +03:00
|
|
|
|
2017-12-14 17:46:30 +03:00
|
|
|
pfree(params[0]);
|
|
|
|
pfree(params[1]);
|
2017-12-27 18:49:30 +03:00
|
|
|
pfree(params[2]);
|
2017-12-28 16:28:38 +03:00
|
|
|
pfree(params[3]);
|
2017-12-14 17:46:30 +03:00
|
|
|
|
|
|
|
return result;
|
2018-02-09 13:22:01 +03:00
|
|
|
}
|