agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v6 01/10] doc: btree deduplication
32+ messages / 6 participants
[nested] [flat]
* [PATCH v6 01/10] doc: btree deduplication
@ 2020-03-31 00:43 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 32+ messages in thread
From: Justin Pryzby @ 2020-03-31 00:43 UTC (permalink / raw)
commit 0d861bbb702f8aa05c2a4e3f1650e7e8df8c8c27
Author: Peter Geoghegan <[email protected]>
---
doc/src/sgml/btree.sgml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/src/sgml/btree.sgml b/doc/src/sgml/btree.sgml
index d03ee4d6fa..69c1ee0e97 100644
--- a/doc/src/sgml/btree.sgml
+++ b/doc/src/sgml/btree.sgml
@@ -642,7 +642,7 @@ options(<replaceable>relopts</replaceable> <type>local_relopts *</type>) returns
</para>
<para>
Deduplication works by periodically merging groups of duplicate
- tuples together, forming a single posting list tuple for each
+ tuples together, forming a single <firstterm>posting list</firstterm> tuple for each
group. The column key value(s) only appear once in this
representation. This is followed by a sorted array of
<acronym>TID</acronym>s that point to rows in the table. This
--
2.17.0
--C+ts3FVlLX8+P6JN
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v6-0002-doc-pg_stat_progress_basebackup.patch"
^ permalink raw reply [nested|flat] 32+ messages in thread
* Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
@ 2022-08-05 10:25 Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-05 10:25 UTC (permalink / raw)
To: PostgreSQL Hackers <[email protected]>
Hi,
I noticed that dir_open_for_write() in walmethods.c uses write() for
WAL file initialization (note that this code is used by pg_receivewal
and pg_basebackup) as opposed to core using pg_pwritev_with_retry() in
XLogFileInitInternal() to avoid partial writes. Do we need to fix
this?
Thoughts?
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-08-06 06:41 ` Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Michael Paquier @ 2022-08-06 06:41 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On Fri, Aug 05, 2022 at 03:55:26PM +0530, Bharath Rupireddy wrote:
> I noticed that dir_open_for_write() in walmethods.c uses write() for
> WAL file initialization (note that this code is used by pg_receivewal
> and pg_basebackup) as opposed to core using pg_pwritev_with_retry() in
> XLogFileInitInternal() to avoid partial writes. Do we need to fix
> this?
0d56acfb has moved pg_pwritev_with_retry to be backend-only in fd.c :/
> Thoughts?
Makes sense to me for the WAL segment pre-padding initialization, as
we still want to point to the beginning of the segment after we are
done with the pre-padding, and the code has an extra lseek().
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../Yu4NDi87SPpKiU%[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
@ 2022-08-07 01:12 ` Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-07 01:12 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On Sat, Aug 6, 2022 at 12:11 PM Michael Paquier <[email protected]> wrote:
>
> On Fri, Aug 05, 2022 at 03:55:26PM +0530, Bharath Rupireddy wrote:
> > I noticed that dir_open_for_write() in walmethods.c uses write() for
> > WAL file initialization (note that this code is used by pg_receivewal
> > and pg_basebackup) as opposed to core using pg_pwritev_with_retry() in
> > XLogFileInitInternal() to avoid partial writes. Do we need to fix
> > this?
>
> 0d56acfb has moved pg_pwritev_with_retry to be backend-only in fd.c :/
Yeah. pg_pwritev_with_retry can also be part of common/file_utils.c/.h
so that everyone can use it.
> > Thoughts?
>
> Makes sense to me for the WAL segment pre-padding initialization, as
> we still want to point to the beginning of the segment after we are
> done with the pre-padding, and the code has an extra lseek().
Thanks. I attached the v1 patch, please review it.
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
Attachments:
[application/octet-stream] v1-0001-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (10.3K, ../../CALj2ACW+j1pkcgoef1fVQzgiPASoQGRe-8yd5vgBQwwMLURFjQ@mail.gmail.com/2-v1-0001-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From 34b5786cec6e8081db005d1c3eb5190f5b6e207b Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 7 Aug 2022 01:08:15 +0000
Subject: [PATCH v1] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() to seek to the beginning
of the WAL segment.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
---
src/backend/access/transam/xlog.c | 35 +++-------
src/backend/storage/file/fd.c | 65 ------------------
src/bin/pg_basebackup/walmethods.c | 22 ++----
src/common/file_utils.c | 104 +++++++++++++++++++++++++++++
src/include/common/file_utils.h | 11 +++
src/include/storage/fd.h | 6 --
6 files changed, 129 insertions(+), 114 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f0150d1e..90473a7af4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2916,7 +2916,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2960,14 +2959,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2978,32 +2974,17 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_with_retry_and_init(fd, wal_segment_size, XLOG_BLCKSZ);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
+ PGAlignedXLogBlock zbuffer;
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
/*
* Otherwise, seeking to the end and writing a solitary byte is
* enough.
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..ac611b836f 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -95,7 +95,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3747,67 +3746,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index e90aa0ba37..8e70fc2d38 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -209,25 +209,15 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
/* Do pre-padding on non-compressed files */
if (pad_to_size && dir_data->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
- {
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- dir_data->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
- }
+ errno = 0;
+ rc = pg_pwritev_with_retry_and_init(fd, pad_to_size, XLOG_BLCKSZ);
- if (lseek(fd, 0, SEEK_SET) != 0)
+ if (rc < 0)
{
- dir_data->lasterrno = errno;
+ /* If errno isn't set, assume problem is no disk space */
+ dir_data->lasterrno = errno ? errno : ENOSPC;
close(fd);
return NULL;
}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..bea8810e26 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,106 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
+
+/*
+ * A convenience wrapper for pg_pwritev_with_retry() that zero-fills the given
+ * file of size total_sz in batches of size block_sz.
+ */
+ssize_t
+pg_pwritev_with_retry_and_init(int fd, int total_sz, int block_sz)
+{
+ PGAlignedXLogBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ ssize_t rc;
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = total_sz / block_sz;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ rc = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if ( rc < 0)
+ break;
+
+ i += iovcnt;
+ }
+
+ return rc;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..3c139afb95 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,13 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
+extern ssize_t pg_pwritev_with_retry_and_init(int fd,
+ int total_sz,
+ int block_sz);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 2b4a8e0ffe..224bdfb9a6 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t amount);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-08-07 02:12 ` Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-08-07 02:12 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 7, 2022 at 1:12 PM Bharath Rupireddy
<[email protected]> wrote:
> On Sat, Aug 6, 2022 at 12:11 PM Michael Paquier <[email protected]> wrote:
> Yeah. pg_pwritev_with_retry can also be part of common/file_utils.c/.h
> so that everyone can use it.
>
> > > Thoughts?
> >
> > Makes sense to me for the WAL segment pre-padding initialization, as
> > we still want to point to the beginning of the segment after we are
> > done with the pre-padding, and the code has an extra lseek().
>
> Thanks. I attached the v1 patch, please review it.
Hi Bharath,
+1 for moving pg_pwritev_with_retry() to file_utils.c, but I think the
commit message should say that is happening. Maybe the move should
even be in a separate patch (IMHO it's nice to separate mechanical
change patches from new logic/work patches).
+/*
+ * A convenience wrapper for pg_pwritev_with_retry() that zero-fills the given
+ * file of size total_sz in batches of size block_sz.
+ */
+ssize_t
+pg_pwritev_with_retry_and_init(int fd, int total_sz, int block_sz)
Hmm, why not give it a proper name that says it writes zeroes?
Size arguments around syscall-like facilities are usually size_t.
+ memset(zbuffer.data, 0, block_sz);
I believe the modern fashion as of a couple of weeks ago is to tell
the compiler to zero-initialise. Since it's a union you'd need
designated initialiser syntax, something like zbuffer = { .data = {0}
}?
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
How can it be OK to use caller supplied block_sz, when
sizeof(zbuffer.data) is statically determined? What is the point of
this argument?
- dir_data->lasterrno = errno;
+ /* If errno isn't set, assume problem is no disk space */
+ dir_data->lasterrno = errno ? errno : ENOSPC;
This coding pattern is used in places where we blame short writes on
lack of disk space without bothering to retry. But the code used in
this patch already handles short writes: it always retries, until
eventually, if you really are out of disk space, you should get an
actual ENOSPC from the operating system. So I don't think the
guess-it-must-be-ENOSPC technique applies here.
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-08-07 05:11 ` Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-07 05:11 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 7, 2022 at 7:43 AM Thomas Munro <[email protected]> wrote:
>
> On Sun, Aug 7, 2022 at 1:12 PM Bharath Rupireddy
> <[email protected]> wrote:
> > On Sat, Aug 6, 2022 at 12:11 PM Michael Paquier <[email protected]> wrote:
> > Yeah. pg_pwritev_with_retry can also be part of common/file_utils.c/.h
> > so that everyone can use it.
> >
> > > > Thoughts?
> > >
> > > Makes sense to me for the WAL segment pre-padding initialization, as
> > > we still want to point to the beginning of the segment after we are
> > > done with the pre-padding, and the code has an extra lseek().
> >
> > Thanks. I attached the v1 patch, please review it.
>
> Hi Bharath,
>
> +1 for moving pg_pwritev_with_retry() to file_utils.c, but I think the
> commit message should say that is happening. Maybe the move should
> even be in a separate patch (IMHO it's nice to separate mechanical
> change patches from new logic/work patches).
Agree. I separated out the changes.
> +/*
> + * A convenience wrapper for pg_pwritev_with_retry() that zero-fills the given
> + * file of size total_sz in batches of size block_sz.
> + */
> +ssize_t
> +pg_pwritev_with_retry_and_init(int fd, int total_sz, int block_sz)
>
> Hmm, why not give it a proper name that says it writes zeroes?
Done.
> Size arguments around syscall-like facilities are usually size_t.
>
> + memset(zbuffer.data, 0, block_sz);
>
> I believe the modern fashion as of a couple of weeks ago is to tell
> the compiler to zero-initialise. Since it's a union you'd need
> designated initialiser syntax, something like zbuffer = { .data = {0}
> }?
Hm, but we have many places still using memset(). If we were to change
these syntaxes, IMO, it must be done separately.
> + iov[i].iov_base = zbuffer.data;
> + iov[i].iov_len = block_sz;
>
> How can it be OK to use caller supplied block_sz, when
> sizeof(zbuffer.data) is statically determined? What is the point of
> this argument?
Yes, removed block_sz function parameter.
> - dir_data->lasterrno = errno;
> + /* If errno isn't set, assume problem is no disk space */
> + dir_data->lasterrno = errno ? errno : ENOSPC;
>
> This coding pattern is used in places where we blame short writes on
> lack of disk space without bothering to retry. But the code used in
> this patch already handles short writes: it always retries, until
> eventually, if you really are out of disk space, you should get an
> actual ENOSPC from the operating system. So I don't think the
> guess-it-must-be-ENOSPC technique applies here.
Done.
Thanks for reviewing. PSA v2 patch-set. Also,I added a CF entry
https://commitfest.postgresql.org/39/3803/ to give the patches a
chance to get tested.
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
Attachments:
[application/x-patch] v2-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch (5.8K, ../../CALj2ACUpU2jHBS62wBN0jvBHjd9TaAMd1aC-m0tx3s4qUZsBKw@mail.gmail.com/2-v2-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch)
download | inline diff:
From c862c9d079c3f78d2eb0405d5c4addd93847f102 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 7 Aug 2022 04:40:10 +0000
Subject: [PATCH v2] Move pg_pwritev_with_retry() to file_utils.c
Move pg_pwritev_with_retry() from file/fd.c to common/file_utils.c
so that non-backend code or FRONTEND cases can also use it.
---
src/backend/storage/file/fd.c | 65 ---------------------------------
src/common/file_utils.c | 65 +++++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 7 ++++
src/include/storage/fd.h | 6 ---
4 files changed, 72 insertions(+), 71 deletions(-)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..ac611b836f 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -95,7 +95,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3747,67 +3746,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..4af216e56c 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,67 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..2c5dbcb0b1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,9 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 2b4a8e0ffe..224bdfb9a6 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t amount);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
[application/x-patch] v2-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (5.5K, ../../CALj2ACUpU2jHBS62wBN0jvBHjd9TaAMd1aC-m0tx3s4qUZsBKw@mail.gmail.com/3-v2-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From f6b18a3ef41f29d16b677e3e3df283554aa33fda Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 7 Aug 2022 05:05:18 +0000
Subject: [PATCH v2] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() to seek to the beginning
of the WAL segment.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
---
src/backend/access/transam/xlog.c | 35 ++++++-------------------
src/bin/pg_basebackup/walmethods.c | 19 +++-----------
src/common/file_utils.c | 42 ++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 3 +++
4 files changed, 57 insertions(+), 42 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f0150d1e..3ed344008d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2916,7 +2916,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2960,14 +2959,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2978,32 +2974,17 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_with_retry_and_write_zeros(fd, wal_segment_size);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
+ PGAlignedXLogBlock zbuffer;
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
/*
* Otherwise, seeking to the end and writing a solitary byte is
* enough.
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index e90aa0ba37..beb8071ecc 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -209,23 +209,12 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
/* Do pre-padding on non-compressed files */
if (pad_to_size && dir_data->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
- {
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- dir_data->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
- }
+ errno = 0;
+ rc = pg_pwritev_with_retry_and_write_zeros(fd, pad_to_size);
- if (lseek(fd, 0, SEEK_SET) != 0)
+ if (rc < 0)
{
dir_data->lasterrno = errno;
close(fd);
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 4af216e56c..daaa426e3d 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -525,3 +525,45 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * A convenience wrapper for pg_pwritev_with_retry() that zero-fills the given
+ * file of size total_sz in batches of size XLOG_BLCKSZ.
+ */
+ssize_t
+pg_pwritev_with_retry_and_write_zeros(int fd, size_t total_sz)
+{
+ PGAlignedXLogBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ size_t block_sz;
+ ssize_t rc;
+
+ block_sz = sizeof(zbuffer.data);
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (int i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = total_sz / block_sz;
+ for (int i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ rc = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if ( rc < 0)
+ break;
+
+ i += iovcnt;
+ }
+
+ return rc;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2c5dbcb0b1..a5af97bb85 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -44,4 +44,7 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
+extern ssize_t pg_pwritev_with_retry_and_write_zeros(int fd,
+ size_t total_sz);
+
#endif /* FILE_UTILS_H */
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-08-07 07:56 ` Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Michael Paquier @ 2022-08-07 07:56 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 07, 2022 at 10:41:49AM +0530, Bharath Rupireddy wrote:
> Agree. I separated out the changes.
+
+/*
+ * A convenience wrapper for pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
If moving this routine, this could use a more explicit description,
especially on errno, for example, that could be consumed by the caller
on failure to know what's happening.
>> +/*
>> + * A convenience wrapper for pg_pwritev_with_retry() that zero-fills the given
>> + * file of size total_sz in batches of size block_sz.
>> + */
>> +ssize_t
>> +pg_pwritev_with_retry_and_init(int fd, int total_sz, int block_sz)
>>
>> Hmm, why not give it a proper name that says it writes zeroes?
>
> Done.
FWIW, when it comes to that we have a couple of routines that just use
'0' to mean such a thing, aka palloc0(). I find 0002 confusing, as it
introduces in fe_utils.c a new wrapper
(pg_pwritev_with_retry_and_write_zeros) on what's already a wrapper
(pg_pwritev_with_retry) for pwrite().
A second thing is that pg_pwritev_with_retry_and_write_zeros() is
designed to work on WAL segments initialization and it uses
XLOG_BLCKSZ and PGAlignedXLogBlock for the job, but there is nothing
in its name that tells us so. This makes me question whether
file_utils.c is a good location for this second thing. Could a new
file be a better location? We have a xlogutils.c in the backend, and
a name similar to that in src/common/ would be one possibility.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
@ 2022-08-07 09:49 ` Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-08-07 09:49 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 7, 2022 at 7:56 PM Michael Paquier <[email protected]> wrote:
> FWIW, when it comes to that we have a couple of routines that just use
> '0' to mean such a thing, aka palloc0(). I find 0002 confusing, as it
> introduces in fe_utils.c a new wrapper
> (pg_pwritev_with_retry_and_write_zeros) on what's already a wrapper
> (pg_pwritev_with_retry) for pwrite().
What about pg_write_initial_zeros(fd, size)? How it writes zeros (ie
using vector I/O, and retrying) seems to be an internal detail, no?
"initial" to make it clearer that it's at offset 0, or maybe
pg_pwrite_zeros(fd, size, offset).
> A second thing is that pg_pwritev_with_retry_and_write_zeros() is
> designed to work on WAL segments initialization and it uses
> XLOG_BLCKSZ and PGAlignedXLogBlock for the job, but there is nothing
> in its name that tells us so. This makes me question whether
> file_utils.c is a good location for this second thing. Could a new
> file be a better location? We have a xlogutils.c in the backend, and
> a name similar to that in src/common/ would be one possibility.
Yeah, I think it should probably be disconnected from XLOG_BLCKSZ, or
maybe it's OK to use BLCKSZ with a comment to say that it's a bit
arbitrary, or maybe it's better to define a new zero buffer of some
arbitrary size just in this code if that is too strange. We could
experiment with different size buffers to see how it performs, bearing
in mind that every time we double it you halve the number of system
calls, but also bearing in mind that at some point it's too much for
the stack. I can tell you that the way that code works today was not
really written with performance in mind (unlike, say, the code
reverted from 9.4 that tried to do this with posix_fallocate()), it
was just finding an excuse to call pwritev(), to exercise new fallback
code being committed for use by later AIO stuff (more patches coming
soon). The retry support was added because it seemed plausible that
some system out there would start to do short writes as we cranked up
the sizes for some implementation reason other than ENOSPC, so we
should make a reusable retry routine.
I think this should also handle the remainder after processing whole
blocks, just for completeness. If I call the code as presented with size
8193, I think this code will only write 8192 bytes.
I think if this ever needs to work on O_DIRECT files there would be an
alignment constraint on the buffer and size, but I don't think we have
to worry about that for now.
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-08-07 15:52 ` Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-07 15:52 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 7, 2022 at 3:19 PM Thomas Munro <[email protected]> wrote:
>
> > A second thing is that pg_pwritev_with_retry_and_write_zeros() is
> > designed to work on WAL segments initialization and it uses
> > XLOG_BLCKSZ and PGAlignedXLogBlock for the job, but there is nothing
> > in its name that tells us so. This makes me question whether
> > file_utils.c is a good location for this second thing. Could a new
> > file be a better location? We have a xlogutils.c in the backend, and
> > a name similar to that in src/common/ would be one possibility.
>
> Yeah, I think it should probably be disconnected from XLOG_BLCKSZ, or
> maybe it's OK to use BLCKSZ with a comment to say that it's a bit
> arbitrary, or maybe it's better to define a new zero buffer of some
> arbitrary size just in this code if that is too strange. We could
> experiment with different size buffers to see how it performs, bearing
> in mind that every time we double it you halve the number of system
> calls, but also bearing in mind that at some point it's too much for
> the stack. I can tell you that the way that code works today was not
> really written with performance in mind (unlike, say, the code
> reverted from 9.4 that tried to do this with posix_fallocate()), it
> was just finding an excuse to call pwritev(), to exercise new fallback
> code being committed for use by later AIO stuff (more patches coming
> soon). The retry support was added because it seemed plausible that
> some system out there would start to do short writes as we cranked up
> the sizes for some implementation reason other than ENOSPC, so we
> should make a reusable retry routine.
Yes, doubling the zerobuffer size to say 2 * XLOG_BLCKSZ or 2 * BLCKSZ
reduces the system calls to half (right now, pg_pwritev_with_retry()
gets called 64 times per 16MB WAL file, it writes in the batches of 32
blocks per call).
Is there a ready-to-use tool or script or specific settings for
pgbench (pgbench command line options or GUC settings) that I can play
with to measure the performance?
> I think this should also handle the remainder after processing whole
> blocks, just for completeness. If I call the code as presented with size
> 8193, I think this code will only write 8192 bytes.
Hm, I will fix it.
> I think if this ever needs to work on O_DIRECT files there would be an
> alignment constraint on the buffer and size, but I don't think we have
> to worry about that for now.
We can add a comment about the above limitation, if required.
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-08-08 12:40 ` Bharath Rupireddy <[email protected]>
2022-08-09 07:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
0 siblings, 2 replies; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-08 12:40 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sun, Aug 7, 2022 at 9:22 PM Bharath Rupireddy
<[email protected]> wrote:
>
> On Sun, Aug 7, 2022 at 3:19 PM Thomas Munro <[email protected]> wrote:
> >
> > > A second thing is that pg_pwritev_with_retry_and_write_zeros() is
> > > designed to work on WAL segments initialization and it uses
> > > XLOG_BLCKSZ and PGAlignedXLogBlock for the job, but there is nothing
> > > in its name that tells us so. This makes me question whether
> > > file_utils.c is a good location for this second thing. Could a new
> > > file be a better location? We have a xlogutils.c in the backend, and
> > > a name similar to that in src/common/ would be one possibility.
> >
> > Yeah, I think it should probably be disconnected from XLOG_BLCKSZ, or
> > maybe it's OK to use BLCKSZ with a comment to say that it's a bit
> > arbitrary, or maybe it's better to define a new zero buffer of some
> > arbitrary size just in this code if that is too strange. We could
> > experiment with different size buffers to see how it performs, bearing
> > in mind that every time we double it you halve the number of system
> > calls, but also bearing in mind that at some point it's too much for
> > the stack. I can tell you that the way that code works today was not
> > really written with performance in mind (unlike, say, the code
> > reverted from 9.4 that tried to do this with posix_fallocate()), it
> > was just finding an excuse to call pwritev(), to exercise new fallback
> > code being committed for use by later AIO stuff (more patches coming
> > soon). The retry support was added because it seemed plausible that
> > some system out there would start to do short writes as we cranked up
> > the sizes for some implementation reason other than ENOSPC, so we
> > should make a reusable retry routine.
>
> Yes, doubling the zerobuffer size to say 2 * XLOG_BLCKSZ or 2 * BLCKSZ
> reduces the system calls to half (right now, pg_pwritev_with_retry()
> gets called 64 times per 16MB WAL file, it writes in the batches of 32
> blocks per call).
>
> Is there a ready-to-use tool or script or specific settings for
> pgbench (pgbench command line options or GUC settings) that I can play
> with to measure the performance?
I played with a simple insert use-case [1] that generates ~380 WAL
files, with different block sizes. To my surprise, I have not seen any
improvement with larger block sizes. I may be doing something wrong
here, suggestions on to test and see the benefits are welcome.
> > I think this should also handle the remainder after processing whole
> > blocks, just for completeness. If I call the code as presented with size
> > 8193, I think this code will only write 8192 bytes.
>
> Hm, I will fix it.
Fixed.
I'm attaching v5 patch-set. I've addressed review comments received so
far and fixed a compiler warning that CF bot complained about.
Please review it further.
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
Attachments:
[application/octet-stream] v3-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch (5.8K, ../../CALj2ACUWUJZ2hrHEd0ibHDYhkxiUbt-oMhWN+-uPzXgZArYikg@mail.gmail.com/2-v3-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch)
download | inline diff:
From 5eb797ebb6f9e4e40b2a07f4f6afdd53307ca713 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Mon, 8 Aug 2022 06:45:21 +0000
Subject: [PATCH v3] Move pg_pwritev_with_retry() to file_utils.c
Move pg_pwritev_with_retry() from file/fd.c to common/file_utils.c
so that non-backend code or FRONTEND cases can also use it.
---
src/backend/storage/file/fd.c | 65 ---------------------------------
src/common/file_utils.c | 65 +++++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 7 ++++
src/include/storage/fd.h | 6 ---
4 files changed, 72 insertions(+), 71 deletions(-)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..ac611b836f 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -95,7 +95,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3747,67 +3746,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..4af216e56c 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,67 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..2c5dbcb0b1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,9 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 2b4a8e0ffe..224bdfb9a6 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t amount);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
[application/octet-stream] v3-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (6.7K, ../../CALj2ACUWUJZ2hrHEd0ibHDYhkxiUbt-oMhWN+-uPzXgZArYikg@mail.gmail.com/3-v3-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From 07657c620665f8e429c261d75af05efa982fae6a Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Mon, 8 Aug 2022 12:32:27 +0000
Subject: [PATCH v3] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() to seek to the beginning
of the WAL segment.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
---
src/backend/access/transam/xlog.c | 35 ++++-------------
src/bin/pg_basebackup/walmethods.c | 19 ++-------
src/common/file_utils.c | 62 ++++++++++++++++++++++++++++++
src/include/c.h | 15 ++++++++
src/include/common/file_utils.h | 2 +
5 files changed, 91 insertions(+), 42 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f0150d1e..072462bd9b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2916,7 +2916,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2960,14 +2959,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2978,32 +2974,17 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_zeros(fd, wal_segment_size);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
+ PGAlignedXLogBlock zbuffer;
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
/*
* Otherwise, seeking to the end and writing a solitary byte is
* enough.
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index e90aa0ba37..5c8f69162a 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -209,23 +209,12 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
/* Do pre-padding on non-compressed files */
if (pad_to_size && dir_data->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
- {
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- dir_data->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
- }
+ errno = 0;
+ rc = pg_pwritev_zeros(fd, pad_to_size);
- if (lseek(fd, 0, SEEK_SET) != 0)
+ if (rc < 0)
{
dir_data->lasterrno = errno;
close(fd);
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 4af216e56c..1c193d14d4 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -525,3 +525,65 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * Function to zero-fill a file with given size. On failure, a negative value
+ * is returned and errno is set appropriately so that the caller can use it
+ * accordingly.
+ */
+ssize_t
+pg_pwritev_zeros(int fd, size_t size)
+{
+ PGAlignedPWriteVBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ size_t block_sz;
+ size_t remaining_size = 0;
+ int i;
+ ssize_t rc = 0;
+
+ block_sz = sizeof(zbuffer.data);
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = size / block_sz;
+ remaining_size = size % block_sz;
+ for (i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ offset = i * block_sz;
+
+ rc = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (rc < 0)
+ return rc;
+
+ i += iovcnt;
+ }
+
+ /* Now, write the remaining size, if any, of the file with zeros. */
+ if (remaining_size > 0)
+ {
+ /* We'll never write more than one block here */
+ int iovcnt = 1;
+ /* Jump on to the end of previously written blocks */
+ off_t offset = i * block_sz;
+
+ iov[0].iov_base = zbuffer.data;
+ iov[0].iov_len = remaining_size;
+
+ rc = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+ }
+
+ return rc;
+}
diff --git a/src/include/c.h b/src/include/c.h
index de9ec04d49..2bb4d67d11 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -1151,6 +1151,21 @@ typedef union PGAlignedXLogBlock
int64 force_align_i64;
} PGAlignedXLogBlock;
+/*
+ * PWRITEV_BLCKSZ is same as XLOG_BLCKSZ for now, however it may change if
+ * writing more bytes per pg_pwritev_with_retry() call is proven to be more
+ * performant.
+ */
+#define PWRITEV_BLCKSZ XLOG_BLCKSZ
+
+/* Same as above structures, but for a PWRITEV_BLCKSZ-sized buffer */
+typedef union PGAlignedPWriteVBlock
+{
+ char data[PWRITEV_BLCKSZ];
+ double force_align_d;
+ int64 force_align_i64;
+} PGAlignedPWriteVBlock;
+
/* msb for char */
#define HIGHBIT (0x80)
#define IS_HIGHBIT_SET(ch) ((unsigned char)(ch) & HIGHBIT)
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2c5dbcb0b1..f8d0d475fa 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -44,4 +44,6 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
+extern ssize_t pg_pwritev_zeros(int fd, size_t size);
+
#endif /* FILE_UTILS_H */
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-08-09 07:30 ` Bharath Rupireddy <[email protected]>
1 sibling, 0 replies; 32+ messages in thread
From: Bharath Rupireddy @ 2022-08-09 07:30 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Aug 8, 2022 at 6:10 PM Bharath Rupireddy
<[email protected]> wrote:
>
> I played with a simple insert use-case [1] that generates ~380 WAL
> files, with different block sizes. To my surprise, I have not seen any
> improvement with larger block sizes. I may be doing something wrong
> here, suggestions on to test and see the benefits are welcome.
>
> > > I think this should also handle the remainder after processing whole
> > > blocks, just for completeness. If I call the code as presented with size
> > > 8193, I think this code will only write 8192 bytes.
> >
> > Hm, I will fix it.
>
> Fixed.
>
> I'm attaching v5 patch-set. I've addressed review comments received so
> far and fixed a compiler warning that CF bot complained about.
>
> Please review it further.
I tried to vary the zero buffer size to see if there's any huge
benefit for the WAL-generating queries. Unfortunately, I didn't see
any benefit on my dev system (16 vcore, 512GB SSD, 32GB RAM) . The use
case I've tried is at [1] and the results are at [2].
Having said that, the use of pg_pwritev_with_retry() in walmethods.c
will definitely reduce number of system calls - on HEAD the
dir_open_for_write() makes pad_to_size/XLOG_BLCKSZ i.e. 16MB/8KB =
2,048 write() calls and with patch it makes only 64
pg_pwritev_with_retry() calls with XLOG_BLCKSZ zero buffer size. The
proposed patches will provide straight 32x reduction in system calls
(for pg_receivewal and pg_basebackup) apart from the safety against
partial writes.
[1]
/* built source code with release flags */
./configure --with-zlib --enable-depend --prefix=$PWD/inst/
--with-openssl --with-readline --with-perl --with-libxml CFLAGS='-O2'
> install.log && make -j 8 install > install.log 2>&1 &
\q
./pg_ctl -D data -l logfile stop
rm -rf data
/* ensured that nothing exists in OS page cache */
free -m
sudo su
sync; echo 3 > /proc/sys/vm/drop_caches
exit
free -m
./initdb -D data
./pg_ctl -D data -l logfile start
./psql -d postgres -c 'ALTER SYSTEM SET max_wal_size = "64GB";'
./psql -d postgres -c 'ALTER SYSTEM SET shared_buffers = "8GB";'
./psql -d postgres -c 'ALTER SYSTEM SET work_mem = "16MB";'
./psql -d postgres -c 'ALTER SYSTEM SET checkpoint_timeout = "1d";'
./pg_ctl -D data -l logfile restart
./psql -d postgres -c 'create table foo(bar int);'
./psql -d postgres
\timing
insert into foo select * from generate_series(1, 100000000); /* this
query generates about 385 WAL files, no checkpoint hence no recycle of
old WAL files, all new WAL files */
[2]
HEAD
Time: 84249.535 ms (01:24.250)
HEAD with wal_init_zero off
Time: 75086.300 ms (01:15.086)
#define PWRITEV_BLCKSZ XLOG_BLCKSZ
Time: 85254.302 ms (01:25.254)
#define PWRITEV_BLCKSZ (4 * XLOG_BLCKSZ)
Time: 83542.885 ms (01:23.543)
#define PWRITEV_BLCKSZ (16 * XLOG_BLCKSZ)
Time: 84035.770 ms (01:24.036)
#define PWRITEV_BLCKSZ (64 * XLOG_BLCKSZ)
Time: 84749.021 ms (01:24.749)
#define PWRITEV_BLCKSZ (256 * XLOG_BLCKSZ)
Time: 84273.466 ms (01:24.273)
#define PWRITEV_BLCKSZ (512 * XLOG_BLCKSZ)
Time: 84233.576 ms (01:24.234)
--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-20 23:00 ` Nathan Bossart <[email protected]>
2022-09-20 23:06 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
1 sibling, 2 replies; 32+ messages in thread
From: Nathan Bossart @ 2022-09-20 23:00 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Aug 08, 2022 at 06:10:23PM +0530, Bharath Rupireddy wrote:
> I'm attaching v5 patch-set. I've addressed review comments received so
> far and fixed a compiler warning that CF bot complained about.
>
> Please review it further.
0001 looks reasonable to me.
+ errno = 0;
+ rc = pg_pwritev_zeros(fd, pad_to_size);
Do we need to reset errno? pg_pwritev_zeros() claims to set errno
appropriately.
+/*
+ * PWRITEV_BLCKSZ is same as XLOG_BLCKSZ for now, however it may change if
+ * writing more bytes per pg_pwritev_with_retry() call is proven to be more
+ * performant.
+ */
+#define PWRITEV_BLCKSZ XLOG_BLCKSZ
This seems like something we should sort out now instead of leaving as
future work. Given your recent note, I think we should just use
XLOG_BLCKSZ and PGAlignedXLogBlock and add a comment about the performance
findings with different buffer sizes.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-20 23:06 ` Nathan Bossart <[email protected]>
1 sibling, 0 replies; 32+ messages in thread
From: Nathan Bossart @ 2022-09-20 23:06 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 20, 2022 at 04:00:26PM -0700, Nathan Bossart wrote:
> On Mon, Aug 08, 2022 at 06:10:23PM +0530, Bharath Rupireddy wrote:
>> I'm attaching v5 patch-set. I've addressed review comments received so
>> far and fixed a compiler warning that CF bot complained about.
>>
>> Please review it further.
>
> 0001 looks reasonable to me.
>
> + errno = 0;
> + rc = pg_pwritev_zeros(fd, pad_to_size);
>
> Do we need to reset errno? pg_pwritev_zeros() claims to set errno
> appropriately.
>
> +/*
> + * PWRITEV_BLCKSZ is same as XLOG_BLCKSZ for now, however it may change if
> + * writing more bytes per pg_pwritev_with_retry() call is proven to be more
> + * performant.
> + */
> +#define PWRITEV_BLCKSZ XLOG_BLCKSZ
>
> This seems like something we should sort out now instead of leaving as
> future work. Given your recent note, I think we should just use
> XLOG_BLCKSZ and PGAlignedXLogBlock and add a comment about the performance
> findings with different buffer sizes.
I also noticed that the latest patch set no longer applies, so I've marked
the commitfest entry as waiting-on-author.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-23 06:16 ` Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
1 sibling, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-23 06:16 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Sep 21, 2022 at 4:30 AM Nathan Bossart <[email protected]> wrote:
>
> 0001 looks reasonable to me.
Thanks for reviewing.
> + errno = 0;
> + rc = pg_pwritev_zeros(fd, pad_to_size);
>
> Do we need to reset errno? pg_pwritev_zeros() claims to set errno
> appropriately.
Right, pg_pwritev_zeros(), (rather pg_pwritev_with_retry() ensures
that pwritev() or pwrite()) sets the correct errno, please see
Thomas's comments [1] as well. Removed it.
> +/*
> + * PWRITEV_BLCKSZ is same as XLOG_BLCKSZ for now, however it may change if
> + * writing more bytes per pg_pwritev_with_retry() call is proven to be more
> + * performant.
> + */
> +#define PWRITEV_BLCKSZ XLOG_BLCKSZ
>
> This seems like something we should sort out now instead of leaving as
> future work. Given your recent note, I think we should just use
> XLOG_BLCKSZ and PGAlignedXLogBlock and add a comment about the performance
> findings with different buffer sizes.
Agreed. Removed the new structure and added a comment.
Another change that I had to do was to allow lseek() even after
pwrite() (via pg_pwritev_zeros()) on Windows in walmethods.c. Without
this, the regression tests start to fail on Windows. And I figured out
that it's not an issue with this patch, it looks like an issue with
pwrite() implementation in win32pwrite.c, see the failure here [2], I
plan to start a separate thread to discuss this.
Please review the attached v4 patch set further.
[1] https://www.postgresql.org/message-id/CA%2BhUKGJKwUrpP0igOFAv5khj3dbHvfWqLzFeo7vtNzDYXv_YZw%40mail.g...
[2] https://github.com/BRupireddy/postgres/tree/use_pwrite_without_lseek_on_windiws
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Attachments:
[application/x-patch] v4-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch (5.8K, ../../CALj2ACVV0FAmgazZ50F-27JSayr4ThXMoGc_N2y=aYbo9wz=0w@mail.gmail.com/2-v4-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch)
download | inline diff:
From c60026519becfc513dc06ddf889caa3b4b0fade3 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Fri, 23 Sep 2022 00:47:56 +0000
Subject: [PATCH v4] Move pg_pwritev_with_retry() to file_utils.c
Move pg_pwritev_with_retry() from file/fd.c to common/file_utils.c
so that non-backend code or FRONTEND cases can also use it.
---
src/backend/storage/file/fd.c | 65 ---------------------------------
src/common/file_utils.c | 65 +++++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 7 ++++
src/include/storage/fd.h | 6 ---
4 files changed, 72 insertions(+), 71 deletions(-)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 073dab2be5..75ba178dfb 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -93,7 +93,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3738,67 +3737,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..4af216e56c 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,67 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..2c5dbcb0b1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,9 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 5a48fccd9c..c0a212487d 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t nbytes);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
[application/x-patch] v4-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (6.9K, ../../CALj2ACVV0FAmgazZ50F-27JSayr4ThXMoGc_N2y=aYbo9wz=0w@mail.gmail.com/3-v4-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From 7de9b6d239214e6a467dd30c2715527ae3d37360 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Fri, 23 Sep 2022 05:44:13 +0000
Subject: [PATCH v4] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() on non-Windows platforms
to seek to the beginning of the WAL segment. It looks like on
Windows, we need an explicit lseek() call here despite using
pwrite() implementation from win32pwrite.c. Otherwise
an error occurs.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
---
src/backend/access/transam/xlog.c | 35 ++++----------
src/bin/pg_basebackup/walmethods.c | 26 +++++-----
src/common/file_utils.c | 77 ++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 2 +
4 files changed, 101 insertions(+), 39 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f32b2124e6..c8a1808133 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2925,7 +2925,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2969,14 +2968,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2987,32 +2983,17 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_zeros(fd, wal_segment_size);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
+ PGAlignedXLogBlock zbuffer;
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
+
/*
* Otherwise, seeking to the end and writing a solitary byte is
* enough.
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index bc2e83d02b..9b4df62447 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -220,28 +220,30 @@ dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
/* Do pre-padding on non-compressed files */
if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
+ rc = pg_pwritev_zeros(fd, pad_to_size);
+
+ if (rc < 0)
{
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- wwmethod->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
+ wwmethod->lasterrno = errno;
+ close(fd);
+ return NULL;
}
+#ifdef WIN32
+ /*
+ * XXX: It looks like on Windows, we need an explicit lseek() call here
+ * despite using pwrite() implementation from win32pwrite.c. Otherwise
+ * an error occurs.
+ */
if (lseek(fd, 0, SEEK_SET) != 0)
{
wwmethod->lasterrno = errno;
close(fd);
return NULL;
}
+#endif
}
/*
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 4af216e56c..40e12c9130 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -525,3 +525,80 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * Function to zero-fill a file with given size. On failure, a negative value
+ * is returned and errno is set appropriately so that the caller can use it
+ * accordingly.
+ */
+ssize_t
+pg_pwritev_zeros(int fd, size_t size)
+{
+ PGAlignedXLogBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ size_t block_sz;
+ size_t remaining_size = 0;
+ int i;
+ ssize_t written;
+ ssize_t total_written = 0;
+
+ /*
+ * XXX: Writing more than one block of size XLOG_BLCKSZ bytes via
+ * PGAlignedXLogBlock structure per vector buffer might improve write
+ * performance on some platforms. However, tests (on some platforms, not
+ * all) show not much improvements with varying block sizes. Hence we stick
+ * to one block in PGAlignedXLogBlock structure.
+ */
+ block_sz = sizeof(zbuffer.data);
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = size / block_sz;
+ remaining_size = size % block_sz;
+ for (i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ i += iovcnt;
+ total_written += written;
+ }
+
+ /* Now, write the remaining size, if any, of the file with zeros. */
+ if (remaining_size > 0)
+ {
+ /* We'll never write more than one block here */
+ int iovcnt = 1;
+
+ /* Jump on to the end of previously written blocks */
+ off_t offset = i * block_sz;
+
+ iov[0].iov_base = zbuffer.data;
+ iov[0].iov_len = remaining_size;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ total_written += written;
+ }
+
+ Assert(total_written == size);
+
+ return total_written;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2c5dbcb0b1..f8d0d475fa 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -44,4 +44,6 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
+extern ssize_t pg_pwritev_zeros(int fd, size_t size);
+
#endif /* FILE_UTILS_H */
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-23 20:24 ` Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 2 replies; 32+ messages in thread
From: Nathan Bossart @ 2022-09-23 20:24 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
+ PGAlignedXLogBlock zbuffer;
+
+ memset(zbuffer.data, 0, XLOG_BLCKSZ);
This seems excessive for only writing a single byte.
+#ifdef WIN32
+ /*
+ * XXX: It looks like on Windows, we need an explicit lseek() call here
+ * despite using pwrite() implementation from win32pwrite.c. Otherwise
+ * an error occurs.
+ */
I think this comment is too vague. Can we describe the error in more
detail? Or better yet, can we fix it as a prerequisite to this patch set?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-24 04:14 ` Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
1 sibling, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-09-24 04:14 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Sep 24, 2022 at 8:24 AM Nathan Bossart <[email protected]> wrote:
> +#ifdef WIN32
> + /*
> + * XXX: It looks like on Windows, we need an explicit lseek() call here
> + * despite using pwrite() implementation from win32pwrite.c. Otherwise
> + * an error occurs.
> + */
>
> I think this comment is too vague. Can we describe the error in more
> detail? Or better yet, can we fix it as a prerequisite to this patch set?
Although WriteFile() with a synchronous file handle and an explicit
offset doesn't use the current file position, it appears that it still
changes it. :-(
You'd think from the documentation[1] that that isn't the case, because it says:
"Considerations for working with synchronous file handles:
* If lpOverlapped is NULL, the write operation starts at the current
file position and WriteFile does not return until the operation is
complete, and the system updates the file pointer before WriteFile
returns.
* If lpOverlapped is not NULL, the write operation starts at the
offset that is specified in the OVERLAPPED structure and WriteFile
does not return until the write operation is complete. The system
updates the OVERLAPPED Internal and InternalHigh fields before
WriteFile returns."
So it's explicitly saying the file pointer is updated in the first
bullet point and not the second, but src/port/win32pwrite.c is doing
the second thing. Yet the following assertion added to Bharath's code
fails[2]:
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -221,6 +221,10 @@ dir_open_for_write(WalWriteMethod *wwmethod,
const char *pathname,
if (pad_to_size && wwmethod->compression_algorithm ==
PG_COMPRESSION_NONE)
{
ssize_t rc;
+ off_t before_offset;
+ off_t after_offset;
+
+ before_offset = lseek(fd, 0, SEEK_CUR);
rc = pg_pwritev_zeros(fd, pad_to_size);
@@ -231,6 +235,9 @@ dir_open_for_write(WalWriteMethod *wwmethod, const
char *pathname,
return NULL;
}
+ after_offset = lseek(fd, 0, SEEK_CUR);
+ Assert(before_offset == after_offset);
+
[1] https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile#synchronization-and...
[2] https://api.cirrus-ci.com/v1/artifact/task/6201051266154496/testrun/build/testrun/pg_basebackup/010_...
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-26 15:03 ` Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-26 15:03 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Sep 24, 2022 at 9:44 AM Thomas Munro <[email protected]> wrote:
>
> Although WriteFile() with a synchronous file handle and an explicit
> offset doesn't use the current file position, it appears that it still
> changes it. :-(
>
> You'd think from the documentation[1] that that isn't the case, because it says:
>
> "Considerations for working with synchronous file handles:
>
> * If lpOverlapped is NULL, the write operation starts at the current
> file position and WriteFile does not return until the operation is
> complete, and the system updates the file pointer before WriteFile
> returns.
>
> * If lpOverlapped is not NULL, the write operation starts at the
> offset that is specified in the OVERLAPPED structure and WriteFile
> does not return until the write operation is complete. The system
> updates the OVERLAPPED Internal and InternalHigh fields before
> WriteFile returns."
>
> So it's explicitly saying the file pointer is updated in the first
> bullet point and not the second, but src/port/win32pwrite.c is doing
> the second thing.
The WriteFile() and pwrite() implementation in win32pwrite.c are
changing the file pointer on Windows, see the following and [4] for
more details.
# Running: pg_basebackup --no-sync -cfast -D
C:\cirrus\build\testrun\pg_basebackup\010_pg_basebackup\data\tmp_test_sV4r/backup2
--no-manifest --waldir
C:\cirrus\build\testrun\pg_basebackup\010_pg_basebackup\data\tmp_test_sV4r/xlog2
pg_basebackup: offset_before 0 and offset_after 16777216 aren't the same
Assertion failed: offset_before == offset_after, file
../src/bin/pg_basebackup/walmethods.c, line 254
Irrespective of what Windows does with file pointers in WriteFile(),
should we add lseek(SEEK_SET) in our own pwrite()'s implementation,
something like [5]? This is rather hackish without fully knowing what
Windows does internally in WriteFile(), but this does fix inherent
issues that our pwrite() callers (there are quite a number of places
that use pwrite() and presumes file pointer doesn't change on Windows)
may have on Windows. See the regression tests passing [6] with the fix
[5].
> Yet the following assertion added to Bharath's code
> fails[2]:
That was a very quick patch though, here's another version adding
offset checks and an assertion [3], see the assertion failures here
[4].
I also think that we need to discuss this issue separately.
Thoughts?
> [1] https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile#synchronization-and...
> [2] https://api.cirrus-ci.com/v1/artifact/task/6201051266154496/testrun/build/testrun/pg_basebackup/010_...
[3] - https://github.com/BRupireddy/postgres/tree/add_pwrite_and_offset_checks_in_walmethods_v2
[4] - https://api.cirrus-ci.com/v1/artifact/task/5294264635621376/testrun/build/testrun/pg_basebackup/010_...
[5]
diff --git a/src/port/win32pwrite.c b/src/port/win32pwrite.c
index 7f2e62e8a7..542b548279 100644
--- a/src/port/win32pwrite.c
+++ b/src/port/win32pwrite.c
@@ -37,5 +37,16 @@ pwrite(int fd, const void *buf, size_t size, off_t offset)
return -1;
}
+ /*
+ * On Windows, it is found that WriteFile() changes the file
pointer and we
+ * want pwrite() to not change. Hence, we explicitly reset the
file pointer
+ * to beginning of the file.
+ */
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ {
+ _dosmaperr(GetLastError());
+ return -1;
+ }
+
return result;
}
[6] - https://github.com/BRupireddy/postgres/tree/add_pwrite_and_offset_checks_in_walmethods_v3
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-26 21:27 ` Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Nathan Bossart @ 2022-09-26 21:27 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Sep 26, 2022 at 08:33:53PM +0530, Bharath Rupireddy wrote:
> Irrespective of what Windows does with file pointers in WriteFile(),
> should we add lseek(SEEK_SET) in our own pwrite()'s implementation,
> something like [5]? This is rather hackish without fully knowing what
> Windows does internally in WriteFile(), but this does fix inherent
> issues that our pwrite() callers (there are quite a number of places
> that use pwrite() and presumes file pointer doesn't change on Windows)
> may have on Windows. See the regression tests passing [6] with the fix
> [5].
I think so. I don't see why we would rather have each caller ensure
pwrite() behaves as documented.
> + /*
> + * On Windows, it is found that WriteFile() changes the file
> pointer and we
> + * want pwrite() to not change. Hence, we explicitly reset the
> file pointer
> + * to beginning of the file.
> + */
> + if (lseek(fd, 0, SEEK_SET) != 0)
> + {
> + _dosmaperr(GetLastError());
> + return -1;
> + }
> +
> return result;
> }
Why reset to the beginning of the file? Shouldn't we reset it to what it
was before the call to pwrite()?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-26 21:37 ` Thomas Munro <[email protected]>
2022-09-26 21:55 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 2 replies; 32+ messages in thread
From: Thomas Munro @ 2022-09-26 21:37 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 27, 2022 at 10:27 AM Nathan Bossart
<[email protected]> wrote:
> On Mon, Sep 26, 2022 at 08:33:53PM +0530, Bharath Rupireddy wrote:
> > Irrespective of what Windows does with file pointers in WriteFile(),
> > should we add lseek(SEEK_SET) in our own pwrite()'s implementation,
> > something like [5]? This is rather hackish without fully knowing what
> > Windows does internally in WriteFile(), but this does fix inherent
> > issues that our pwrite() callers (there are quite a number of places
> > that use pwrite() and presumes file pointer doesn't change on Windows)
> > may have on Windows. See the regression tests passing [6] with the fix
> > [5].
>
> I think so. I don't see why we would rather have each caller ensure
> pwrite() behaves as documented.
I don't think so, that's an extra kernel call. I think I'll just have
to revert part of my recent change that removed the pg_ prefix from
those function names in our code, and restore the comment that warns
you about the portability hazard (I thought it went away with HP-UX
10, where we were literally calling lseek() before every write()).
The majority of users of these functions don't intermix them with
calls to read()/write(), so they don't care about the file position,
so I think it's just something we'll have to continue to be mindful of
in the places that do.
Unless, that is, I can find a way to stop it from doing that... I've
added this to my Windows to-do list. I am going to have a round of
Windows hacking quite soon.
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-26 21:55 ` Nathan Bossart <[email protected]>
1 sibling, 0 replies; 32+ messages in thread
From: Nathan Bossart @ 2022-09-26 21:55 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 27, 2022 at 10:37:38AM +1300, Thomas Munro wrote:
> I don't think so, that's an extra kernel call. I think I'll just have
> to revert part of my recent change that removed the pg_ prefix from
> those function names in our code, and restore the comment that warns
> you about the portability hazard (I thought it went away with HP-UX
> 10, where we were literally calling lseek() before every write()).
> The majority of users of these functions don't intermix them with
> calls to read()/write(), so they don't care about the file position,
> so I think it's just something we'll have to continue to be mindful of
> in the places that do.
Ah, you're right, it's probably best to avoid the extra system call for the
majority of callers that don't care about the file position. I retract my
previous message.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-27 05:42 ` Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
1 sibling, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-27 05:42 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 27, 2022 at 3:08 AM Thomas Munro <[email protected]> wrote:
>
> I don't think so, that's an extra kernel call. I think I'll just have
> to revert part of my recent change that removed the pg_ prefix from
> those function names in our code, and restore the comment that warns
> you about the portability hazard (I thought it went away with HP-UX
> 10, where we were literally calling lseek() before every write()).
> The majority of users of these functions don't intermix them with
> calls to read()/write(), so they don't care about the file position,
> so I think it's just something we'll have to continue to be mindful of
> in the places that do.
Yes, all of the existing pwrite() callers don't care about the file
position, but the new callers such as the actual idea and patch
proposed here in this thread cares.
Is this the commit cf112c122060568aa06efe4e6e6fb9b2dd4f1090 part of
which [1] you're planning to revert? If so, will we have the
pg_{pwrite, pread} back? IIUC, we don't have lseek(SEEK_SET) in
pg_{pwrite, pread} right? It is the callers responsibility to set the
file position correctly if they wish to, isn't it? Oftentimes,
developers miss the notes in the function comments and use these
functions expecting them to not change file position which works well
on non-Windows platforms but fails on Windows.
This makes me think that we can have pwrite(), pread() introduced by
cf112c122060568aa06efe4e6e6fb9b2dd4f1090 as-is and re-introduce
pg_{pwrite, pread} with pwrite()/pread()+lseek(SEEK_SET) in
win32pwrite.c and win32pread.c. These functions reduce the caller's
efforts and reduce the duplicate code. If okay, I'm happy to lend my
hands on this patch.
Thoughts?
> Unless, that is, I can find a way to stop it from doing that... I've
> added this to my Windows to-do list. I am going to have a round of
> Windows hacking quite soon.
Thanks! I think it's time for me to start a new thread just for this
to get more attention and opinion from other hackers and not to
sidetrack the original idea and patch proposed in this thread.
[1] https://github.com/postgres/postgres/blob/REL_14_STABLE/src/port/pwrite.c#L11
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-27 09:30 ` Thomas Munro <[email protected]>
2022-09-27 12:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-09-27 09:30 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 27, 2022 at 6:43 PM Bharath Rupireddy
<[email protected]> wrote:
> On Tue, Sep 27, 2022 at 3:08 AM Thomas Munro <[email protected]> wrote:
> >
> > I don't think so, that's an extra kernel call. I think I'll just have
> > to revert part of my recent change that removed the pg_ prefix from
> > those function names in our code, and restore the comment that warns
> > you about the portability hazard (I thought it went away with HP-UX
> > 10, where we were literally calling lseek() before every write()).
> > The majority of users of these functions don't intermix them with
> > calls to read()/write(), so they don't care about the file position,
> > so I think it's just something we'll have to continue to be mindful of
> > in the places that do.
>
> Yes, all of the existing pwrite() callers don't care about the file
> position, but the new callers such as the actual idea and patch
> proposed here in this thread cares.
>
> Is this the commit cf112c122060568aa06efe4e6e6fb9b2dd4f1090 part of
> which [1] you're planning to revert?
Yeah, just the renaming parts of that. The lseek()-based emulation is
definitely not coming back. Something like the attached.
From fc72be32668f0c37b899fd922d6986f39d8a6a2a Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Tue, 27 Sep 2022 21:12:03 +1300
Subject: [PATCH] Restore pg_pread and friends.
Commit cf112c12 was a little too hasty in getting rid of the pg_
prefixes where we use pread(), pwrite() and vectored variants. We
dropped support for ancient Unixes that needed to use lseek() to
implement replacements for those, but it turned out that Windows also
changes the current position even when you pass in an offset to
ReadFile() and WriteFile(), if the file handle is synchronous.
Switching to asynchronous file handles would fix that, but have other
complications. For now let's just put back the pg_ prefix and add some
comments to highlight the non-standard side-effect.
Reported-by: Bharath Rupireddy <[email protected]>
Discussion: https://postgr.es/m/20220923202439.GA1156054%40nathanxps13
---
.../pg_stat_statements/pg_stat_statements.c | 4 +-
src/backend/access/heap/rewriteheap.c | 2 +-
src/backend/access/transam/slru.c | 4 +-
src/backend/access/transam/xlog.c | 4 +-
src/backend/access/transam/xlogreader.c | 2 +-
src/backend/access/transam/xlogrecovery.c | 2 +-
src/backend/backup/basebackup.c | 2 +-
src/backend/replication/walreceiver.c | 2 +-
src/backend/storage/file/fd.c | 8 +--
src/backend/utils/init/miscinit.c | 2 +-
src/bin/pg_test_fsync/pg_test_fsync.c | 50 +++++++++----------
src/include/access/xlogreader.h | 4 +-
src/include/port.h | 9 ++++
src/include/port/pg_iovec.h | 13 ++++-
src/include/port/win32_port.h | 4 +-
src/port/preadv.c | 4 +-
src/port/pwritev.c | 4 +-
src/port/win32pread.c | 2 +-
src/port/win32pwrite.c | 2 +-
19 files changed, 71 insertions(+), 53 deletions(-)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index ba868f0de9..73439c0199 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2103,9 +2103,9 @@ qtext_store(const char *query, int query_len,
if (fd < 0)
goto error;
- if (pwrite(fd, query, query_len, off) != query_len)
+ if (pg_pwrite(fd, query, query_len, off) != query_len)
goto error;
- if (pwrite(fd, "\0", 1, off + query_len) != 1)
+ if (pg_pwrite(fd, "\0", 1, off + query_len) != 1)
goto error;
CloseTransientFile(fd);
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2f08fbe8d3..b01b39b008 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -1150,7 +1150,7 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
/* write out tail end of mapping file (again) */
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_LOGICAL_REWRITE_MAPPING_WRITE);
- if (pwrite(fd, data, len, xlrec->offset) != len)
+ if (pg_pwrite(fd, data, len, xlrec->offset) != len)
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index c9a7b97949..b65cb49d7f 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -718,7 +718,7 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_SLRU_READ);
- if (pread(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
+ if (pg_pread(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
{
pgstat_report_wait_end();
slru_errcause = SLRU_READ_FAILED;
@@ -873,7 +873,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_SLRU_WRITE);
- if (pwrite(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
+ if (pg_pwrite(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
{
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1dd6df0fe1..7f9f350531 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2196,7 +2196,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
INSTR_TIME_SET_CURRENT(start);
pgstat_report_wait_start(WAIT_EVENT_WAL_WRITE);
- written = pwrite(openLogFile, from, nleft, startoffset);
+ written = pg_pwrite(openLogFile, from, nleft, startoffset);
pgstat_report_wait_end();
/*
@@ -3018,7 +3018,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* enough.
*/
errno = 0;
- if (pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
{
/* if write didn't set errno, assume no disk space */
save_errno = errno ? errno : ENOSPC;
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 4d6c34e0fc..5a8fe81f82 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -1544,7 +1544,7 @@ WALRead(XLogReaderState *state,
/* Reset errno first; eases reporting non-errno-affecting errors */
errno = 0;
- readbytes = pread(state->seg.ws_file, p, segbytes, (off_t) startoff);
+ readbytes = pg_pread(state->seg.ws_file, p, segbytes, (off_t) startoff);
#ifndef FRONTEND
pgstat_report_wait_end();
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b41e682664..cb07694aea 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -3271,7 +3271,7 @@ retry:
readOff = targetPageOff;
pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- r = pread(readFile, readBuf, XLOG_BLCKSZ, (off_t) readOff);
+ r = pg_pread(readFile, readBuf, XLOG_BLCKSZ, (off_t) readOff);
if (r != XLOG_BLCKSZ)
{
char fname[MAXFNAMELEN];
diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c
index 411cac9be3..e252ad7421 100644
--- a/src/backend/backup/basebackup.c
+++ b/src/backend/backup/basebackup.c
@@ -1828,7 +1828,7 @@ basebackup_read_file(int fd, char *buf, size_t nbytes, off_t offset,
int rc;
pgstat_report_wait_start(WAIT_EVENT_BASEBACKUP_READ);
- rc = pread(fd, buf, nbytes, offset);
+ rc = pg_pread(fd, buf, nbytes, offset);
pgstat_report_wait_end();
if (rc < 0)
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index f6ef0ace2c..3767466ef3 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -915,7 +915,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
/* OK to write the logs */
errno = 0;
- byteswritten = pwrite(recvFile, buf, segbytes, (off_t) startoff);
+ byteswritten = pg_pwrite(recvFile, buf, segbytes, (off_t) startoff);
if (byteswritten <= 0)
{
char xlogfname[MAXFNAMELEN];
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 073dab2be5..e4d954578c 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2053,7 +2053,7 @@ FileRead(File file, char *buffer, int amount, off_t offset,
retry:
pgstat_report_wait_start(wait_event_info);
- returnCode = pread(vfdP->fd, buffer, amount, offset);
+ returnCode = pg_pread(vfdP->fd, buffer, amount, offset);
pgstat_report_wait_end();
if (returnCode < 0)
@@ -2135,7 +2135,7 @@ FileWrite(File file, char *buffer, int amount, off_t offset,
retry:
errno = 0;
pgstat_report_wait_start(wait_event_info);
- returnCode = pwrite(VfdCache[file].fd, buffer, amount, offset);
+ returnCode = pg_pwrite(VfdCache[file].fd, buffer, amount, offset);
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
@@ -3740,7 +3740,7 @@ data_sync_elevel(int elevel)
}
/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
+ * A convenience wrapper for pg_pwritev() that retries on partial write. If an
* error is returned, it is unspecified how much has been written.
*/
ssize_t
@@ -3760,7 +3760,7 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
for (;;)
{
/* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
+ part = pg_pwritev(fd, iov, iovcnt, offset);
if (part < 0)
return -1;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..4d341d3f7f 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -1527,7 +1527,7 @@ AddToDataDirLockFile(int target_line, const char *str)
len = strlen(destbuffer);
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE);
- if (pwrite(fd, destbuffer, len, 0) != len)
+ if (pg_pwrite(fd, destbuffer, len, 0) != len)
{
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
diff --git a/src/bin/pg_test_fsync/pg_test_fsync.c b/src/bin/pg_test_fsync/pg_test_fsync.c
index 585b3ef731..5f8cbb75ff 100644
--- a/src/bin/pg_test_fsync/pg_test_fsync.c
+++ b/src/bin/pg_test_fsync/pg_test_fsync.c
@@ -312,10 +312,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
}
STOP_TIMER;
@@ -337,10 +337,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
fdatasync(tmpfile);
}
@@ -359,10 +359,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
if (fsync(tmpfile) != 0)
die("fsync failed");
@@ -383,10 +383,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
if (pg_fsync_writethrough(tmpfile) != 0)
die("fsync failed");
@@ -415,10 +415,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
/*
* This can generate write failures if the filesystem has
@@ -480,10 +480,10 @@ test_open_sync(const char *msg, int writes_size)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < 16 / writes_size; writes++)
- if (pwrite(tmpfile,
- buf,
- writes_size * 1024,
- writes * writes_size * 1024) !=
+ if (pg_pwrite(tmpfile,
+ buf,
+ writes_size * 1024,
+ writes * writes_size * 1024) !=
writes_size * 1024)
die("write failed");
}
@@ -582,7 +582,7 @@ test_non_sync(void)
START_TIMER;
for (ops = 0; alarm_triggered == false; ops++)
{
- if (pwrite(tmpfile, buf, XLOG_BLCKSZ, 0) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile, buf, XLOG_BLCKSZ, 0) != XLOG_BLCKSZ)
die("write failed");
}
STOP_TIMER;
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 6dcde2523a..e87f91316a 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -378,11 +378,11 @@ extern void XLogReaderResetError(XLogReaderState *state);
/*
* Error information from WALRead that both backend and frontend caller can
- * process. Currently only errors from pread can be reported.
+ * process. Currently only errors from pg_pread can be reported.
*/
typedef struct WALReadError
{
- int wre_errno; /* errno set by the last pread() */
+ int wre_errno; /* errno set by the last pg_pread() */
int wre_off; /* Offset we tried to read from. */
int wre_req; /* Bytes requested to be read. */
int wre_read; /* Bytes read by the last read(). */
diff --git a/src/include/port.h b/src/include/port.h
index 3562d471b5..69d8818d61 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -213,6 +213,15 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2,
extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0);
extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2);
+#ifndef WIN32
+/*
+ * We add a pg_ prefix as a warning that the Windows implementations have the
+ * non-standard side-effect of changing the current file position.
+ */
+#define pg_pread pread
+#define pg_pwrite pwrite
+#endif
+
/*
* We use __VA_ARGS__ for printf to prevent replacing references to
* the "printf" format archetype in format() attribute declarations.
diff --git a/src/include/port/pg_iovec.h b/src/include/port/pg_iovec.h
index ecdddba7fc..9e1d5dbab3 100644
--- a/src/include/port/pg_iovec.h
+++ b/src/include/port/pg_iovec.h
@@ -35,12 +35,21 @@ struct iovec
/* Define a reasonable maximum that is safe to use on the stack. */
#define PG_IOV_MAX Min(IOV_MAX, 32)
+/*
+ * Note that pg_preadv and pg_writev have a pg_ prefix as a warning that the
+ * Windows implementations have the side-effect of changing the file position.
+ */
+
#if !HAVE_DECL_PREADV
-extern ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+extern ssize_t pg_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+#else
+#define pg_preadv preadv
#endif
#if !HAVE_DECL_PWRITEV
-extern ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+extern ssize_t pg_pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+#else
+#define pg_pwritev pwritev
#endif
#endif /* PG_IOVEC_H */
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 707f8760ca..a22867d295 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -564,9 +564,9 @@ typedef unsigned short mode_t;
#endif
/* in port/win32pread.c */
-extern ssize_t pread(int fd, void *buf, size_t nbyte, off_t offset);
+extern ssize_t pg_pread(int fd, void *buf, size_t nbyte, off_t offset);
/* in port/win32pwrite.c */
-extern ssize_t pwrite(int fd, const void *buf, size_t nbyte, off_t offset);
+extern ssize_t pg_pwrite(int fd, const void *buf, size_t nbyte, off_t offset);
#endif /* PG_WIN32_PORT_H */
diff --git a/src/port/preadv.c b/src/port/preadv.c
index 188e10f065..ce5863b696 100644
--- a/src/port/preadv.c
+++ b/src/port/preadv.c
@@ -19,14 +19,14 @@
#include "port/pg_iovec.h"
ssize_t
-preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+pg_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset)
{
ssize_t sum = 0;
ssize_t part;
for (int i = 0; i < iovcnt; ++i)
{
- part = pread(fd, iov[i].iov_base, iov[i].iov_len, offset);
+ part = pg_pread(fd, iov[i].iov_base, iov[i].iov_len, offset);
if (part < 0)
{
if (i == 0)
diff --git a/src/port/pwritev.c b/src/port/pwritev.c
index de9b7e4e3d..6712a8986c 100644
--- a/src/port/pwritev.c
+++ b/src/port/pwritev.c
@@ -19,14 +19,14 @@
#include "port/pg_iovec.h"
ssize_t
-pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+pg_pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset)
{
ssize_t sum = 0;
ssize_t part;
for (int i = 0; i < iovcnt; ++i)
{
- part = pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset);
+ part = pg_pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset);
if (part < 0)
{
if (i == 0)
diff --git a/src/port/win32pread.c b/src/port/win32pread.c
index ebcdd33756..fb6f00bcbf 100644
--- a/src/port/win32pread.c
+++ b/src/port/win32pread.c
@@ -17,7 +17,7 @@
#include <windows.h>
ssize_t
-pread(int fd, void *buf, size_t size, off_t offset)
+pg_pread(int fd, void *buf, size_t size, off_t offset)
{
OVERLAPPED overlapped = {0};
HANDLE handle;
diff --git a/src/port/win32pwrite.c b/src/port/win32pwrite.c
index 7f2e62e8a7..aa374d0ffc 100644
--- a/src/port/win32pwrite.c
+++ b/src/port/win32pwrite.c
@@ -17,7 +17,7 @@
#include <windows.h>
ssize_t
-pwrite(int fd, const void *buf, size_t size, off_t offset)
+pg_pwrite(int fd, const void *buf, size_t size, off_t offset)
{
OVERLAPPED overlapped = {0};
HANDLE handle;
--
2.37.0 (Apple Git-136)
Attachments:
[text/plain] 0001-Restore-pg_pread-and-friends.txt (17.2K, ../../CA+hUKG+ZiAF1Sxik9+biUJS2phb3umR0DWdh-JpvYGf_RWa_wQ@mail.gmail.com/2-0001-Restore-pg_pread-and-friends.txt)
download | inline diff:
From fc72be32668f0c37b899fd922d6986f39d8a6a2a Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Tue, 27 Sep 2022 21:12:03 +1300
Subject: [PATCH] Restore pg_pread and friends.
Commit cf112c12 was a little too hasty in getting rid of the pg_
prefixes where we use pread(), pwrite() and vectored variants. We
dropped support for ancient Unixes that needed to use lseek() to
implement replacements for those, but it turned out that Windows also
changes the current position even when you pass in an offset to
ReadFile() and WriteFile(), if the file handle is synchronous.
Switching to asynchronous file handles would fix that, but have other
complications. For now let's just put back the pg_ prefix and add some
comments to highlight the non-standard side-effect.
Reported-by: Bharath Rupireddy <[email protected]>
Discussion: https://postgr.es/m/20220923202439.GA1156054%40nathanxps13
---
.../pg_stat_statements/pg_stat_statements.c | 4 +-
src/backend/access/heap/rewriteheap.c | 2 +-
src/backend/access/transam/slru.c | 4 +-
src/backend/access/transam/xlog.c | 4 +-
src/backend/access/transam/xlogreader.c | 2 +-
src/backend/access/transam/xlogrecovery.c | 2 +-
src/backend/backup/basebackup.c | 2 +-
src/backend/replication/walreceiver.c | 2 +-
src/backend/storage/file/fd.c | 8 +--
src/backend/utils/init/miscinit.c | 2 +-
src/bin/pg_test_fsync/pg_test_fsync.c | 50 +++++++++----------
src/include/access/xlogreader.h | 4 +-
src/include/port.h | 9 ++++
src/include/port/pg_iovec.h | 13 ++++-
src/include/port/win32_port.h | 4 +-
src/port/preadv.c | 4 +-
src/port/pwritev.c | 4 +-
src/port/win32pread.c | 2 +-
src/port/win32pwrite.c | 2 +-
19 files changed, 71 insertions(+), 53 deletions(-)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index ba868f0de9..73439c0199 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2103,9 +2103,9 @@ qtext_store(const char *query, int query_len,
if (fd < 0)
goto error;
- if (pwrite(fd, query, query_len, off) != query_len)
+ if (pg_pwrite(fd, query, query_len, off) != query_len)
goto error;
- if (pwrite(fd, "\0", 1, off + query_len) != 1)
+ if (pg_pwrite(fd, "\0", 1, off + query_len) != 1)
goto error;
CloseTransientFile(fd);
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2f08fbe8d3..b01b39b008 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -1150,7 +1150,7 @@ heap_xlog_logical_rewrite(XLogReaderState *r)
/* write out tail end of mapping file (again) */
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_LOGICAL_REWRITE_MAPPING_WRITE);
- if (pwrite(fd, data, len, xlrec->offset) != len)
+ if (pg_pwrite(fd, data, len, xlrec->offset) != len)
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index c9a7b97949..b65cb49d7f 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -718,7 +718,7 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_SLRU_READ);
- if (pread(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
+ if (pg_pread(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
{
pgstat_report_wait_end();
slru_errcause = SLRU_READ_FAILED;
@@ -873,7 +873,7 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruWriteAll fdata)
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_SLRU_WRITE);
- if (pwrite(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
+ if (pg_pwrite(fd, shared->page_buffer[slotno], BLCKSZ, offset) != BLCKSZ)
{
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1dd6df0fe1..7f9f350531 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2196,7 +2196,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
INSTR_TIME_SET_CURRENT(start);
pgstat_report_wait_start(WAIT_EVENT_WAL_WRITE);
- written = pwrite(openLogFile, from, nleft, startoffset);
+ written = pg_pwrite(openLogFile, from, nleft, startoffset);
pgstat_report_wait_end();
/*
@@ -3018,7 +3018,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* enough.
*/
errno = 0;
- if (pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
{
/* if write didn't set errno, assume no disk space */
save_errno = errno ? errno : ENOSPC;
diff --git a/src/backend/access/transam/xlogreader.c b/src/backend/access/transam/xlogreader.c
index 4d6c34e0fc..5a8fe81f82 100644
--- a/src/backend/access/transam/xlogreader.c
+++ b/src/backend/access/transam/xlogreader.c
@@ -1544,7 +1544,7 @@ WALRead(XLogReaderState *state,
/* Reset errno first; eases reporting non-errno-affecting errors */
errno = 0;
- readbytes = pread(state->seg.ws_file, p, segbytes, (off_t) startoff);
+ readbytes = pg_pread(state->seg.ws_file, p, segbytes, (off_t) startoff);
#ifndef FRONTEND
pgstat_report_wait_end();
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b41e682664..cb07694aea 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -3271,7 +3271,7 @@ retry:
readOff = targetPageOff;
pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- r = pread(readFile, readBuf, XLOG_BLCKSZ, (off_t) readOff);
+ r = pg_pread(readFile, readBuf, XLOG_BLCKSZ, (off_t) readOff);
if (r != XLOG_BLCKSZ)
{
char fname[MAXFNAMELEN];
diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c
index 411cac9be3..e252ad7421 100644
--- a/src/backend/backup/basebackup.c
+++ b/src/backend/backup/basebackup.c
@@ -1828,7 +1828,7 @@ basebackup_read_file(int fd, char *buf, size_t nbytes, off_t offset,
int rc;
pgstat_report_wait_start(WAIT_EVENT_BASEBACKUP_READ);
- rc = pread(fd, buf, nbytes, offset);
+ rc = pg_pread(fd, buf, nbytes, offset);
pgstat_report_wait_end();
if (rc < 0)
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index f6ef0ace2c..3767466ef3 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -915,7 +915,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
/* OK to write the logs */
errno = 0;
- byteswritten = pwrite(recvFile, buf, segbytes, (off_t) startoff);
+ byteswritten = pg_pwrite(recvFile, buf, segbytes, (off_t) startoff);
if (byteswritten <= 0)
{
char xlogfname[MAXFNAMELEN];
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 073dab2be5..e4d954578c 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2053,7 +2053,7 @@ FileRead(File file, char *buffer, int amount, off_t offset,
retry:
pgstat_report_wait_start(wait_event_info);
- returnCode = pread(vfdP->fd, buffer, amount, offset);
+ returnCode = pg_pread(vfdP->fd, buffer, amount, offset);
pgstat_report_wait_end();
if (returnCode < 0)
@@ -2135,7 +2135,7 @@ FileWrite(File file, char *buffer, int amount, off_t offset,
retry:
errno = 0;
pgstat_report_wait_start(wait_event_info);
- returnCode = pwrite(VfdCache[file].fd, buffer, amount, offset);
+ returnCode = pg_pwrite(VfdCache[file].fd, buffer, amount, offset);
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
@@ -3740,7 +3740,7 @@ data_sync_elevel(int elevel)
}
/*
- * A convenience wrapper for pwritev() that retries on partial write. If an
+ * A convenience wrapper for pg_pwritev() that retries on partial write. If an
* error is returned, it is unspecified how much has been written.
*/
ssize_t
@@ -3760,7 +3760,7 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
for (;;)
{
/* Write as much as we can. */
- part = pwritev(fd, iov, iovcnt, offset);
+ part = pg_pwritev(fd, iov, iovcnt, offset);
if (part < 0)
return -1;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 683f616b1a..4d341d3f7f 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -1527,7 +1527,7 @@ AddToDataDirLockFile(int target_line, const char *str)
len = strlen(destbuffer);
errno = 0;
pgstat_report_wait_start(WAIT_EVENT_LOCK_FILE_ADDTODATADIR_WRITE);
- if (pwrite(fd, destbuffer, len, 0) != len)
+ if (pg_pwrite(fd, destbuffer, len, 0) != len)
{
pgstat_report_wait_end();
/* if write didn't set errno, assume problem is no disk space */
diff --git a/src/bin/pg_test_fsync/pg_test_fsync.c b/src/bin/pg_test_fsync/pg_test_fsync.c
index 585b3ef731..5f8cbb75ff 100644
--- a/src/bin/pg_test_fsync/pg_test_fsync.c
+++ b/src/bin/pg_test_fsync/pg_test_fsync.c
@@ -312,10 +312,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
}
STOP_TIMER;
@@ -337,10 +337,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
fdatasync(tmpfile);
}
@@ -359,10 +359,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
if (fsync(tmpfile) != 0)
die("fsync failed");
@@ -383,10 +383,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
die("write failed");
if (pg_fsync_writethrough(tmpfile) != 0)
die("fsync failed");
@@ -415,10 +415,10 @@ test_sync(int writes_per_op)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < writes_per_op; writes++)
- if (pwrite(tmpfile,
- buf,
- XLOG_BLCKSZ,
- writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile,
+ buf,
+ XLOG_BLCKSZ,
+ writes * XLOG_BLCKSZ) != XLOG_BLCKSZ)
/*
* This can generate write failures if the filesystem has
@@ -480,10 +480,10 @@ test_open_sync(const char *msg, int writes_size)
for (ops = 0; alarm_triggered == false; ops++)
{
for (writes = 0; writes < 16 / writes_size; writes++)
- if (pwrite(tmpfile,
- buf,
- writes_size * 1024,
- writes * writes_size * 1024) !=
+ if (pg_pwrite(tmpfile,
+ buf,
+ writes_size * 1024,
+ writes * writes_size * 1024) !=
writes_size * 1024)
die("write failed");
}
@@ -582,7 +582,7 @@ test_non_sync(void)
START_TIMER;
for (ops = 0; alarm_triggered == false; ops++)
{
- if (pwrite(tmpfile, buf, XLOG_BLCKSZ, 0) != XLOG_BLCKSZ)
+ if (pg_pwrite(tmpfile, buf, XLOG_BLCKSZ, 0) != XLOG_BLCKSZ)
die("write failed");
}
STOP_TIMER;
diff --git a/src/include/access/xlogreader.h b/src/include/access/xlogreader.h
index 6dcde2523a..e87f91316a 100644
--- a/src/include/access/xlogreader.h
+++ b/src/include/access/xlogreader.h
@@ -378,11 +378,11 @@ extern void XLogReaderResetError(XLogReaderState *state);
/*
* Error information from WALRead that both backend and frontend caller can
- * process. Currently only errors from pread can be reported.
+ * process. Currently only errors from pg_pread can be reported.
*/
typedef struct WALReadError
{
- int wre_errno; /* errno set by the last pread() */
+ int wre_errno; /* errno set by the last pg_pread() */
int wre_off; /* Offset we tried to read from. */
int wre_req; /* Bytes requested to be read. */
int wre_read; /* Bytes read by the last read(). */
diff --git a/src/include/port.h b/src/include/port.h
index 3562d471b5..69d8818d61 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -213,6 +213,15 @@ extern int pg_fprintf(FILE *stream, const char *fmt,...) pg_attribute_printf(2,
extern int pg_vprintf(const char *fmt, va_list args) pg_attribute_printf(1, 0);
extern int pg_printf(const char *fmt,...) pg_attribute_printf(1, 2);
+#ifndef WIN32
+/*
+ * We add a pg_ prefix as a warning that the Windows implementations have the
+ * non-standard side-effect of changing the current file position.
+ */
+#define pg_pread pread
+#define pg_pwrite pwrite
+#endif
+
/*
* We use __VA_ARGS__ for printf to prevent replacing references to
* the "printf" format archetype in format() attribute declarations.
diff --git a/src/include/port/pg_iovec.h b/src/include/port/pg_iovec.h
index ecdddba7fc..9e1d5dbab3 100644
--- a/src/include/port/pg_iovec.h
+++ b/src/include/port/pg_iovec.h
@@ -35,12 +35,21 @@ struct iovec
/* Define a reasonable maximum that is safe to use on the stack. */
#define PG_IOV_MAX Min(IOV_MAX, 32)
+/*
+ * Note that pg_preadv and pg_writev have a pg_ prefix as a warning that the
+ * Windows implementations have the side-effect of changing the file position.
+ */
+
#if !HAVE_DECL_PREADV
-extern ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+extern ssize_t pg_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+#else
+#define pg_preadv preadv
#endif
#if !HAVE_DECL_PWRITEV
-extern ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+extern ssize_t pg_pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);
+#else
+#define pg_pwritev pwritev
#endif
#endif /* PG_IOVEC_H */
diff --git a/src/include/port/win32_port.h b/src/include/port/win32_port.h
index 707f8760ca..a22867d295 100644
--- a/src/include/port/win32_port.h
+++ b/src/include/port/win32_port.h
@@ -564,9 +564,9 @@ typedef unsigned short mode_t;
#endif
/* in port/win32pread.c */
-extern ssize_t pread(int fd, void *buf, size_t nbyte, off_t offset);
+extern ssize_t pg_pread(int fd, void *buf, size_t nbyte, off_t offset);
/* in port/win32pwrite.c */
-extern ssize_t pwrite(int fd, const void *buf, size_t nbyte, off_t offset);
+extern ssize_t pg_pwrite(int fd, const void *buf, size_t nbyte, off_t offset);
#endif /* PG_WIN32_PORT_H */
diff --git a/src/port/preadv.c b/src/port/preadv.c
index 188e10f065..ce5863b696 100644
--- a/src/port/preadv.c
+++ b/src/port/preadv.c
@@ -19,14 +19,14 @@
#include "port/pg_iovec.h"
ssize_t
-preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+pg_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset)
{
ssize_t sum = 0;
ssize_t part;
for (int i = 0; i < iovcnt; ++i)
{
- part = pread(fd, iov[i].iov_base, iov[i].iov_len, offset);
+ part = pg_pread(fd, iov[i].iov_base, iov[i].iov_len, offset);
if (part < 0)
{
if (i == 0)
diff --git a/src/port/pwritev.c b/src/port/pwritev.c
index de9b7e4e3d..6712a8986c 100644
--- a/src/port/pwritev.c
+++ b/src/port/pwritev.c
@@ -19,14 +19,14 @@
#include "port/pg_iovec.h"
ssize_t
-pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+pg_pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset)
{
ssize_t sum = 0;
ssize_t part;
for (int i = 0; i < iovcnt; ++i)
{
- part = pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset);
+ part = pg_pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset);
if (part < 0)
{
if (i == 0)
diff --git a/src/port/win32pread.c b/src/port/win32pread.c
index ebcdd33756..fb6f00bcbf 100644
--- a/src/port/win32pread.c
+++ b/src/port/win32pread.c
@@ -17,7 +17,7 @@
#include <windows.h>
ssize_t
-pread(int fd, void *buf, size_t size, off_t offset)
+pg_pread(int fd, void *buf, size_t size, off_t offset)
{
OVERLAPPED overlapped = {0};
HANDLE handle;
diff --git a/src/port/win32pwrite.c b/src/port/win32pwrite.c
index 7f2e62e8a7..aa374d0ffc 100644
--- a/src/port/win32pwrite.c
+++ b/src/port/win32pwrite.c
@@ -17,7 +17,7 @@
#include <windows.h>
ssize_t
-pwrite(int fd, const void *buf, size_t size, off_t offset)
+pg_pwrite(int fd, const void *buf, size_t size, off_t offset)
{
OVERLAPPED overlapped = {0};
HANDLE handle;
--
2.37.0 (Apple Git-136)
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-27 12:03 ` Bharath Rupireddy <[email protected]>
2022-09-27 19:01 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-27 12:03 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Sep 27, 2022 at 3:01 PM Thomas Munro <[email protected]> wrote:
>
> Something like the attached.
Isn't it also better to add notes in win32pread.c and win32pwrite.c
about the callers doing lseek(SEEK_SET) if they wish to and Windows
implementations changing file position? We can also add a TODO item
about replacing pg_ versions with pread and friends someday when
Windows fixes the issue? Having it in the commit and include/port.h is
good, but the comments in win32pread.c and win32pwrite.c make life
easier IMO. Otherwise, the patch LGTM.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 12:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-27 19:01 ` Thomas Munro <[email protected]>
2022-09-28 04:43 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-09-27 19:01 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Sep 28, 2022 at 1:03 AM Bharath Rupireddy
<[email protected]> wrote:>
> On Tue, Sep 27, 2022 at 3:01 PM Thomas Munro <[email protected]> wrote:
> > Something like the attached.
>
> Isn't it also better to add notes in win32pread.c and win32pwrite.c
> about the callers doing lseek(SEEK_SET) if they wish to and Windows
> implementations changing file position? We can also add a TODO item
> about replacing pg_ versions with pread and friends someday when
> Windows fixes the issue? Having it in the commit and include/port.h is
> good, but the comments in win32pread.c and win32pwrite.c make life
> easier IMO. Otherwise, the patch LGTM.
Thanks, will do. FWIW I doubt the OS itself will change released
behaviour like that, but I did complain about the straight-up
misleading documentation and they agreed and fixed it[1].
After some looking around, the only way I could find to avoid this
behaviour is to switch to async handles, which do behave as documented
in this respect, but then you can't use plain read/write at all unless
you write replacement wrappers for those too, and AFAICT that adds
more system calls (start write, wait for write to finish) and
complexity/weirdness without any real payoff so it seems like the
wrong direction, at least without more research that I'm not pursuing
currently. (FWIW in AIO porting experiments a while back we used
async handles to get IOs running concurrently with CPU work and
possibly other IOs so it was actually worth doing, but that was only
for smgr and the main wal writing code where there's no intermixed
plain read/write calls as you have here, so the problem doesn't even
come up.)
[1] https://github.com/MicrosoftDocs/sdk-api/pull/1309
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 12:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 19:01 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-28 04:43 ` Bharath Rupireddy <[email protected]>
2022-09-28 06:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-28 04:43 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Sep 28, 2022 at 12:32 AM Thomas Munro <[email protected]> wrote:
>
> On Wed, Sep 28, 2022 at 1:03 AM Bharath Rupireddy
> <[email protected]> wrote:>
> > On Tue, Sep 27, 2022 at 3:01 PM Thomas Munro <[email protected]> wrote:
> > > Something like the attached.
> >
> > Isn't it also better to add notes in win32pread.c and win32pwrite.c
> > about the callers doing lseek(SEEK_SET) if they wish to and Windows
> > implementations changing file position? We can also add a TODO item
> > about replacing pg_ versions with pread and friends someday when
> > Windows fixes the issue? Having it in the commit and include/port.h is
> > good, but the comments in win32pread.c and win32pwrite.c make life
> > easier IMO. Otherwise, the patch LGTM.
>
> Thanks, will do.
I'm looking forward to getting it in.
> FWIW I doubt the OS itself will change released
> behaviour like that, but I did complain about the straight-up
> misleading documentation and they agreed and fixed it[1].
>
> [1] https://github.com/MicrosoftDocs/sdk-api/pull/1309
Great! Is there any plan to request for change in WriteFile() to not
alter file position?
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 12:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 19:01 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-28 04:43 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-28 06:11 ` Thomas Munro <[email protected]>
0 siblings, 0 replies; 32+ messages in thread
From: Thomas Munro @ 2022-09-28 06:11 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Sep 28, 2022 at 5:43 PM Bharath Rupireddy
<[email protected]> wrote:
> On Wed, Sep 28, 2022 at 12:32 AM Thomas Munro <[email protected]> wrote:
> > FWIW I doubt the OS itself will change released
> > behaviour like that, but I did complain about the straight-up
> > misleading documentation and they agreed and fixed it[1].
> >
> > [1] https://github.com/MicrosoftDocs/sdk-api/pull/1309
>
> Great! Is there any plan to request for change in WriteFile() to not
> alter file position?
Not from me. I stick to open source problems. Reporting bugs in
documentation is legitimate self defence, though.
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-29 06:02 ` Bharath Rupireddy <[email protected]>
2022-09-29 17:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
1 sibling, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-29 06:02 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Sep 24, 2022 at 1:54 AM Nathan Bossart <[email protected]> wrote:
>
> + PGAlignedXLogBlock zbuffer;
> +
> + memset(zbuffer.data, 0, XLOG_BLCKSZ);
>
> This seems excessive for only writing a single byte.
Yes, I removed it now, instead doing pg_pwrite(fd, "\0", 1,
wal_segment_size - 1).
> +#ifdef WIN32
> + /*
> + * XXX: It looks like on Windows, we need an explicit lseek() call here
> + * despite using pwrite() implementation from win32pwrite.c. Otherwise
> + * an error occurs.
> + */
>
> I think this comment is too vague. Can we describe the error in more
> detail? Or better yet, can we fix it as a prerequisite to this patch set?
The commit b6d8a60aba322678585ebe11dab072a37ac32905 brings back
pg_pwrite() and its friends. This puts the responsibility of doing
lseek(SEEK_SET) on the callers if they wish to.
Please see the v5 patch set and review it further.
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Attachments:
[application/octet-stream] v5-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch (6.1K, ../../CALj2ACWvZA4jYSWE=6a27rDFzTsuNN9SiL9cJQ-JUwEos3tR4A@mail.gmail.com/2-v5-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch)
download | inline diff:
From b53c51c3816a62d613b8cfbb2ef10011f1255b4f Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Thu, 29 Sep 2022 03:44:01 +0000
Subject: [PATCH v5] Move pg_pwritev_with_retry() to file_utils.c
Move pg_pwritev_with_retry() from file/fd.c to common/file_utils.c
so that non-backend code or FRONTEND cases can also use it.
Author: Bharath Rupireddy
Reviewed-by: Thomas Munro
Reviewed-by: Nathan Bossart
Discussion: https://www.postgresql.org/message-id/CA%2BhUKGJKwUrpP0igOFAv5khj3dbHvfWqLzFeo7vtNzDYXv_YZw%40mail.gmail.com
---
src/backend/storage/file/fd.c | 65 ---------------------------------
src/common/file_utils.c | 65 +++++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 7 ++++
src/include/storage/fd.h | 6 ---
4 files changed, 72 insertions(+), 71 deletions(-)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index e4d954578c..4151cafec5 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -93,7 +93,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3738,67 +3737,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pg_pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pg_pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..4a4bdc31c4 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,67 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pg_pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pg_pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..2c5dbcb0b1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,9 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 5a48fccd9c..c0a212487d 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t nbytes);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
[application/octet-stream] v5-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (7.2K, ../../CALj2ACWvZA4jYSWE=6a27rDFzTsuNN9SiL9cJQ-JUwEos3tR4A@mail.gmail.com/3-v5-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From 35ab77794c4731c7a6466726e83e880111c9519a Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Thu, 29 Sep 2022 04:40:25 +0000
Subject: [PATCH v5] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() on non-Windows platforms
to seek to the beginning of the WAL segment. It looks like on
Windows, we need an explicit lseek() call here despite using
pwrite() implementation from win32pwrite.c. Otherwise
an error occurs.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
Author: Bharath Rupireddy
Reviewed-by: Nathan Bossart
Discussion: https://www.postgresql.org/message-id/CALj2ACUq7nAb7%3DbJNbK3yYmp-SZhJcXFR_pLk8un6XgDzDF3OA%40mail.gmail.com
---
src/backend/access/transam/xlog.c | 33 ++-----------
src/bin/pg_basebackup/walmethods.c | 26 +++++-----
src/common/file_utils.c | 77 ++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 2 +
4 files changed, 98 insertions(+), 40 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e15256db8..ecfb4b13fb 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2925,7 +2925,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2969,14 +2968,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2987,29 +2983,10 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_zeros(fd, wal_segment_size);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
@@ -3018,7 +2995,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* enough.
*/
errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ if (pg_pwrite(fd, "\0", 1, wal_segment_size - 1) != 1)
{
/* if write didn't set errno, assume no disk space */
save_errno = errno ? errno : ENOSPC;
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index bc2e83d02b..9eb4d35a40 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -220,28 +220,30 @@ dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
/* Do pre-padding on non-compressed files */
if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
+ rc = pg_pwritev_zeros(fd, pad_to_size);
+
+ if (rc < 0)
{
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- wwmethod->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
+ wwmethod->lasterrno = errno;
+ close(fd);
+ return NULL;
}
+#ifdef WIN32
+ /*
+ * WriteFile() on Windows changes the current file position, hence we
+ * need an explicit lseek() here. See pg_pwrite() implementation in
+ * win32pwrite.c for more details.
+ */
if (lseek(fd, 0, SEEK_SET) != 0)
{
wwmethod->lasterrno = errno;
close(fd);
return NULL;
}
+#endif
}
/*
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 4a4bdc31c4..30ce31201f 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -525,3 +525,80 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * Function to zero-fill a file with given size. On failure, a negative value
+ * is returned and errno is set appropriately so that the caller can use it
+ * accordingly.
+ */
+ssize_t
+pg_pwritev_zeros(int fd, size_t size)
+{
+ PGAlignedXLogBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ size_t block_sz;
+ size_t remaining_size = 0;
+ int i;
+ ssize_t written;
+ ssize_t total_written = 0;
+
+ /*
+ * XXX: Writing more than one block of size XLOG_BLCKSZ bytes via
+ * PGAlignedXLogBlock structure per vector buffer might improve write
+ * performance on some platforms. However, tests (on some platforms, not
+ * all) show not much improvements with varying block sizes. Hence we stick
+ * to one block in PGAlignedXLogBlock structure.
+ */
+ block_sz = sizeof(zbuffer.data);
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = size / block_sz;
+ remaining_size = size % block_sz;
+ for (i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ i += iovcnt;
+ total_written += written;
+ }
+
+ /* Now, write the remaining size, if any, of the file with zeros. */
+ if (remaining_size > 0)
+ {
+ /* We'll never write more than one block here */
+ int iovcnt = 1;
+
+ /* Jump on to the end of previously written blocks */
+ off_t offset = i * block_sz;
+
+ iov[0].iov_base = zbuffer.data;
+ iov[0].iov_len = remaining_size;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ total_written += written;
+ }
+
+ Assert(total_written == size);
+
+ return total_written;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2c5dbcb0b1..f8d0d475fa 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -44,4 +44,6 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
+extern ssize_t pg_pwritev_zeros(int fd, size_t size);
+
#endif /* FILE_UTILS_H */
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-29 17:27 ` Nathan Bossart <[email protected]>
2022-09-30 01:15 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Nathan Bossart @ 2022-09-29 17:27 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Sep 29, 2022 at 11:32:32AM +0530, Bharath Rupireddy wrote:
> On Sat, Sep 24, 2022 at 1:54 AM Nathan Bossart <[email protected]> wrote:
>>
>> + PGAlignedXLogBlock zbuffer;
>> +
>> + memset(zbuffer.data, 0, XLOG_BLCKSZ);
>>
>> This seems excessive for only writing a single byte.
>
> Yes, I removed it now, instead doing pg_pwrite(fd, "\0", 1,
> wal_segment_size - 1).
I don't think removing the use of PGAlignedXLogBlock here introduces any
sort of alignment risk, so this should be alright.
+#ifdef WIN32
+ /*
+ * WriteFile() on Windows changes the current file position, hence we
+ * need an explicit lseek() here. See pg_pwrite() implementation in
+ * win32pwrite.c for more details.
+ */
Should we really surround this with a WIN32 check, or should we just
unconditionally lseek() here? I understand that this helps avoid an extra
system call on many platforms, but in theory another platform introduced in
the future could have the same problem, and this seems like something that
could easily be missed. Presumably we could do something fancier to
indicate pg_pwrite()'s behavior in this regard, but I don't know if that
sort of complexity is really worth it in order to save an lseek().
+ iov[0].iov_base = zbuffer.data;
This seems superfluous, but I don't think it's hurting anything.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-29 17:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
@ 2022-09-30 01:15 ` Thomas Munro <[email protected]>
2022-09-30 02:39 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Thomas Munro @ 2022-09-30 01:15 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Sep 30, 2022 at 6:27 AM Nathan Bossart <[email protected]> wrote:
> On Thu, Sep 29, 2022 at 11:32:32AM +0530, Bharath Rupireddy wrote:
> > On Sat, Sep 24, 2022 at 1:54 AM Nathan Bossart <[email protected]> wrote:
> >>
> >> + PGAlignedXLogBlock zbuffer;
> >> +
> >> + memset(zbuffer.data, 0, XLOG_BLCKSZ);
> >>
> >> This seems excessive for only writing a single byte.
> >
> > Yes, I removed it now, instead doing pg_pwrite(fd, "\0", 1,
> > wal_segment_size - 1).
>
> I don't think removing the use of PGAlignedXLogBlock here introduces any
> sort of alignment risk, so this should be alright.
>
> +#ifdef WIN32
> + /*
> + * WriteFile() on Windows changes the current file position, hence we
> + * need an explicit lseek() here. See pg_pwrite() implementation in
> + * win32pwrite.c for more details.
> + */
>
> Should we really surround this with a WIN32 check, or should we just
> unconditionally lseek() here? I understand that this helps avoid an extra
> system call on many platforms, but in theory another platform introduced in
> the future could have the same problem, and this seems like something that
> could easily be missed. Presumably we could do something fancier to
> indicate pg_pwrite()'s behavior in this regard, but I don't know if that
> sort of complexity is really worth it in order to save an lseek().
+1 for just doing it always, with a one-liner comment like
"pg_pwritev*() might move the file position". No reason to spam the
source tree with more explanations of the exact reason. If someone
ever comes up with another case where p- and non-p- I/O functions are
intermixed and it's really worth saving a system call (don't get me
wrong, we call lseek() an obscene amount elsewhere and I'd like to fix
that, but this case isn't hot?) then I like your idea of a macro to
tell you whether you need to.
Earlier I wondered why we'd want to include "pg_pwritev" in the name
of this zero-filling function (pwritev being an internal
implementation detail), but now it seems like maybe a good idea
because it highlights the file position portability problem by being a
member of that family of similarly-named functions.
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-29 17:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-30 01:15 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
@ 2022-09-30 02:39 ` Bharath Rupireddy <[email protected]>
2022-09-30 03:09 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 32+ messages in thread
From: Bharath Rupireddy @ 2022-09-30 02:39 UTC (permalink / raw)
To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Sep 30, 2022 at 6:46 AM Thomas Munro <[email protected]> wrote:
>
> +1 for just doing it always, with a one-liner comment like
> "pg_pwritev*() might move the file position". No reason to spam the
> source tree with more explanations of the exact reason.
+1 for resetting the file position in a platform-independent manner.
But, a description there won't hurt IMO and it saves time for the
hackers who spend time there and think why it's that way.
> If someone
> ever comes up with another case where p- and non-p- I/O functions are
> intermixed and it's really worth saving a system call (don't get me
> wrong, we call lseek() an obscene amount elsewhere and I'd like to fix
> that, but this case isn't hot?) then I like your idea of a macro to
> tell you whether you need to.
I don't think we go that route as the code isn't a hot path and an
extra system call wouldn't hurt performance much, a comment there
should work.
> Earlier I wondered why we'd want to include "pg_pwritev" in the name
> of this zero-filling function (pwritev being an internal
> implementation detail), but now it seems like maybe a good idea
> because it highlights the file position portability problem by being a
> member of that family of similarly-named functions.
Hm.
On Thu, Sep 29, 2022 at 10:57 PM Nathan Bossart
<[email protected]> wrote:
>
> + iov[0].iov_base = zbuffer.data;
>
> This seems superfluous, but I don't think it's hurting anything.
Yes, I removed it. Adding a comment, something like [1], would make it
more verbose, hence I've not added.
I'm attaching the v6 patch set, please review it further.
[1]
/*
* Use the first vector buffer to write the remaining size. Note that
* zero buffer was already pointed to it above, hence just specifying
* the size is enough here.
*/
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Attachments:
[application/x-patch] v6-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch (6.1K, ../../CALj2ACWKjREewMTDPj_5sCXESdXAmN7d6njDpAtkE8MFr0ZeQA@mail.gmail.com/2-v6-0001-Move-pg_pwritev_with_retry-to-file_utils.c.patch)
download | inline diff:
From 7ae3f77c0444e6c5383e13a502457813b850fd08 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Fri, 30 Sep 2022 01:26:03 +0000
Subject: [PATCH v6] Move pg_pwritev_with_retry() to file_utils.c
Move pg_pwritev_with_retry() from file/fd.c to common/file_utils.c
so that non-backend code or FRONTEND cases can also use it.
Author: Bharath Rupireddy
Reviewed-by: Thomas Munro
Reviewed-by: Nathan Bossart
Discussion: https://www.postgresql.org/message-id/CA%2BhUKGJKwUrpP0igOFAv5khj3dbHvfWqLzFeo7vtNzDYXv_YZw%40mail.gmail.com
---
src/backend/storage/file/fd.c | 65 ---------------------------------
src/common/file_utils.c | 65 +++++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 7 ++++
src/include/storage/fd.h | 6 ---
4 files changed, 72 insertions(+), 71 deletions(-)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index e4d954578c..4151cafec5 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -93,7 +93,6 @@
#include "common/pg_prng.h"
#include "miscadmin.h"
#include "pgstat.h"
-#include "port/pg_iovec.h"
#include "portability/mem.h"
#include "postmaster/startup.h"
#include "storage/fd.h"
@@ -3738,67 +3737,3 @@ data_sync_elevel(int elevel)
{
return data_sync_retry ? elevel : PANIC;
}
-
-/*
- * A convenience wrapper for pg_pwritev() that retries on partial write. If an
- * error is returned, it is unspecified how much has been written.
- */
-ssize_t
-pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
-{
- struct iovec iov_copy[PG_IOV_MAX];
- ssize_t sum = 0;
- ssize_t part;
-
- /* We'd better have space to make a copy, in case we need to retry. */
- if (iovcnt > PG_IOV_MAX)
- {
- errno = EINVAL;
- return -1;
- }
-
- for (;;)
- {
- /* Write as much as we can. */
- part = pg_pwritev(fd, iov, iovcnt, offset);
- if (part < 0)
- return -1;
-
-#ifdef SIMULATE_SHORT_WRITE
- part = Min(part, 4096);
-#endif
-
- /* Count our progress. */
- sum += part;
- offset += part;
-
- /* Step over iovecs that are done. */
- while (iovcnt > 0 && iov->iov_len <= part)
- {
- part -= iov->iov_len;
- ++iov;
- --iovcnt;
- }
-
- /* Are they all done? */
- if (iovcnt == 0)
- {
- /* We don't expect the kernel to write more than requested. */
- Assert(part == 0);
- break;
- }
-
- /*
- * Move whatever's left to the front of our mutable copy and adjust
- * the leading iovec.
- */
- Assert(iovcnt > 0);
- memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
- Assert(iov->iov_len > part);
- iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
- iov_copy[0].iov_len -= part;
- iov = iov_copy;
- }
-
- return sum;
-}
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index df4d6d240c..4a4bdc31c4 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -28,6 +28,7 @@
#ifdef FRONTEND
#include "common/logging.h"
#endif
+#include "port/pg_iovec.h"
#ifdef FRONTEND
@@ -460,3 +461,67 @@ get_dirent_type(const char *path,
return result;
}
+
+/*
+ * A convenience wrapper for pg_pwritev() that retries on partial write. If an
+ * error is returned, it is unspecified how much has been written.
+ */
+ssize_t
+pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
+{
+ struct iovec iov_copy[PG_IOV_MAX];
+ ssize_t sum = 0;
+ ssize_t part;
+
+ /* We'd better have space to make a copy, in case we need to retry. */
+ if (iovcnt > PG_IOV_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (;;)
+ {
+ /* Write as much as we can. */
+ part = pg_pwritev(fd, iov, iovcnt, offset);
+ if (part < 0)
+ return -1;
+
+#ifdef SIMULATE_SHORT_WRITE
+ part = Min(part, 4096);
+#endif
+
+ /* Count our progress. */
+ sum += part;
+ offset += part;
+
+ /* Step over iovecs that are done. */
+ while (iovcnt > 0 && iov->iov_len <= part)
+ {
+ part -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+
+ /* Are they all done? */
+ if (iovcnt == 0)
+ {
+ /* We don't expect the kernel to write more than requested. */
+ Assert(part == 0);
+ break;
+ }
+
+ /*
+ * Move whatever's left to the front of our mutable copy and adjust
+ * the leading iovec.
+ */
+ Assert(iovcnt > 0);
+ memmove(iov_copy, iov, sizeof(*iov) * iovcnt);
+ Assert(iov->iov_len > part);
+ iov_copy[0].iov_base = (char *) iov_copy[0].iov_base + part;
+ iov_copy[0].iov_len -= part;
+ iov = iov_copy;
+ }
+
+ return sum;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2811744c12..2c5dbcb0b1 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -24,6 +24,8 @@ typedef enum PGFileType
PGFILETYPE_LNK
} PGFileType;
+struct iovec; /* avoid including port/pg_iovec.h here */
+
#ifdef FRONTEND
extern int fsync_fname(const char *fname, bool isdir);
extern void fsync_pgdata(const char *pg_data, int serverVersion);
@@ -37,4 +39,9 @@ extern PGFileType get_dirent_type(const char *path,
bool look_through_symlinks,
int elevel);
+extern ssize_t pg_pwritev_with_retry(int fd,
+ const struct iovec *iov,
+ int iovcnt,
+ off_t offset);
+
#endif /* FILE_UTILS_H */
diff --git a/src/include/storage/fd.h b/src/include/storage/fd.h
index 5a48fccd9c..c0a212487d 100644
--- a/src/include/storage/fd.h
+++ b/src/include/storage/fd.h
@@ -51,8 +51,6 @@ typedef enum RecoveryInitSyncMethod
RECOVERY_INIT_SYNC_METHOD_SYNCFS
} RecoveryInitSyncMethod;
-struct iovec; /* avoid including port/pg_iovec.h here */
-
typedef int File;
@@ -178,10 +176,6 @@ extern int pg_fsync_no_writethrough(int fd);
extern int pg_fsync_writethrough(int fd);
extern int pg_fdatasync(int fd);
extern void pg_flush_data(int fd, off_t offset, off_t nbytes);
-extern ssize_t pg_pwritev_with_retry(int fd,
- const struct iovec *iov,
- int iovcnt,
- off_t offset);
extern int pg_truncate(const char *path, off_t length);
extern void fsync_fname(const char *fname, bool isdir);
extern int fsync_fname_ext(const char *fname, bool isdir, bool ignore_perm, int elevel);
--
2.34.1
[application/x-patch] v6-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch (7.3K, ../../CALj2ACWKjREewMTDPj_5sCXESdXAmN7d6njDpAtkE8MFr0ZeQA@mail.gmail.com/3-v6-0002-Use-pg_pwritev_with_retry-instead-of-write-in-wal.patch)
download | inline diff:
From 97a21536fd4ca274fc8730a99b3b27aea214aa70 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Fri, 30 Sep 2022 01:59:43 +0000
Subject: [PATCH v6] Use pg_pwritev_with_retry() instead of write() in
walmethods.c
Use pg_pwritev_with_retry() while prepadding a WAL segment
instead of write() in walmethods.c dir_open_for_write() to avoid
partial writes. As the pg_pwritev_with_retry() function uses
pwritev, we can avoid explicit lseek() on non-Windows platforms
to seek to the beginning of the WAL segment. It looks like on
Windows, we need an explicit lseek() call here despite using
pwrite() implementation from win32pwrite.c. Otherwise
an error occurs.
These changes are inline with how core postgres initializes the
WAL segment in XLogFileInitInternal().
Author: Bharath Rupireddy
Reviewed-by: Nathan Bossart
Reviewed-by: Thomas Munro
Discussion: https://www.postgresql.org/message-id/CALj2ACUq7nAb7%3DbJNbK3yYmp-SZhJcXFR_pLk8un6XgDzDF3OA%40mail.gmail.com
---
src/backend/access/transam/xlog.c | 33 ++-----------
src/bin/pg_basebackup/walmethods.c | 27 ++++++-----
src/common/file_utils.c | 76 ++++++++++++++++++++++++++++++
src/include/common/file_utils.h | 2 +
4 files changed, 98 insertions(+), 40 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e15256db8..ecfb4b13fb 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2925,7 +2925,6 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
bool *added, char *path)
{
char tmppath[MAXPGPATH];
- PGAlignedXLogBlock zbuffer;
XLogSegNo installed_segno;
XLogSegNo max_segno;
int fd;
@@ -2969,14 +2968,11 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m", tmppath)));
- memset(zbuffer.data, 0, XLOG_BLCKSZ);
-
pgstat_report_wait_start(WAIT_EVENT_WAL_INIT_WRITE);
save_errno = 0;
if (wal_init_zero)
{
- struct iovec iov[PG_IOV_MAX];
- int blocks;
+ ssize_t rc;
/*
* Zero-fill the file. With this setting, we do this the hard way to
@@ -2987,29 +2983,10 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* indirect blocks are down on disk. Therefore, fdatasync(2) or
* O_DSYNC will be sufficient to sync future writes to the log file.
*/
+ rc = pg_pwritev_zeros(fd, wal_segment_size);
- /* Prepare to write out a lot of copies of our zero buffer at once. */
- for (int i = 0; i < lengthof(iov); ++i)
- {
- iov[i].iov_base = zbuffer.data;
- iov[i].iov_len = XLOG_BLCKSZ;
- }
-
- /* Loop, writing as many blocks as we can for each system call. */
- blocks = wal_segment_size / XLOG_BLCKSZ;
- for (int i = 0; i < blocks;)
- {
- int iovcnt = Min(blocks - i, lengthof(iov));
- off_t offset = i * XLOG_BLCKSZ;
-
- if (pg_pwritev_with_retry(fd, iov, iovcnt, offset) < 0)
- {
- save_errno = errno;
- break;
- }
-
- i += iovcnt;
- }
+ if (rc < 0)
+ save_errno = errno;
}
else
{
@@ -3018,7 +2995,7 @@ XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
* enough.
*/
errno = 0;
- if (pg_pwrite(fd, zbuffer.data, 1, wal_segment_size - 1) != 1)
+ if (pg_pwrite(fd, "\0", 1, wal_segment_size - 1) != 1)
{
/* if write didn't set errno, assume no disk space */
save_errno = errno ? errno : ENOSPC;
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index bc2e83d02b..da18a738d4 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -220,22 +220,25 @@ dir_open_for_write(WalWriteMethod *wwmethod, const char *pathname,
/* Do pre-padding on non-compressed files */
if (pad_to_size && wwmethod->compression_algorithm == PG_COMPRESSION_NONE)
{
- PGAlignedXLogBlock zerobuf;
- int bytes;
+ ssize_t rc;
- memset(zerobuf.data, 0, XLOG_BLCKSZ);
- for (bytes = 0; bytes < pad_to_size; bytes += XLOG_BLCKSZ)
+ rc = pg_pwritev_zeros(fd, pad_to_size);
+
+ if (rc < 0)
{
- errno = 0;
- if (write(fd, zerobuf.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
- {
- /* If write didn't set errno, assume problem is no disk space */
- wwmethod->lasterrno = errno ? errno : ENOSPC;
- close(fd);
- return NULL;
- }
+ wwmethod->lasterrno = errno;
+ close(fd);
+ return NULL;
}
+ /*
+ * pg_pwritev*() might move the file position on some platforms, for
+ * instance, see pg_pwrite() implementation in win32pwrite.c. We want
+ * to explicitly reset the file position in a platform-independent
+ * manner for extensibility even though it costs an extra system call
+ * on the platforms where the file position is guaranteed to not
+ * change.
+ */
if (lseek(fd, 0, SEEK_SET) != 0)
{
wwmethod->lasterrno = errno;
diff --git a/src/common/file_utils.c b/src/common/file_utils.c
index 4a4bdc31c4..47ece647d7 100644
--- a/src/common/file_utils.c
+++ b/src/common/file_utils.c
@@ -525,3 +525,79 @@ pg_pwritev_with_retry(int fd, const struct iovec *iov, int iovcnt, off_t offset)
return sum;
}
+
+/*
+ * Function to zero-fill a file with given size. On failure, a negative value
+ * is returned and errno is set appropriately so that the caller can use it
+ * accordingly.
+ */
+ssize_t
+pg_pwritev_zeros(int fd, size_t size)
+{
+ PGAlignedXLogBlock zbuffer;
+ struct iovec iov[PG_IOV_MAX];
+ int blocks;
+ size_t block_sz;
+ size_t remaining_size = 0;
+ int i;
+ ssize_t written;
+ ssize_t total_written = 0;
+
+ /*
+ * XXX: Writing more than one block of size XLOG_BLCKSZ bytes via
+ * PGAlignedXLogBlock structure per vector buffer might improve write
+ * performance on some platforms. However, tests (on some platforms, not
+ * all) show not much improvements with varying block sizes. Hence we stick
+ * to one block in PGAlignedXLogBlock structure for now.
+ */
+ block_sz = sizeof(zbuffer.data);
+
+ memset(zbuffer.data, 0, block_sz);
+
+ /* Prepare to write out a lot of copies of our zero buffer at once. */
+ for (i = 0; i < lengthof(iov); ++i)
+ {
+ iov[i].iov_base = zbuffer.data;
+ iov[i].iov_len = block_sz;
+ }
+
+ /* Loop, writing as many blocks as we can for each system call. */
+ blocks = size / block_sz;
+ remaining_size = size % block_sz;
+ for (i = 0; i < blocks;)
+ {
+ int iovcnt = Min(blocks - i, lengthof(iov));
+ off_t offset = i * block_sz;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ i += iovcnt;
+ total_written += written;
+ }
+
+ /* Now, write the remaining size, if any, of the file with zeros. */
+ if (remaining_size > 0)
+ {
+ /* We'll never write more than one block here */
+ int iovcnt = 1;
+
+ /* Jump on to the end of previously written blocks */
+ off_t offset = i * block_sz;
+
+ iov[0].iov_len = remaining_size;
+
+ written = pg_pwritev_with_retry(fd, iov, iovcnt, offset);
+
+ if (written < 0)
+ return written;
+
+ total_written += written;
+ }
+
+ Assert(total_written == size);
+
+ return total_written;
+}
diff --git a/src/include/common/file_utils.h b/src/include/common/file_utils.h
index 2c5dbcb0b1..f8d0d475fa 100644
--- a/src/include/common/file_utils.h
+++ b/src/include/common/file_utils.h
@@ -44,4 +44,6 @@ extern ssize_t pg_pwritev_with_retry(int fd,
int iovcnt,
off_t offset);
+extern ssize_t pg_pwritev_zeros(int fd, size_t size);
+
#endif /* FILE_UTILS_H */
--
2.34.1
^ permalink raw reply [nested|flat] 32+ messages in thread
* Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes?
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-29 17:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-30 01:15 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-30 02:39 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
@ 2022-09-30 03:09 ` Nathan Bossart <[email protected]>
0 siblings, 0 replies; 32+ messages in thread
From: Nathan Bossart @ 2022-09-30 03:09 UTC (permalink / raw)
To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Sep 30, 2022 at 08:09:04AM +0530, Bharath Rupireddy wrote:
> I'm attaching the v6 patch set, please review it further.
Looks reasonable to me.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 32+ messages in thread
* [PATCH v53 4/7] Add CONCURRENTLY option to REPACK command.
@ 2026-03-11 14:16 Antonin Houska <[email protected]>
0 siblings, 0 replies; 32+ messages in thread
From: Antonin Houska @ 2026-03-11 14:16 UTC (permalink / raw)
The REPACK command copies the relation data into a new file, creates new
indexes and eventually swaps the files. To make sure that the old file does
not change during the copying, the relation is locked in an exclusive mode,
which prevents applications from both reading and writing. (To keep the data
consistent, we'd only need to prevent the applications from writing, but even
reading needs to be blocked before we can swap the files - otherwise some
applications could continue using the old file. Currently, REPACK takes the
simple approach and acquires the exclusive lock in the beginning.
This patch introduces an alternative workflow, which only requires the
exclusive lock when the relation (and index) files are being swapped.
(Supposedly, the swapping should be pretty fast.) On the other hand, when we
copy the data to the new file, we allow applications to read from the relation
and even to write to it.
First, we scan the relation using a "historic snapshot", and insert all the
tuples satisfying this snapshot into the new relation.
Second, logical decoding is used to capture the data changes done by
applications during the copying (i.e. changes not yet committed from the
perspective of the historic snapshot mentioned above), and those are applied
to the new file before we acquire the exclusive lock that we need to swap the
files. (Of course, more data changes can take place while we are waiting for
the lock - these will be applied to the new file after we have acquired the
lock and before we swap the files.)
While the "concurrent data" changes are applied at specific stages (we cannot
do that until the intial copy is finished and indexes are built), a background
worker performs the decoding all the time. This way we minimize the amount of
not-yet-decoded WAL, so that archiving / recycling of WAL segments is not
delayed much. The decoded changes are written to files and passed to the
backed performing REPACK.
Since the logical decoding system, during its startup, waits until all the
transactions which already have XID assigned have finished, there is a risk of
deadlock if a transaction that already changed anything in the database tries
to acquire a conflicting lock on the table REPACK CONCURRENTLY is working
on. As an example, consider transaction running CREATE INDEX command on the
table that is being REPACKed CONCURRENTLY. On the other hand, DML commands
(INSERT, UPDATE, DELETE) are not a problem as their lock does not conflict
with REPACK CONCURRENTLY.
The current approach is that we accept the risk. If we tried to avoid it, it'd
be necessary to unlock the table before the logical decoding is setup and lock
it again afterwards. Such temporary unlocking would imply re-checking if the
table still meets all the requirements for REPACK CONCURRENTLY.
The WAL records produced by running DML commands on the new relation are
intentionally not fed to the logical decoding system. Doing so would introduce
significant overhead, and - as the new relation is never available for logical
replication - it would be useless.
---
doc/src/sgml/monitoring.sgml | 37 +-
doc/src/sgml/mvcc.sgml | 12 +-
doc/src/sgml/ref/repack.sgml | 118 +-
src/Makefile | 1 +
src/backend/access/heap/heapam.c | 25 +-
src/backend/access/heap/heapam_handler.c | 269 ++-
src/backend/access/heap/rewriteheap.c | 6 +-
src/backend/catalog/system_views.sql | 19 +-
src/backend/commands/Makefile | 1 +
src/backend/commands/matview.c | 1 +
src/backend/commands/meson.build | 1 +
src/backend/commands/repack.c | 1843 ++++++++++++++++-
src/backend/commands/repack_worker.c | 549 +++++
src/backend/commands/tablecmds.c | 1 +
src/backend/commands/vacuum.c | 12 +-
src/backend/executor/nodeModifyTable.c | 2 +-
src/backend/libpq/pqmq.c | 5 +
src/backend/meson.build | 1 +
src/backend/postmaster/bgworker.c | 5 +
src/backend/replication/logical/decode.c | 28 +-
src/backend/replication/logical/logical.c | 6 +-
.../replication/pgoutput_repack/Makefile | 32 +
.../replication/pgoutput_repack/meson.build | 18 +
.../pgoutput_repack/pgoutput_repack.c | 297 +++
src/backend/storage/ipc/procsignal.c | 3 +
.../storage/lmgr/generate-lwlocknames.pl | 2 +-
src/backend/tcop/postgres.c | 3 +
.../utils/activity/wait_event_names.txt | 1 +
src/bin/psql/tab-complete.in.c | 4 +-
src/include/access/heapam_xlog.h | 2 +
src/include/access/tableam.h | 16 +-
src/include/commands/progress.h | 17 +-
src/include/commands/repack.h | 15 +-
src/include/commands/repack_internal.h | 128 ++
src/include/replication/decode.h | 4 +
src/include/storage/lockdefs.h | 4 +-
src/include/storage/procsignal.h | 1 +
src/test/modules/injection_points/Makefile | 2 +
.../injection_points/expected/repack.out | 113 +
.../expected/repack_toast.out | 65 +
src/test/modules/injection_points/meson.build | 2 +
.../injection_points/specs/repack.spec | 142 ++
.../injection_points/specs/repack_toast.spec | 112 +
src/test/regress/expected/rules.out | 19 +-
src/tools/pgindent/typedefs.list | 6 +
45 files changed, 3681 insertions(+), 269 deletions(-)
create mode 100644 src/backend/commands/repack_worker.c
create mode 100644 src/backend/replication/pgoutput_repack/Makefile
create mode 100644 src/backend/replication/pgoutput_repack/meson.build
create mode 100644 src/backend/replication/pgoutput_repack/pgoutput_repack.c
create mode 100644 src/include/commands/repack_internal.h
create mode 100644 src/test/modules/injection_points/expected/repack.out
create mode 100644 src/test/modules/injection_points/expected/repack_toast.out
create mode 100644 src/test/modules/injection_points/specs/repack.spec
create mode 100644 src/test/modules/injection_points/specs/repack_toast.spec
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 312374da5e0..9678877bf3d 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -6990,14 +6990,35 @@ FROM pg_stat_get_backend_idset() AS backendid;
<row>
<entry role="catalog_table_entry"><para role="column_definition">
- <structfield>heap_tuples_written</structfield> <type>bigint</type>
+ <structfield>heap_tuples_inserted</structfield> <type>bigint</type>
</para>
<para>
- Number of heap tuples written.
+ Number of heap tuples inserted.
This counter only advances when the phase is
<literal>seq scanning heap</literal>,
- <literal>index scanning heap</literal>
- or <literal>writing new heap</literal>.
+ <literal>index scanning heap</literal>,
+ <literal>writing new heap</literal>
+ or <literal>catch-up</literal>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>heap_tuples_updated</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of heap tuples updated.
+ This counter only advances when the phase is <literal>catch-up</literal>.
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>heap_tuples_deleted</structfield> <type>bigint</type>
+ </para>
+ <para>
+ Number of heap tuples deleted.
+ This counter only advances when the phase is <literal>catch-up</literal>.
</para></entry>
</row>
@@ -7078,6 +7099,14 @@ FROM pg_stat_get_backend_idset() AS backendid;
<command>REPACK</command> is currently writing the new heap.
</entry>
</row>
+ <row>
+ <entry><literal>catch-up</literal></entry>
+ <entry>
+ <command>REPACK CONCURRENTLY</command> is currently processing the DML
+ commands that other transactions executed during any of the preceding
+ phases.
+ </entry>
+ </row>
<row>
<entry><literal>swapping relation files</literal></entry>
<entry>
diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml
index e775260936a..241caeb3593 100644
--- a/doc/src/sgml/mvcc.sgml
+++ b/doc/src/sgml/mvcc.sgml
@@ -1845,15 +1845,17 @@ SELECT pg_advisory_lock(q.id) FROM
<title>Caveats</title>
<para>
- Some DDL commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link> and the
- table-rewriting forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>, are not
+ Some commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link>, the
+ table-rewriting forms of <link linkend="sql-altertable"><command>ALTER
+ TABLE</command></link> and <command>REPACK</command> with
+ the <literal>CONCURRENTLY</literal> option, are not
MVCC-safe. This means that after the truncation or rewrite commits, the
table will appear empty to concurrent transactions, if they are using a
- snapshot taken before the DDL command committed. This will only be an
+ snapshot taken before the command committed. This will only be an
issue for a transaction that did not access the table in question
- before the DDL command started — any transaction that has done so
+ before the command started — any transaction that has done so
would hold at least an <literal>ACCESS SHARE</literal> table lock,
- which would block the DDL command until that transaction completes.
+ which would block the truncating or rewriting command until that transaction completes.
So these commands will not cause any apparent inconsistency in the
table contents for successive queries on the target table, but they
could cause visible inconsistency between the contents of the target
diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index 8ccf7c7a417..e993dfb3108 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -28,6 +28,7 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
ANALYZE [ <replaceable class="parameter">boolean</replaceable> ]
+ CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -54,7 +55,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
processes every table and materialized view in the current database that
the current user has the <literal>MAINTAIN</literal> privilege on. This
form of <command>REPACK</command> cannot be executed inside a transaction
- block.
+ block. Also, this form is not allowed if
+ the <literal>CONCURRENTLY</literal> option is used.
</para>
<para>
@@ -67,7 +69,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
When a table is being repacked, an <literal>ACCESS EXCLUSIVE</literal> lock
is acquired on it. This prevents any other database operations (both reads
and writes) from operating on the table until the <command>REPACK</command>
- is finished.
+ is finished. If you want to keep the table accessible during the repacking,
+ consider using the <literal>CONCURRENTLY</literal> option.
</para>
<refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering">
@@ -198,6 +201,117 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONCURRENTLY</literal></term>
+ <listitem>
+ <para>
+ Allow other transactions to use the table while it is being repacked.
+ </para>
+
+ <para>
+ Internally, <command>REPACK</command> copies the contents of the table
+ (ignoring dead tuples) into a new file, sorted by the specified index,
+ and also creates a new file for each index. Then it swaps the old and
+ new files for the table and all the indexes, and deletes the old
+ files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make
+ sure that the old files do not change during the processing because the
+ changes would get lost due to the swap.
+ </para>
+
+ <para>
+ With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS
+ EXCLUSIVE</literal> lock is only acquired to swap the table and index
+ files. The data changes that took place during the creation of the new
+ table and index files are captured using logical decoding
+ (<xref linkend="logicaldecoding"/>) and applied before
+ the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock
+ is typically held only for the time needed to swap the files, which
+ should be pretty short. However, the time might still be noticeable if
+ too many data changes have been done to the table while
+ <command>REPACK</command> was waiting for the lock: those changes must
+ be processed just before the files are swapped, while the
+ <literal>ACCESS EXCLUSIVE</literal> lock is being held.
+ </para>
+
+ <para>
+ Note that <command>REPACK</command> with the
+ <literal>CONCURRENTLY</literal> option does not try to order the rows
+ inserted into the table after the repacking started. Also
+ note <command>REPACK</command> might fail to complete due to DDL
+ commands executed on the table by other transactions during the
+ repacking.
+ </para>
+
+ <note>
+ <para>
+ In addition to the temporary space requirements explained in
+ <xref linkend="sql-repack-notes-on-resources"/>,
+ the <literal>CONCURRENTLY</literal> option can add to the usage of
+ temporary space a bit more. The reason is that other transactions can
+ perform DML operations which cannot be applied to the new file until
+ <command>REPACK</command> has copied all the existing tuples from the
+ old file. Thus the tuples inserted into the old file during the copying
+ are also stored separately in a temporary file, until they can be
+ processed.
+ </para>
+ </note>
+
+ <para>
+ The <literal>CONCURRENTLY</literal> option cannot be used in the
+ following cases:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ The table is <literal>UNLOGGED</literal>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The table is partitioned.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The table lacks a primary key and index-based replica identity.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The table is a system catalog or a <acronym>TOAST</acronym> table.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <command>REPACK</command> is executed inside a transaction block.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link>
+ configuration parameter does not allow for creation of an additional
+ replication slot.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <warning>
+ <para>
+ <command>REPACK</command> with the <literal>CONCURRENTLY</literal>
+ option is not MVCC-safe, see <xref linkend="mvcc-caveats"/> for
+ details.
+ </para>
+ </warning>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>VERBOSE</literal></term>
<listitem>
diff --git a/src/Makefile b/src/Makefile
index 2f31a2f20a7..b18c9a14ffa 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -23,6 +23,7 @@ SUBDIRS = \
interfaces \
backend/replication/libpqwalreceiver \
backend/replication/pgoutput \
+ backend/replication/pgoutput_repack \
fe_utils \
bin \
pl \
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index e06ce2db2cf..f3f62b98789 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -61,7 +61,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup,
static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup,
HeapTuple newtup, HeapTuple old_key_tuple,
- bool all_visible_cleared, bool new_all_visible_cleared);
+ bool all_visible_cleared, bool new_all_visible_cleared,
+ bool walLogical);
#ifdef USE_ASSERT_CHECKING
static void check_lock_if_inplace_updateable_rel(Relation relation,
const ItemPointerData *otid,
@@ -2716,6 +2717,7 @@ heap_delete(Relation relation, const ItemPointerData *tid,
uint16 new_infomask,
new_infomask2;
bool changingPart = (options & TABLE_DELETE_CHANGING_PARTITION) != 0;
+ bool walLogical = (options & TABLE_DELETE_NO_LOGICAL) == 0;
bool have_tuple_lock = false;
bool iscombo;
bool all_visible_cleared = false;
@@ -2950,7 +2952,8 @@ l1:
* Compute replica identity tuple before entering the critical section so
* we don't PANIC upon a memory allocation failure.
*/
- old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
+ old_key_tuple = walLogical ?
+ ExtractReplicaIdentity(relation, &tp, true, &old_key_copied) : NULL;
/*
* If this is the first possibly-multixact-able operation in the current
@@ -3040,6 +3043,15 @@ l1:
xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY;
}
+ /*
+ * Unlike UPDATE, DELETE is decoded even if there is no old key, so it
+ * does not help to clear both XLH_DELETE_CONTAINS_OLD_TUPLE and
+ * XLH_DELETE_CONTAINS_OLD_KEY. Thus we need an extra flag. TODO
+ * Consider not decoding tuples w/o the old tuple/key instead.
+ */
+ if (!walLogical)
+ xlrec.flags |= XLH_DELETE_NO_LOGICAL;
+
XLogBeginInsert();
XLogRegisterData(&xlrec, SizeOfHeapDelete);
@@ -3190,6 +3202,7 @@ heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup,
HeapTuple heaptup;
HeapTuple old_key_tuple = NULL;
bool old_key_copied = false;
+ bool walLogical = (options & TABLE_UPDATE_NO_LOGICAL) == 0;
Page page,
newpage;
BlockNumber block;
@@ -4071,7 +4084,8 @@ l2:
newbuf, &oldtup, heaptup,
old_key_tuple,
all_visible_cleared,
- all_visible_cleared_new);
+ all_visible_cleared_new,
+ walLogical);
if (newbuf != buffer)
{
PageSetLSN(newpage, recptr);
@@ -8747,7 +8761,8 @@ static XLogRecPtr
log_heap_update(Relation reln, Buffer oldbuf,
Buffer newbuf, HeapTuple oldtup, HeapTuple newtup,
HeapTuple old_key_tuple,
- bool all_visible_cleared, bool new_all_visible_cleared)
+ bool all_visible_cleared, bool new_all_visible_cleared,
+ bool walLogical)
{
xl_heap_update xlrec;
xl_heap_header xlhdr;
@@ -8758,7 +8773,7 @@ log_heap_update(Relation reln, Buffer oldbuf,
suffixlen = 0;
XLogRecPtr recptr;
Page page = BufferGetPage(newbuf);
- bool need_tuple_data = RelationIsLogicallyLogged(reln);
+ bool need_tuple_data = walLogical && RelationIsLogicallyLogged(reln);
bool init;
int bufflags;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 07f07188d46..7265dfd2927 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -51,6 +51,11 @@
static void reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate);
+static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull,
+ BulkInsertState bistate);
+static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap,
+ Relation NewHeap, Datum *values, bool *isnull);
static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer,
HeapTuple tuple,
@@ -589,13 +594,15 @@ static void
heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
Relation OldIndex, bool use_sort,
TransactionId OldestXmin,
+ Snapshot snapshot,
TransactionId *xid_cutoff,
MultiXactId *multi_cutoff,
double *num_tuples,
double *tups_vacuumed,
double *tups_recently_dead)
{
- RewriteState rwstate;
+ RewriteState rwstate = NULL;
+ BulkInsertState bistate = NULL;
IndexScanDesc indexScan;
TableScanDesc tableScan;
HeapScanDesc heapScan;
@@ -609,6 +616,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
bool *isnull;
BufferHeapTupleTableSlot *hslot;
BlockNumber prev_cblock = InvalidBlockNumber;
+ bool concurrent = snapshot != NULL;
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation(OldHeap);
@@ -624,9 +632,14 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
values = palloc_array(Datum, natts);
isnull = palloc_array(bool, natts);
- /* Initialize the rewrite operation */
- rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff,
- *multi_cutoff);
+ /*
+ * Initialize the rewrite operation.
+ */
+ if (!concurrent)
+ rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin,
+ *xid_cutoff, *multi_cutoff);
+ else
+ bistate = GetBulkInsertState();
/* Set up sorting if wanted */
@@ -641,6 +654,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
* Prepare to scan the OldHeap. To ensure we see recently-dead tuples
* that still need to be copied, we scan with SnapshotAny and use
* HeapTupleSatisfiesVacuum for the visibility test.
+ *
+ * In the CONCURRENTLY case, we do regular MVCC visibility tests, using
+ * the snapshot passed by the caller.
*/
if (OldIndex != NULL && !use_sort)
{
@@ -657,7 +673,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
tableScan = NULL;
heapScan = NULL;
- indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0,
+ indexScan = index_beginscan(OldHeap, OldIndex,
+ snapshot ? snapshot : SnapshotAny,
+ NULL, 0, 0,
SO_NONE);
index_rescan(indexScan, NULL, 0, NULL, 0);
}
@@ -667,7 +685,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
- tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL,
+ tableScan = table_beginscan(OldHeap,
+ snapshot ? snapshot : SnapshotAny,
+ 0, (ScanKey) NULL,
SO_NONE);
heapScan = (HeapScanDesc) tableScan;
indexScan = NULL;
@@ -744,83 +764,91 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
buf = hslot->buffer;
/*
- * To be able to guarantee that we can set the hint bit, acquire an
- * exclusive lock on the old buffer. We need the hint bits, set in
- * heapam_relation_copy_for_cluster() -> HeapTupleSatisfiesVacuum(),
- * to be set, as otherwise reform_and_rewrite_tuple() ->
- * rewrite_heap_tuple() will get confused. Specifically,
- * rewrite_heap_tuple() checks for HEAP_XMAX_INVALID in the old tuple
- * to determine whether to check the old-to-new mapping hash table.
- *
- * It'd be better if we somehow could avoid setting hint bits on the
- * old page. One reason to use VACUUM FULL are very bloated tables -
- * rewriting most of the old table during VACUUM FULL doesn't exactly
- * help...
+ * Regarding CONCURRENTLY, see the comments on MVCC snapshot above.
*/
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
-
- switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
+ if (!concurrent)
{
- case HEAPTUPLE_DEAD:
- /* Definitely dead */
- isdead = true;
- break;
- case HEAPTUPLE_RECENTLY_DEAD:
- *tups_recently_dead += 1;
- pg_fallthrough;
- case HEAPTUPLE_LIVE:
- /* Live or recently dead, must copy it */
- isdead = false;
- break;
- case HEAPTUPLE_INSERT_IN_PROGRESS:
+ /*
+ * To be able to guarantee that we can set the hint bit, acquire
+ * an exclusive lock on the old buffer. We need the hint bits, set
+ * in heapam_relation_copy_for_cluster() ->
+ * HeapTupleSatisfiesVacuum(), to be set, as otherwise
+ * reform_and_rewrite_tuple() -> rewrite_heap_tuple() will get
+ * confused. Specifically, rewrite_heap_tuple() checks for
+ * HEAP_XMAX_INVALID in the old tuple to determine whether to
+ * check the old-to-new mapping hash table.
+ *
+ * It'd be better if we somehow could avoid setting hint bits on
+ * the old page. One reason to use VACUUM FULL are very bloated
+ * tables - rewriting most of the old table during VACUUM FULL
+ * doesn't exactly help...
+ */
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
- /*
- * Since we hold exclusive lock on the relation, normally the
- * only way to see this is if it was inserted earlier in our
- * own transaction. However, it can happen in system
- * catalogs, since we tend to release write lock before commit
- * there. Give a warning if neither case applies; but in any
- * case we had better copy it.
- */
- if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
- elog(WARNING, "concurrent insert in progress within table \"%s\"",
- RelationGetRelationName(OldHeap));
- /* treat as live */
- isdead = false;
- break;
- case HEAPTUPLE_DELETE_IN_PROGRESS:
-
- /*
- * Similar situation to INSERT_IN_PROGRESS case.
- */
- if (!is_system_catalog &&
- !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
- elog(WARNING, "concurrent delete in progress within table \"%s\"",
- RelationGetRelationName(OldHeap));
- /* treat as recently dead */
- *tups_recently_dead += 1;
- isdead = false;
- break;
- default:
- elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
- isdead = false; /* keep compiler quiet */
- break;
- }
-
- LockBuffer(buf, BUFFER_LOCK_UNLOCK);
-
- if (isdead)
- {
- *tups_vacuumed += 1;
- /* heap rewrite module still needs to see it... */
- if (rewrite_heap_dead_tuple(rwstate, tuple))
+ switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))
{
- /* A previous recently-dead tuple is now known dead */
- *tups_vacuumed += 1;
- *tups_recently_dead -= 1;
+ case HEAPTUPLE_DEAD:
+ /* Definitely dead */
+ isdead = true;
+ break;
+ case HEAPTUPLE_RECENTLY_DEAD:
+ *tups_recently_dead += 1;
+ pg_fallthrough;
+ case HEAPTUPLE_LIVE:
+ /* Live or recently dead, must copy it */
+ isdead = false;
+ break;
+ case HEAPTUPLE_INSERT_IN_PROGRESS:
+
+ /*
+ * As long as we hold exclusive lock on the relation,
+ * normally the only way to see this is if it was inserted
+ * earlier in our own transaction. However, it can happen
+ * in system catalogs, since we tend to release write lock
+ * before commit there. Give a warning if neither case
+ * applies; but in any case we had better copy it.
+ */
+ if (!is_system_catalog &&
+ !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data)))
+ elog(WARNING, "concurrent insert in progress within table \"%s\"",
+ RelationGetRelationName(OldHeap));
+ /* treat as live */
+ isdead = false;
+ break;
+ case HEAPTUPLE_DELETE_IN_PROGRESS:
+
+ /*
+ * Similar situation to INSERT_IN_PROGRESS case.
+ */
+ if (!is_system_catalog &&
+ !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
+ elog(WARNING, "concurrent delete in progress within table \"%s\"",
+ RelationGetRelationName(OldHeap));
+ /* treat as recently dead */
+ *tups_recently_dead += 1;
+ isdead = false;
+ break;
+ default:
+ elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
+ isdead = false; /* keep compiler quiet */
+ break;
+ }
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+ if (isdead)
+ {
+ *tups_vacuumed += 1;
+ /* heap rewrite module still needs to see it... */
+ if (rewrite_heap_dead_tuple(rwstate, tuple))
+ {
+ /* A previous recently-dead tuple is now known dead */
+ *tups_vacuumed += 1;
+ *tups_recently_dead -= 1;
+ }
+
+ continue;
}
- continue;
}
*num_tuples += 1;
@@ -839,12 +867,16 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
{
const int ct_index[] = {
PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
- PROGRESS_REPACK_HEAP_TUPLES_WRITTEN
+ PROGRESS_REPACK_HEAP_TUPLES_INSERTED
};
int64 ct_val[2];
- reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
- values, isnull, rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple, OldHeap, NewHeap,
+ values, isnull, rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
/*
* In indexscan mode and also VACUUM FULL, report increase in
@@ -892,12 +924,17 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
break;
n_tuples += 1;
- reform_and_rewrite_tuple(tuple,
- OldHeap, NewHeap,
- values, isnull,
- rwstate);
+ if (!concurrent)
+ reform_and_rewrite_tuple(tuple,
+ OldHeap, NewHeap,
+ values, isnull,
+ rwstate);
+ else
+ heap_insert_for_repack(tuple, OldHeap, NewHeap,
+ values, isnull, bistate);
+
/* Report n_tuples */
- pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN,
+ pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED,
n_tuples);
}
@@ -905,7 +942,10 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
}
/* Write out any remaining tuples, and fsync if needed */
- end_heap_rewrite(rwstate);
+ if (rwstate)
+ end_heap_rewrite(rwstate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
/* Clean up */
pfree(values);
@@ -2303,27 +2343,66 @@ static void
reform_and_rewrite_tuple(HeapTuple tuple,
Relation OldHeap, Relation NewHeap,
Datum *values, bool *isnull, RewriteState rwstate)
+{
+ HeapTuple copiedTuple;
+
+ copiedTuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ /* The heap rewrite module does the rest */
+ rewrite_heap_tuple(rwstate, tuple, copiedTuple);
+
+ heap_freetuple(copiedTuple);
+}
+
+/*
+ * Insert tuple when processing REPACK CONCURRENTLY.
+ *
+ * rewriteheap.c is not used in the CONCURRENTLY case because it'd be
+ * difficult to do the same in the catch-up phase (as the logical
+ * decoding does not provide us with sufficient visibility
+ * information). Thus we must use heap_insert() both during the
+ * catch-up and here.
+ *
+ * We pass the NO_LOGICAL flag to heap_insert() in order to skip logical
+ * decoding: as soon as REPACK CONCURRENTLY swaps the relation files, it drops
+ * this relation, so no logical replication subscription should need the data.
+ *
+ * BulkInsertState is used because many tuples are inserted in the typical
+ * case.
+ */
+static void
+heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull, BulkInsertState bistate)
+{
+ tuple = reform_tuple(tuple, OldHeap, NewHeap, values, isnull);
+
+ heap_insert(NewHeap, tuple, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, bistate);
+
+ heap_freetuple(tuple);
+}
+
+/*
+ * Deform tuple, set values of dropped columns to NULL, form a new tuple and
+ * return it.
+ */
+static HeapTuple
+reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap,
+ Datum *values, bool *isnull)
{
TupleDesc oldTupDesc = RelationGetDescr(OldHeap);
TupleDesc newTupDesc = RelationGetDescr(NewHeap);
- HeapTuple copiedTuple;
int i;
heap_deform_tuple(tuple, oldTupDesc, values, isnull);
- /* Be sure to null out any dropped columns */
for (i = 0; i < newTupDesc->natts; i++)
{
if (TupleDescCompactAttr(newTupDesc, i)->attisdropped)
isnull[i] = true;
}
- copiedTuple = heap_form_tuple(newTupDesc, values, isnull);
-
- /* The heap rewrite module does the rest */
- rewrite_heap_tuple(rwstate, tuple, copiedTuple);
-
- heap_freetuple(copiedTuple);
+ return heap_form_tuple(newTupDesc, values, isnull);
}
/*
diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index f707b102c72..5a5398a76ae 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -621,9 +621,9 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
uint32 options = HEAP_INSERT_SKIP_FSM;
/*
- * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data
- * for the TOAST table are not logically decoded. The main heap is
- * WAL-logged as XLOG FPI records, which are not logically decoded.
+ * While rewriting the heap for REPACK, make sure data for the TOAST
+ * table are not logically decoded. The main heap is WAL-logged as
+ * XLOG FPI records, which are not logically decoded.
*/
options |= HEAP_INSERT_NO_LOGICAL;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index eba25aa3e4d..64ef4897571 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1343,16 +1343,19 @@ CREATE VIEW pg_stat_progress_repack AS
WHEN 2 THEN 'index scanning heap'
WHEN 3 THEN 'sorting tuples'
WHEN 4 THEN 'writing new heap'
- WHEN 5 THEN 'swapping relation files'
- WHEN 6 THEN 'rebuilding index'
- WHEN 7 THEN 'performing final cleanup'
+ WHEN 5 THEN 'catch-up'
+ WHEN 6 THEN 'swapping relation files'
+ WHEN 7 THEN 'rebuilding index'
+ WHEN 8 THEN 'performing final cleanup'
END AS phase,
CAST(S.param3 AS oid) AS repack_index_relid,
S.param4 AS heap_tuples_scanned,
- S.param5 AS heap_tuples_written,
- S.param6 AS heap_blks_total,
- S.param7 AS heap_blks_scanned,
- S.param8 AS index_rebuild_count
+ S.param5 AS heap_tuples_inserted,
+ S.param6 AS heap_tuples_updated,
+ S.param7 AS heap_tuples_deleted,
+ S.param8 AS heap_blks_total,
+ S.param9 AS heap_blks_scanned,
+ S.param10 AS index_rebuild_count
FROM pg_stat_get_progress_info('REPACK') AS S
LEFT JOIN pg_database D ON S.datid = D.oid;
@@ -1370,7 +1373,7 @@ CREATE VIEW pg_stat_progress_cluster AS
phase,
repack_index_relid AS cluster_index_relid,
heap_tuples_scanned,
- heap_tuples_written,
+ heap_tuples_inserted + heap_tuples_updated AS heap_tuples_written,
heap_blks_total,
heap_blks_scanned,
index_rebuild_count
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index fe1bba3a9b9..5b9d084977e 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -51,6 +51,7 @@ OBJS = \
propgraphcmds.o \
publicationcmds.o \
repack.o \
+ repack_worker.o \
schemacmds.o \
seclabel.o \
sequence.o \
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index 5db4fe75dce..f7d8007f796 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -893,6 +893,7 @@ static void
refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence)
{
finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true,
+ true, /* reindex */
RecentXmin, ReadNextMultiXactId(), relpersistence);
}
diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build
index f624aae74af..9f258d566eb 100644
--- a/src/backend/commands/meson.build
+++ b/src/backend/commands/meson.build
@@ -39,6 +39,7 @@ backend_sources += files(
'propgraphcmds.c',
'publicationcmds.c',
'repack.c',
+ 'repack_worker.c',
'schemacmds.c',
'seclabel.c',
'sequence.c',
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 20f0a572236..f2759cdbef1 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -4,6 +4,22 @@
* REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses
* parts of this code.
*
+ * There are two somewhat different ways to rewrite a table. In non-
+ * concurrent mode, it's easy: take AccessExclusiveLock, create a new
+ * transient relation, copy the tuples over to the relfilenode of the new
+ * relation, swap the relfilenodes, then drop the old relation.
+ *
+ * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock,
+ * then do an initial copy as above. However, while the tuples are being
+ * copied, concurrent transactions could modify the table. To cope with those
+ * changes, we rely on logical decoding to obtain them from WAL. A bgworker
+ * consumes WAL while the initial copy is ongoing (to prevent excessive WAL
+ * from being reserved), and accumulates the changes in a file. Once the
+ * initial copy is complete, we read the changes from the file and re-apply
+ * them on the new heap. Then we upgrade our ShareUpdateExclusiveLock to
+ * AccessExclusiveLock and swap the relfilenodes. This way, the time we hold
+ * a strong lock on the table is much reduced, and the bloat is eliminated.
+ *
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
@@ -17,6 +33,7 @@
#include "postgres.h"
#include "access/amapi.h"
+#include "access/detoast.h"
#include "access/heapam.h"
#include "access/multixact.h"
#include "access/relscan.h"
@@ -24,6 +41,7 @@
#include "access/toast_internals.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "access/xloginsert.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
@@ -31,22 +49,30 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_control.h"
#include "catalog/pg_inherits.h"
#include "catalog/toasting.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "commands/repack.h"
+#include "commands/repack_internal.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
+#include "executor/executor.h"
+#include "libpq/pqformat.h"
+#include "libpq/pqmq.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
+#include "storage/proc.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/injection_point.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -54,6 +80,7 @@
#include "utils/relmapper.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "utils/wait_event_types.h"
/*
* This struct is used to pass around the information on tables to be
@@ -66,12 +93,79 @@ typedef struct
Oid indexOid;
} RelToCluster;
+/*
+ * The first file exported by the decoding worker must contain a snapshot, the
+ * following ones contain the data changes.
+ */
+#define WORKER_FILE_SNAPSHOT 0
+
+/*
+ * Information needed to apply concurrent data changes.
+ */
+typedef struct ChangeContext
+{
+ /* The relation the changes are applied to. */
+ Relation cc_rel;
+
+ /* Needed to update indexes of rel_dst. */
+ ResultRelInfo *cc_rri;
+ EState *cc_estate;
+
+ /*
+ * Existing tuples to UPDATE and DELETE are located via this index. We
+ * keep the scankey in partially initialized state to avoid repeated work.
+ * sk_argument is completed on the fly.
+ */
+ Relation cc_ident_index;
+ ScanKey cc_ident_key;
+ int cc_ident_key_nentries;
+
+ /* Sequential number of the file containing the changes. */
+ int cc_file_seq;
+} ChangeContext;
+
+/* FIXME probably in repack_worker.h */
+pid_t backend_pid;
+ProcNumber backend_proc_number;
+
+/*
+ * Backend-local information to control the decoding worker.
+ */
+typedef struct DecodingWorker
+{
+ /* The worker. */
+ BackgroundWorkerHandle *handle;
+
+ /* DecodingWorkerShared is in this segment. */
+ dsm_segment *seg;
+
+ /* Handle of the error queue. */
+ shm_mq_handle *error_mqh;
+} DecodingWorker;
+
+/* Pointer to currently running decoding worker. */
+static DecodingWorker *decoding_worker = NULL;
+
+/*
+ * Is there a message sent by a repack worker that the backend needs to
+ * receive?
+ */
+volatile sig_atomic_t RepackMessagePending = false;
+
+static LOCKMODE RepackLockLevel(bool concurrent);
static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
- Oid indexOid, Oid userid, int options);
-static void rebuild_relation(Relation OldHeap, Relation index, bool verbose);
+ Oid indexOid, Oid userid, LOCKMODE lmode,
+ int options);
+static void check_repack_concurrently_requirements(Relation rel,
+ Oid *ident_idx_p);
+static void rebuild_relation(Relation OldHeap, Relation index, bool verbose,
+ Oid ident_idx);
static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
- bool verbose, bool *pSwapToastByContent,
- TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
+ Snapshot snapshot,
+ bool verbose,
+ bool *pSwapToastByContent,
+ TransactionId *pFreezeXid,
+ MultiXactId *pCutoffMulti);
static List *get_tables_to_repack(RepackCommand cmd, bool usingindex,
MemoryContext permcxt);
static List *get_tables_to_repack_partitioned(RepackCommand cmd,
@@ -79,10 +173,47 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
MemoryContext permcxt);
static bool repack_is_permitted_for_relation(RepackCommand cmd,
Oid relid, Oid userid);
+
+static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
+static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
+ ChangeContext *chgcxt);
+static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
+ TupleTableSlot *ondisk_tuple,
+ ChangeContext *chgcxt);
+static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
+static void restore_tuple(BufFile *file, Relation relation,
+ TupleTableSlot *slot);
+static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
+ TupleTableSlot *src);
+static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
+ TupleTableSlot *locator,
+ TupleTableSlot *received);
+static void process_concurrent_changes(XLogRecPtr end_of_wal,
+ ChangeContext *chgcxt,
+ bool done);
+static void initialize_change_context(ChangeContext *chgcxt,
+ Relation relation,
+ Oid ident_index_id);
+static void release_change_context(ChangeContext *chgcxt);
+static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
+ Oid identIdx,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti);
+static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes);
+static void copy_index_constraints(Relation old_index, Oid new_index_id,
+ Oid new_heap_id);
static Relation process_single_relation(RepackStmt *stmt,
+ LOCKMODE lockmode,
+ bool isTopLevel,
ClusterParams *params);
static Oid determine_clustered_index(Relation rel, bool usingindex,
const char *indexname);
+
+static void start_repack_decoding_worker(Oid relid);
+static void stop_repack_decoding_worker(void);
+static Snapshot get_initial_snapshot(DecodingWorker *worker);
+
+static void ProcessRepackMessage(StringInfo msg);
static const char *RepackCommandAsString(RepackCommand cmd);
@@ -115,6 +246,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
ClusterParams params = {0};
Relation rel = NULL;
MemoryContext repack_context;
+ LOCKMODE lockmode;
List *rtcs;
/* Parse option list */
@@ -125,6 +257,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
else if (strcmp(opt->defname, "analyze") == 0 ||
strcmp(opt->defname, "analyse") == 0)
params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0;
+ else if (strcmp(opt->defname, "concurrently") == 0 &&
+ defGetBoolean(opt))
+ {
+ if (stmt->command != REPACK_COMMAND_REPACK)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CONCURRENTLY option not supported for %s",
+ RepackCommandAsString(stmt->command)));
+ params.options |= CLUOPT_CONCURRENT;
+ }
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
@@ -134,13 +276,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
parser_errposition(pstate, opt->location));
}
+ /* Determine the lock mode to use. */
+ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
+
/*
* If a single relation is specified, process it and we're done ... unless
* the relation is a partitioned table, in which case we fall through.
*/
if (stmt->relation != NULL)
{
- rel = process_single_relation(stmt, ¶ms);
+ rel = process_single_relation(stmt, lockmode, isTopLevel, ¶ms);
if (rel == NULL)
return; /* all done */
}
@@ -156,10 +301,29 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"REPACK (ANALYZE)"));
/*
- * By here, we know we are in a multi-table situation. In order to avoid
- * holding locks for too long, we want to process each table in its own
- * transaction. This forces us to disallow running inside a user
- * transaction block.
+ * By here, we know we are in a multi-table situation.
+ *
+ * Concurrent processing is currently considered rather special (e.g. in
+ * terms of resources consumed) so it is not performed in bulk.
+ */
+ if (params.options & CLUOPT_CONCURRENT)
+ {
+ if (rel != NULL)
+ {
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+ ereport(ERROR,
+ errmsg("REPACK CONCURRENTLY not supported for partitioned tables"),
+ errhint("Consider running the command for individual partitions."));
+ }
+ else
+ ereport(ERROR,
+ errmsg("REPACK CONCURRENTLY requires explicit table name"));
+ }
+
+ /*
+ * In order to avoid holding locks for too long, we want to process each
+ * table in its own transaction. This forces us to disallow running
+ * inside a user transaction block.
*/
PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command));
@@ -168,6 +332,12 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
"Repack",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Since we open a new transaction for each relation, we have to check
+ * that the relation still is what we think it is.
+ *
+ * In single-transaction CLUSTER, we don't need the overhead.
+ */
params.options |= CLUOPT_RECHECK;
/*
@@ -253,7 +423,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
* Open the target table, coping with the case where it has been
* dropped.
*/
- rel = try_table_open(rtc->tableOid, AccessExclusiveLock);
+ rel = try_table_open(rtc->tableOid, lockmode);
if (rel == NULL)
{
CommitTransactionCommand();
@@ -264,7 +434,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Process this table */
- cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms);
+ cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms, isTopLevel);
/* cluster_rel closes the relation, but keeps lock */
PopActiveSnapshot();
@@ -278,6 +448,22 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
MemoryContextDelete(repack_context);
}
+/*
+ * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
+ * operation to avoid any lock-upgrade hazards. In the concurrent case, we
+ * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the
+ * processing and only acquire AccessExclusiveLock at the end, to swap the
+ * relation -- supposedly for a short time.
+ */
+static LOCKMODE
+RepackLockLevel(bool concurrent)
+{
+ if (concurrent)
+ return ShareUpdateExclusiveLock;
+ else
+ return AccessExclusiveLock;
+}
+
/*
* cluster_rel
*
@@ -293,22 +479,51 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
* If indexOid is InvalidOid, the table will be rewritten in physical order
* instead of index order.
*
+ * Note that, in the concurrent case, the function releases the lock at some
+ * point, in order to get AccessExclusiveLock for the final steps (i.e. to
+ * swap the relation files). To make things simpler, the caller should expect
+ * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The
+ * AccessExclusiveLock is kept till the end of the transaction.)
+ *
* 'cmd' indicates which command is being executed, to be used for error
* messages.
*/
void
cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
- ClusterParams *params)
+ ClusterParams *params, bool isTopLevel)
{
Oid tableOid = RelationGetRelid(OldHeap);
+ Relation index;
+ LOCKMODE lmode;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
bool verbose = ((params->options & CLUOPT_VERBOSE) != 0);
bool recheck = ((params->options & CLUOPT_RECHECK) != 0);
- Relation index;
+ bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
+ Oid ident_idx = InvalidOid;
- Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false));
+ /* Determine the lock mode to use. */
+ lmode = RepackLockLevel(concurrent);
+
+ /*
+ * Check some preconditions in the concurrent case. This also obtains the
+ * replica index OID.
+ */
+ if (concurrent)
+ {
+ /*
+ * Make sure we're not in a transaction block.
+ *
+ * The reason is that repack_setup_logical_decoding() could deadlock
+ * if there's an XID already assigned. It would be possible to run in
+ * a transaction block if we had no XID, but this restriction is
+ * simpler for users to understand and we don't lose anything.
+ */
+ PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
+
+ check_repack_concurrently_requirements(OldHeap, &ident_idx);
+ }
/* Check for user-requested abort. */
CHECK_FOR_INTERRUPTS();
@@ -328,16 +543,15 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
RestrictSearchPath();
/*
- * Since we may open a new transaction for each relation, we have to check
- * that the relation still is what we think it is.
+ * Recheck that the relation is still what it was when we started.
*
- * If this is a single-transaction CLUSTER, we can skip these tests. We
- * *must* skip the one on indisclustered since it would reject an attempt
- * to cluster a not-previously-clustered index.
+ * Note that it's critical to skip this in single-relation CLUSTER;
+ * otherwise, we would reject an attempt to cluster using a
+ * not-previously-clustered index.
*/
if (recheck &&
!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
- params->options))
+ lmode, params->options))
goto out;
/*
@@ -353,6 +567,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
errmsg("cannot execute %s on a shared catalog",
RepackCommandAsString(cmd)));
+ /*
+ * The CONCURRENTLY case should have been rejected earlier because it does
+ * not support system catalogs.
+ */
+ Assert(!(OldHeap->rd_rel->relisshared && concurrent));
+
/*
* Don't process temp tables of other backends ... their local buffer
* manager is not going to cope.
@@ -374,7 +594,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
if (OidIsValid(indexOid))
{
/* verify the index is good and lock it */
- check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock);
+ check_index_is_clusterable(OldHeap, indexOid, lmode);
/* also open it */
index = index_open(indexOid, NoLock);
}
@@ -409,7 +629,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
!RelationIsPopulated(OldHeap))
{
- relation_close(OldHeap, AccessExclusiveLock);
+ if (index)
+ index_close(index, lmode);
+ relation_close(OldHeap, lmode);
goto out;
}
@@ -422,11 +644,34 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* invalid, because we move tuples around. Promote them to relation
* locks. Predicate locks on indexes will be promoted when they are
* reindexed.
+ *
+ * During concurrent processing, the heap as well as its indexes stay in
+ * operation, so we postpone this step until they are locked using
+ * AccessExclusiveLock near the end of the processing.
*/
- TransferPredicateLocksToHeapRelation(OldHeap);
+ if (!concurrent)
+ TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
- rebuild_relation(OldHeap, index, verbose);
+ PG_TRY();
+ {
+ rebuild_relation(OldHeap, index, verbose, ident_idx);
+ }
+ PG_FINALLY();
+ {
+ if (concurrent)
+ {
+ /*
+ * Since during normal operation the worker was already asked to
+ * exit, stopping it explicitly is especially important on ERROR.
+ * However it still seems a good practice to make sure that the
+ * worker never survives the REPACK command.
+ */
+ stop_repack_decoding_worker();
+ }
+ }
+ PG_END_TRY();
+
/* rebuild_relation closes OldHeap, and index if valid */
out:
@@ -445,14 +690,14 @@ out:
*/
static bool
cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
- Oid userid, int options)
+ Oid userid, LOCKMODE lmode, int options)
{
Oid tableOid = RelationGetRelid(OldHeap);
/* Check that the user still has privileges for the relation */
if (!repack_is_permitted_for_relation(cmd, tableOid, userid))
{
- relation_close(OldHeap, AccessExclusiveLock);
+ relation_close(OldHeap, lmode);
return false;
}
@@ -466,7 +711,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
*/
if (RELATION_IS_OTHER_TEMP(OldHeap))
{
- relation_close(OldHeap, AccessExclusiveLock);
+ relation_close(OldHeap, lmode);
return false;
}
@@ -477,7 +722,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
*/
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
{
- relation_close(OldHeap, AccessExclusiveLock);
+ relation_close(OldHeap, lmode);
return false;
}
@@ -488,7 +733,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
!get_index_isclustered(indexOid))
{
- relation_close(OldHeap, AccessExclusiveLock);
+ relation_close(OldHeap, lmode);
return false;
}
}
@@ -500,7 +745,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
* Verify that the specified heap and index are valid to cluster on
*
* Side effect: obtains lock on the index. The caller may
- * in some cases already have AccessExclusiveLock on the table, but
+ * in some cases already have a lock of the same strength on the table, but
* not in all cases so we can't rely on the table-level lock for
* protection here.
*/
@@ -626,17 +871,94 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
}
/*
- * rebuild_relation: rebuild an existing relation in index or physical order
+ * Check if the CONCURRENTLY option is legal for the relation.
*
- * OldHeap: table to rebuild.
- * index: index to cluster by, or NULL to rewrite in physical order.
- *
- * On entry, heap and index (if one is given) must be open, and
- * AccessExclusiveLock held on them.
- * On exit, they are closed, but locks on them are not released.
+ * *Ident_idx_p receives OID of the identity index.
*/
static void
-rebuild_relation(Relation OldHeap, Relation index, bool verbose)
+check_repack_concurrently_requirements(Relation rel, Oid *ident_idx_p)
+{
+ char relpersistence,
+ replident;
+ Oid ident_idx;
+
+ /* Data changes in system relations are not logically decoded. */
+ if (IsCatalogRelation(rel))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot repack relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("REPACK CONCURRENTLY is not supported for catalog relations."));
+
+ /*
+ * reorderbuffer.c does not seem to handle processing of TOAST relation
+ * alone.
+ */
+ if (IsToastRelation(rel))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot repack relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("REPACK CONCURRENTLY is not supported for TOAST relations, unless the main relation is repacked too."));
+
+ relpersistence = rel->rd_rel->relpersistence;
+ if (relpersistence != RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot repack relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("REPACK CONCURRENTLY is only allowed for permanent relations."));
+
+ /* With NOTHING, WAL does not contain the old tuple. */
+ replident = rel->rd_rel->relreplident;
+ if (replident == REPLICA_IDENTITY_NOTHING)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot repack relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Relation \"%s\" has insufficient replication identity.",
+ RelationGetRelationName(rel)));
+
+ /*
+ * If the identity index is not set due to replica identity being, PK
+ * might exist.
+ */
+ ident_idx = RelationGetReplicaIndex(rel);
+ if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex))
+ ident_idx = rel->rd_pkindex;
+ if (!OidIsValid(ident_idx))
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot process relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Relation \"%s\" has no identity index.",
+ RelationGetRelationName(rel)));
+
+ *ident_idx_p = ident_idx;
+}
+
+
+/*
+ * rebuild_relation: rebuild an existing relation in index or physical order
+ *
+ * OldHeap: table to rebuild. See cluster_rel() for comments on the required
+ * lock strength.
+ *
+ * index: index to cluster by, or NULL to rewrite in physical order.
+ *
+ * ident_idx: identity index, to handle replaying of concurrent data changes
+ * to the new heap. InvalidOid if there's no CONCURRENTLY option.
+ *
+ * On entry, heap and index (if one is given) must be open, and the
+ * appropriate lock held on them -- AccessExclusiveLock for exclusive
+ * processing and ShareUpdateExclusiveLock for concurrent processing.
+ *
+ * On exit, they are closed, but still locked with AccessExclusiveLock.
+ * (The function handles the lock upgrade if 'concurrent' is true.)
+ */
+static void
+rebuild_relation(Relation OldHeap, Relation index, bool verbose,
+ Oid ident_idx)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid accessMethod = OldHeap->rd_rel->relam;
@@ -644,13 +966,55 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
Oid OIDNewHeap;
Relation NewHeap;
char relpersistence;
- bool is_system_catalog;
bool swap_toast_by_content;
TransactionId frozenXid;
MultiXactId cutoffMulti;
+ bool concurrent = OidIsValid(ident_idx);
+ Snapshot snapshot = NULL;
+#if USE_ASSERT_CHECKING
+ LOCKMODE lmode;
- Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) &&
- (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false)));
+ lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
+
+ Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
+ Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false));
+#endif
+
+ if (concurrent)
+ {
+ /*
+ * The worker needs to be member of the locking group we're the leader
+ * of. We ought to become the leader before the worker starts. The
+ * worker will join the group as soon as it starts.
+ *
+ * This is to make sure that the deadlock described below is
+ * detectable by deadlock.c: if the worker waits for a transaction to
+ * complete and we are waiting for the worker output, then effectively
+ * we (i.e. this backend) are waiting for that transaction.
+ */
+ BecomeLockGroupLeader();
+
+ /*
+ * Start the worker that decodes data changes applied while we're
+ * copying the table contents.
+ *
+ * Note that the worker has to wait for all transactions with XID
+ * already assigned to finish. If some of those transactions is
+ * waiting for a lock conflicting with ShareUpdateExclusiveLock on our
+ * table (e.g. it runs CREATE INDEX), we can end up in a deadlock.
+ * Not sure this risk is worth unlocking/locking the table (and its
+ * clustering index) and checking again if it's still eligible for
+ * REPACK CONCURRENTLY.
+ */
+ start_repack_decoding_worker(tableOid);
+
+ /*
+ * Wait until the worker has the initial snapshot and retrieve it.
+ */
+ snapshot = get_initial_snapshot(decoding_worker);
+
+ PushActiveSnapshot(snapshot);
+ }
/* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */
if (index != NULL)
@@ -658,7 +1022,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
/* Remember info about rel before closing OldHeap */
relpersistence = OldHeap->rd_rel->relpersistence;
- is_system_catalog = IsSystemRelation(OldHeap);
/*
* Create the transient table that will receive the re-ordered data.
@@ -674,30 +1037,59 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
NewHeap = table_open(OIDNewHeap, NoLock);
/* Copy the heap data into the new table in the desired order */
- copy_table_data(NewHeap, OldHeap, index, verbose,
+ copy_table_data(NewHeap, OldHeap, index, snapshot, verbose,
&swap_toast_by_content, &frozenXid, &cutoffMulti);
+ /* The historic snapshot won't be needed anymore. */
+ if (snapshot)
+ {
+ PopActiveSnapshot();
+ UpdateActiveSnapshotCommandId();
+ }
- /* Close relcache entries, but keep lock until transaction commit */
- table_close(OldHeap, NoLock);
- if (index)
- index_close(index, NoLock);
+ if (concurrent)
+ {
+ Assert(!swap_toast_by_content);
- /*
- * Close the new relation so it can be dropped as soon as the storage is
- * swapped. The relation is not visible to others, so no need to unlock it
- * explicitly.
- */
- table_close(NewHeap, NoLock);
+ /*
+ * Close the index, but keep the lock. Both heaps will be closed by
+ * the following call.
+ */
+ if (index)
+ index_close(index, NoLock);
- /*
- * Swap the physical files of the target and transient tables, then
- * rebuild the target's indexes and throw away the transient table.
- */
- finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
- swap_toast_by_content, false, true,
- frozenXid, cutoffMulti,
- relpersistence);
+ rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx,
+ frozenXid, cutoffMulti);
+
+ pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+ PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
+ }
+ else
+ {
+ bool is_system_catalog = IsSystemRelation(OldHeap);
+
+ /* Close relcache entries, but keep lock until transaction commit */
+ table_close(OldHeap, NoLock);
+ if (index)
+ index_close(index, NoLock);
+
+ /*
+ * Close the new relation so it can be dropped as soon as the storage
+ * is swapped. The relation is not visible to others, so no need to
+ * unlock it explicitly.
+ */
+ table_close(NewHeap, NoLock);
+
+ /*
+ * Swap the physical files of the target and transient tables, then
+ * rebuild the target's indexes and throw away the transient table.
+ */
+ finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
+ swap_toast_by_content, false, true,
+ true, /* reindex */
+ frozenXid, cutoffMulti,
+ relpersistence);
+ }
}
@@ -832,15 +1224,18 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
/*
* Do the physical copying of table data.
*
+ * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass
+ * iff concurrent processing is required.
+ *
* There are three output parameters:
* *pSwapToastByContent is set true if toast tables must be swapped by content.
* *pFreezeXid receives the TransactionId used as freeze cutoff point.
* *pCutoffMulti receives the MultiXactId used as a cutoff point.
*/
static void
-copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verbose,
- bool *pSwapToastByContent, TransactionId *pFreezeXid,
- MultiXactId *pCutoffMulti)
+copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
+ Snapshot snapshot, bool verbose, bool *pSwapToastByContent,
+ TransactionId *pFreezeXid, MultiXactId *pCutoffMulti)
{
Relation relRelation;
HeapTuple reltup;
@@ -857,6 +1252,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
char *nspname;
+ bool concurrent = snapshot != NULL;
+ LOCKMODE lmode;
+
+ lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock;
pg_rusage_init(&ru0);
@@ -885,7 +1284,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
* will be held till end of transaction.
*/
if (OldHeap->rd_rel->reltoastrelid)
- LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
+ LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode);
/*
* If both tables have TOAST tables, perform toast swap by content. It is
@@ -894,7 +1293,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
* swap by links. This is okay because swap by content is only essential
* for system catalogs, and we don't support schema changes for them.
*/
- if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid)
+ if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid &&
+ !concurrent)
{
*pSwapToastByContent = true;
@@ -915,6 +1315,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
* follow the toast pointers to the wrong place. (It would actually
* work for values copied over from the old toast table, but not for
* any values that we toast which were previously not toasted.)
+ *
+ * This would not work with CONCURRENTLY because we may need to delete
+ * TOASTed tuples from the new heap. With this hack, we'd delete them
+ * from the old heap.
*/
NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;
}
@@ -990,7 +1394,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
* values (e.g. because the AM doesn't use freezing).
*/
table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
- cutoffs.OldestXmin, &cutoffs.FreezeLimit,
+ cutoffs.OldestXmin, snapshot,
+ &cutoffs.FreezeLimit,
&cutoffs.MultiXactCutoff,
&num_tuples, &tups_vacuumed,
&tups_recently_dead);
@@ -999,7 +1404,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb
*pFreezeXid = cutoffs.FreezeLimit;
*pCutoffMulti = cutoffs.MultiXactCutoff;
- /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
+ /*
+ * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again.
+ * In the CONCURRENTLY case, we need to set it again before applying the
+ * concurrent changes.
+ */
NewHeap->rd_toastoid = InvalidOid;
num_pages = RelationGetNumberOfBlocks(NewHeap);
@@ -1457,14 +1866,13 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool swap_toast_by_content,
bool check_constraints,
bool is_internal,
+ bool reindex,
TransactionId frozenXid,
MultiXactId cutoffMulti,
char newrelpersistence)
{
ObjectAddress object;
Oid mapped_tables[4];
- int reindex_flags;
- ReindexParams reindex_params = {0};
int i;
/* Report that we are now swapping relation files */
@@ -1490,39 +1898,47 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
if (is_system_catalog)
CacheInvalidateCatalog(OIDOldHeap);
- /*
- * Rebuild each index on the relation (but not the toast table, which is
- * all-new at this point). It is important to do this before the DROP
- * step because if we are processing a system catalog that will be used
- * during DROP, we want to have its indexes available. There is no
- * advantage to the other order anyway because this is all transactional,
- * so no chance to reclaim disk space before commit. We do not need a
- * final CommandCounterIncrement() because reindex_relation does it.
- *
- * Note: because index_build is called via reindex_relation, it will never
- * set indcheckxmin true for the indexes. This is OK even though in some
- * sense we are building new indexes rather than rebuilding existing ones,
- * because the new heap won't contain any HOT chains at all, let alone
- * broken ones, so it can't be necessary to set indcheckxmin.
- */
- reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
- if (check_constraints)
- reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
+ if (reindex)
+ {
+ int reindex_flags;
+ ReindexParams reindex_params = {0};
- /*
- * Ensure that the indexes have the same persistence as the parent
- * relation.
- */
- if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
- reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
- else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
- reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
+ /*
+ * Rebuild each index on the relation (but not the toast table, which
+ * is all-new at this point). It is important to do this before the
+ * DROP step because if we are processing a system catalog that will
+ * be used during DROP, we want to have its indexes available. There
+ * is no advantage to the other order anyway because this is all
+ * transactional, so no chance to reclaim disk space before commit. We
+ * do not need a final CommandCounterIncrement() because
+ * reindex_relation does it.
+ *
+ * Note: because index_build is called via reindex_relation, it will
+ * never set indcheckxmin true for the indexes. This is OK even
+ * though in some sense we are building new indexes rather than
+ * rebuilding existing ones, because the new heap won't contain any
+ * HOT chains at all, let alone broken ones, so it can't be necessary
+ * to set indcheckxmin.
+ */
+ reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
+ if (check_constraints)
+ reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
- /* Report that we are now reindexing relations */
- pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
- PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+ /*
+ * Ensure that the indexes have the same persistence as the parent
+ * relation.
+ */
+ if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
+ reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
+ else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
+ reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
- reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
+ /* Report that we are now reindexing relations */
+ pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+ PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+
+ reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
+ }
/* Report that we are now doing clean up */
pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
@@ -1566,6 +1982,17 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
object.objectId = OIDNewHeap;
object.objectSubId = 0;
+ if (!reindex)
+ {
+ /*
+ * Make sure the changes in pg_class are visible. This is especially
+ * important if !swap_toast_by_content, so that the correct TOAST
+ * relation is dropped. (reindex_relation() above did not help in this
+ * case))
+ */
+ CommandCounterIncrement();
+ }
+
/*
* The new relation is local to our transaction and we know nothing
* depends on it, so DROP_RESTRICT should be OK.
@@ -1605,7 +2032,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
/* Get the associated valid index to be renamed */
toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid,
- NoLock);
+ AccessExclusiveLock);
/* rename the toast table ... */
snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
@@ -1876,7 +2303,8 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
* case, if an index name is given, it's up to the caller to resolve it.
*/
static Relation
-process_single_relation(RepackStmt *stmt, ClusterParams *params)
+process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
+ ClusterParams *params)
{
Relation rel;
Oid tableOid;
@@ -1893,13 +2321,9 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params)
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ANALYZE option must be specified when a column list is provided"));
- /*
- * Find, lock, and check permissions on the table. We obtain
- * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
- * single-transaction case.
- */
+ /* Find, lock, and check permissions on the table. */
tableOid = RangeVarGetRelidExtended(stmt->relation->relation,
- AccessExclusiveLock,
+ lockmode,
0,
RangeVarCallbackMaintainsTable,
NULL);
@@ -1924,13 +2348,14 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params)
return rel;
else
{
- Oid indexOid;
+ Oid indexOid = InvalidOid;
indexOid = determine_clustered_index(rel, stmt->usingindex,
stmt->indexname);
if (OidIsValid(indexOid))
- check_index_is_clusterable(rel, indexOid, AccessExclusiveLock);
- cluster_rel(stmt->command, rel, indexOid, params);
+ check_index_is_clusterable(rel, indexOid, lockmode);
+
+ cluster_rel(stmt->command, rel, indexOid, params, isTopLevel);
/*
* Do an analyze, if requested. We close the transaction and start a
@@ -2025,3 +2450,1199 @@ RepackCommandAsString(RepackCommand cmd)
}
return "???"; /* keep compiler quiet */
}
+
+/*
+ * Apply all the changes stored in 'file'.
+ */
+static void
+apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
+{
+ ConcurrentChangeKind kind = '\0';
+ Relation rel = chgcxt->cc_rel;
+ TupleTableSlot *spilled_tuple;
+ TupleTableSlot *old_update_tuple;
+ TupleTableSlot *ondisk_tuple;
+ bool have_old_tuple = false;
+ MemoryContext oldcxt;
+
+ spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
+ &TTSOpsVirtual);
+ ondisk_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
+ table_slot_callbacks(rel));
+ old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
+ &TTSOpsVirtual);
+
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
+ while (true)
+ {
+ size_t nread;
+ ConcurrentChangeKind prevkind = kind;
+
+ CHECK_FOR_INTERRUPTS();
+
+ nread = BufFileReadMaybeEOF(file, &kind, 1, true);
+ if (nread == 0) /* done with the file? */
+ break;
+
+ /*
+ * If this is the old tuple for an update, read it into the tuple slot
+ * and go to the next one. The update itself will be executed on the
+ * next iteration, when we receive the NEW tuple.
+ */
+ if (kind == CHANGE_UPDATE_OLD)
+ {
+ restore_tuple(file, rel, old_update_tuple);
+ have_old_tuple = true;
+ continue;
+ }
+
+ /*
+ * Just before an UPDATE or DELETE, we must update the command
+ * counter, because the change could refer to a tuple that we have
+ * just inserted; and before an INSERT, we have to do this also if the
+ * previous command was either update or delete.
+ *
+ * With this approach we don't spend so many CCIs for long strings of
+ * only INSERTs, which can't affect one another.
+ */
+ if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
+ (kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
+ prevkind == CHANGE_DELETE)))
+ {
+ CommandCounterIncrement();
+ UpdateActiveSnapshotCommandId();
+ }
+
+ /*
+ * Now restore the tuple into the slot and execute the change.
+ */
+ restore_tuple(file, rel, spilled_tuple);
+
+ if (kind == CHANGE_INSERT)
+ {
+ apply_concurrent_insert(rel, spilled_tuple, chgcxt);
+ }
+ else if (kind == CHANGE_DELETE)
+ {
+ bool found;
+
+ /* Find the tuple to be deleted */
+ found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
+ if (!found)
+ elog(ERROR, "failed to find target tuple");
+ apply_concurrent_delete(rel, ondisk_tuple);
+ }
+ else if (kind == CHANGE_UPDATE_NEW)
+ {
+ TupleTableSlot *key;
+ bool found;
+
+ if (have_old_tuple)
+ key = old_update_tuple;
+ else
+ key = spilled_tuple;
+
+ /* Find the tuple to be updated or deleted. */
+ found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
+ if (!found)
+ elog(ERROR, "failed to find target tuple");
+
+ /*
+ * If 'tup' contains TOAST pointers, they point to the old
+ * relation's toast. Copy the corresponding TOAST pointers for the
+ * new relation from the existing tuple. (The fact that we
+ * received a TOAST pointer here implies that the attribute hasn't
+ * changed.)
+ */
+ adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
+
+ apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
+
+ ExecClearTuple(old_update_tuple);
+ have_old_tuple = false;
+ }
+ else
+ elog(ERROR, "unrecognized kind of change: %d", kind);
+
+ ResetPerTupleExprContext(chgcxt->cc_estate);
+ }
+
+ /* Cleanup. */
+ ExecDropSingleTupleTableSlot(spilled_tuple);
+ ExecDropSingleTupleTableSlot(ondisk_tuple);
+ ExecDropSingleTupleTableSlot(old_update_tuple);
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Apply an insert from the spill of concurrent changes to the new copy of the
+ * table.
+ */
+static void
+apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
+ ChangeContext *chgcxt)
+{
+ /* Put the tuple in the table, but make sure it won't be decoded */
+ table_tuple_insert(rel, slot, GetCurrentCommandId(true),
+ HEAP_INSERT_NO_LOGICAL, NULL);
+
+ /* Update indexes with this new tuple. */
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ 0,
+ slot,
+ NIL, NULL);
+ pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
+}
+
+/*
+ * Apply an update from the spill of concurrent changes to the new copy of the
+ * table.
+ */
+static void
+apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
+ TupleTableSlot *ondisk_tuple,
+ ChangeContext *chgcxt)
+{
+ LockTupleMode lockmode;
+ TM_FailureData tmfd;
+ TU_UpdateIndexes update_indexes;
+ TM_Result res;
+
+ /*
+ * Carry out the update, skipping logical decoding for it.
+ */
+ res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
+ GetCurrentCommandId(true),
+ TABLE_UPDATE_NO_LOGICAL,
+ InvalidSnapshot,
+ InvalidSnapshot,
+ false,
+ &tmfd, &lockmode, &update_indexes);
+ if (res != TM_Ok)
+ ereport(ERROR,
+ errmsg("failed to apply concurrent UPDATE"));
+
+ if (update_indexes != TU_None)
+ {
+ uint32 flags = EIIT_IS_UPDATE;
+
+ if (update_indexes == TU_Summarizing)
+ flags |= EIIT_ONLY_SUMMARIZING;
+ ExecInsertIndexTuples(chgcxt->cc_rri,
+ chgcxt->cc_estate,
+ flags,
+ spilled_tuple,
+ NIL, NULL);
+ }
+
+ pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
+}
+
+static void
+apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
+{
+ TM_Result res;
+ TM_FailureData tmfd;
+
+ /*
+ * Delete tuple from the new heap, skipping logical decoding for it.
+ */
+ res = table_tuple_delete(rel, &(slot->tts_tid),
+ GetCurrentCommandId(true),
+ TABLE_DELETE_NO_LOGICAL,
+ InvalidSnapshot, InvalidSnapshot,
+ false,
+ &tmfd);
+
+ if (res != TM_Ok)
+ ereport(ERROR,
+ errmsg("failed to apply concurrent DELETE"));
+
+ pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1);
+}
+
+/*
+ * Read tuple from file and put it in the input slot.
+ *
+ * External attributes are stored in separate memory chunks, in order to avoid
+ * exceeding MaxAllocSize - that could happen if the individual attributes are
+ * smaller than MaxAllocSize but the whole tuple is bigger.
+ */
+static void
+restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
+{
+ uint32 t_len;
+ HeapTuple tup;
+ MemoryContext oldcxt;
+ int natt_ext;
+
+ oldcxt = MemoryContextSwitchTo(slot->tts_mcxt);
+
+ /* Read the tuple. */
+ BufFileReadExact(file, &t_len, sizeof(t_len));
+ tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len);
+ tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE);
+ BufFileReadExact(file, tup->t_data, t_len);
+ tup->t_len = t_len;
+ ItemPointerSetInvalid(&tup->t_self);
+ tup->t_tableOid = RelationGetRelid(relation);
+
+ /*
+ * Put the tuple we read in a slot. This deforms it, so that we can hack
+ * the external attributes in place.
+ */
+ ExecForceStoreHeapTuple(tup, slot, false);
+
+ /*
+ * Next, read any attributes we stored separately into the tts_values
+ * array elements expecting them, if any. This matches
+ * repack_store_change.
+ */
+ BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
+ if (natt_ext > 0)
+ {
+ TupleDesc desc = slot->tts_tupleDescriptor;
+
+ for (int i = 0; i < desc->natts; i++)
+ {
+ CompactAttribute *attr = TupleDescCompactAttr(desc, i);
+ varlena *varlen;
+ union
+ {
+ alignas(int32) varlena hdr;
+ char data[sizeof(void *)];
+ } chunk_header;
+ void *value;
+ Size varlensz;
+
+ if (attr->attisdropped || attr->attlen != -1)
+ continue;
+ if (slot_attisnull(slot, i + 1))
+ continue;
+ varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
+ if (!VARATT_IS_EXTERNAL(varlen))
+ continue;
+ slot_getsomeattrs(slot, i + 1);
+
+ BufFileReadExact(file, &chunk_header, VARHDRSZ);
+ varlensz = VARSIZE_ANY(&chunk_header);
+
+ value = palloc(varlensz);
+ SET_VARSIZE(value, VARSIZE_ANY(&chunk_header));
+ BufFileReadExact(file, (char *) value + VARHDRSZ, varlensz - VARHDRSZ);
+
+ slot->tts_values[i] = PointerGetDatum(value);
+ natt_ext--;
+ }
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Adjust 'dest' replacing any EXTERNAL_ONDISK toast pointers with the
+ * corresponding ones from 'src'.
+ */
+static void
+adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *src)
+{
+ TupleDesc desc = dest->tts_tupleDescriptor;
+
+ for (int i = 0; i < desc->natts; i++)
+ {
+ CompactAttribute *attr = TupleDescCompactAttr(desc, i);
+ varlena *varlena_dst;
+
+ if (attr->attisdropped)
+ continue;
+ if (attr->attlen != -1)
+ continue;
+ if (slot_attisnull(dest, i + 1))
+ continue;
+
+ slot_getsomeattrs(dest, i + 1);
+
+ varlena_dst = (varlena *) DatumGetPointer(dest->tts_values[i]);
+ if (!VARATT_IS_EXTERNAL_ONDISK(varlena_dst))
+ continue;
+ slot_getsomeattrs(src, i + 1);
+
+ /*
+ * XXX We simply replace the pointer to the Datum from the other one,
+ * which is probably bogus.
+ */
+ dest->tts_values[i] = src->tts_values[i];
+ }
+}
+
+/*
+ * Find the tuple to be updated or deleted by the given data change, whose
+ * tuple has already been loaded into locator.
+ *
+ * If the tuple is found, put it in retrieved and return true. If the tuple is
+ * not found, return false.
+ */
+static bool
+find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
+ TupleTableSlot *retrieved)
+{
+ Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
+ IndexScanDesc scan;
+ bool retval;
+
+ /*
+ * Scan key is passed by caller, so it does not have to be constructed
+ * multiple times. Key entries have all fields initialized, except for
+ * sk_argument.
+ *
+ * Use the incoming tuple to finalize the scan key.
+ */
+ for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
+ {
+ ScanKey entry = &chgcxt->cc_ident_key[i];
+ AttrNumber attno = idx->indkey.values[i];
+
+ entry->sk_argument = locator->tts_values[attno - 1];
+ Assert(!locator->tts_isnull[attno - 1]);
+ }
+
+ /* XXX no instrumentation for now */
+ scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
+ NULL, chgcxt->cc_ident_key_nentries, 0, 0);
+ index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
+ retval = index_getnext_slot(scan, ForwardScanDirection, retrieved);
+ index_endscan(scan);
+
+ return retval;
+}
+
+/*
+ * Decode and apply concurrent changes, up to (and including) the record whose
+ * LSN is 'end_of_wal'.
+ *
+ * XXX the names "process_concurrent_changes" and "apply_concurrent_changes"
+ * are far too similar to each other.
+ */
+static void
+process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
+{
+ DecodingWorkerShared *shared;
+ char fname[MAXPGPATH];
+ BufFile *file;
+
+ pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+ PROGRESS_REPACK_PHASE_CATCH_UP);
+
+ /* Ask the worker for the file. */
+ shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
+ SpinLockAcquire(&shared->mutex);
+ shared->lsn_upto = end_of_wal;
+ shared->done = done;
+ SpinLockRelease(&shared->mutex);
+
+ /*
+ * The worker needs to finish processing of the current WAL record. Even
+ * if it's idle, it'll need to close the output file. Thus we're likely to
+ * wait, so prepare for sleep.
+ */
+ ConditionVariablePrepareToSleep(&shared->cv);
+ for (;;)
+ {
+ int last_exported;
+
+ SpinLockAcquire(&shared->mutex);
+ last_exported = shared->last_exported;
+ SpinLockRelease(&shared->mutex);
+
+ /*
+ * Has the worker exported the file we are waiting for?
+ */
+ if (last_exported == chgcxt->cc_file_seq)
+ break;
+
+ ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+ }
+ ConditionVariableCancelSleep();
+
+ /* Open the file. */
+ DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
+ file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
+ apply_concurrent_changes(file, chgcxt);
+
+ BufFileClose(file);
+
+ /* Get ready for the next file. */
+ chgcxt->cc_file_seq++;
+}
+
+/*
+ * Initialize the ChangeContext struct for the given relation, with
+ * the given index as identity index.
+ */
+static void
+initialize_change_context(ChangeContext *chgcxt,
+ Relation relation, Oid ident_index_id)
+{
+ chgcxt->cc_rel = relation;
+
+ /* Only initialize fields needed by ExecInsertIndexTuples(). */
+ chgcxt->cc_estate = CreateExecutorState();
+
+ chgcxt->cc_rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo));
+ InitResultRelInfo(chgcxt->cc_rri, relation, 0, 0, 0);
+ ExecOpenIndices(chgcxt->cc_rri, false);
+
+ /*
+ * The table's relcache entry already has the relcache entry for the
+ * identity index; find that.
+ */
+ chgcxt->cc_ident_index = NULL;
+ for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
+ {
+ Relation ind_rel;
+
+ ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
+ if (ind_rel->rd_id == ident_index_id)
+ {
+ chgcxt->cc_ident_index = ind_rel;
+ break;
+ }
+ }
+ if (chgcxt->cc_ident_index == NULL)
+ elog(ERROR, "failed to find identity index");
+
+ /* Set up for scanning said identity index */
+ {
+ Form_pg_index indexForm;
+
+ indexForm = chgcxt->cc_ident_index->rd_index;
+ chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
+ chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
+ for (int i = 0; i < indexForm->indnkeyatts; i++)
+ {
+ ScanKey entry;
+ Oid opfamily,
+ opcintype,
+ opno,
+ opcode;
+
+ entry = &chgcxt->cc_ident_key[i];
+
+ opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
+ opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
+ opno = get_opfamily_member(opfamily, opcintype, opcintype,
+ BTEqualStrategyNumber);
+ if (!OidIsValid(opno))
+ elog(ERROR, "failed to find = operator for type %u", opcintype);
+ opcode = get_opcode(opno);
+ if (!OidIsValid(opcode))
+ elog(ERROR, "failed to find = operator for operator %u", opno);
+
+ /* Initialize everything but argument. */
+ ScanKeyInit(entry,
+ i + 1,
+ BTEqualStrategyNumber, opcode,
+ (Datum) NULL);
+ entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
+ }
+ }
+
+ chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
+}
+
+/*
+ * Free up resources taken by a ChangeContext.
+ */
+static void
+release_change_context(ChangeContext *chgcxt)
+{
+ ExecCloseIndices(chgcxt->cc_rri);
+ FreeExecutorState(chgcxt->cc_estate);
+ /* XXX are these pfrees necessary? */
+ pfree(chgcxt->cc_rri);
+ pfree(chgcxt->cc_ident_key);
+}
+
+/*
+ * The final steps of rebuild_relation() for concurrent processing.
+ *
+ * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its
+ * clustering index (if one is passed) are still locked in a mode that allows
+ * concurrent data changes. On exit, both tables and their indexes are closed,
+ * but locked in AccessExclusiveLock mode.
+ */
+static void
+rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
+ Oid identIdx, TransactionId frozenXid,
+ MultiXactId cutoffMulti)
+{
+ LOCKMODE lockmode_old PG_USED_FOR_ASSERTS_ONLY;
+ List *ind_oids_new;
+ Oid old_table_oid = RelationGetRelid(OldHeap);
+ Oid new_table_oid = RelationGetRelid(NewHeap);
+ List *ind_oids_old = RelationGetIndexList(OldHeap);
+ ListCell *lc,
+ *lc2;
+ char relpersistence;
+ bool is_system_catalog;
+ Oid ident_idx_new;
+ XLogRecPtr end_of_wal;
+ List *indexrels;
+ ChangeContext chgcxt;
+
+ /* Like in cluster_rel(). */
+ lockmode_old = ShareUpdateExclusiveLock;
+ Assert(CheckRelationLockedByMe(OldHeap, lockmode_old, false));
+ /* This is expected from the caller. */
+ Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false));
+
+ /*
+ * Unlike the exclusive case, we build new indexes for the new relation
+ * rather than swapping the storage and reindexing the old relation. The
+ * point is that the index build can take some time, so we do it before we
+ * get AccessExclusiveLock on the old heap and therefore we cannot swap
+ * the heap storage yet.
+ *
+ * index_create() will lock the new indexes using AccessExclusiveLock - no
+ * need to change that. At the same time, we use ShareUpdateExclusiveLock
+ * to lock the existing indexes - that should be enough to prevent others
+ * from changing them while we're repacking the relation. The lock on
+ * table should prevent others from changing the index column list, but
+ * might not be enough for commands like ALTER INDEX ... SET ... (Those
+ * are not necessarily dangerous, but can make user confused if the
+ * changes they do get lost due to REPACK.)
+ */
+ ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
+
+ /*
+ * The identity index in the new relation appears in the same relative
+ * position as the corresponding index in the old relation. Find it.
+ */
+ ident_idx_new = InvalidOid;
+ foreach_oid(ind_old, ind_oids_old)
+ {
+ if (identIdx == ind_old)
+ {
+ ident_idx_new = list_nth_oid(ind_oids_new,
+ foreach_current_index(ind_old));
+ break;
+ }
+ }
+ if (!OidIsValid(ident_idx_new))
+ elog(ERROR, "could not find index matching \"%s\" at the new relation",
+ get_rel_name(identIdx));
+
+ /* Gather information to apply concurrent changes. */
+ initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
+
+ /*
+ * During testing, wait for another backend to perform concurrent data
+ * changes which we will process below.
+ */
+ INJECTION_POINT("repack-concurrently-before-lock", NULL);
+
+ /*
+ * Flush all WAL records inserted so far (possibly except for the last
+ * incomplete page; see GetInsertRecPtr), to minimize the amount of data
+ * we need to flush while holding exclusive lock on the source table.
+ */
+ XLogFlush(GetXLogInsertEndRecPtr());
+ end_of_wal = GetFlushRecPtr(NULL);
+
+ /*
+ * Apply concurrent changes first time, to minimize the time we need to
+ * hold AccessExclusiveLock. (Quite some amount of WAL could have been
+ * written during the data copying and index creation.)
+ */
+ process_concurrent_changes(end_of_wal, &chgcxt, false);
+
+ /*
+ * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
+ * is one), all its indexes, so that we can swap the files.
+ */
+ LockRelationOid(old_table_oid, AccessExclusiveLock);
+
+ /*
+ * Lock all indexes now, not only the clustering one: all indexes need to
+ * have their files swapped. While doing that, store their relation
+ * references in a zero-terminated array, to handle predicate locks below.
+ */
+ indexrels = NIL;
+ foreach_oid(ind_oid, ind_oids_old)
+ {
+ Relation index;
+
+ index = index_open(ind_oid, AccessExclusiveLock);
+
+ /*
+ * Some things about the index may have changed before we locked the
+ * index, such as ALTER INDEX RENAME. We don't need to do anything
+ * here to absorb those changes in the new index.
+ */
+ indexrels = lappend(indexrels, index);
+ }
+
+ /*
+ * Lock the OldHeap's TOAST relation exclusively - again, the lock is
+ * needed to swap the files.
+ */
+ if (OidIsValid(OldHeap->rd_rel->reltoastrelid))
+ LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
+
+ /*
+ * Tuples and pages of the old heap will be gone, but the heap will stay.
+ */
+ TransferPredicateLocksToHeapRelation(OldHeap);
+ foreach_ptr(RelationData, index, indexrels)
+ {
+ TransferPredicateLocksToHeapRelation(index);
+ index_close(index, NoLock);
+ }
+ list_free(indexrels);
+
+ /*
+ * Flush WAL again, to make sure that all changes committed while we were
+ * waiting for the exclusive lock are available for decoding.
+ */
+ XLogFlush(GetXLogInsertEndRecPtr());
+ end_of_wal = GetFlushRecPtr(NULL);
+
+ /*
+ * Apply the concurrent changes again. Indicate that the decoding worker
+ * won't be needed anymore.
+ */
+ process_concurrent_changes(end_of_wal, &chgcxt, true);
+
+ /* Remember info about rel before closing OldHeap */
+ relpersistence = OldHeap->rd_rel->relpersistence;
+ is_system_catalog = IsSystemRelation(OldHeap);
+
+ pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+ PROGRESS_REPACK_PHASE_SWAP_REL_FILES);
+
+ /*
+ * Even ShareUpdateExclusiveLock should have prevented others from
+ * creating / dropping indexes (even using the CONCURRENTLY option), so we
+ * do not need to check whether the lists match.
+ */
+ forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ {
+ Oid ind_old = lfirst_oid(lc);
+ Oid ind_new = lfirst_oid(lc2);
+ Oid mapped_tables[4] = {0};
+
+ swap_relation_files(ind_old, ind_new,
+ (old_table_oid == RelationRelationId),
+ false, /* swap_toast_by_content */
+ true,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+
+#ifdef USE_ASSERT_CHECKING
+
+ /*
+ * Concurrent processing is not supported for system relations, so
+ * there should be no mapped tables.
+ */
+ for (int i = 0; i < 4; i++)
+ Assert(!OidIsValid(mapped_tables[i]));
+#endif
+ }
+
+ /* The new indexes must be visible for deletion. */
+ CommandCounterIncrement();
+
+ /* Close the old heap but keep lock until transaction commit. */
+ table_close(OldHeap, NoLock);
+ /* Close the new heap. (We didn't have to open its indexes). */
+ table_close(NewHeap, NoLock);
+
+ /* Cleanup what we don't need anymore. (And close the identity index.) */
+ release_change_context(&chgcxt);
+
+ /*
+ * Swap the relations and their TOAST relations and TOAST indexes. This
+ * also drops the new relation and its indexes.
+ *
+ * (System catalogs are currently not supported.)
+ */
+ Assert(!is_system_catalog);
+ finish_heap_swap(old_table_oid, new_table_oid,
+ is_system_catalog,
+ false, /* swap_toast_by_content */
+ false,
+ true,
+ false, /* reindex */
+ frozenXid, cutoffMulti,
+ relpersistence);
+}
+
+/*
+ * Build indexes on NewHeap according to those on OldHeap.
+ *
+ * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end
+ * up locked using ShareUpdateExclusiveLock.
+ *
+ * A list of OIDs of the corresponding indexes created on NewHeap is
+ * returned. The order of items does match, so we can use these arrays to swap
+ * index storage.
+ */
+static List *
+build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
+{
+ List *result = NIL;
+
+ pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+ PROGRESS_REPACK_PHASE_REBUILD_INDEX);
+
+ foreach_oid(oldindex, OldIndexes)
+ {
+ Oid newindex;
+ char *newName;
+ Relation ind;
+
+ ind = index_open(oldindex, ShareUpdateExclusiveLock);
+
+ newName = ChooseRelationName(get_rel_name(oldindex),
+ NULL,
+ "repacknew",
+ get_rel_namespace(ind->rd_index->indrelid),
+ false);
+ newindex = index_create_copy(NewHeap, false, oldindex,
+ ind->rd_rel->reltablespace, newName);
+ copy_index_constraints(ind, newindex, RelationGetRelid(NewHeap));
+ result = lappend_oid(result, newindex);
+
+ index_close(ind, NoLock);
+ }
+
+ return result;
+}
+
+/*
+ * Create a transient copy of a constraint -- supported by a transient
+ * copy of the index that supports the original constraint.
+ *
+ * When repacking a table that contains exclusion constraints, the executor
+ * relies on these constraints being properly catalogued. These copies are
+ * to support that.
+ *
+ * We don't need the constraints for anything else (the original constraints
+ * will be there once repack completes), so we add pg_depend entries so that
+ * the are dropped when the transient table is dropped.
+ */
+static void
+copy_index_constraints(Relation old_index, Oid new_index_id, Oid new_heap_id)
+{
+ ScanKeyData skey;
+ Relation rel;
+ TupleDesc desc;
+ SysScanDesc scan;
+ HeapTuple tup;
+ ObjectAddress objrel;
+
+ rel = table_open(ConstraintRelationId, RowExclusiveLock);
+ ObjectAddressSet(objrel, RelationRelationId, new_heap_id);
+
+ /*
+ * Retrieve the constraints supported by the old index and create an
+ * identical one that points to the new index.
+ */
+ ScanKeyInit(&skey,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(old_index->rd_index->indrelid));
+ scan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true,
+ NULL, 1, &skey);
+ desc = RelationGetDescr(rel);
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(tup);
+ Oid oid;
+ Datum values[Natts_pg_constraint] = {0};
+ bool nulls[Natts_pg_constraint] = {0};
+ bool replaces[Natts_pg_constraint] = {0};
+ HeapTuple new_tup;
+ ObjectAddress objcon;
+
+ if (conform->conindid != RelationGetRelid(old_index))
+ continue;
+
+ oid = GetNewOidWithIndex(rel, ConstraintOidIndexId,
+ Anum_pg_constraint_oid);
+ values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(oid);
+ replaces[Anum_pg_constraint_oid - 1] = true;
+ values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(new_heap_id);
+ replaces[Anum_pg_constraint_conrelid - 1] = true;
+ values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(new_index_id);
+ replaces[Anum_pg_constraint_conindid - 1] = true;
+
+ new_tup = heap_modify_tuple(tup, desc, values, nulls, replaces);
+
+ /* Insert it into the catalog. */
+ CatalogTupleInsert(rel, new_tup);
+
+ /* Create a dependency so it's removed when we drop the new heap. */
+ ObjectAddressSet(objcon, ConstraintRelationId, oid);
+ recordDependencyOn(&objcon, &objrel, DEPENDENCY_AUTO);
+ }
+ systable_endscan(scan);
+
+ table_close(rel, RowExclusiveLock);
+
+ CommandCounterIncrement();
+}
+
+/*
+ * Try to start a background worker to perform logical decoding of data
+ * changes applied to relation while REPACK CONCURRENTLY is copying its
+ * contents to a new table.
+ */
+static void
+start_repack_decoding_worker(Oid relid)
+{
+ Size size;
+ dsm_segment *seg;
+ DecodingWorkerShared *shared;
+ shm_mq *mq;
+ shm_mq_handle *mqh;
+ BackgroundWorker bgw;
+
+ /* Setup shared memory. */
+ size = BUFFERALIGN(offsetof(DecodingWorkerShared, error_queue)) +
+ BUFFERALIGN(REPACK_ERROR_QUEUE_SIZE);
+ seg = dsm_create(size, 0);
+ shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+ shared->lsn_upto = InvalidXLogRecPtr;
+ shared->done = false;
+ SharedFileSetInit(&shared->sfs, seg);
+ shared->last_exported = -1;
+ SpinLockInit(&shared->mutex);
+ shared->dbid = MyDatabaseId;
+
+ /*
+ * This is the UserId set in cluster_rel(). Security context shouldn't be
+ * needed for decoding worker.
+ */
+ shared->roleid = GetUserId();
+ shared->relid = relid;
+ ConditionVariableInit(&shared->cv);
+ shared->backend_proc = MyProc;
+ shared->backend_pid = MyProcPid;
+ shared->backend_proc_number = MyProcNumber;
+
+ mq = shm_mq_create((char *) BUFFERALIGN(shared->error_queue),
+ REPACK_ERROR_QUEUE_SIZE);
+ shm_mq_set_receiver(mq, MyProc);
+ mqh = shm_mq_attach(mq, seg, NULL);
+
+ memset(&bgw, 0, sizeof(bgw));
+ snprintf(bgw.bgw_name, BGW_MAXLEN,
+ "REPACK decoding worker for relation \"%s\"",
+ get_rel_name(relid));
+ snprintf(bgw.bgw_type, BGW_MAXLEN, "REPACK decoding worker");
+ bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
+ BGWORKER_BACKEND_DATABASE_CONNECTION;
+ bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+ bgw.bgw_restart_time = BGW_NEVER_RESTART;
+ snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
+ snprintf(bgw.bgw_function_name, BGW_MAXLEN, "RepackWorkerMain");
+ bgw.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg));
+ bgw.bgw_notify_pid = MyProcPid;
+
+ decoding_worker = palloc0_object(DecodingWorker);
+ if (!RegisterDynamicBackgroundWorker(&bgw, &decoding_worker->handle))
+ ereport(ERROR,
+ errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+ errmsg("out of background worker slots"),
+ errhint("You might need to increase \"%s\".", "max_worker_processes"));
+
+ decoding_worker->seg = seg;
+ decoding_worker->error_mqh = mqh;
+
+ /*
+ * The decoding setup must be done before the caller can have XID assigned
+ * for any reason, otherwise the worker might end up in a deadlock,
+ * waiting for the caller's transaction to end. Therefore wait here until
+ * the worker indicates that it has the logical decoding initialized.
+ */
+ ConditionVariablePrepareToSleep(&shared->cv);
+ for (;;)
+ {
+ bool initialized;
+
+ SpinLockAcquire(&shared->mutex);
+ initialized = shared->initialized;
+ SpinLockRelease(&shared->mutex);
+
+ if (initialized)
+ break;
+
+ ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+ }
+ ConditionVariableCancelSleep();
+}
+
+/*
+ * Stop the decoding worker and cleanup the related resources.
+ *
+ * The worker stops on its own when it knows there is no more work to do, but
+ * we need to stop it explicitly at least on ERROR in the launching backend.
+ */
+static void
+stop_repack_decoding_worker(void)
+{
+ BgwHandleStatus status;
+
+ /* Haven't reached the worker startup? */
+ if (decoding_worker == NULL)
+ return;
+
+ /* Could not register the worker? */
+ if (decoding_worker->handle == NULL)
+ return;
+
+ TerminateBackgroundWorker(decoding_worker->handle);
+ /* The worker should really exit before the REPACK command does. */
+ HOLD_INTERRUPTS();
+ status = WaitForBackgroundWorkerShutdown(decoding_worker->handle);
+ RESUME_INTERRUPTS();
+
+ if (status == BGWH_POSTMASTER_DIED)
+ ereport(FATAL,
+ errcode(ERRCODE_ADMIN_SHUTDOWN),
+ errmsg("postmaster exited during REPACK command"));
+
+ shm_mq_detach(decoding_worker->error_mqh);
+
+ /*
+ * If we could not cancel the current sleep due to ERROR, do that before
+ * we detach from the shared memory the condition variable is located in.
+ * If we did not, the bgworker ERROR handling code would try and fail
+ * badly.
+ */
+ ConditionVariableCancelSleep();
+
+ dsm_detach(decoding_worker->seg);
+ pfree(decoding_worker);
+ decoding_worker = NULL;
+}
+
+/*
+ * Get the initial snapshot from the decoding worker.
+ */
+static Snapshot
+get_initial_snapshot(DecodingWorker *worker)
+{
+ DecodingWorkerShared *shared;
+ char fname[MAXPGPATH];
+ BufFile *file;
+ Size snap_size;
+ char *snap_space;
+ Snapshot snapshot;
+
+ shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg);
+
+ /*
+ * The worker needs to initialize the logical decoding, which usually
+ * takes some time. Therefore it makes sense to prepare for the sleep
+ * first.
+ */
+ ConditionVariablePrepareToSleep(&shared->cv);
+ for (;;)
+ {
+ int last_exported;
+
+ SpinLockAcquire(&shared->mutex);
+ last_exported = shared->last_exported;
+ SpinLockRelease(&shared->mutex);
+
+ /*
+ * Has the worker exported the file we are waiting for?
+ */
+ if (last_exported == WORKER_FILE_SNAPSHOT)
+ break;
+
+ ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
+ }
+ ConditionVariableCancelSleep();
+
+ /* Read the snapshot from a file. */
+ DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT);
+ file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
+ BufFileReadExact(file, &snap_size, sizeof(snap_size));
+ snap_space = (char *) palloc(snap_size);
+ BufFileReadExact(file, snap_space, snap_size);
+ BufFileClose(file);
+
+ /* Restore it. */
+ snapshot = RestoreSnapshot(snap_space);
+ pfree(snap_space);
+
+ return snapshot;
+}
+
+/*
+ * Generate worker's file name into 'fname', which must be of size MAXPGPATH.
+ * If relations of the same 'relid' happen to be processed at the same time,
+ * they must be from different databases and therefore different backends must
+ * be involved.
+ */
+void
+DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
+{
+ /* The PID is already present in the fileset name, so we needn't add it */
+ snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
+}
+
+/*
+ * Handle receipt of an interrupt indicating a repack worker message.
+ *
+ * Note: this is called within a signal handler! All we can do is set
+ * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
+ * ProcessRepackMessages().
+ */
+void
+HandleRepackMessageInterrupt(void)
+{
+ InterruptPending = true;
+ RepackMessagePending = true;
+ SetLatch(MyLatch);
+}
+
+/*
+ * Process any queued protocol messages received from the repack worker.
+ */
+void
+ProcessRepackMessages(void)
+{
+ MemoryContext oldcontext;
+ static MemoryContext hpm_context = NULL;
+
+ /*
+ * Nothing to do if we haven't launched the worker yet or have already
+ * terminated it.
+ */
+ if (decoding_worker == NULL)
+ return;
+
+ /*
+ * This is invoked from ProcessInterrupts(), and since some of the
+ * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
+ * for recursive calls if more signals are received while this runs. It's
+ * unclear that recursive entry would be safe, and it doesn't seem useful
+ * even if it is safe, so let's block interrupts until done.
+ */
+ HOLD_INTERRUPTS();
+
+ /*
+ * Moreover, CurrentMemoryContext might be pointing almost anywhere. We
+ * don't want to risk leaking data into long-lived contexts, so let's do
+ * our work here in a private context that we can reset on each use.
+ */
+ if (hpm_context == NULL) /* first time through? */
+ hpm_context = AllocSetContextCreate(TopMemoryContext,
+ "ProcessRepackMessages",
+ ALLOCSET_DEFAULT_SIZES);
+ else
+ MemoryContextReset(hpm_context);
+
+ oldcontext = MemoryContextSwitchTo(hpm_context);
+
+ /* OK to process messages. Reset the flag saying there are more to do. */
+ RepackMessagePending = false;
+
+ /*
+ * Read as many messages as we can from the worker, but stop when no more
+ * messages can be read from the worker without blocking.
+ */
+ while (true)
+ {
+ shm_mq_result res;
+ Size nbytes;
+ void *data;
+
+ res = shm_mq_receive(decoding_worker->error_mqh, &nbytes,
+ &data, true);
+ if (res == SHM_MQ_WOULD_BLOCK)
+ break;
+ else if (res == SHM_MQ_SUCCESS)
+ {
+ StringInfoData msg;
+
+ initStringInfo(&msg);
+ appendBinaryStringInfo(&msg, data, nbytes);
+ ProcessRepackMessage(&msg);
+ pfree(msg.data);
+ }
+ else
+ {
+ /*
+ * The decoding worker is special in that it exits as soon as it
+ * has its work done. Thus the DETACHED result code is fine.
+ */
+ Assert(res == SHM_MQ_DETACHED);
+
+ break;
+ }
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Might as well clear the context on our way out */
+ MemoryContextReset(hpm_context);
+
+ RESUME_INTERRUPTS();
+}
+
+/*
+ * Process a single protocol message received from a single parallel worker.
+ */
+static void
+ProcessRepackMessage(StringInfo msg)
+{
+ char msgtype;
+
+ msgtype = pq_getmsgbyte(msg);
+
+ switch (msgtype)
+ {
+ case PqMsg_ErrorResponse:
+ case PqMsg_NoticeResponse:
+ {
+ ErrorData edata;
+
+ /* Parse ErrorResponse or NoticeResponse. */
+ pq_parse_errornotice(msg, &edata);
+
+ /* Death of a worker isn't enough justification for suicide. */
+ edata.elevel = Min(edata.elevel, ERROR);
+
+ /*
+ * Add a context line to show that this is a message
+ * propagated from the worker. Otherwise, it can sometimes be
+ * confusing to understand what actually happened.
+ */
+ if (edata.context)
+ edata.context = psprintf("%s\n%s", edata.context,
+ _("REPACK decoding worker"));
+ else
+ edata.context = pstrdup(_("REPACK decoding worker"));
+
+ /* Rethrow error or print notice. */
+ ThrowErrorData(&edata);
+
+ break;
+ }
+
+ default:
+ {
+ elog(ERROR, "unrecognized message type received from decoding worker: %c (message length %d bytes)",
+ msgtype, msg->len);
+ }
+ }
+}
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
new file mode 100644
index 00000000000..c85166ba849
--- /dev/null
+++ b/src/backend/commands/repack_worker.c
@@ -0,0 +1,549 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_worker.c
+ * Implementation of the background worker for ad-hoc logical decoding
+ * during REPACK (CONCURRENTLY).
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/repack_worker.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/table.h"
+#include "access/xlog_internal.h"
+#include "access/xlogutils.h"
+#include "access/xlogwait.h"
+#include "commands/repack.h"
+#include "commands/repack_internal.h"
+#include "libpq/pqmq.h"
+#include "replication/snapbuild.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "tcop/tcopprot.h"
+#include "utils/memutils.h"
+
+#define REPL_PLUGIN_NAME "pgoutput_repack"
+
+static void repack_worker_internal(dsm_segment *seg);
+static void RepackWorkerShutdown(int code, Datum arg);
+static LogicalDecodingContext *repack_setup_logical_decoding(Oid relid);
+static void repack_cleanup_logical_decoding(LogicalDecodingContext *ctx);
+static void export_initial_snapshot(Snapshot snapshot,
+ DecodingWorkerShared *shared);
+static bool decode_concurrent_changes(LogicalDecodingContext *ctx,
+ DecodingWorkerShared *shared);
+
+
+/* Is this process a REPACK worker? */
+static bool am_repack_worker = false;
+
+/* The WAL segment being decoded. */
+static XLogSegNo repack_current_segment = 0;
+
+/*
+ * Keep track of the table we're processing, to skip logical decoding of data
+ * from other relations.
+ */
+static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid};
+static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid};
+
+
+/* REPACK decoding worker entry point */
+void
+RepackWorkerMain(Datum main_arg)
+{
+ dsm_segment *seg;
+ DecodingWorkerShared *shared;
+ shm_mq *mq;
+ shm_mq_handle *mqh;
+
+ am_repack_worker = true;
+
+ /*
+ * Override the default bgworker_die() with die() so we can use
+ * CHECK_FOR_INTERRUPTS().
+ */
+ pqsignal(SIGTERM, die);
+ BackgroundWorkerUnblockSignals();
+
+ seg = dsm_attach(DatumGetUInt32(main_arg));
+ if (seg == NULL)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not map dynamic shared memory segment"));
+
+ shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+
+ /* Arrange to signal the leader if we exit. */
+ backend_pid = shared->backend_pid;
+ backend_proc_number = shared->backend_proc_number;
+ before_shmem_exit(RepackWorkerShutdown, PointerGetDatum(seg));
+
+ /*
+ * Join locking group - see the comments around the call of
+ * start_repack_decoding_worker().
+ */
+ if (!BecomeLockGroupMember(shared->backend_proc, backend_pid))
+ /* The leader is not running anymore. */
+ return;
+
+ /*
+ * Setup a queue to send error messages to the backend that launched this
+ * worker.
+ */
+ mq = (shm_mq *) (char *) BUFFERALIGN(shared->error_queue);
+ shm_mq_set_sender(mq, MyProc);
+ mqh = shm_mq_attach(mq, seg, NULL);
+ pq_redirect_to_shm_mq(seg, mqh);
+ pq_set_parallel_leader(shared->backend_pid,
+ shared->backend_proc_number);
+
+ /* Connect to the database. */
+ BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid, 0);
+
+ repack_worker_internal(seg);
+}
+
+static void
+repack_worker_internal(dsm_segment *seg)
+{
+ DecodingWorkerShared *shared;
+ LogicalDecodingContext *decoding_ctx;
+ SharedFileSet *sfs;
+ Snapshot snapshot;
+
+ /*
+ * Transaction is needed to open relation, and it also provides us with a
+ * resource owner.
+ */
+ StartTransactionCommand();
+
+ shared = (DecodingWorkerShared *) dsm_segment_address(seg);
+
+ /*
+ * Not sure the spinlock is needed here - the backend should not change
+ * anything in the shared memory until we have serialized the snapshot.
+ */
+ SpinLockAcquire(&shared->mutex);
+ Assert(!XLogRecPtrIsValid(shared->lsn_upto));
+ sfs = &shared->sfs;
+ SpinLockRelease(&shared->mutex);
+
+ SharedFileSetAttach(sfs, seg);
+
+ /*
+ * Prepare to capture the concurrent data changes ourselves.
+ */
+ decoding_ctx = repack_setup_logical_decoding(shared->relid);
+
+ /* Announce that we're ready. */
+ SpinLockAcquire(&shared->mutex);
+ shared->initialized = true;
+ SpinLockRelease(&shared->mutex);
+ ConditionVariableSignal(&shared->cv);
+
+ /* There doesn't seem to a nice API to set these */
+ XactIsoLevel = XACT_REPEATABLE_READ;
+ XactReadOnly = true;
+
+ /* Build the initial snapshot and export it. */
+ snapshot = SnapBuildInitialSnapshot(decoding_ctx->snapshot_builder);
+ export_initial_snapshot(snapshot, shared);
+
+ /*
+ * Only historic snapshots should be used now. Do not let us restrict the
+ * progress of xmin horizon.
+ */
+ InvalidateCatalogSnapshot();
+
+ for (;;)
+ {
+ bool stop = decode_concurrent_changes(decoding_ctx, shared);
+
+ if (stop)
+ break;
+
+ }
+
+ /* Cleanup. */
+ repack_cleanup_logical_decoding(decoding_ctx);
+ CommitTransactionCommand();
+}
+
+/*
+ * See ParallelWorkerShutdown for details.
+ */
+static void
+RepackWorkerShutdown(int code, Datum arg)
+{
+ SendProcSignal(backend_pid,
+ PROCSIG_REPACK_MESSAGE,
+ backend_proc_number);
+
+ dsm_detach((dsm_segment *) DatumGetPointer(arg));
+}
+
+bool
+AmRepackWorker(void)
+{
+ return am_repack_worker;
+}
+
+/*
+ * This function is much like pg_create_logical_replication_slot() except that
+ * the new slot is neither released (if anyone else could read changes from
+ * our slot, we could miss changes other backends do while we copy the
+ * existing data into temporary table), nor persisted (it's easier to handle
+ * crash by restarting all the work from scratch).
+ */
+static LogicalDecodingContext *
+repack_setup_logical_decoding(Oid relid)
+{
+ Relation rel;
+ Oid toastrelid;
+ LogicalDecodingContext *ctx;
+ NameData slotname;
+ RepackDecodingState *dstate;
+ MemoryContext oldcxt;
+
+ /*
+ * REPACK CONCURRENTLY is not allowed in a transaction block, so this
+ * should never fire.
+ */
+ Assert(!TransactionIdIsValid(GetTopTransactionIdIfAny()));
+
+ /*
+ * Make sure we can use logical decoding.
+ */
+ CheckSlotPermissions();
+ CheckLogicalDecodingRequirements();
+
+ /*
+ * A single backend should not execute multiple REPACK commands at a time,
+ * so use PID to make the slot unique.
+ *
+ * RS_TEMPORARY so that the slot gets cleaned up on ERROR.
+ */
+ snprintf(NameStr(slotname), NAMEDATALEN, "repack_%d", MyProcPid);
+ ReplicationSlotCreate(NameStr(slotname), true, RS_TEMPORARY, false, false,
+ false);
+
+ EnsureLogicalDecodingEnabled();
+
+ /*
+ * Neither prepare_write nor do_write callback nor update_progress is
+ * useful for us.
+ */
+ ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME,
+ NIL,
+ true,
+ InvalidXLogRecPtr,
+ XL_ROUTINE(.page_read = read_local_xlog_page,
+ .segment_open = wal_segment_open,
+ .segment_close = wal_segment_close),
+ NULL, NULL, NULL);
+
+ /*
+ * We don't have control on setting fast_forward, so at least check it.
+ */
+ Assert(!ctx->fast_forward);
+
+ /* Avoid logical decoding of other relations. */
+ rel = table_open(relid, AccessShareLock);
+ repacked_rel_locator = rel->rd_locator;
+ toastrelid = rel->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+
+ /* Avoid logical decoding of other TOAST relations. */
+ toastrel = table_open(toastrelid, AccessShareLock);
+ repacked_rel_toast_locator = toastrel->rd_locator;
+ table_close(toastrel, AccessShareLock);
+ }
+ table_close(rel, AccessShareLock);
+
+ DecodingContextFindStartpoint(ctx);
+
+ /*
+ * decode_concurrent_changes() needs non-blocking callback.
+ */
+ ctx->reader->routine.page_read = read_local_xlog_page_no_wait;
+
+ /* Some WAL records should have been read. */
+ Assert(ctx->reader->EndRecPtr != InvalidXLogRecPtr);
+
+ /*
+ * Initialize repack_current_segment so that we can notice WAL segment
+ * boundaries.
+ */
+ XLByteToSeg(ctx->reader->EndRecPtr, repack_current_segment,
+ wal_segment_size);
+
+ /* Our private state belongs to the decoding context. */
+ oldcxt = MemoryContextSwitchTo(ctx->context);
+
+ /*
+ * read_local_xlog_page_no_wait() needs to be able to indicate the end of
+ * WAL.
+ */
+ ctx->reader->private_data = palloc0_object(ReadLocalXLogPageNoWaitPrivate);
+ dstate = palloc0_object(RepackDecodingState);
+ MemoryContextSwitchTo(oldcxt);
+
+#ifdef USE_ASSERT_CHECKING
+ dstate->relid = relid;
+#endif
+
+ dstate->change_cxt = AllocSetContextCreate(ctx->context,
+ "REPACK - change",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* The file will be set as soon as we have it opened. */
+ dstate->file = NULL;
+
+ /*
+ * Memory context and resource owner for long-lived resources.
+ */
+ dstate->worker_cxt = CurrentMemoryContext;
+ dstate->worker_resowner = CurrentResourceOwner;
+
+ ctx->output_writer_private = dstate;
+
+ return ctx;
+}
+
+static void
+repack_cleanup_logical_decoding(LogicalDecodingContext *ctx)
+{
+ RepackDecodingState *dstate;
+
+ dstate = (RepackDecodingState *) ctx->output_writer_private;
+ if (dstate->slot)
+ ExecDropSingleTupleTableSlot(dstate->slot);
+
+ FreeDecodingContext(ctx);
+ ReplicationSlotDropAcquired();
+}
+
+/*
+ * Make snapshot available to the backend that launched the decoding worker.
+ */
+static void
+export_initial_snapshot(Snapshot snapshot, DecodingWorkerShared *shared)
+{
+ char fname[MAXPGPATH];
+ BufFile *file;
+ Size snap_size;
+ char *snap_space;
+
+ snap_size = EstimateSnapshotSpace(snapshot);
+ snap_space = (char *) palloc(snap_size);
+ SerializeSnapshot(snapshot, snap_space);
+
+ DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+ file = BufFileCreateFileSet(&shared->sfs.fs, fname);
+ /* To make restoration easier, write the snapshot size first. */
+ BufFileWrite(file, &snap_size, sizeof(snap_size));
+ BufFileWrite(file, snap_space, snap_size);
+ pfree(snap_space);
+ BufFileClose(file);
+
+ /* Increase the counter to tell the backend that the file is available. */
+ SpinLockAcquire(&shared->mutex);
+ shared->last_exported++;
+ SpinLockRelease(&shared->mutex);
+ ConditionVariableSignal(&shared->cv);
+}
+
+/*
+ * Decode logical changes from the WAL sequence and store them to a file.
+ *
+ * If true is returned, there is no more work for the worker.
+ */
+static bool
+decode_concurrent_changes(LogicalDecodingContext *ctx,
+ DecodingWorkerShared *shared)
+{
+ RepackDecodingState *dstate;
+ XLogRecPtr lsn_upto;
+ bool done;
+ char fname[MAXPGPATH];
+
+ dstate = (RepackDecodingState *) ctx->output_writer_private;
+
+ /* Open the output file. */
+ DecodingWorkerFileName(fname, shared->relid, shared->last_exported + 1);
+ dstate->file = BufFileCreateFileSet(&shared->sfs.fs, fname);
+
+ SpinLockAcquire(&shared->mutex);
+ lsn_upto = shared->lsn_upto;
+ done = shared->done;
+ SpinLockRelease(&shared->mutex);
+
+ while (true)
+ {
+ XLogRecord *record;
+ XLogSegNo segno_new;
+ char *errm = NULL;
+ XLogRecPtr end_lsn;
+
+ CHECK_FOR_INTERRUPTS();
+
+ record = XLogReadRecord(ctx->reader, &errm);
+ if (record)
+ {
+ LogicalDecodingProcessRecord(ctx, ctx->reader);
+
+ /*
+ * If WAL segment boundary has been crossed, inform the decoding
+ * system that the catalog_xmin can advance.
+ *
+ * TODO Does it make sense to confirm more often? Segment size
+ * seems appropriate for restart_lsn (because less than a segment
+ * cannot be recycled anyway), however more frequent checks might
+ * be beneficial for catalog_xmin.
+ */
+ end_lsn = ctx->reader->EndRecPtr;
+ XLByteToSeg(end_lsn, segno_new, wal_segment_size);
+ if (segno_new != repack_current_segment)
+ {
+ LogicalConfirmReceivedLocation(end_lsn);
+ elog(DEBUG1, "REPACK: confirmed receive location %X/%X",
+ (uint32) (end_lsn >> 32), (uint32) end_lsn);
+ repack_current_segment = segno_new;
+ }
+ }
+ else
+ {
+ ReadLocalXLogPageNoWaitPrivate *priv;
+
+ if (errm)
+ ereport(ERROR,
+ errmsg("%s", errm));
+
+ /*
+ * In the decoding loop we do not want to get blocked when there
+ * is no more WAL available, otherwise the loop would become
+ * uninterruptible.
+ */
+ priv = (ReadLocalXLogPageNoWaitPrivate *) ctx->reader->private_data;
+ if (priv->end_of_wal)
+ /* Do not miss the end of WAL condition next time. */
+ priv->end_of_wal = false;
+ else
+ ereport(ERROR,
+ errmsg("could not read WAL record"));
+ }
+
+ /*
+ * Whether we could read new record or not, keep checking if
+ * 'lsn_upto' was specified.
+ */
+ if (!XLogRecPtrIsValid(lsn_upto))
+ {
+ SpinLockAcquire(&shared->mutex);
+ lsn_upto = shared->lsn_upto;
+ /* 'done' should be set at the same time as 'lsn_upto' */
+ done = shared->done;
+ SpinLockRelease(&shared->mutex);
+ }
+ if (XLogRecPtrIsValid(lsn_upto) &&
+ ctx->reader->EndRecPtr >= lsn_upto)
+ break;
+
+ if (record == NULL)
+ {
+ int64 timeout = 0;
+ WaitLSNResult res;
+
+ /*
+ * Before we retry reading, wait until new WAL is flushed.
+ *
+ * There is a race condition such that the backend executing
+ * REPACK determines 'lsn_upto', but before it sets the shared
+ * variable, we reach the end of WAL. In that case we'd need to
+ * wait until the next WAL flush (unrelated to REPACK). Although
+ * that should not be a problem in a busy system, it might be
+ * noticeable in other cases, including regression tests (which
+ * are not necessarily executed in parallel). Therefore it makes
+ * sense to use timeout.
+ *
+ * If lsn_upto is valid, WAL records having LSN lower than that
+ * should already have been flushed to disk.
+ */
+ if (!XLogRecPtrIsValid(lsn_upto))
+ timeout = 100L;
+ res = WaitForLSN(WAIT_LSN_TYPE_PRIMARY_FLUSH,
+ ctx->reader->EndRecPtr + 1,
+ timeout);
+ if (res != WAIT_LSN_RESULT_SUCCESS &&
+ res != WAIT_LSN_RESULT_TIMEOUT)
+ ereport(ERROR,
+ errmsg("waiting for WAL failed"));
+ }
+ }
+
+ /*
+ * Close the file so we can make it available to the backend.
+ */
+ BufFileClose(dstate->file);
+ dstate->file = NULL;
+ SpinLockAcquire(&shared->mutex);
+ shared->lsn_upto = InvalidXLogRecPtr;
+ shared->last_exported++;
+ SpinLockRelease(&shared->mutex);
+ ConditionVariableSignal(&shared->cv);
+
+ return done;
+}
+
+/*
+ * Does the WAL record contain a data change that this backend does not need
+ * to decode on behalf of REPACK (CONCURRENTLY)?
+ */
+bool
+change_useless_for_repack(XLogRecordBuffer *buf)
+{
+ XLogReaderState *r = buf->record;
+ RelFileLocator locator;
+
+ /* TOAST locator should not be set unless the main is. */
+ Assert(!OidIsValid(repacked_rel_toast_locator.relNumber) ||
+ OidIsValid(repacked_rel_locator.relNumber));
+
+ /*
+ * Backends not involved in REPACK (CONCURRENTLY) should not do the
+ * filtering.
+ */
+ if (!OidIsValid(repacked_rel_locator.relNumber))
+ return false;
+
+ /*
+ * If the record does not contain the block 0, it's probably not INSERT /
+ * UPDATE / DELETE. In any case, we do not have enough information to
+ * filter the change out.
+ */
+ if (!XLogRecGetBlockTagExtended(r, 0, &locator, NULL, NULL, NULL))
+ return false;
+
+ /*
+ * Decode the change if it belongs to the table we are repacking, or if it
+ * belongs to its TOAST relation.
+ */
+ if (RelFileLocatorEquals(locator, repacked_rel_locator))
+ return false;
+ if (OidIsValid(repacked_rel_toast_locator.relNumber) &&
+ RelFileLocatorEquals(locator, repacked_rel_toast_locator))
+ return false;
+
+ /* Filter out changes of other tables. */
+ return true;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e2882a50b3b..eec09ba1ded 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6058,6 +6058,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
finish_heap_swap(tab->relid, OIDNewHeap,
false, false, true,
!OidIsValid(tab->newTableSpace),
+ true, /* reindex */
RecentXmin,
ReadNextMultiXactId(),
persistence);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index b179b62b5c8..149d15f8f91 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -127,7 +127,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy, bool isTopLevel);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -630,7 +630,8 @@ vacuum(List *relations, const VacuumParams *params, BufferAccessStrategy bstrate
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, *params, bstrategy))
+ if (!vacuum_rel(vrel->oid, vrel->relation, *params, bstrategy,
+ isTopLevel))
continue;
}
@@ -2004,7 +2005,7 @@ vac_truncate_clog(TransactionId frozenXID,
*/
static bool
vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
- BufferAccessStrategy bstrategy)
+ BufferAccessStrategy bstrategy, bool isTopLevel)
{
LOCKMODE lmode;
Relation rel;
@@ -2295,7 +2296,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
/* VACUUM FULL is a variant of REPACK; see repack.c */
cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid,
- &cluster_params);
+ &cluster_params, isTopLevel);
/* cluster_rel closes the relation, but keeps lock */
rel = NULL;
@@ -2338,7 +2339,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
toast_vacuum_params.toast_parent = relid;
- vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy);
+ vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy,
+ isTopLevel);
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index dfd7b33aa9b..a09b96a0868 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2575,7 +2575,7 @@ lreplace:
0,
estate->es_snapshot,
estate->es_crosscheck_snapshot,
- true /* wait for commit */ ,
+ true,
&context->tmfd, &updateCxt->lockmode,
&updateCxt->updateIndexes);
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index 22e5164adbf..21ce180c78d 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
#include "postgres.h"
#include "access/parallel.h"
+#include "commands/repack.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/pqmq.h"
@@ -177,6 +178,10 @@ mq_putmessage(char msgtype, const char *s, size_t len)
SendProcSignal(pq_mq_parallel_leader_pid,
PROCSIG_PARALLEL_APPLY_MESSAGE,
pq_mq_parallel_leader_proc_number);
+ else if (AmRepackWorker())
+ SendProcSignal(pq_mq_parallel_leader_pid,
+ PROCSIG_REPACK_MESSAGE,
+ pq_mq_parallel_leader_proc_number);
else
{
Assert(IsParallelWorker());
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 4f5292d8f88..5e3cfe2d6f8 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -219,5 +219,6 @@ pg_test_mod_args = pg_mod_args + {
subdir('jit/llvm')
subdir('replication/libpqwalreceiver')
subdir('replication/pgoutput')
+subdir('replication/pgoutput_repack')
subdir('snowball')
subdir('utils/mb/conversion_procs')
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 536aff7ca05..704772e4c22 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,7 @@
#include "postgres.h"
#include "access/parallel.h"
+#include "commands/repack.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -137,6 +138,10 @@ static const struct
.fn_name = "ParallelWorkerMain",
.fn_addr = ParallelWorkerMain
},
+ {
+ .fn_name = "RepackWorkerMain",
+ .fn_addr = RepackWorkerMain
+ },
{
.fn_name = "SequenceSyncWorkerMain",
.fn_addr = SequenceSyncWorkerMain
diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c
index 4299d0bc867..1353d004aac 100644
--- a/src/backend/replication/logical/decode.c
+++ b/src/backend/replication/logical/decode.c
@@ -33,6 +33,7 @@
#include "access/xlogreader.h"
#include "access/xlogrecord.h"
#include "catalog/pg_control.h"
+#include "commands/repack.h"
#include "replication/decode.h"
#include "replication/logical.h"
#include "replication/message.h"
@@ -442,7 +443,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
{
case XLOG_HEAP2_MULTI_INSERT:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeMultiInsert(ctx, buf);
break;
case XLOG_HEAP2_NEW_CID:
@@ -504,7 +506,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
{
case XLOG_HEAP_INSERT:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeInsert(ctx, buf);
break;
@@ -516,19 +519,22 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
case XLOG_HEAP_HOT_UPDATE:
case XLOG_HEAP_UPDATE:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeUpdate(ctx, buf);
break;
case XLOG_HEAP_DELETE:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeDelete(ctx, buf);
break;
case XLOG_HEAP_TRUNCATE:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeTruncate(ctx, buf);
break;
@@ -544,7 +550,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
case XLOG_HEAP_CONFIRM:
if (SnapBuildProcessChange(builder, xid, buf->origptr) &&
- !ctx->fast_forward)
+ !ctx->fast_forward &&
+ !change_useless_for_repack(buf))
DecodeSpecConfirm(ctx, buf);
break;
@@ -1041,6 +1048,15 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf)
xlrec = (xl_heap_delete *) XLogRecGetData(r);
+ /*
+ * Ignore changes which are considered useless for logical decoding.
+ * Currently such changes are created by REPACK (CONCURRENTLY) when
+ * replays DELETE commands on the new table (which is not yet visible to
+ * other transactions).
+ */
+ if (xlrec->flags & XLH_DELETE_NO_LOGICAL)
+ return;
+
/* only interested in our database */
XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL);
if (target_locator.dbOid != ctx->slot->data.database)
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 8ceaf64d164..dc46a372ead 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -194,7 +194,11 @@ StartupDecodingContext(List *output_plugin_options,
ctx->slot = slot;
- ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, ctx);
+ /*
+ * TODO A separate patch for PG core, unless there's really a reason to
+ * pass ctx for private_data (May extensions expect ctx?).
+ */
+ ctx->reader = XLogReaderAllocate(wal_segment_size, NULL, xl_routine, NULL);
if (!ctx->reader)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
diff --git a/src/backend/replication/pgoutput_repack/Makefile b/src/backend/replication/pgoutput_repack/Makefile
new file mode 100644
index 00000000000..4efeb713b70
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/Makefile
@@ -0,0 +1,32 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/replication/pgoutput_repack
+#
+# IDENTIFICATION
+# src/backend/replication/pgoutput_repack
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/replication/pgoutput_repack
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ $(WIN32RES) \
+ pgoutput_repack.o
+PGFILEDESC = "pgoutput_repack - logical replication output plugin for REPACK command"
+NAME = pgoutput_repack
+
+all: all-shared-lib
+
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
diff --git a/src/backend/replication/pgoutput_repack/meson.build b/src/backend/replication/pgoutput_repack/meson.build
new file mode 100644
index 00000000000..6a88c0fb08d
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/meson.build
@@ -0,0 +1,18 @@
+# Copyright (c) 2022-2026, PostgreSQL Global Development Group
+
+pgoutput_repack_sources = files(
+ 'pgoutput_repack.c',
+)
+
+if host_system == 'windows'
+ pgoutput_repack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'pgoutput_repack',
+ '--FILEDESC', 'pgoutput_repack - logical replication output plugin for REPACK command',])
+endif
+
+pgoutput_repack = shared_module('pgoutput_repack',
+ pgoutput_repack_sources,
+ kwargs: pg_mod_args,
+)
+
+backend_targets += pgoutput_repack
diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
new file mode 100644
index 00000000000..31e09e47a16
--- /dev/null
+++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c
@@ -0,0 +1,297 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgoutput_repack.c
+ * Logical Replication output plugin for REPACK command
+ *
+ * Copyright (c) 2012-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/replication/pgoutput_repack/pgoutput_repack.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "commands/repack_internal.h"
+#include "replication/snapbuild.h"
+#include "utils/memutils.h"
+
+PG_MODULE_MAGIC;
+
+static void repack_startup(LogicalDecodingContext *ctx,
+ OutputPluginOptions *opt, bool is_init);
+static void repack_shutdown(LogicalDecodingContext *ctx);
+static void repack_begin_txn(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn);
+static void repack_commit_txn(LogicalDecodingContext *ctx,
+ ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
+static void repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ Relation rel, ReorderBufferChange *change);
+static void repack_store_change(LogicalDecodingContext *ctx, Relation relation,
+ ConcurrentChangeKind kind, HeapTuple tuple);
+
+void
+_PG_output_plugin_init(OutputPluginCallbacks *cb)
+{
+ cb->startup_cb = repack_startup;
+ cb->begin_cb = repack_begin_txn;
+ cb->change_cb = repack_process_change;
+ cb->commit_cb = repack_commit_txn;
+ cb->shutdown_cb = repack_shutdown;
+}
+
+
+/* initialize this plugin */
+static void
+repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
+ bool is_init)
+{
+ ctx->output_plugin_private = NULL;
+
+ /* Probably unnecessary, as we don't use the SQL interface ... */
+ opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
+
+ /*
+ * By declaring that our output plugin does not need shared catalogs, we
+ * avoid waiting for completion of transactions running in databases other
+ * than the one we're connected to.
+ */
+ opt->need_shared_catalogs = false;
+
+ if (ctx->output_plugin_options != NIL)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("this plugin does not expect any options"));
+ }
+}
+
+static void
+repack_shutdown(LogicalDecodingContext *ctx)
+{
+}
+
+/*
+ * As we don't release the slot during processing of particular table, there's
+ * no room for SQL interface, even for debugging purposes. Therefore we need
+ * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin
+ * callbacks. (Although we might want to write custom callbacks, this API
+ * seems to be unnecessarily generic for our purposes.)
+ */
+
+/* BEGIN callback */
+static void
+repack_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+}
+
+/* COMMIT callback */
+static void
+repack_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ XLogRecPtr commit_lsn)
+{
+}
+
+/*
+ * Callback for individual changed tuples
+ */
+static void
+repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+ Relation relation, ReorderBufferChange *change)
+{
+ RepackDecodingState *private PG_USED_FOR_ASSERTS_ONLY =
+ (RepackDecodingState *) ctx->output_writer_private;
+
+ /* Changes of other relation should not have been decoded. */
+ Assert(RelationGetRelid(relation) == private->relid);
+
+ /* Decode entry depending on its type */
+ switch (change->action)
+ {
+ case REORDER_BUFFER_CHANGE_INSERT:
+ {
+ HeapTuple newtuple;
+
+ newtuple = change->data.tp.newtuple;
+
+ /*
+ * Identity checks in the main function should have made this
+ * impossible.
+ */
+ if (newtuple == NULL)
+ elog(ERROR, "incomplete insert info");
+
+ repack_store_change(ctx, relation, CHANGE_INSERT, newtuple);
+ }
+ break;
+ case REORDER_BUFFER_CHANGE_UPDATE:
+ {
+ HeapTuple oldtuple,
+ newtuple;
+
+ oldtuple = change->data.tp.oldtuple;
+ newtuple = change->data.tp.newtuple;
+
+ if (newtuple == NULL)
+ elog(ERROR, "incomplete update info");
+
+ if (oldtuple != NULL)
+ repack_store_change(ctx, relation, CHANGE_UPDATE_OLD, oldtuple);
+
+ repack_store_change(ctx, relation, CHANGE_UPDATE_NEW, newtuple);
+ }
+ break;
+ case REORDER_BUFFER_CHANGE_DELETE:
+ {
+ HeapTuple oldtuple;
+
+ oldtuple = change->data.tp.oldtuple;
+
+ if (oldtuple == NULL)
+ elog(ERROR, "incomplete delete info");
+
+ repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple);
+ }
+ break;
+ default:
+
+ /*
+ * Should not come here. This includes TRUNCATE of the table being
+ * processed. heap_decode() cannot check the file locator easily,
+ * but we assume that TRUNCATE uses AccessExclusiveLock on the
+ * table so it should not occur during REPACK (CONCURRENTLY).
+ */
+ Assert(false);
+ break;
+ }
+}
+
+/*
+ * Write the given tuple, with the given change kind, to the repack spill
+ * file. Later, the repack decoding worker can read these and replay
+ * the operations on the new copy of the table.
+ *
+ * For each change affecting the table being repacked, we store enough
+ * information about each tuple in it, so that it can be replayed in the
+ * new copy of the table.
+ *
+ * XXX for DELETE and the UPDATE OLD tuples, we could store just the
+ * replication identity instead of the full tuple.
+ */
+static void
+repack_store_change(LogicalDecodingContext *ctx, Relation relation,
+ ConcurrentChangeKind kind, HeapTuple tuple)
+{
+ RepackDecodingState *dstate;
+ MemoryContext oldcxt;
+ BufFile *file;
+ List *attrs_ext = NIL;
+ int natt_ext;
+
+ dstate = (RepackDecodingState *) ctx->output_writer_private;
+ file = dstate->file;
+
+ /* Store the change kind. */
+ BufFileWrite(file, &kind, 1);
+
+ /* Use a frequently-reset context to avoid dealing with leaks manually */
+ oldcxt = MemoryContextSwitchTo(dstate->change_cxt);
+
+ /*
+ * If the tuple contains "external indirect" attributes, we need to write
+ * the contents to the file because we have no control over that memory.
+ */
+ if (HeapTupleHasExternal(tuple))
+ {
+ TupleDesc desc = RelationGetDescr(relation);
+ TupleTableSlot *slot;
+
+ /* Initialize the slot, if not done already */
+ if (dstate->slot == NULL)
+ {
+ ResourceOwner saveResourceOwner;
+
+ MemoryContextSwitchTo(dstate->worker_cxt);
+ saveResourceOwner = CurrentResourceOwner;
+ CurrentResourceOwner = dstate->worker_resowner;
+ dstate->slot = MakeSingleTupleTableSlot(desc, &TTSOpsHeapTuple);
+ MemoryContextSwitchTo(dstate->change_cxt);
+ CurrentResourceOwner = saveResourceOwner;
+ }
+
+ slot = dstate->slot;
+ ExecStoreHeapTuple(tuple, slot, false);
+
+ /*
+ * Loop over all attributes, and find out which ones we need to spill
+ * separately, to wit: each one that's a non-null varlena and stored
+ * out of line.
+ */
+ for (int i = 0; i < desc->natts; i++)
+ {
+ CompactAttribute *attr = TupleDescCompactAttr(desc, i);
+ varlena *varlen;
+
+ if (attr->attisdropped || attr->attlen != -1 ||
+ slot_attisnull(slot, i + 1))
+ continue;
+
+ slot_getsomeattrs(slot, i + 1);
+
+ /*
+ * This is a non-null varlena datum, but we only care if it's
+ * out-of-line
+ */
+ varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
+ if (!VARATT_IS_EXTERNAL(varlen))
+ continue;
+
+ /*
+ * We spill any indirect-external attributes separately from the
+ * heap tuple. Anything else is written as is.
+ */
+ if (VARATT_IS_EXTERNAL_INDIRECT(varlen))
+ attrs_ext = lappend(attrs_ext, varlen);
+ else
+ {
+ /*
+ * Logical decoding should not produce "external expanded"
+ * attributes (those actually should never appear on disk), so
+ * only TOASTed attribute can be seen here.
+ *
+ * We get here if the table has external values but only
+ * in-line values are being updated now.
+ */
+ Assert(VARATT_IS_EXTERNAL_ONDISK(varlen));
+ }
+ }
+
+ ExecClearTuple(slot);
+ }
+
+ /*
+ * First, write the original heap tuple, prefixed by its length. Note
+ * that the external-toast tag for each toasted attribute will be present
+ * in what we write, so that we know where to restore each one later.
+ */
+ BufFileWrite(file, &tuple->t_len, sizeof(tuple->t_len));
+ BufFileWrite(file, tuple->t_data, tuple->t_len);
+
+ /* Then, write the number of external attributes we found. */
+ natt_ext = list_length(attrs_ext);
+ BufFileWrite(file, &natt_ext, sizeof(natt_ext));
+
+ /* Finally, the attributes themselves, if any */
+ foreach_ptr(varlena, attr_val, attrs_ext)
+ {
+ attr_val = detoast_external_attr(attr_val);
+ BufFileWrite(file, attr_val, VARSIZE_ANY(attr_val));
+ /* These attributes could be large, so free them right away */
+ pfree(attr_val);
+ }
+
+ /* Cleanup. */
+ MemoryContextSwitchTo(oldcxt);
+ MemoryContextReset(dstate->change_cxt);
+}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 02d28df1c6a..d9fd40d00ba 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -709,6 +709,9 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
HandleParallelApplyMessageInterrupt();
+ if (CheckProcSignal(PROCSIG_REPACK_MESSAGE))
+ HandleRepackMessageInterrupt();
+
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
HandleRecoveryConflictInterrupt();
diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl
index b49007167b0..2e7f1054e62 100644
--- a/src/backend/storage/lmgr/generate-lwlocknames.pl
+++ b/src/backend/storage/lmgr/generate-lwlocknames.pl
@@ -162,7 +162,7 @@ while (<$lwlocklist>)
die
"$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but "
- . " missing from lwlocklist.h"
+ . "missing from lwlocklist.h"
if $lwlock_count < scalar @wait_event_lwlocks;
die
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9fbaa5c00f0..47a2960a197 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3577,6 +3577,9 @@ ProcessInterrupts(void)
if (ParallelApplyMessagePending)
ProcessParallelApplyMessages();
+
+ if (RepackMessagePending)
+ ProcessRepackMessages();
}
/*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 0a6d16f8154..7bda5298558 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -156,6 +156,7 @@ RECOVERY_CONFLICT_SNAPSHOT "Waiting for recovery conflict resolution for a vacuu
RECOVERY_CONFLICT_TABLESPACE "Waiting for recovery conflict resolution for dropping a tablespace."
RECOVERY_END_COMMAND "Waiting for <xref linkend="guc-recovery-end-command"/> to complete."
RECOVERY_PAUSE "Waiting for recovery to be resumed."
+REPACK_WORKER_EXPORT "Waiting for decoding worker to export a new output file."
REPLICATION_ORIGIN_DROP "Waiting for a replication origin to become inactive so it can be dropped."
REPLICATION_SLOT_DROP "Waiting for a replication slot to become inactive so it can be dropped."
RESTORE_COMMAND "Waiting for <xref linkend="guc-restore-command"/> to complete."
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 53bf1e21721..1a22f162ce6 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -5231,8 +5231,8 @@ match_previous_words(int pattern_id,
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("ANALYZE", "VERBOSE");
- else if (TailMatches("ANALYZE", "VERBOSE"))
+ COMPLETE_WITH("ANALYZE", "CONCURRENTLY", "VERBOSE");
+ else if (TailMatches("ANALYZE", "CONCURRENTLY", "VERBOSE"))
COMPLETE_WITH("ON", "OFF");
}
diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h
index 516806fcca2..fdca7d821c8 100644
--- a/src/include/access/heapam_xlog.h
+++ b/src/include/access/heapam_xlog.h
@@ -104,6 +104,8 @@
#define XLH_DELETE_CONTAINS_OLD_KEY (1<<2)
#define XLH_DELETE_IS_SUPER (1<<3)
#define XLH_DELETE_IS_PARTITION_MOVE (1<<4)
+/* See heap_delete() */
+#define XLH_DELETE_NO_LOGICAL (1<<5)
/* convenience macro for checking whether any form of old tuple was logged */
#define XLH_DELETE_CONTAINS_OLD \
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 4647785fd35..e95073035af 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -284,9 +284,10 @@ typedef struct TM_IndexDeleteOp
/* "options" flag bits for table_tuple_delete */
#define TABLE_DELETE_CHANGING_PARTITION (1 << 0)
+#define TABLE_DELETE_NO_LOGICAL (1 << 1)
/* "options" flag bits for table_tuple_update */
-/* XXX none at present */
+#define TABLE_UPDATE_NO_LOGICAL (1 << 0)
/* flag bits for table_tuple_lock */
/* Follow tuples whose update is in progress if lock modes don't conflict */
@@ -662,6 +663,7 @@ typedef struct TableAmRoutine
Relation OldIndex,
bool use_sort,
TransactionId OldestXmin,
+ Snapshot snapshot,
TransactionId *xid_cutoff,
MultiXactId *multi_cutoff,
double *num_tuples,
@@ -1527,6 +1529,7 @@ table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
* TABLE_DELETE_CHANGING_PARTITION: the tuple is being moved to another
* partition table due to an update of the partition key.
* crosscheck - if not InvalidSnapshot, also check tuple against this
+ * XXX document options
* wait - true if should wait for any conflicting update to commit/abort
*
* Output parameters:
@@ -1563,7 +1566,12 @@ table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
* cmax/cmin if successful)
* options - bitmask of options. No values are currently recognized.
* crosscheck - if not InvalidSnapshot, also check old tuple against this
- * wait - true if should wait for any conflicting update to commit/abort
+ * options - These allow the caller to specify options that may change the
+ * behavior of the AM. The AM will ignore options that it does not support.
+ * TABLE_UPDATE_WAIT -- set if should wait for any conflicting update to
+ * commit/abort
+ * TABLE_UPDATE_NO_LOGICAL -- force-disables the emitting of logical
+ * decoding information for the tuple.
*
* Output parameters:
* slot - newly constructed tuple data to store
@@ -1725,6 +1733,8 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator)
* not needed for the relation's AM
* - *xid_cutoff - ditto
* - *multi_cutoff - ditto
+ * - snapshot - if != NULL, ignore data changes done by transactions that this
+ * (MVCC) snapshot considers still in-progress or in the future.
*
* Output parameters:
* - *xid_cutoff - rel's new relfrozenxid value, may be invalid
@@ -1737,6 +1747,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
Relation OldIndex,
bool use_sort,
TransactionId OldestXmin,
+ Snapshot snapshot,
TransactionId *xid_cutoff,
MultiXactId *multi_cutoff,
double *num_tuples,
@@ -1745,6 +1756,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
{
OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex,
use_sort, OldestXmin,
+ snapshot,
xid_cutoff, multi_cutoff,
num_tuples, tups_vacuumed,
tups_recently_dead);
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 67948667a97..2a12920c75f 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -86,10 +86,12 @@
#define PROGRESS_REPACK_PHASE 1
#define PROGRESS_REPACK_INDEX_RELID 2
#define PROGRESS_REPACK_HEAP_TUPLES_SCANNED 3
-#define PROGRESS_REPACK_HEAP_TUPLES_WRITTEN 4
-#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 5
-#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 6
-#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 7
+#define PROGRESS_REPACK_HEAP_TUPLES_INSERTED 4
+#define PROGRESS_REPACK_HEAP_TUPLES_UPDATED 5
+#define PROGRESS_REPACK_HEAP_TUPLES_DELETED 6
+#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 7
+#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 8
+#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 9
/*
* Phases of repack (as advertised via PROGRESS_REPACK_PHASE).
@@ -98,9 +100,10 @@
#define PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP 2
#define PROGRESS_REPACK_PHASE_SORT_TUPLES 3
#define PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP 4
-#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 5
-#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 6
-#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 7
+#define PROGRESS_REPACK_PHASE_CATCH_UP 5
+#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 6
+#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 7
+#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 8
/* Progress parameters for CREATE INDEX */
/* 3, 4 and 5 reserved for "waitfor" metrics */
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index 85061158b0c..fd16e74b179 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -13,6 +13,8 @@
#ifndef REPACK_H
#define REPACK_H
+#include <signal.h>
+
#include "nodes/parsenodes.h"
#include "parser/parse_node.h"
#include "storage/lockdefs.h"
@@ -25,6 +27,7 @@
#define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for
* indisclustered */
#define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */
+#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */
/* options for CLUSTER */
typedef struct ClusterParams
@@ -32,11 +35,13 @@ typedef struct ClusterParams
uint32 options; /* bitmask of CLUOPT_* */
} ClusterParams;
+extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
+
extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
- ClusterParams *params);
+ ClusterParams *params, bool isTopLevel);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
@@ -48,8 +53,16 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool swap_toast_by_content,
bool check_constraints,
bool is_internal,
+ bool reindex,
TransactionId frozenXid,
MultiXactId cutoffMulti,
char newrelpersistence);
+extern void HandleRepackMessageInterrupt(void);
+extern void ProcessRepackMessages(void);
+
+/* in repack_worker.c */
+extern void RepackWorkerMain(Datum main_arg);
+extern bool AmRepackWorker(void);
+
#endif /* REPACK_H */
diff --git a/src/include/commands/repack_internal.h b/src/include/commands/repack_internal.h
new file mode 100644
index 00000000000..cf495afd6a4
--- /dev/null
+++ b/src/include/commands/repack_internal.h
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * repack_internal.h
+ * header for REPACK internals
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994-5, Regents of the University of California
+ *
+ * src/include/commands/repack_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef REPACK_INTERNAL_H
+#define REPACK_INTERNAL_H
+
+#include "nodes/execnodes.h"
+#include "replication/decode.h"
+#include "postmaster/bgworker.h"
+#include "replication/logical.h"
+#include "storage/buffile.h"
+#include "storage/sharedfileset.h"
+#include "storage/shm_mq.h"
+#include "utils/resowner.h"
+
+/*
+ * Stored as a single byte in the output file.
+ */
+typedef char ConcurrentChangeKind;
+
+#define CHANGE_INSERT 'i'
+#define CHANGE_UPDATE_OLD 'u'
+#define CHANGE_UPDATE_NEW 'U'
+#define CHANGE_DELETE 'd'
+
+extern pid_t backend_pid;
+extern ProcNumber backend_proc_number;
+
+/*
+ * Logical decoding state.
+ *
+ * The output plugin uses it to store the data changes that it decodes from
+ * WAL while the table contents is being copied to a new storage.
+ */
+typedef struct RepackDecodingState
+{
+#ifdef USE_ASSERT_CHECKING
+ /* The relation whose changes we're decoding. */
+ Oid relid;
+#endif
+
+ /* Per-change memory context. */
+ MemoryContext change_cxt;
+
+ /* A tuple slot used to pass tuples back and forth */
+ TupleTableSlot *slot;
+
+ /*
+ * Memory context and resource owner of the decoding worker's transaction.
+ */
+ MemoryContext worker_cxt;
+ ResourceOwner worker_resowner;
+
+ /* The current output file. */
+ BufFile *file;
+} RepackDecodingState;
+
+/*
+ * Layout of shared memory used for communication between backend and the
+ * worker that performs logical decoding of data changes
+ */
+typedef struct DecodingWorkerShared
+{
+ /* Is the decoding initialized? */
+ bool initialized;
+
+ /*
+ * Once the worker has reached this LSN, it should close the current
+ * output file and either create a new one or exit, according to the field
+ * 'done'. If the value is InvalidXLogRecPtr, the worker should decode all
+ * the WAL available and keep checking this field. It is ok if the worker
+ * had already decoded records whose LSN is >= lsn_upto before this field
+ * has been set.
+ */
+ XLogRecPtr lsn_upto;
+
+ /* Exit after closing the current file? */
+ bool done;
+
+ /* The output is stored here. */
+ SharedFileSet sfs;
+
+ /* Number of the last file exported by the worker. */
+ int last_exported;
+
+ /* Synchronize access to the fields above. */
+ slock_t mutex;
+
+ /* Database to connect to. */
+ Oid dbid;
+
+ /* Role to connect as. */
+ Oid roleid;
+
+ /* Decode data changes of this relation. */
+ Oid relid;
+
+ /* The backend uses this to wait for the worker. */
+ ConditionVariable cv;
+
+ /* Info to signal the backend. */
+ PGPROC *backend_proc;
+ pid_t backend_pid;
+ ProcNumber backend_proc_number;
+
+ /*
+ * Memory the queue is located in.
+ *
+ * For considerations on the value see the comments of
+ * PARALLEL_ERROR_QUEUE_SIZE.
+ */
+#define REPACK_ERROR_QUEUE_SIZE 16384
+ char error_queue[FLEXIBLE_ARRAY_MEMBER];
+} DecodingWorkerShared;
+
+extern void DecodingWorkerFileName(char *fname, Oid relid, uint32 seq);
+
+
+#endif /* REPACK_INTERNAL_H */
diff --git a/src/include/replication/decode.h b/src/include/replication/decode.h
index 107e43ef750..2ef17790205 100644
--- a/src/include/replication/decode.h
+++ b/src/include/replication/decode.h
@@ -32,4 +32,8 @@ extern void logicalmsg_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf
extern void LogicalDecodingProcessRecord(LogicalDecodingContext *ctx,
XLogReaderState *record);
+/* in commands/repack_worker.c */
+extern bool change_useless_for_repack(XLogRecordBuffer *buf);
+
+
#endif
diff --git a/src/include/storage/lockdefs.h b/src/include/storage/lockdefs.h
index b73bb5618e6..3785b009808 100644
--- a/src/include/storage/lockdefs.h
+++ b/src/include/storage/lockdefs.h
@@ -36,8 +36,8 @@ typedef int LOCKMODE;
#define AccessShareLock 1 /* SELECT */
#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
-#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL), ANALYZE, CREATE
- * INDEX CONCURRENTLY */
+#define ShareUpdateExclusiveLock 4 /* VACUUM (non-exclusive), ANALYZE, CREATE
+ * INDEX CONCURRENTLY, REPACK CONCURRENTLY */
#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW
* SHARE */
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index cc4f26aa33d..c4f4a08a612 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -36,6 +36,7 @@ typedef enum
PROCSIG_BARRIER, /* global barrier interrupt */
PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
+ PROCSIG_REPACK_MESSAGE, /* Message from repack worker */
PROCSIG_RECOVERY_CONFLICT, /* backend is blocking recovery, check
* PGPROC->pendingRecoveryConflicts for the
* reason */
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index a41d781f8c9..2cd7d87c533 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -14,6 +14,8 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
ISOLATION = basic \
inplace \
+ repack \
+ repack_toast \
syscache-update-pruned \
heap_lock_update
diff --git a/src/test/modules/injection_points/expected/repack.out b/src/test/modules/injection_points/expected/repack.out
new file mode 100644
index 00000000000..b575e9052ee
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack.out
@@ -0,0 +1,113 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wait_before_lock change_existing change_new change_subxact1 change_subxact2 check2 wakeup_before_lock check1
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step wait_before_lock:
+ REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+ <waiting ...>
+step change_existing:
+ UPDATE repack_test SET i=10 where i=1;
+ UPDATE repack_test SET j=20 where i=2;
+ UPDATE repack_test SET i=30 where i=3;
+ UPDATE repack_test SET i=40 where i=30;
+ DELETE FROM repack_test WHERE i=4;
+
+step change_new:
+ INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8);
+ UPDATE repack_test SET i=50 where i=5;
+ UPDATE repack_test SET j=60 where i=6;
+ DELETE FROM repack_test WHERE i=7;
+
+step change_subxact1:
+ BEGIN;
+ INSERT INTO repack_test(i, j) VALUES (100, 100);
+ SAVEPOINT s1;
+ UPDATE repack_test SET i=101 where i=100;
+ SAVEPOINT s2;
+ UPDATE repack_test SET i=102 where i=101;
+ COMMIT;
+
+step change_subxact2:
+ BEGIN;
+ SAVEPOINT s1;
+ INSERT INTO repack_test(i, j) VALUES (110, 110);
+ ROLLBACK TO SAVEPOINT s1;
+ INSERT INTO repack_test(i, j) VALUES (110, 111);
+ COMMIT;
+
+step check2:
+ INSERT INTO relfilenodes(node)
+ SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+ SELECT i, j FROM repack_test ORDER BY i, j;
+
+ INSERT INTO data_s2(i, j)
+ SELECT i, j FROM repack_test;
+
+ i| j
+---+---
+ 2| 20
+ 6| 60
+ 8| 8
+ 10| 1
+ 40| 3
+ 50| 5
+102|100
+110|111
+(8 rows)
+
+step wakeup_before_lock:
+ SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step wait_before_lock: <... completed>
+step check1:
+ INSERT INTO relfilenodes(node)
+ SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+ SELECT count(DISTINCT node) FROM relfilenodes;
+
+ SELECT i, j FROM repack_test ORDER BY i, j;
+
+ INSERT INTO data_s1(i, j)
+ SELECT i, j FROM repack_test;
+
+ SELECT count(*)
+ FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+ WHERE d1.i ISNULL OR d2.i ISNULL;
+
+count
+-----
+ 2
+(1 row)
+
+ i| j
+---+---
+ 2| 20
+ 6| 60
+ 8| 8
+ 10| 1
+ 40| 3
+ 50| 5
+102|100
+110|111
+(8 rows)
+
+count
+-----
+ 0
+(1 row)
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
diff --git a/src/test/modules/injection_points/expected/repack_toast.out b/src/test/modules/injection_points/expected/repack_toast.out
new file mode 100644
index 00000000000..b56dde134f8
--- /dev/null
+++ b/src/test/modules/injection_points/expected/repack_toast.out
@@ -0,0 +1,65 @@
+Parsed test spec with 2 sessions
+
+starting permutation: wait_before_lock change check2 wakeup_before_lock check1
+injection_points_attach
+-----------------------
+
+(1 row)
+
+step wait_before_lock:
+ REPACK (CONCURRENTLY) repack_test;
+ <waiting ...>
+step change:
+ UPDATE repack_test SET j=get_long_string() where i=2;
+ DELETE FROM repack_test WHERE i=3;
+ INSERT INTO repack_test(i, j) VALUES (4, get_long_string());
+ UPDATE repack_test SET i=3 where i=1;
+
+step check2:
+ INSERT INTO relfilenodes(node)
+ SELECT c2.relfilenode
+ FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+ WHERE c1.relname='repack_test';
+
+ INSERT INTO data_s2(i, j)
+ SELECT i, j FROM repack_test;
+
+step wakeup_before_lock:
+ SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+
+(1 row)
+
+step wait_before_lock: <... completed>
+step check1:
+ INSERT INTO relfilenodes(node)
+ SELECT c2.relfilenode
+ FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+ WHERE c1.relname='repack_test';
+
+ SELECT count(DISTINCT node) FROM relfilenodes;
+
+ INSERT INTO data_s1(i, j)
+ SELECT i, j FROM repack_test;
+
+ SELECT count(*)
+ FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+ WHERE d1.i ISNULL OR d2.i ISNULL;
+
+count
+-----
+ 4
+(1 row)
+
+count
+-----
+ 0
+(1 row)
+
+injection_points_detach
+-----------------------
+
+(1 row)
+
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index fcc85414515..a414abb924b 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -45,6 +45,8 @@ tests += {
'specs': [
'basic',
'inplace',
+ 'repack',
+ 'repack_toast',
'syscache-update-pruned',
'heap_lock_update',
],
diff --git a/src/test/modules/injection_points/specs/repack.spec b/src/test/modules/injection_points/specs/repack.spec
new file mode 100644
index 00000000000..d727a9b056b
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack.spec
@@ -0,0 +1,142 @@
+# REPACK (CONCURRENTLY) ... USING INDEX ...;
+setup
+{
+ CREATE EXTENSION injection_points;
+
+ CREATE TABLE repack_test(i int PRIMARY KEY, j int);
+ INSERT INTO repack_test(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4);
+
+ CREATE TABLE relfilenodes(node oid);
+
+ CREATE TABLE data_s1(i int, j int);
+ CREATE TABLE data_s2(i int, j int);
+}
+
+teardown
+{
+ DROP TABLE repack_test;
+ DROP EXTENSION injection_points;
+
+ DROP TABLE relfilenodes;
+ DROP TABLE data_s1;
+ DROP TABLE data_s2;
+}
+
+session s1
+setup
+{
+ SELECT injection_points_set_local();
+ SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+# Perform the initial load and wait for s2 to do some data changes.
+step wait_before_lock
+{
+ REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey;
+}
+# Check the table from the perspective of s1.
+#
+# Besides the contents, we also check that relfilenode has changed.
+
+# Have each session write the contents into a table and use FULL JOIN to check
+# if the outputs are identical.
+step check1
+{
+ INSERT INTO relfilenodes(node)
+ SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+ SELECT count(DISTINCT node) FROM relfilenodes;
+
+ SELECT i, j FROM repack_test ORDER BY i, j;
+
+ INSERT INTO data_s1(i, j)
+ SELECT i, j FROM repack_test;
+
+ SELECT count(*)
+ FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+ WHERE d1.i ISNULL OR d2.i ISNULL;
+}
+teardown
+{
+ SELECT injection_points_detach('repack-concurrently-before-lock');
+}
+
+session s2
+# Change the existing data. UPDATE changes both key and non-key columns. Also
+# update one row twice to test whether tuple version generated by this session
+# can be found.
+step change_existing
+{
+ UPDATE repack_test SET i=10 where i=1;
+ UPDATE repack_test SET j=20 where i=2;
+ UPDATE repack_test SET i=30 where i=3;
+ UPDATE repack_test SET i=40 where i=30;
+ DELETE FROM repack_test WHERE i=4;
+}
+# Insert new rows and UPDATE / DELETE some of them. Again, update both key and
+# non-key column.
+step change_new
+{
+ INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8);
+ UPDATE repack_test SET i=50 where i=5;
+ UPDATE repack_test SET j=60 where i=6;
+ DELETE FROM repack_test WHERE i=7;
+}
+
+# When applying concurrent data changes, we should see the effects of an
+# in-progress subtransaction.
+#
+# XXX Not sure this test is useful now - it was designed for the patch that
+# preserves tuple visibility and which therefore modifies
+# TransactionIdIsCurrentTransactionId().
+step change_subxact1
+{
+ BEGIN;
+ INSERT INTO repack_test(i, j) VALUES (100, 100);
+ SAVEPOINT s1;
+ UPDATE repack_test SET i=101 where i=100;
+ SAVEPOINT s2;
+ UPDATE repack_test SET i=102 where i=101;
+ COMMIT;
+}
+
+# When applying concurrent data changes, we should not see the effects of a
+# rolled back subtransaction.
+#
+# XXX Is this test useful? See above.
+step change_subxact2
+{
+ BEGIN;
+ SAVEPOINT s1;
+ INSERT INTO repack_test(i, j) VALUES (110, 110);
+ ROLLBACK TO SAVEPOINT s1;
+ INSERT INTO repack_test(i, j) VALUES (110, 111);
+ COMMIT;
+}
+
+# Check the table from the perspective of s2.
+step check2
+{
+ INSERT INTO relfilenodes(node)
+ SELECT relfilenode FROM pg_class WHERE relname='repack_test';
+
+ SELECT i, j FROM repack_test ORDER BY i, j;
+
+ INSERT INTO data_s2(i, j)
+ SELECT i, j FROM repack_test;
+}
+step wakeup_before_lock
+{
+ SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+# Test if data changes introduced while one session is performing REPACK
+# CONCURRENTLY find their way into the table.
+permutation
+ wait_before_lock
+ change_existing
+ change_new
+ change_subxact1
+ change_subxact2
+ check2
+ wakeup_before_lock
+ check1
diff --git a/src/test/modules/injection_points/specs/repack_toast.spec b/src/test/modules/injection_points/specs/repack_toast.spec
new file mode 100644
index 00000000000..b878b198971
--- /dev/null
+++ b/src/test/modules/injection_points/specs/repack_toast.spec
@@ -0,0 +1,112 @@
+# REPACK (CONCURRENTLY);
+#
+# Test handling of TOAST. At the same time, no tuplesort.
+setup
+{
+ CREATE EXTENSION injection_points;
+
+ -- Return a string that needs to be TOASTed.
+ CREATE FUNCTION get_long_string()
+ RETURNS text
+ LANGUAGE sql as $$
+ SELECT string_agg(chr(65 + trunc(25 * random())::int), '')
+ FROM generate_series(1, 2048) s(x);
+ $$;
+
+ CREATE TABLE repack_test(i int PRIMARY KEY, j text);
+ INSERT INTO repack_test(i, j) VALUES (1, get_long_string()),
+ (2, get_long_string()), (3, get_long_string());
+
+ CREATE TABLE relfilenodes(node oid);
+
+ CREATE TABLE data_s1(i int, j text);
+ CREATE TABLE data_s2(i int, j text);
+}
+
+teardown
+{
+ DROP TABLE repack_test;
+ DROP EXTENSION injection_points;
+ DROP FUNCTION get_long_string();
+
+ DROP TABLE relfilenodes;
+ DROP TABLE data_s1;
+ DROP TABLE data_s2;
+}
+
+session s1
+setup
+{
+ SELECT injection_points_set_local();
+ SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+# Perform the initial load and wait for s2 to do some data changes.
+step wait_before_lock
+{
+ REPACK (CONCURRENTLY) repack_test;
+}
+# Check the table from the perspective of s1.
+#
+# Besides the contents, we also check that relfilenode has changed.
+
+# Have each session write the contents into a table and use FULL JOIN to check
+# if the outputs are identical.
+step check1
+{
+ INSERT INTO relfilenodes(node)
+ SELECT c2.relfilenode
+ FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+ WHERE c1.relname='repack_test';
+
+ SELECT count(DISTINCT node) FROM relfilenodes;
+
+ INSERT INTO data_s1(i, j)
+ SELECT i, j FROM repack_test;
+
+ SELECT count(*)
+ FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j)
+ WHERE d1.i ISNULL OR d2.i ISNULL;
+}
+teardown
+{
+ SELECT injection_points_detach('repack-concurrently-before-lock');
+}
+
+session s2
+step change
+# Separately test UPDATE of both plain ("i") and TOASTed ("j") attribute. In
+# the first case, the new tuple we get from reorderbuffer.c contains "j" as a
+# TOAST pointer, which we need to update so it points to the new heap. In the
+# latter case, we receive "j" as "external indirect" value - here we test that
+# the decoding worker writes the tuple to a file correctly and that the
+# backend executing REPACK manages to restore it.
+{
+ UPDATE repack_test SET j=get_long_string() where i=2;
+ DELETE FROM repack_test WHERE i=3;
+ INSERT INTO repack_test(i, j) VALUES (4, get_long_string());
+ UPDATE repack_test SET i=3 where i=1;
+}
+# Check the table from the perspective of s2.
+step check2
+{
+ INSERT INTO relfilenodes(node)
+ SELECT c2.relfilenode
+ FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid
+ WHERE c1.relname='repack_test';
+
+ INSERT INTO data_s2(i, j)
+ SELECT i, j FROM repack_test;
+}
+step wakeup_before_lock
+{
+ SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+# Test if data changes introduced while one session is performing REPACK
+# CONCURRENTLY find their way into the table.
+permutation
+ wait_before_lock
+ change
+ check2
+ wakeup_before_lock
+ check1
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 81a73c426d2..45994ff0222 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2021,7 +2021,7 @@ pg_stat_progress_cluster| SELECT pid,
phase,
repack_index_relid AS cluster_index_relid,
heap_tuples_scanned,
- heap_tuples_written,
+ (heap_tuples_inserted + heap_tuples_updated) AS heap_tuples_written,
heap_blks_total,
heap_blks_scanned,
index_rebuild_count
@@ -2136,17 +2136,20 @@ pg_stat_progress_repack| SELECT s.pid,
WHEN 2 THEN 'index scanning heap'::text
WHEN 3 THEN 'sorting tuples'::text
WHEN 4 THEN 'writing new heap'::text
- WHEN 5 THEN 'swapping relation files'::text
- WHEN 6 THEN 'rebuilding index'::text
- WHEN 7 THEN 'performing final cleanup'::text
+ WHEN 5 THEN 'catch-up'::text
+ WHEN 6 THEN 'swapping relation files'::text
+ WHEN 7 THEN 'rebuilding index'::text
+ WHEN 8 THEN 'performing final cleanup'::text
ELSE NULL::text
END AS phase,
(s.param3)::oid AS repack_index_relid,
s.param4 AS heap_tuples_scanned,
- s.param5 AS heap_tuples_written,
- s.param6 AS heap_blks_total,
- s.param7 AS heap_blks_scanned,
- s.param8 AS index_rebuild_count
+ s.param5 AS heap_tuples_inserted,
+ s.param6 AS heap_tuples_updated,
+ s.param7 AS heap_tuples_deleted,
+ s.param8 AS heap_blks_total,
+ s.param9 AS heap_blks_scanned,
+ s.param10 AS index_rebuild_count
FROM (pg_stat_get_progress_info('REPACK'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20)
LEFT JOIN pg_database d ON ((s.datid = d.oid)));
pg_stat_progress_vacuum| SELECT s.pid,
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c72f6c59573..9f9c653ff05 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -430,6 +430,7 @@ CatCacheHeader
CatalogId
CatalogIdMapEntry
CatalogIndexState
+ChangeContext
ChangeVarNodes_callback
ChangeVarNodes_context
ChannelName
@@ -509,6 +510,7 @@ CompressFileHandle
CompressionLocation
CompressorState
ComputeXidHorizonsResult
+ConcurrentChangeKind
ConditionVariable
ConditionVariableMinimallyPadded
ConditionalStack
@@ -654,6 +656,8 @@ DeclareCursorStmt
DecodedBkpBlock
DecodedXLogRecord
DecodingOutputState
+DecodingWorker
+DecodingWorkerShared
DefElem
DefElemAction
DefaultACLInfo
@@ -1317,6 +1321,7 @@ IndexElem
IndexFetchHeapData
IndexFetchTableData
IndexInfo
+IndexInsertState
IndexList
IndexOnlyScan
IndexOnlyScanState
@@ -2632,6 +2637,7 @@ ReorderBufferTupleCidKey
ReorderBufferUpdateProgressTxnCB
ReorderTuple
RepackCommand
+RepackDecodingState
RepackStmt
ReparameterizeForeignPathByChild_function
ReplOriginId
--
2.47.3
--qr3jlalmmcpkiodg
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="v53-0005-Check-for-transaction-block-early-in-ExecRepack.patch"
^ permalink raw reply [nested|flat] 32+ messages in thread
end of thread, other threads:[~2026-03-11 14:16 UTC | newest]
Thread overview: 32+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-03-31 00:43 [PATCH v6 01/10] doc: btree deduplication Justin Pryzby <[email protected]>
2022-08-05 10:25 Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-06 06:41 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 01:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 02:12 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 05:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-07 07:56 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Michael Paquier <[email protected]>
2022-08-07 09:49 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-08-07 15:52 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-08 12:40 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-08-09 07:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-20 23:00 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-20 23:06 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-23 06:16 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-23 20:24 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-24 04:14 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 15:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-26 21:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-26 21:37 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-26 21:55 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-27 05:42 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 09:30 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-27 12:03 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-27 19:01 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-28 04:43 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-28 06:11 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-29 06:02 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-29 17:27 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2022-09-30 01:15 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Thomas Munro <[email protected]>
2022-09-30 02:39 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Bharath Rupireddy <[email protected]>
2022-09-30 03:09 ` Re: Use pg_pwritev_with_retry() instead of write() in dir_open_for_write() to avoid partial writes? Nathan Bossart <[email protected]>
2026-03-11 14:16 [PATCH v53 4/7] Add CONCURRENTLY option to REPACK command. Antonin Houska <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox