Skip to content

Commit fb05f3c

Browse files
committed
pg_basebackup: Add support for relocating tablespaces
Tablespaces can be relocated in plain backup mode by specifying one or more -T olddir=newdir options. Author: Steeve Lennmark <steevel@handeldsbanken.se> Reviewed-by: Peter Eisentraut <peter_e@gmx.net>
1 parent 77585bc commit fb05f3c

File tree

2 files changed

+204
-8
lines changed

2 files changed

+204
-8
lines changed

doc/src/sgml/ref/pg_basebackup.sgml

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,33 @@ PostgreSQL documentation
202202
</listitem>
203203
</varlistentry>
204204

205+
<varlistentry>
206+
<term><option>-T <replaceable class="parameter">olddir</replaceable>=<replaceable class="parameter">newdir</replaceable></option></term>
207+
<term><option>--tablespace-mapping=<replaceable class="parameter">olddir</replaceable>=<replaceable class="parameter">newdir</replaceable></option></term>
208+
<listitem>
209+
<para>
210+
Relocate the tablespace in directory <replaceable>olddir</replaceable>
211+
to <replaceable>newdir</replaceable> during the backup. To be
212+
effective, <replaceable>olddir</replaceable> must exactly match the
213+
path specification of the tablespace as it is currently defined. (But
214+
it is not an error if there is no tablespace
215+
in <replaceable>olddir</replaceable> contained in the backup.)
216+
Both <replaceable>olddir</replaceable>
217+
and <replaceable>newdir</replaceable> must be absolute paths. If a
218+
path happens to contain a <literal>=</literal> sign, escape it with a
219+
backslash. This option can be specified multiple times for multiple
220+
tablespaces. See examples below.
221+
</para>
222+
223+
<para>
224+
If a tablespace is relocated in this way, the symbolic links inside
225+
the main data directory are updated to point to the new location. So
226+
the new data directory is ready to be used for a new server instance
227+
with all tablespaces in the updated locations.
228+
</para>
229+
</listitem>
230+
</varlistentry>
231+
205232
<varlistentry>
206233
<term><option>--xlogdir=<replaceable class="parameter">xlogdir</replaceable></option></term>
207234
<listitem>
@@ -528,9 +555,13 @@ PostgreSQL documentation
528555
</para>
529556

530557
<para>
531-
The way <productname>PostgreSQL</productname> manages tablespaces, the path
532-
for all additional tablespaces must be identical whenever a backup is
533-
restored. The main data directory, however, is relocatable to any location.
558+
Tablespaces will in plain format by default be backed up to the same path
559+
they have on the server, unless the
560+
option <replaceable>--tablespace-mapping</replaceable> is used. Without
561+
this option, running a plain format base backup on the same host as the
562+
server will not work if tablespaces are in use, because the backup would
563+
have to be written to the same directory locations as the original
564+
tablespaces.
534565
</para>
535566

536567
<para>
@@ -570,6 +601,15 @@ PostgreSQL documentation
570601
(This command will fail if there are multiple tablespaces in the
571602
database.)
572603
</para>
604+
605+
<para>
606+
To create a backup of a local database where the tablespace in
607+
<filename>/opt/ts</filename> is relocated
608+
to <filename>./backup/ts</filename>:
609+
<screen>
610+
<prompt>$</prompt> <userinput>pg_basebackup -D backup/data -T /opt/ts=$(pwd)/backup/ts</userinput>
611+
</screen>
612+
</para>
573613
</refsect1>
574614

575615
<refsect1>

src/bin/pg_basebackup/pg_basebackup.c

Lines changed: 161 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,24 @@
3535
#include "streamutil.h"
3636

3737

38+
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
39+
40+
typedef struct TablespaceListCell
41+
{
42+
struct TablespaceListCell *next;
43+
char old_dir[MAXPGPATH];
44+
char new_dir[MAXPGPATH];
45+
} TablespaceListCell;
46+
47+
typedef struct TablespaceList
48+
{
49+
TablespaceListCell *head;
50+
TablespaceListCell *tail;
51+
} TablespaceList;
52+
3853
/* Global options */
3954
static char *basedir = NULL;
55+
static TablespaceList tablespace_dirs = {NULL, NULL};
4056
static char *xlog_dir = "";
4157
static char format = 'p'; /* p(lain)/t(ar) */
4258
static char *label = "pg_basebackup base backup";
@@ -90,6 +106,10 @@ static void BaseBackup(void);
90106
static bool reached_end_position(XLogRecPtr segendpos, uint32 timeline,
91107
bool segment_finished);
92108

109+
static const char *get_tablespace_mapping(const char *dir);
110+
static void update_tablespace_symlink(Oid oid, const char *old_dir);
111+
static void tablespace_list_append(const char *arg);
112+
93113

94114
static void disconnect_and_exit(int code)
95115
{
@@ -110,6 +130,77 @@ static void disconnect_and_exit(int code)
110130
}
111131

112132

133+
/*
134+
* Split argument into old_dir and new_dir and append to tablespace mapping
135+
* list.
136+
*/
137+
static void
138+
tablespace_list_append(const char *arg)
139+
{
140+
TablespaceListCell *cell = (TablespaceListCell *) pg_malloc0(sizeof(TablespaceListCell));
141+
char *dst;
142+
char *dst_ptr;
143+
const char *arg_ptr;
144+
145+
dst_ptr = dst = cell->old_dir;
146+
for (arg_ptr = arg; *arg_ptr; arg_ptr++)
147+
{
148+
if (dst_ptr - dst >= MAXPGPATH)
149+
{
150+
fprintf(stderr, _("%s: directory name too long\n"), progname);
151+
exit(1);
152+
}
153+
154+
if (*arg_ptr == '\\' && *(arg_ptr + 1) == '=')
155+
; /* skip backslash escaping = */
156+
else if (*arg_ptr == '=' && (arg_ptr == arg || *(arg_ptr - 1) != '\\'))
157+
{
158+
if (*cell->new_dir)
159+
{
160+
fprintf(stderr, _("%s: multiple \"=\" signs in tablespace mapping\n"), progname);
161+
exit(1);
162+
}
163+
else
164+
dst = dst_ptr = cell->new_dir;
165+
}
166+
else
167+
*dst_ptr++ = *arg_ptr;
168+
}
169+
170+
if (!*cell->old_dir || !*cell->new_dir)
171+
{
172+
fprintf(stderr,
173+
_("%s: invalid tablespace mapping format \"%s\", must be \"OLDDIR=NEWDIR\"\n"),
174+
progname, arg);
175+
exit(1);
176+
}
177+
178+
/* This check isn't absolutely necessary. But all tablespaces are created
179+
* with absolute directories, so specifying a non-absolute path here would
180+
* just never match, possibly confusing users. It's also good to be
181+
* consistent with the new_dir check. */
182+
if (!is_absolute_path(cell->old_dir))
183+
{
184+
fprintf(stderr, _("%s: old directory not absolute in tablespace mapping: %s\n"),
185+
progname, cell->old_dir);
186+
exit(1);
187+
}
188+
189+
if (!is_absolute_path(cell->new_dir))
190+
{
191+
fprintf(stderr, _("%s: new directory not absolute in tablespace mapping: %s\n"),
192+
progname, cell->new_dir);
193+
exit(1);
194+
}
195+
196+
if (tablespace_dirs.tail)
197+
tablespace_dirs.tail->next = cell;
198+
else
199+
tablespace_dirs.head = cell;
200+
tablespace_dirs.tail = cell;
201+
}
202+
203+
113204
#ifdef HAVE_LIBZ
114205
static const char *
115206
get_gz_error(gzFile gzf)
@@ -137,6 +228,8 @@ usage(void)
137228
printf(_(" -F, --format=p|t output format (plain (default), tar)\n"));
138229
printf(_(" -R, --write-recovery-conf\n"
139230
" write recovery.conf after backup\n"));
231+
printf(_(" -T, --tablespace-mapping=OLDDIR=NEWDIR\n"
232+
" relocate tablespace in OLDDIR to NEWDIR\n"));
140233
printf(_(" -x, --xlog include required WAL files in backup (fetch mode)\n"));
141234
printf(_(" -X, --xlog-method=fetch|stream\n"
142235
" include required WAL files with specified method\n"));
@@ -899,15 +992,60 @@ ReceiveTarFile(PGconn *conn, PGresult *res, int rownum)
899992
PQfreemem(copybuf);
900993
}
901994

995+
996+
/*
997+
* Retrieve tablespace path, either relocated or original depending on whether
998+
* -T was passed or not.
999+
*/
1000+
static const char *
1001+
get_tablespace_mapping(const char *dir)
1002+
{
1003+
TablespaceListCell *cell;
1004+
1005+
for (cell = tablespace_dirs.head; cell; cell = cell->next)
1006+
if (strcmp(dir, cell->old_dir) == 0)
1007+
return cell->new_dir;
1008+
1009+
return dir;
1010+
}
1011+
1012+
1013+
/*
1014+
* Update symlinks to reflect relocated tablespace.
1015+
*/
1016+
static void
1017+
update_tablespace_symlink(Oid oid, const char *old_dir)
1018+
{
1019+
const char *new_dir = get_tablespace_mapping(old_dir);
1020+
1021+
if (strcmp(old_dir, new_dir) != 0)
1022+
{
1023+
char *linkloc = psprintf("%s/pg_tblspc/%d", basedir, oid);
1024+
1025+
if (unlink(linkloc) != 0 && errno != ENOENT)
1026+
{
1027+
fprintf(stderr, _("%s: could not remove symbolic link \"%s\": %s"),
1028+
progname, linkloc, strerror(errno));
1029+
disconnect_and_exit(1);
1030+
}
1031+
if (symlink(new_dir, linkloc) != 0)
1032+
{
1033+
fprintf(stderr, _("%s: could not create symbolic link \"%s\": %s"),
1034+
progname, linkloc, strerror(errno));
1035+
disconnect_and_exit(1);
1036+
}
1037+
}
1038+
}
1039+
1040+
9021041
/*
9031042
* Receive a tar format stream from the connection to the server, and unpack
9041043
* the contents of it into a directory. Only files, directories and
9051044
* symlinks are supported, no other kinds of special files.
9061045
*
9071046
* If the data is for the main data directory, it will be restored in the
9081047
* specified directory. If it's for another tablespace, it will be restored
909-
* in the original directory, since relocation of tablespaces is not
910-
* supported.
1048+
* in the original or mapped directory.
9111049
*/
9121050
static void
9131051
ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
@@ -923,7 +1061,7 @@ ReceiveAndUnpackTarFile(PGconn *conn, PGresult *res, int rownum)
9231061
if (basetablespace)
9241062
strlcpy(current_path, basedir, sizeof(current_path));
9251063
else
926-
strlcpy(current_path, PQgetvalue(res, rownum, 1), sizeof(current_path));
1064+
strlcpy(current_path, get_tablespace_mapping(PQgetvalue(res, rownum, 1)), sizeof(current_path));
9271065

9281066
/*
9291067
* Get the COPY data
@@ -1503,7 +1641,10 @@ BaseBackup(void)
15031641
* we do anything anyway.
15041642
*/
15051643
if (format == 'p' && !PQgetisnull(res, i, 1))
1506-
verify_dir_is_empty_or_create(PQgetvalue(res, i, 1));
1644+
{
1645+
char *path = (char *) get_tablespace_mapping(PQgetvalue(res, i, 1));
1646+
verify_dir_is_empty_or_create(path);
1647+
}
15071648
}
15081649

15091650
/*
@@ -1545,6 +1686,17 @@ BaseBackup(void)
15451686
progress_report(PQntuples(res), NULL, true);
15461687
fprintf(stderr, "\n"); /* Need to move to next line */
15471688
}
1689+
1690+
if (format == 'p' && tablespace_dirs.head != NULL)
1691+
{
1692+
for (i = 0; i < PQntuples(res); i++)
1693+
{
1694+
Oid tblspc_oid = atooid(PQgetvalue(res, i, 0));
1695+
if (tblspc_oid)
1696+
update_tablespace_symlink(tblspc_oid, PQgetvalue(res, i, 1));
1697+
}
1698+
}
1699+
15481700
PQclear(res);
15491701

15501702
/*
@@ -1696,6 +1848,7 @@ main(int argc, char **argv)
16961848
{"format", required_argument, NULL, 'F'},
16971849
{"checkpoint", required_argument, NULL, 'c'},
16981850
{"write-recovery-conf", no_argument, NULL, 'R'},
1851+
{"tablespace-mapping", required_argument, NULL, 'T'},
16991852
{"xlog", no_argument, NULL, 'x'},
17001853
{"xlog-method", required_argument, NULL, 'X'},
17011854
{"gzip", no_argument, NULL, 'z'},
@@ -1735,7 +1888,7 @@ main(int argc, char **argv)
17351888
}
17361889
}
17371890

1738-
while ((c = getopt_long(argc, argv, "D:F:RxX:l:zZ:d:c:h:p:U:s:wWvP",
1891+
while ((c = getopt_long(argc, argv, "D:F:RT:xX:l:zZ:d:c:h:p:U:s:wWvP",
17391892
long_options, &option_index)) != -1)
17401893
{
17411894
switch (c)
@@ -1759,6 +1912,9 @@ main(int argc, char **argv)
17591912
case 'R':
17601913
writerecoveryconf = true;
17611914
break;
1915+
case 'T':
1916+
tablespace_list_append(optarg);
1917+
break;
17621918
case 'x':
17631919
if (includewal)
17641920
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy