aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorThomas Munro <tmunro@postgresql.org>2024-03-06 11:39:50 +1300
committerThomas Munro <tmunro@postgresql.org>2024-03-06 12:01:01 +1300
commitd93627bcbe5001750e7611f0e637200e2d81dcff (patch)
tree3ee757589d5c5f9eb3d40c0dc788133cd5614343 /src
parent2bce0ad67f93af6d1889ec611a8f618245291e3f (diff)
downloadpostgresql-d93627bcbe5001750e7611f0e637200e2d81dcff.tar.gz
postgresql-d93627bcbe5001750e7611f0e637200e2d81dcff.zip
Add --copy-file-range option to pg_upgrade.
The copy_file_range() system call is available on at least Linux and FreeBSD, and asks the kernel to use efficient ways to copy ranges of a file. Options available to the kernel include sharing block ranges (similar to --clone mode), and pushing down block copies to the storage layer. For automated testing, see PG_TEST_PG_UPGRADE_MODE. (Perhaps in a later commit we could consider setting this mode for one of the CI targets.) Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Discussion: https://postgr.es/m/CA%2BhUKGKe7Hb0-UNih8VD5UNZy5-ojxFb3Pr3xSBBL8qj2M2%3DdQ%40mail.gmail.com
Diffstat (limited to 'src')
-rw-r--r--src/bin/pg_upgrade/TESTING4
-rw-r--r--src/bin/pg_upgrade/check.c3
-rw-r--r--src/bin/pg_upgrade/file.c78
-rw-r--r--src/bin/pg_upgrade/option.c7
-rw-r--r--src/bin/pg_upgrade/pg_upgrade.h4
-rw-r--r--src/bin/pg_upgrade/relfilenumber.c8
-rw-r--r--src/include/pg_config.h.in3
7 files changed, 104 insertions, 3 deletions
diff --git a/src/bin/pg_upgrade/TESTING b/src/bin/pg_upgrade/TESTING
index 81a4324a76d..00842ac6ec3 100644
--- a/src/bin/pg_upgrade/TESTING
+++ b/src/bin/pg_upgrade/TESTING
@@ -20,8 +20,8 @@ export oldinstall=...otherversion/ (old version's install base path)
See DETAILS below for more information about creation of the dump.
You can also test the different transfer modes (--copy, --link,
---clone) by setting the environment variable PG_TEST_PG_UPGRADE_MODE
-to the respective command-line option, like
+--clone, --copy-file-range) by setting the environment variable
+PG_TEST_PG_UPGRADE_MODE to the respective command-line option, like
make check PG_TEST_PG_UPGRADE_MODE=--link
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index e36a7328bf0..5ab8fe80091 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -235,6 +235,9 @@ check_new_cluster(void)
break;
case TRANSFER_MODE_COPY:
break;
+ case TRANSFER_MODE_COPY_FILE_RANGE:
+ check_copy_file_range();
+ break;
case TRANSFER_MODE_LINK:
check_hard_link();
break;
diff --git a/src/bin/pg_upgrade/file.c b/src/bin/pg_upgrade/file.c
index 4850a682cb5..beba376f2ee 100644
--- a/src/bin/pg_upgrade/file.c
+++ b/src/bin/pg_upgrade/file.c
@@ -10,6 +10,7 @@
#include "postgres_fe.h"
#include <sys/stat.h>
+#include <limits.h>
#include <fcntl.h>
#ifdef HAVE_COPYFILE_H
#include <copyfile.h>
@@ -141,6 +142,45 @@ copyFile(const char *src, const char *dst,
/*
+ * copyFileByRange()
+ *
+ * Copies a relation file from src to dst.
+ * schemaName/relName are relation's SQL name (used for error messages only).
+ */
+void
+copyFileByRange(const char *src, const char *dst,
+ const char *schemaName, const char *relName)
+{
+#ifdef HAVE_COPY_FILE_RANGE
+ int src_fd;
+ int dest_fd;
+ ssize_t nbytes;
+
+ if ((src_fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not open file \"%s\": %s",
+ schemaName, relName, src, strerror(errno));
+
+ if ((dest_fd = open(dst, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not create file \"%s\": %s",
+ schemaName, relName, dst, strerror(errno));
+
+ do
+ {
+ nbytes = copy_file_range(src_fd, NULL, dest_fd, NULL, SSIZE_MAX, 0);
+ if (nbytes < 0)
+ pg_fatal("error while copying relation \"%s.%s\": could not copy file range from \"%s\" to \"%s\": %s",
+ schemaName, relName, src, dst, strerror(errno));
+ }
+ while (nbytes > 0);
+
+ close(src_fd);
+ close(dest_fd);
+#endif
+}
+
+
+/*
* linkFile()
*
* Hard-links a relation file from src to dst.
@@ -359,6 +399,44 @@ check_file_clone(void)
}
void
+check_copy_file_range(void)
+{
+ char existing_file[MAXPGPATH];
+ char new_link_file[MAXPGPATH];
+
+ snprintf(existing_file, sizeof(existing_file), "%s/PG_VERSION", old_cluster.pgdata);
+ snprintf(new_link_file, sizeof(new_link_file), "%s/PG_VERSION.copy_file_range_test", new_cluster.pgdata);
+ unlink(new_link_file); /* might fail */
+
+#if defined(HAVE_COPY_FILE_RANGE)
+ {
+ int src_fd;
+ int dest_fd;
+
+ if ((src_fd = open(existing_file, O_RDONLY | PG_BINARY, 0)) < 0)
+ pg_fatal("could not open file \"%s\": %s",
+ existing_file, strerror(errno));
+
+ if ((dest_fd = open(new_link_file, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
+ pg_file_create_mode)) < 0)
+ pg_fatal("could not create file \"%s\": %s",
+ new_link_file, strerror(errno));
+
+ if (copy_file_range(src_fd, NULL, dest_fd, NULL, SSIZE_MAX, 0) < 0)
+ pg_fatal("could not copy file range between old and new data directories: %s",
+ strerror(errno));
+
+ close(src_fd);
+ close(dest_fd);
+ }
+#else
+ pg_fatal("copy_file_range not supported on this platform");
+#endif
+
+ unlink(new_link_file);
+}
+
+void
check_hard_link(void)
{
char existing_file[MAXPGPATH];
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index 2917ec2329e..8949c58de80 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -58,7 +58,8 @@ parseCommandLine(int argc, char *argv[])
{"verbose", no_argument, NULL, 'v'},
{"clone", no_argument, NULL, 1},
{"copy", no_argument, NULL, 2},
- {"sync-method", required_argument, NULL, 3},
+ {"copy-file-range", no_argument, NULL, 3},
+ {"sync-method", required_argument, NULL, 4},
{NULL, 0, NULL, 0}
};
@@ -203,6 +204,9 @@ parseCommandLine(int argc, char *argv[])
break;
case 3:
+ user_opts.transfer_mode = TRANSFER_MODE_COPY_FILE_RANGE;
+ break;
+ case 4:
if (!parse_sync_method(optarg, &unused))
exit(1);
user_opts.sync_method = pg_strdup(optarg);
@@ -301,6 +305,7 @@ usage(void)
printf(_(" -V, --version display version information, then exit\n"));
printf(_(" --clone clone instead of copying files to new cluster\n"));
printf(_(" --copy copy files to new cluster (default)\n"));
+ printf(_(" --copy-file-range copy files to new cluster with copy_file_range\n"));
printf(_(" --sync-method=METHOD set method for syncing files to disk\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index d9a848cbfde..857d715049e 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -256,6 +256,7 @@ typedef enum
{
TRANSFER_MODE_CLONE,
TRANSFER_MODE_COPY,
+ TRANSFER_MODE_COPY_FILE_RANGE,
TRANSFER_MODE_LINK,
} transferMode;
@@ -402,11 +403,14 @@ void cloneFile(const char *src, const char *dst,
const char *schemaName, const char *relName);
void copyFile(const char *src, const char *dst,
const char *schemaName, const char *relName);
+void copyFileByRange(const char *src, const char *dst,
+ const char *schemaName, const char *relName);
void linkFile(const char *src, const char *dst,
const char *schemaName, const char *relName);
void rewriteVisibilityMap(const char *fromfile, const char *tofile,
const char *schemaName, const char *relName);
void check_file_clone(void);
+void check_copy_file_range(void);
void check_hard_link(void);
/* fopen_priv() is no longer different from fopen() */
diff --git a/src/bin/pg_upgrade/relfilenumber.c b/src/bin/pg_upgrade/relfilenumber.c
index 7ca221ee190..a1fc5fec78d 100644
--- a/src/bin/pg_upgrade/relfilenumber.c
+++ b/src/bin/pg_upgrade/relfilenumber.c
@@ -37,6 +37,9 @@ transfer_all_new_tablespaces(DbInfoArr *old_db_arr, DbInfoArr *new_db_arr,
case TRANSFER_MODE_COPY:
prep_status_progress("Copying user relation files");
break;
+ case TRANSFER_MODE_COPY_FILE_RANGE:
+ prep_status_progress("Copying user relation files with copy_file_range");
+ break;
case TRANSFER_MODE_LINK:
prep_status_progress("Linking user relation files");
break;
@@ -250,6 +253,11 @@ transfer_relfile(FileNameMap *map, const char *type_suffix, bool vm_must_add_fro
old_file, new_file);
copyFile(old_file, new_file, map->nspname, map->relname);
break;
+ case TRANSFER_MODE_COPY_FILE_RANGE:
+ pg_log(PG_VERBOSE, "copying \"%s\" to \"%s\" with copy_file_range",
+ old_file, new_file);
+ copyFileByRange(old_file, new_file, map->nspname, map->relname);
+ break;
case TRANSFER_MODE_LINK:
pg_log(PG_VERBOSE, "linking \"%s\" to \"%s\"",
old_file, new_file);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 07e73567dc7..591e1ca3df6 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -78,6 +78,9 @@
/* Define to 1 if you have the <copyfile.h> header file. */
#undef HAVE_COPYFILE_H
+/* Define to 1 if you have the `copy_file_range' function. */
+#undef HAVE_COPY_FILE_RANGE
+
/* Define to 1 if you have the <crtdefs.h> header file. */
#undef HAVE_CRTDEFS_H