diff options
author | Magnus Hagander <magnus@hagander.net> | 2016-04-05 20:03:49 +0200 |
---|---|---|
committer | Magnus Hagander <magnus@hagander.net> | 2016-04-05 20:03:49 +0200 |
commit | 7117685461af50f50c03f43e6a622284c8d54694 (patch) | |
tree | 2e330c9d197d14afe26ecee0df128daf6980bf2a /src/backend/access/transam/xlogfuncs.c | |
parent | 9457b591b949d3c256dd91043df71fb11657227a (diff) | |
download | postgresql-7117685461af50f50c03f43e6a622284c8d54694.tar.gz postgresql-7117685461af50f50c03f43e6a622284c8d54694.zip |
Implement backup API functions for non-exclusive backups
Previously non-exclusive backups had to be done using the replication protocol
and pg_basebackup. With this commit it's now possible to make them using
pg_start_backup/pg_stop_backup as well, as long as the backup program can
maintain a persistent connection to the database.
Doing this, backup_label and tablespace_map are returned as results from
pg_stop_backup() instead of being written to the data directory. This makes
the server safe from a crash during an ongoing backup, which can be a problem
with exclusive backups.
The old syntax of the functions remain and work exactly as before, but since the
new syntax is safer this should eventually be deprecated and removed.
Only reference documentation is included. The main section on backup still needs
to be rewritten to cover this, but since that is already scheduled for a separate
large rewrite, it's not included in this patch.
Reviewed by David Steele and Amit Kapila
Diffstat (limited to 'src/backend/access/transam/xlogfuncs.c')
-rw-r--r-- | src/backend/access/transam/xlogfuncs.c | 185 |
1 files changed, 183 insertions, 2 deletions
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 9ec6b2ae4f1..6a2fffc64b1 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -28,14 +28,37 @@ #include "replication/walreceiver.h" #include "storage/smgr.h" #include "utils/builtins.h" +#include "utils/memutils.h" #include "utils/numeric.h" #include "utils/guc.h" #include "utils/pg_lsn.h" #include "utils/timestamp.h" +#include "utils/tuplestore.h" #include "storage/fd.h" +#include "storage/ipc.h" /* + * Store label file and tablespace map during non-exclusive backups. + */ +static StringInfo label_file; +static StringInfo tblspc_map_file; +static bool exclusive_backup_running = false; +static bool nonexclusive_backup_running = false; + +/* + * Called when the backend exits with a running non-exclusive base backup, + * to clean up state. + */ +static void +nonexclusive_base_backup_cleanup(int code, Datum arg) +{ + do_pg_abort_backup(); + ereport(WARNING, + (errmsg("aborting backup due to backend exiting before pg_stop_backup was called"))); +} + +/* * pg_start_backup: set up for taking an on-line backup dump * * Essentially what this does is to create a backup label file in $PGDATA, @@ -49,6 +72,7 @@ pg_start_backup(PG_FUNCTION_ARGS) { text *backupid = PG_GETARG_TEXT_P(0); bool fast = PG_GETARG_BOOL(1); + bool exclusive = PG_GETARG_BOOL(2); char *backupidstr; XLogRecPtr startpoint; DIR *dir; @@ -60,14 +84,42 @@ pg_start_backup(PG_FUNCTION_ARGS) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser or replication role to run a backup"))); + if (exclusive_backup_running || nonexclusive_backup_running) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("a backup is already in progress in this session"))); + /* Make sure we can open the directory with tablespaces in it */ dir = AllocateDir("pg_tblspc"); if (!dir) ereport(ERROR, (errmsg("could not open directory \"%s\": %m", "pg_tblspc"))); - startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL, - dir, NULL, NULL, false, true); + if (exclusive) + { + startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL, + dir, NULL, NULL, false, true); + exclusive_backup_running = true; + } + else + { + MemoryContext oldcontext; + + /* + * Label file and tablespace map file need to be long-lived, since they + * are read in pg_stop_backup. + */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + label_file = makeStringInfo(); + tblspc_map_file = makeStringInfo(); + MemoryContextSwitchTo(oldcontext); + + startpoint = do_pg_start_backup(backupidstr, fast, NULL, label_file, + dir, NULL, tblspc_map_file, false, true); + nonexclusive_backup_running = true; + + before_shmem_exit(nonexclusive_base_backup_cleanup, (Datum) 0); + } FreeDir(dir); @@ -86,6 +138,10 @@ pg_start_backup(PG_FUNCTION_ARGS) * record for that and the file is for informational and debug purposes only. * * Note: different from CancelBackup which just cancels online backup mode. + * + * Note: this version is only called to stop an exclusive backup. The function + * pg_stop_backup_v2 (overloaded as pg_stop_backup in SQL) is called to + * stop non-exclusive backups. */ Datum pg_stop_backup(PG_FUNCTION_ARGS) @@ -97,11 +153,136 @@ pg_stop_backup(PG_FUNCTION_ARGS) (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to run a backup")))); + if (nonexclusive_backup_running) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("non-exclusive backup in progress"), + errhint("did you mean to use pg_stop_backup('f')?"))); + + /* + * Exclusive backups were typically started in a different connection, + * so don't try to verify that exclusive_backup_running is set in this one. + * Actual verification that an exclusive backup is in fact running is handled + * inside do_pg_stop_backup. + */ stoppoint = do_pg_stop_backup(NULL, true, NULL); + exclusive_backup_running = false; + PG_RETURN_LSN(stoppoint); } + +/* + * pg_stop_backup_v2: finish taking exclusive or nonexclusive on-line backup. + * + * Works the same as pg_stop_backup, except for non-exclusive backups it returns + * the backup label and tablespace map files as text fields in as part of the + * resultset. + */ +Datum +pg_stop_backup_v2(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + Datum values[3]; + bool nulls[3]; + + bool exclusive = PG_GETARG_BOOL(0); + XLogRecPtr stoppoint; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + if (!superuser() && !has_rolreplication(GetUserId())) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser or replication role to run a backup")))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, 0, sizeof(nulls)); + + if (exclusive) + { + if (nonexclusive_backup_running) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("non-exclusive backup in progress"), + errhint("did you mean to use pg_stop_backup('f')?"))); + + /* + * Stop the exclusive backup, and since we're in an exclusive backup + * return NULL for both backup_label and tablespace_map. + */ + stoppoint = do_pg_stop_backup(NULL, true, NULL); + exclusive_backup_running = false; + + nulls[1] = true; + nulls[2] = true; + } + else + { + if (!nonexclusive_backup_running) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("non-exclusive backup is not in progress"), + errhint("did you mean to use pg_stop_backup('t')?"))); + + /* + * Stop the non-exclusive backup. Return a copy of the backup + * label and tablespace map so they can be written to disk by + * the caller. + */ + stoppoint = do_pg_stop_backup(label_file->data, true, NULL); + nonexclusive_backup_running = false; + cancel_before_shmem_exit(nonexclusive_base_backup_cleanup, (Datum) 0); + + values[1] = CStringGetTextDatum(label_file->data); + values[2] = CStringGetTextDatum(tblspc_map_file->data); + + /* Free structures allocated in TopMemoryContext */ + pfree(label_file->data); + pfree(label_file); + label_file = NULL; + pfree(tblspc_map_file->data); + pfree(tblspc_map_file); + tblspc_map_file = NULL; + } + + /* Stoppoint is included on both exclusive and nonexclusive backups */ + values[0] = LSNGetDatum(stoppoint); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + tuplestore_donestoring(typstore); + + return (Datum) 0; +} + /* * pg_switch_xlog: switch to next xlog file */ |