Skip to content

Commit 9f038ee

Browse files
committed
Add support backup from replica.
1 parent 43b7455 commit 9f038ee

File tree

6 files changed

+129
-38
lines changed

6 files changed

+129
-38
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ server because of a reason or another. Its differential backup
1313
facility reduces the amount of data necessary to be taken between
1414
two consecutive backups.
1515

16+
Main features:
17+
* incremental backup from WAL and PTRACK
18+
* backup from replica
19+
* multithreaded backup and restore
20+
* autonomous backup without archive command (will need slot replication)
21+
1622
Download
1723
--------
1824

backup.c

Lines changed: 112 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const char *progname = "pg_arman";
4141
parray *backup_files_list;
4242
static volatile uint32 total_copy_files_increment;
4343
static uint32 total_files_num;
44+
static PGconn *start_stop_connect = NULL;
4445

4546
typedef struct
4647
{
@@ -61,7 +62,7 @@ static void confirm_block_size(const char *name, int blcksz);
6162
static void pg_start_backup(const char *label, bool smooth, pgBackup *backup);
6263
static void pg_stop_backup(pgBackup *backup);
6364
static bool pg_is_standby(void);
64-
static void get_lsn(PGresult *res, XLogRecPtr *lsn);
65+
static void get_lsn(PGconn *conn, PGresult *res, XLogRecPtr *lsn, bool stop_backup);
6566
static void get_xid(PGresult *res, uint32 *xid);
6667
static void pg_ptrack_clear(void);
6768
static char *pg_ptrack_get_and_clear(Oid tablespace_oid,
@@ -74,7 +75,7 @@ static void create_file_list(parray *files,
7475
const char *subdir,
7576
const char *prefix,
7677
bool is_append);
77-
static void wait_for_archive(pgBackup *backup, const char *sql);
78+
static void wait_for_archive(PGconn *conn, pgBackup *backup, const char *sql, bool stop_backup);
7879
static void make_pagemap_from_ptrack(parray *files);
7980
static void StreamLog(void *arg);
8081

@@ -112,7 +113,7 @@ do_backup_database(parray *backup_list, pgBackupOption bkupopt)
112113
pgBackup *prev_backup = NULL;
113114

114115
/* Block backup operations on a standby */
115-
if (pg_is_standby())
116+
if (pg_is_standby() && !from_replica)
116117
elog(ERROR, "Backup cannot run on a standby.");
117118

118119
elog(LOG, "database backup start");
@@ -164,18 +165,21 @@ do_backup_database(parray *backup_list, pgBackupOption bkupopt)
164165
strncat(label, " with pg_arman", lengthof(label));
165166
pg_start_backup(label, smooth_checkpoint, &current);
166167

167-
/* If backup_label does not exist in $PGDATA, stop taking backup */
168-
snprintf(path, lengthof(path), "%s/backup_label", pgdata);
169-
make_native_path(path);
170-
if (!fileExists(path))
171-
has_backup_label = false;
172-
173-
/* Leave if no backup file */
174-
if (!has_backup_label)
168+
if(!from_replica)
175169
{
176-
elog(LOG, "backup_label does not exist, stopping backup");
177-
pg_stop_backup(NULL);
178-
elog(ERROR, "backup_label does not exist in PGDATA.");
170+
/* If backup_label does not exist in $PGDATA, stop taking backup */
171+
snprintf(path, lengthof(path), "%s/backup_label", pgdata);
172+
make_native_path(path);
173+
if (!fileExists(path))
174+
has_backup_label = false;
175+
176+
/* Leave if no backup file */
177+
if (!has_backup_label)
178+
{
179+
elog(LOG, "backup_label does not exist, stopping backup");
180+
pg_stop_backup(NULL);
181+
elog(ERROR, "backup_label does not exist in PGDATA.");
182+
}
179183
}
180184

181185
/*
@@ -246,7 +250,7 @@ do_backup_database(parray *backup_list, pgBackupOption bkupopt)
246250
if (current.backup_mode == BACKUP_MODE_DIFF_PAGE)
247251
{
248252
/* Enforce archiving of last segment and wait for it to be here */
249-
wait_for_archive(&current, "SELECT * FROM pg_switch_xlog()");
253+
wait_for_archive(connection, &current, "SELECT * FROM pg_switch_xlog()", false);
250254

251255
/* Now build the page map */
252256
parray_qsort(backup_files_list, pgFileComparePathDesc);
@@ -563,6 +567,13 @@ check_server_version(void)
563567
(server_version / 100) % 100,
564568
server_version % 100, "9.5");
565569

570+
if (from_replica && server_version < 90600)
571+
elog(ERROR,
572+
"server version is %d.%d.%d, must be %s or higher for backup from replica.",
573+
server_version / 10000,
574+
(server_version / 100) % 100,
575+
server_version % 100, "9.6");
576+
566577
/* confirm block_size (BLCKSZ) and wal_block_size (XLOG_BLCKSZ) */
567578
confirm_block_size("block_size", BLCKSZ);
568579
confirm_block_size("wal_block_size", XLOG_BLCKSZ);
@@ -601,16 +612,27 @@ pg_start_backup(const char *label, bool smooth, pgBackup *backup)
601612

602613
params[0] = label;
603614

604-
reconnect();
615+
if (start_stop_connect == NULL)
616+
start_stop_connect = pgut_connect(ERROR);
605617

606618
/* 2nd argument is 'fast'*/
607619
params[1] = smooth ? "false" : "true";
608-
res = execute("SELECT pg_start_backup($1, $2)", 2, params);
620+
if (from_replica)
621+
res = pgut_execute(start_stop_connect,
622+
"SELECT pg_start_backup($1, $2, false)",
623+
2,
624+
params,
625+
ERROR);
626+
else
627+
res = pgut_execute(start_stop_connect,
628+
"SELECT pg_start_backup($1, $2)",
629+
2,
630+
params,
631+
ERROR);
609632

610633
if (backup != NULL)
611-
get_lsn(res, &backup->start_lsn);
634+
get_lsn(start_stop_connect, res, &backup->start_lsn, false);
612635
PQclear(res);
613-
disconnect();
614636
}
615637

616638
static void
@@ -670,7 +692,7 @@ pg_ptrack_get_and_clear(Oid tablespace_oid, Oid db_oid, Oid rel_oid, size_t *res
670692
}
671693

672694
static void
673-
wait_for_archive(pgBackup *backup, const char *sql)
695+
wait_for_archive(PGconn *conn, pgBackup *backup, const char *sql, bool stop_backup)
674696
{
675697
PGresult *res;
676698
char ready_path[MAXPGPATH];
@@ -680,17 +702,18 @@ wait_for_archive(pgBackup *backup, const char *sql)
680702
TimeLineID tli;
681703
XLogSegNo targetSegNo;
682704

683-
reconnect();
705+
if (conn == NULL)
706+
conn = pgut_connect(ERROR);
684707

685708
/* Remove annoying NOTICE messages generated by backend */
686-
res = execute("SET client_min_messages = warning;", 0, NULL);
709+
res = pgut_execute(conn, "SET client_min_messages = warning;", 0, NULL, ERROR);
687710
PQclear(res);
688711

689712
/* And execute the query wanted */
690-
res = execute(sql, 0, NULL);
713+
res = pgut_execute(conn, sql, 0, NULL, ERROR);
691714

692715
/* Get LSN from execution result */
693-
get_lsn(res, &lsn);
716+
get_lsn(conn, res, &lsn, stop_backup);
694717

695718
/*
696719
* Enforce TLI obtention if backup is not present as this code
@@ -720,13 +743,15 @@ wait_for_archive(pgBackup *backup, const char *sql)
720743

721744
PQclear(res);
722745

723-
res = execute(TXID_CURRENT_SQL, 0, NULL);
746+
if (from_replica)
747+
res = pgut_execute(conn, TXID_CURRENT_IF_SQL, 0, NULL, ERROR);
748+
else
749+
res = pgut_execute(conn, TXID_CURRENT_SQL, 0, NULL, ERROR);
724750
if (backup != NULL)
725751
{
726752
get_xid(res, &backup->recovery_xid);
727753
backup->recovery_time = time(NULL);
728754
}
729-
disconnect();
730755

731756
/* wait until switched WAL is archived */
732757
try_count = 0;
@@ -756,17 +781,22 @@ pg_stop_backup(pgBackup *backup)
756781
PGresult *res;
757782
TimeLineID tli;
758783

759-
reconnect();
784+
//reconnect();
785+
if (start_stop_connect == NULL)
786+
start_stop_connect = pgut_connect(ERROR);
760787

761788
/* Remove annoying NOTICE messages generated by backend */
762-
res = execute("SET client_min_messages = warning;", 0, NULL);
789+
res = pgut_execute(start_stop_connect, "SET client_min_messages = warning;", 0, NULL, ERROR);
763790
PQclear(res);
764791

765792
/* And execute the query wanted */
766-
res = execute("SELECT * FROM pg_stop_backup()", 0, NULL);
793+
if (from_replica)
794+
res = pgut_execute(start_stop_connect,"SELECT * FROM pg_stop_backup(false)", 0, NULL, ERROR);
795+
else
796+
res = pgut_execute(start_stop_connect,"SELECT * FROM pg_stop_backup()", 0, NULL, ERROR);
767797

768798
/* Get LSN from execution result */
769-
get_lsn(res, &stop_backup_lsn);
799+
get_lsn(start_stop_connect, res, &stop_backup_lsn, true);
770800
PQclear(res);
771801

772802
/*
@@ -786,18 +816,33 @@ pg_stop_backup(pgBackup *backup)
786816
(uint32) backup->stop_lsn);
787817
}
788818

789-
res = execute(TXID_CURRENT_SQL, 0, NULL);
819+
if (from_replica)
820+
res = pgut_execute(start_stop_connect, TXID_CURRENT_IF_SQL, 0, NULL, ERROR);
821+
else
822+
res = pgut_execute(start_stop_connect, TXID_CURRENT_SQL, 0, NULL, ERROR);
790823
if (backup != NULL)
791824
{
792825
get_xid(res, &backup->recovery_xid);
793826
backup->recovery_time = time(NULL);
794827
}
795828
PQclear(res);
796-
disconnect();
829+
//disconnect();
830+
pgut_disconnect(start_stop_connect);
797831
}
798832
else
799-
wait_for_archive(backup,
800-
"SELECT * FROM pg_stop_backup()");
833+
{
834+
if (from_replica)
835+
wait_for_archive(start_stop_connect,
836+
backup,
837+
"SELECT * FROM pg_stop_backup(false)",
838+
true);
839+
else
840+
wait_for_archive(start_stop_connect,
841+
backup,
842+
"SELECT * FROM pg_stop_backup()",
843+
true);
844+
pgut_disconnect(start_stop_connect);
845+
}
801846
}
802847

803848

@@ -818,15 +863,15 @@ pg_is_standby(void)
818863
* Get LSN from result of pg_start_backup() or pg_stop_backup().
819864
*/
820865
static void
821-
get_lsn(PGresult *res, XLogRecPtr *lsn)
866+
get_lsn(PGconn *conn, PGresult *res, XLogRecPtr *lsn, bool stop_backup)
822867
{
823868
uint32 xlogid;
824869
uint32 xrecoff;
825870

826-
if (res == NULL || PQntuples(res) != 1 || PQnfields(res) != 1)
871+
if (res == NULL || PQntuples(res) != 1 || (PQnfields(res) != 1 && PQnfields(res) != 3))
827872
elog(ERROR,
828873
"result of backup command is invalid: %s",
829-
PQerrorMessage(connection));
874+
PQerrorMessage(conn));
830875

831876
/*
832877
* Extract timeline and LSN from results of pg_stop_backup()
@@ -836,6 +881,36 @@ get_lsn(PGresult *res, XLogRecPtr *lsn)
836881
XLogDataFromLSN(PQgetvalue(res, 0, 0), &xlogid, &xrecoff);
837882
/* Calculate LSN */
838883
*lsn = (XLogRecPtr) ((uint64) xlogid << 32) | xrecoff;
884+
885+
if (stop_backup && from_replica && PQnfields(res) == 3)
886+
{
887+
char path[MAXPGPATH];
888+
char path_backup_label[MAXPGPATH];
889+
char path_tablespace_map[MAXPGPATH];
890+
FILE *fp;
891+
892+
pgBackupGetPath(&current, path, lengthof(path), DATABASE_DIR);
893+
snprintf(path_backup_label, lengthof(path_backup_label), "%s/backup_label", path);
894+
snprintf(path_tablespace_map, lengthof(path_tablespace_map), "%s/tablespace_map", path);
895+
896+
fp = fopen(path_backup_label, "w");
897+
if (fp == NULL)
898+
elog(ERROR, "can't open backup label file \"%s\": %s",
899+
path_backup_label, strerror(errno));
900+
901+
fwrite(PQgetvalue(res, 0, 1), 1, strlen(PQgetvalue(res, 0, 1)), fp);
902+
fclose(fp);
903+
if (strlen(PQgetvalue(res, 0, 2)) == 0)
904+
return;
905+
906+
fp = fopen(path_tablespace_map, "w");
907+
if (fp == NULL)
908+
elog(ERROR, "can't open tablespace map file \"%s\": %s",
909+
path_tablespace_map, strerror(errno));
910+
911+
fwrite(PQgetvalue(res, 0, 2), 1, strlen(PQgetvalue(res, 0, 2)), fp);
912+
fclose(fp);
913+
}
839914
}
840915

841916
/*

doc/pg_arman.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,14 +241,18 @@ absolute paths; relative paths are not allowed.
241241
Only files exceeded one of those settings are deleted.
242242

243243
**-j**=NUMBER / **--threads**=NUMBER:
244-
Number of threads for backup.
244+
Number of threads for backup.
245245

246246
**--stream**:
247247
Enable stream replication for save WAL during backup process.
248248

249249
**--disable-ptrack-clear**:
250250
Disable clear ptrack files for postgres without ptrack patch.
251251

252+
**--from-replica**:
253+
Use non exclusive start backup for replica. Only for 9.6 and higher.
254+
255+
252256
### RESTORE OPTIONS
253257

254258
The parameters whose name start are started with --recovery refer to

expected/option.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Backup options:
2828
--keep-data-days=DAY keep enough data backup to recover to DAY days age
2929
--disable-ptrack-clear disable clear ptrack for postgres without ptrack
3030
--backup-pg-log start backup pg_log directory
31+
--from-replica use non exclusive start backup for replica
3132

3233
Restore options:
3334
--recovery-target-time time stamp up to which recovery will proceed

pg_arman.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ static int keep_data_generations = KEEP_INFINITE;
3636
static int keep_data_days = KEEP_INFINITE;
3737
int num_threads = 1;
3838
bool stream_wal = false;
39+
bool from_replica = false;
3940
bool disable_ptrack_clear = false;
4041
static bool backup_logs = false;
4142
static bool backup_validate = false;
@@ -69,6 +70,7 @@ static pgut_option options[] =
6970
{ 'b', 10, "backup-pg-log", &backup_logs },
7071
{ 'f', 'b', "backup-mode", opt_backup_mode, SOURCE_ENV },
7172
{ 'b', 'C', "smooth-checkpoint", &smooth_checkpoint, SOURCE_ENV },
73+
{ 'b', 12, "from-replica", &from_replica },
7274
/* options with only long name (keep-xxx) */
7375
{ 'i', 1, "keep-data-generations", &keep_data_generations, SOURCE_ENV },
7476
{ 'i', 2, "keep-data-days", &keep_data_days, SOURCE_ENV },
@@ -250,6 +252,7 @@ pgut_help(bool details)
250252
printf(_(" --keep-data-days=DAY keep enough data backup to recover to DAY days age\n"));
251253
printf(_(" --disable-ptrack-clear disable clear ptrack for postgres without ptrack\n"));
252254
printf(_(" --backup-pg-log start backup pg_log directory\n"));
255+
printf(_(" --from-replica use non exclusive start backup for replica\n"));
253256
printf(_("\nRestore options:\n"));
254257
printf(_(" --recovery-target-time time stamp up to which recovery will proceed\n"));
255258
printf(_(" --recovery-target-xid transaction ID up to which recovery will proceed\n"));

pg_arman.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
/* Query to fetch current transaction ID */
2929
#define TXID_CURRENT_SQL "SELECT txid_current();"
30+
#define TXID_CURRENT_IF_SQL "SELECT txid_snapshot_xmax(txid_current_snapshot());"
3031

3132
/* Directory/File names */
3233
#define DATABASE_DIR "database"
@@ -211,6 +212,7 @@ extern parray *backup_files_list;
211212

212213
extern int num_threads;
213214
extern bool stream_wal;
215+
extern bool from_replica;
214216
extern bool disable_ptrack_clear;
215217
extern bool progress;
216218

0 commit comments

Comments
 (0)