public inbox for [email protected]  
help / color / mirror / Atom feed
Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
40+ messages / 7 participants
[nested] [flat]

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
@ 2022-02-15 17:09 Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2022-02-15 17:09 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-02-10 21:30:45 +0530, Bharath Rupireddy wrote:
> From 4801ff2c3b1e7bc7076205b676d4e3bc4a4ed308 Mon Sep 17 00:00:00 2001
> From: Bharath Rupireddy <[email protected]>
> Date: Thu, 10 Feb 2022 15:58:58 +0000
> Subject: [PATCH v8] Replace ReadDir with ReadDirExtended
> 
> Replace ReadDir with ReadDirExtended (in CheckPointSnapBuild) and
> get rid of lstat entirely.

I think this might be based on a slight misunderstanding / bad phrasing on my
part.  We can use get_dirent_type() to optimize away the lstat on most
platforms, ReadDirExtended itself doesn't do that automatically. I was trying
to reference removing lstat calls by using get_dirent_type() in more places...


> We still use ReadDir in CheckPointLogicalRewriteHeap
> because unable to read directory would result a NULL from
> ReadDirExtended and we may miss to fsync the remaining map files,
> so here let's error out with ReadDir.

Then why is this skipping the lstat?


> Also, convert "could not parse filename" and "could not remove file"
> errors to LOG messages in  CheckPointLogicalRewriteHeap. This will
> enable checkpoint not to waste the amount of work that it had done.

I still doubt this is a good idea.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
@ 2022-02-15 17:57 ` Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-02-15 17:57 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Feb 15, 2022 at 09:09:52AM -0800, Andres Freund wrote:
> On 2022-02-10 21:30:45 +0530, Bharath Rupireddy wrote:
>> Replace ReadDir with ReadDirExtended (in CheckPointSnapBuild) and
>> get rid of lstat entirely.
> 
> I think this might be based on a slight misunderstanding / bad phrasing on my
> part.  We can use get_dirent_type() to optimize away the lstat on most
> platforms, ReadDirExtended itself doesn't do that automatically. I was trying
> to reference removing lstat calls by using get_dirent_type() in more places...
> 
> 
>> We still use ReadDir in CheckPointLogicalRewriteHeap
>> because unable to read directory would result a NULL from
>> ReadDirExtended and we may miss to fsync the remaining map files,
>> so here let's error out with ReadDir.
> 
> Then why is this skipping the lstat?
> 
> 
>> Also, convert "could not parse filename" and "could not remove file"
>> errors to LOG messages in  CheckPointLogicalRewriteHeap. This will
>> enable checkpoint not to waste the amount of work that it had done.
> 
> I still doubt this is a good idea.

IIUC you are advocating for something more like the attached patches.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com


Attachments:

  [text/x-diff] v9-0001-make-use-of-get_dirent_type-in-replication-code.patch (3.5K, ../../20220215175752.GA2413813@nathanxps13/2-v9-0001-make-use-of-get_dirent_type-in-replication-code.patch)
  download | inline diff:
From 8dddbf0241258e7b52ab664e077193d5008ee6cd Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:40:53 -0800
Subject: [PATCH v9 1/3] make use of get_dirent_type() in replication code

---
 src/backend/access/heap/rewriteheap.c       | 4 ++--
 src/backend/replication/logical/snapbuild.c | 5 ++---
 src/backend/replication/slot.c              | 4 ++--
 3 files changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2a53826736..d64d7aae2e 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, ERROR) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 83fca8a77d..df50abfd98 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1947,15 +1948,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, ERROR) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 5da5fa825a..480a26bc7f 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1428,7 +1429,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1438,7 +1438,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, ERROR) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
-- 
2.25.1



  [text/x-diff] v9-0002-add-error-checking-for-call-to-lstat-in-replicati.patch (1.1K, ../../20220215175752.GA2413813@nathanxps13/3-v9-0002-add-error-checking-for-call-to-lstat-in-replicati.patch)
  download | inline diff:
From 1cb3fdc3e7ed323691ec37b76b95055ad9af9757 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:46:44 -0800
Subject: [PATCH v9 2/3] add error checking for call to lstat() in replication
 code

---
 src/backend/replication/logical/reorderbuffer.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index c2d9be81fa..9b9e15858e 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4812,7 +4812,11 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 	sprintf(path, "pg_replslot/%s", slotname);
 
 	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+	if (lstat(path, &statbuf) != 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m", path)));
+	else if (!S_ISDIR(statbuf.st_mode))
 		return;
 
 	spill_dir = AllocateDir(path);
-- 
2.25.1



  [text/x-diff] v9-0003-minor-improvements-to-replication-code.patch (1.7K, ../../20220215175752.GA2413813@nathanxps13/4-v9-0003-minor-improvements-to-replication-code.patch)
  download | inline diff:
From 7d2bed2e783aab2506bae27d1e4df90cc1b65dd4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:53:58 -0800
Subject: [PATCH v9 3/3] minor improvements to replication code

---
 src/backend/replication/logical/snapbuild.c | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index df50abfd98..6a60e942b7 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1965,16 +1965,10 @@ CheckPointSnapBuild(void)
 		 * everything but are postfixed by .$pid.tmp. We can just remove them
 		 * the same as other files because there can be none that are
 		 * currently being written that are older than cutoff.
-		 *
-		 * We just log a message if a file doesn't fit the pattern, it's
-		 * probably some editors lock/state file or similar...
 		 */
 		if (sscanf(snap_de->d_name, "%X-%X.snap", &hi, &lo) != 2)
-		{
-			ereport(LOG,
+			ereport(ERROR,
 					(errmsg("could not parse file name \"%s\"", path)));
-			continue;
-		}
 
 		lsn = ((uint64) hi) << 32 | lo;
 
@@ -1983,19 +1977,11 @@ CheckPointSnapBuild(void)
 		{
 			elog(DEBUG1, "removing snapbuild snapshot %s", path);
 
-			/*
-			 * It's not particularly harmful, though strange, if we can't
-			 * remove the file here. Don't prevent the checkpoint from
-			 * completing, that'd be a cure worse than the disease.
-			 */
 			if (unlink(path) < 0)
-			{
-				ereport(LOG,
+				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not remove file \"%s\": %m",
 								path)));
-				continue;
-			}
 		}
 	}
 	FreeDir(snap_dir);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-02-15 18:10   ` Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Andres Freund @ 2022-02-15 18:10 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-02-15 09:57:53 -0800, Nathan Bossart wrote:
> IIUC you are advocating for something more like the attached patches.

At least for patch 1 yes. Don't have the cycles just now to look at the
others.

I generally think it'd be a good exercise to go through an use
get_dirent_type() in nearly all ReadDir() style loops - it's a nice efficiency
win in general, and IIRC a particularly big one on windows.

It'd probably be good to add a reference to get_dirent_type() to
ReadDir[Extended]()'s docs.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
@ 2022-02-15 18:37     ` Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-02-15 18:37 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Feb 15, 2022 at 10:10:34AM -0800, Andres Freund wrote:
> I generally think it'd be a good exercise to go through an use
> get_dirent_type() in nearly all ReadDir() style loops - it's a nice efficiency
> win in general, and IIRC a particularly big one on windows.
> 
> It'd probably be good to add a reference to get_dirent_type() to
> ReadDir[Extended]()'s docs.

Agreed.  I might give this a try.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-02-15 23:11       ` Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-02-15 23:11 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Feb 15, 2022 at 10:37:58AM -0800, Nathan Bossart wrote:
> On Tue, Feb 15, 2022 at 10:10:34AM -0800, Andres Freund wrote:
>> I generally think it'd be a good exercise to go through an use
>> get_dirent_type() in nearly all ReadDir() style loops - it's a nice efficiency
>> win in general, and IIRC a particularly big one on windows.
>> 
>> It'd probably be good to add a reference to get_dirent_type() to
>> ReadDir[Extended]()'s docs.
> 
> Agreed.  I might give this a try.

Alright, here is a new patch set where I've tried to replace as many
stat()/lstat() calls as possible with get_dirent_type().  0002 and 0003 are
the same as v9.  I noticed a few remaining stat()/lstat() calls that don't
appear to be doing proper error checking, but I haven't had a chance to try
fixing those yet.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com


Attachments:

  [text/x-diff] v10-0001-make-more-use-of-get_dirent_type.patch (10.7K, ../../20220215231123.GA2584239@nathanxps13/2-v10-0001-make-more-use-of-get_dirent_type.patch)
  download | inline diff:
From 23db5cb9bf04519b9341b6195ca224e0f8bc03cb Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:40:53 -0800
Subject: [PATCH v10 1/3] make more use of get_dirent_type()

---
 src/backend/access/heap/rewriteheap.c       |  4 +-
 src/backend/replication/logical/snapbuild.c |  5 +--
 src/backend/replication/slot.c              |  4 +-
 src/backend/storage/file/copydir.c          | 21 +++--------
 src/backend/storage/file/fd.c               | 20 +++++-----
 src/backend/utils/misc/guc-file.l           | 42 ++++++++-------------
 src/timezone/pgtz.c                         |  8 +---
 7 files changed, 41 insertions(+), 63 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2a53826736..d64d7aae2e 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, ERROR) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 83fca8a77d..df50abfd98 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1947,15 +1948,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, ERROR) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 5da5fa825a..480a26bc7f 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1428,7 +1429,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1438,7 +1438,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, ERROR) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 14b77f2861..4a4a79b40a 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2783,6 +2783,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2798,6 +2802,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3234,17 +3242,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index c70543fa74..8f10252fa4 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1018,7 +1019,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1035,32 +1036,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1068,6 +1046,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.25.1



  [text/x-diff] v10-0002-add-error-checking-for-call-to-lstat-in-replicat.patch (1.1K, ../../20220215231123.GA2584239@nathanxps13/3-v10-0002-add-error-checking-for-call-to-lstat-in-replicat.patch)
  download | inline diff:
From 70c3ab552e729aeb84b006d0fdfa0df0e524b277 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:46:44 -0800
Subject: [PATCH v10 2/3] add error checking for call to lstat() in replication
 code

---
 src/backend/replication/logical/reorderbuffer.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index c2d9be81fa..9b9e15858e 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4812,7 +4812,11 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 	sprintf(path, "pg_replslot/%s", slotname);
 
 	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+	if (lstat(path, &statbuf) != 0)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not stat file \"%s\": %m", path)));
+	else if (!S_ISDIR(statbuf.st_mode))
 		return;
 
 	spill_dir = AllocateDir(path);
-- 
2.25.1



  [text/x-diff] v10-0003-minor-improvements-to-replication-code.patch (1.7K, ../../20220215231123.GA2584239@nathanxps13/4-v10-0003-minor-improvements-to-replication-code.patch)
  download | inline diff:
From d71585d0baca6c08be499fa7b6d42517e952d488 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:53:58 -0800
Subject: [PATCH v10 3/3] minor improvements to replication code

---
 src/backend/replication/logical/snapbuild.c | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)

diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index df50abfd98..6a60e942b7 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1965,16 +1965,10 @@ CheckPointSnapBuild(void)
 		 * everything but are postfixed by .$pid.tmp. We can just remove them
 		 * the same as other files because there can be none that are
 		 * currently being written that are older than cutoff.
-		 *
-		 * We just log a message if a file doesn't fit the pattern, it's
-		 * probably some editors lock/state file or similar...
 		 */
 		if (sscanf(snap_de->d_name, "%X-%X.snap", &hi, &lo) != 2)
-		{
-			ereport(LOG,
+			ereport(ERROR,
 					(errmsg("could not parse file name \"%s\"", path)));
-			continue;
-		}
 
 		lsn = ((uint64) hi) << 32 | lo;
 
@@ -1983,19 +1977,11 @@ CheckPointSnapBuild(void)
 		{
 			elog(DEBUG1, "removing snapbuild snapshot %s", path);
 
-			/*
-			 * It's not particularly harmful, though strange, if we can't
-			 * remove the file here. Don't prevent the checkpoint from
-			 * completing, that'd be a cure worse than the disease.
-			 */
 			if (unlink(path) < 0)
-			{
-				ereport(LOG,
+				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not remove file \"%s\": %m",
 								path)));
-				continue;
-			}
 		}
 	}
 	FreeDir(snap_dir);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-03-24 00:17         ` Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2022-03-24 00:17 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Feb 16, 2022 at 12:11 PM Nathan Bossart
<[email protected]> wrote:
> On Tue, Feb 15, 2022 at 10:37:58AM -0800, Nathan Bossart wrote:
> > On Tue, Feb 15, 2022 at 10:10:34AM -0800, Andres Freund wrote:
> >> I generally think it'd be a good exercise to go through an use
> >> get_dirent_type() in nearly all ReadDir() style loops - it's a nice efficiency
> >> win in general, and IIRC a particularly big one on windows.
> >>
> >> It'd probably be good to add a reference to get_dirent_type() to
> >> ReadDir[Extended]()'s docs.
> >
> > Agreed.  I might give this a try.
>
> Alright, here is a new patch set where I've tried to replace as many
> stat()/lstat() calls as possible with get_dirent_type().  0002 and 0003 are
> the same as v9.  I noticed a few remaining stat()/lstat() calls that don't
> appear to be doing proper error checking, but I haven't had a chance to try
> fixing those yet.

0001:  These get_dirent_type() conversions are nice.  LGTM.

0002:

     /* we're only handling directories here, skip if it's not ours */
-    if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+    if (lstat(path, &statbuf) != 0)
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file \"%s\": %m", path)));
+    else if (!S_ISDIR(statbuf.st_mode))
         return;

Why is this a good place to silently ignore non-directories?
StartupReorderBuffer() is already in charge of skipping random
detritus found in the directory, so would it be better to do "if
(get_dirent_type(...) != PGFILETYPE_DIR) continue" there, and then
drop the lstat() stanza from ReorderBufferCleanupSeralizedTXNs()
completely?  Then perhaps its ReadDirExtended() shoud be using ERROR
instead of INFO, so that missing/non-dir/b0rked directories raise an
error.  I don't understand why it's reporting readdir() errors at INFO
but unlink() errors at ERROR, and as far as I can see the other paths
that reach this code shouldn't be sending in paths to non-directories
here unless something is seriously busted and that's ERROR-worthy.

0003:  I haven't studied this cure vs disease thing... no opinion today.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-03-29 22:48           ` Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-03-29 22:48 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Thanks for taking a look!

On Thu, Mar 24, 2022 at 01:17:01PM +1300, Thomas Munro wrote:
>      /* we're only handling directories here, skip if it's not ours */
> -    if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
> +    if (lstat(path, &statbuf) != 0)
> +        ereport(ERROR,
> +                (errcode_for_file_access(),
> +                 errmsg("could not stat file \"%s\": %m", path)));
> +    else if (!S_ISDIR(statbuf.st_mode))
>          return;
> 
> Why is this a good place to silently ignore non-directories?
> StartupReorderBuffer() is already in charge of skipping random
> detritus found in the directory, so would it be better to do "if
> (get_dirent_type(...) != PGFILETYPE_DIR) continue" there, and then
> drop the lstat() stanza from ReorderBufferCleanupSeralizedTXNs()
> completely?  Then perhaps its ReadDirExtended() shoud be using ERROR
> instead of INFO, so that missing/non-dir/b0rked directories raise an
> error.

My guess is that this was done because ReorderBufferCleanupSerializedTXNs()
is also called from ReorderBufferAllocate() and ReorderBufferFree().
However, it is odd that we just silently return if the slot path isn't a
directory in those cases.  I think we could use get_dirent_type() in
StartupReorderBuffer() as you suggested, and then we could let ReadDir()
ERROR for non-directories for the other callers of
ReorderBufferCleanupSerializedTXNs().  WDYT?

> I don't understand why it's reporting readdir() errors at INFO
> but unlink() errors at ERROR, and as far as I can see the other paths
> that reach this code shouldn't be sending in paths to non-directories
> here unless something is seriously busted and that's ERROR-worthy.

I agree.  I'll switch it to ReadDir() in the next revision so that we ERROR
instead of INFO.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-03-30 16:21             ` Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-03-30 16:21 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Mar 29, 2022 at 03:48:32PM -0700, Nathan Bossart wrote:
> On Thu, Mar 24, 2022 at 01:17:01PM +1300, Thomas Munro wrote:
>>      /* we're only handling directories here, skip if it's not ours */
>> -    if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
>> +    if (lstat(path, &statbuf) != 0)
>> +        ereport(ERROR,
>> +                (errcode_for_file_access(),
>> +                 errmsg("could not stat file \"%s\": %m", path)));
>> +    else if (!S_ISDIR(statbuf.st_mode))
>>          return;
>> 
>> Why is this a good place to silently ignore non-directories?
>> StartupReorderBuffer() is already in charge of skipping random
>> detritus found in the directory, so would it be better to do "if
>> (get_dirent_type(...) != PGFILETYPE_DIR) continue" there, and then
>> drop the lstat() stanza from ReorderBufferCleanupSeralizedTXNs()
>> completely?  Then perhaps its ReadDirExtended() shoud be using ERROR
>> instead of INFO, so that missing/non-dir/b0rked directories raise an
>> error.
> 
> My guess is that this was done because ReorderBufferCleanupSerializedTXNs()
> is also called from ReorderBufferAllocate() and ReorderBufferFree().
> However, it is odd that we just silently return if the slot path isn't a
> directory in those cases.  I think we could use get_dirent_type() in
> StartupReorderBuffer() as you suggested, and then we could let ReadDir()
> ERROR for non-directories for the other callers of
> ReorderBufferCleanupSerializedTXNs().  WDYT?
> 
>> I don't understand why it's reporting readdir() errors at INFO
>> but unlink() errors at ERROR, and as far as I can see the other paths
>> that reach this code shouldn't be sending in paths to non-directories
>> here unless something is seriously busted and that's ERROR-worthy.
> 
> I agree.  I'll switch it to ReadDir() in the next revision so that we ERROR
> instead of INFO.

Here is an updated patch set.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com


^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-04-08 20:18               ` Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-11 20:25                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Nathan Bossart @ 2022-04-08 20:18 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Andres Freund <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Mar 30, 2022 at 09:21:30AM -0700, Nathan Bossart wrote:
> Here is an updated patch set.

rebased

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com


Attachments:

  [text/x-diff] v12-0001-make-more-use-of-get_dirent_type.patch (12.3K, ../../20220408201857.GA1586536@nathanxps13/2-v12-0001-make-more-use-of-get_dirent_type.patch)
  download | inline diff:
From a92ccf47c9c334eea5b8d07b8fcab7031181c37e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:40:53 -0800
Subject: [PATCH v12 1/2] make more use of get_dirent_type()

---
 src/backend/access/heap/rewriteheap.c         |  4 +-
 .../replication/logical/reorderbuffer.c       | 12 +++---
 src/backend/replication/logical/snapbuild.c   |  5 +--
 src/backend/replication/slot.c                |  4 +-
 src/backend/storage/file/copydir.c            | 21 +++-------
 src/backend/storage/file/fd.c                 | 20 +++++----
 src/backend/utils/misc/guc-file.l             | 42 +++++++------------
 src/timezone/pgtz.c                           |  8 +---
 8 files changed, 48 insertions(+), 68 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 2a53826736..d64d7aae2e 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, ERROR) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 5adc016d44..63ef55f3f7 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -91,6 +91,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -4408,15 +4409,10 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 {
 	DIR		   *spill_dir;
 	struct dirent *spill_de;
-	struct stat statbuf;
 	char		path[MAXPGPATH * 2 + 12];
 
 	sprintf(path, "pg_replslot/%s", slotname);
 
-	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
-		return;
-
 	spill_dir = AllocateDir(path);
 	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
 	{
@@ -4464,6 +4460,7 @@ StartupReorderBuffer(void)
 {
 	DIR		   *logical_dir;
 	struct dirent *logical_de;
+	char		path[MAXPGPATH * 2 + 12];
 
 	logical_dir = AllocateDir("pg_replslot");
 	while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
@@ -4476,6 +4473,11 @@ StartupReorderBuffer(void)
 		if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
 			continue;
 
+		/* we're only handling directories here, skip if it's not ours */
+		sprintf(path, "pg_replslot/%s", logical_de->d_name);
+		if (get_dirent_type(path, logical_de, false, ERROR) != PGFILETYPE_DIR)
+			continue;
+
 		/*
 		 * ok, has to be a surviving logical slot, iterate and delete
 		 * everything starting with xid-*
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 83fca8a77d..df50abfd98 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1947,15 +1948,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, ERROR) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c35ea7c35b..5ee68e71b8 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1485,7 +1486,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1495,7 +1495,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, ERROR) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 14b77f2861..4a4a79b40a 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2783,6 +2783,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2798,6 +2802,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3234,17 +3242,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index c70543fa74..8f10252fa4 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1018,7 +1019,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1035,32 +1036,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1068,6 +1046,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.25.1



  [text/x-diff] v12-0002-minor-improvements-to-replication-code.patch (2.4K, ../../20220408201857.GA1586536@nathanxps13/3-v12-0002-minor-improvements-to-replication-code.patch)
  download | inline diff:
From 96b3c44bf9850b106751facd1e7d2d77543fe9d4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Tue, 15 Feb 2022 09:53:58 -0800
Subject: [PATCH v12 2/2] minor improvements to replication code

---
 .../replication/logical/reorderbuffer.c        |  2 +-
 src/backend/replication/logical/snapbuild.c    | 18 ++----------------
 2 files changed, 3 insertions(+), 17 deletions(-)

diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 63ef55f3f7..532c3e4630 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4414,7 +4414,7 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 	sprintf(path, "pg_replslot/%s", slotname);
 
 	spill_dir = AllocateDir(path);
-	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
+	while ((spill_de = ReadDir(spill_dir, path)) != NULL)
 	{
 		/* only look at names that can be ours */
 		if (strncmp(spill_de->d_name, "xid", 3) == 0)
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index df50abfd98..6a60e942b7 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1965,16 +1965,10 @@ CheckPointSnapBuild(void)
 		 * everything but are postfixed by .$pid.tmp. We can just remove them
 		 * the same as other files because there can be none that are
 		 * currently being written that are older than cutoff.
-		 *
-		 * We just log a message if a file doesn't fit the pattern, it's
-		 * probably some editors lock/state file or similar...
 		 */
 		if (sscanf(snap_de->d_name, "%X-%X.snap", &hi, &lo) != 2)
-		{
-			ereport(LOG,
+			ereport(ERROR,
 					(errmsg("could not parse file name \"%s\"", path)));
-			continue;
-		}
 
 		lsn = ((uint64) hi) << 32 | lo;
 
@@ -1983,19 +1977,11 @@ CheckPointSnapBuild(void)
 		{
 			elog(DEBUG1, "removing snapbuild snapshot %s", path);
 
-			/*
-			 * It's not particularly harmful, though strange, if we can't
-			 * remove the file here. Don't prevent the checkpoint from
-			 * completing, that'd be a cure worse than the disease.
-			 */
 			if (unlink(path) < 0)
-			{
-				ereport(LOG,
+				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not remove file \"%s\": %m",
 								path)));
-				continue;
-			}
 		}
 	}
 	FreeDir(snap_dir);
-- 
2.25.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-07-08 16:09                 ` Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-07-08 16:09 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sat, Apr 9, 2022 at 1:49 AM Nathan Bossart <[email protected]> wrote:
>
> On Wed, Mar 30, 2022 at 09:21:30AM -0700, Nathan Bossart wrote:
> > Here is an updated patch set.
>
> rebased

Thanks.

0001 - there are many places where lstat/stat is being used - don't we
need to replace all or most of them with get_dirent_type?

0002 - I'm not quite happy with this patch, with the change,
checkpoint errors out, if it can't remove just a file - the comments
there says it all. Is there any strong reason for this change?

- /*
- * It's not particularly harmful, though strange, if we can't
- * remove the file here. Don't prevent the checkpoint from
- * completing, that'd be a cure worse than the disease.
- */
  if (unlink(path) < 0)
- {
- ereport(LOG,
+ ereport(ERROR,
  (errcode_for_file_access(),
  errmsg("could not remove file \"%s\": %m",
  path)));
- continue;
- }

Regards,
Bharath Rupireddy.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-07-08 17:14                   ` Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-07-08 17:14 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Jul 08, 2022 at 09:39:10PM +0530, Bharath Rupireddy wrote:
> 0001 - there are many places where lstat/stat is being used - don't we
> need to replace all or most of them with get_dirent_type?

It's been a while since I wrote this one, but I believe my intent was to
replace as many [l]stat() calls in ReadDir()-style loops as possible with
get_dirent_type().  Are there any that I've missed?

> 0002 - I'm not quite happy with this patch, with the change,
> checkpoint errors out, if it can't remove just a file - the comments
> there says it all. Is there any strong reason for this change?

Andres noted several concerns upthread.  In short, ignoring unexpected
errors makes them harder to debug and likely masks bugs.

FWIW I agree that it is unfortunate that a relatively non-critical error
here leads to checkpoint failures, which can cause much worse problems down
the road.  I think this is one of the reasons for moving tasks like this
out of the checkpointer, as I'm trying to do with the proposed custodian
process [0].

[0] https://commitfest.postgresql.org/38/3448/

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-07-18 11:23                     ` Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-07-18 11:23 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Fri, Jul 8, 2022 at 10:44 PM Nathan Bossart <[email protected]> wrote:
>
> > 0002 - I'm not quite happy with this patch, with the change,
> > checkpoint errors out, if it can't remove just a file - the comments
> > there says it all. Is there any strong reason for this change?
>
> Andres noted several concerns upthread.  In short, ignoring unexpected
> errors makes them harder to debug and likely masks bugs.
>
> FWIW I agree that it is unfortunate that a relatively non-critical error
> here leads to checkpoint failures, which can cause much worse problems down
> the road.  I think this is one of the reasons for moving tasks like this
> out of the checkpointer, as I'm trying to do with the proposed custodian
> process [0].
>
> [0] https://commitfest.postgresql.org/38/3448/

IMHO, we can keep it as-is and if required can be changed as part of
the patch set [0], as this change without [0] can cause a checkpoint
to fail. Furthermore, I would like it if we convert "could not parse
filename" and "could not remove file" ERRORs of
CheckPointLogicalRewriteHeap to LOGs until [0] gets in - others may
have different opinions though.

Just wondering - do we ever have a problem if we can't remove the
snapshot or mapping file?

May be unrelated, RemoveTempXlogFiles doesn't even bother to check if
the temp wal file is removed.

Regards,
Bharath Rupireddy.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-07-18 18:18                       ` Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-07-18 18:18 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Jul 18, 2022 at 04:53:18PM +0530, Bharath Rupireddy wrote:
> Just wondering - do we ever have a problem if we can't remove the
> snapshot or mapping file?

Besides running out of disk space, there appears to be a transaction ID
wraparound risk with the mappings files.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-09 03:10                         ` Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 04:07                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Thomas Munro @ 2022-08-09 03:10 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Working on get_dirent_type() reminded me of this thread.  I was going
to commit the first of these patches, but then I noticed Andres said
he was planning to, so I'll wait another day.  Here they are, with
commit messages but otherwise unchanged from Nathan's v12 except for a
slight comment tweak:

-               /* we're only handling directories here, skip if it's
not ours */
+               /* we're only handling directories here, skip if it's not one */

The only hunk I'm having second thoughts about is the following, which
makes unexpected stray files break checkpoints:

-                * We just log a message if a file doesn't fit the pattern, it's
-                * probably some editors lock/state file or similar...
                 */
                if (sscanf(snap_de->d_name, "%X-%X.snap", &hi, &lo) != 2)
-               {
-                       ereport(LOG,
+                       ereport(ERROR,
                                        (errmsg("could not parse file
name \"%s\"", path)));

Bharath mentioned other places that loop over stat(), but I think
those are places that want stuff we don't already have, like st_size.


Attachments:

  [text/x-patch] v13-0001-Make-more-use-of-get_dirent_type.patch (13.1K, ../../CA+hUKGKnRur_Uj6oUL1g+ffpe3-ibz0vOqYENuc0JnzWNb3htQ@mail.gmail.com/2-v13-0001-Make-more-use-of-get_dirent_type.patch)
  download | inline diff:
From f0be5188830655b9ce9375b6d13b1ca728dbfcdd Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Tue, 9 Aug 2022 13:11:08 +1200
Subject: [PATCH v13 1/2] Make more use of get_dirent_type().

We don't need to call stat() or lstat() in several ReadDir() loops, if
d_type is available, as it usually is on most systems.  If not,
get_dirent_type() will do that under the covers.

In a couple of places this will now raise an ERROR if an internal
stat/lstat fails, where previously errors were silently ignored.  ENOENT
was not expected in these locations, because the loop in question is the
one responsible for unlinking files during checkpoint, or runs at
startup.

Author: Nathan Bossart <[email protected]>
Reviewed-by: Thomas Munro <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 src/backend/access/heap/rewriteheap.c         |  4 +-
 .../replication/logical/reorderbuffer.c       | 12 +++---
 src/backend/replication/logical/snapbuild.c   |  5 +--
 src/backend/replication/slot.c                |  4 +-
 src/backend/storage/file/copydir.c            | 21 +++-------
 src/backend/storage/file/fd.c                 | 20 +++++----
 src/backend/utils/misc/guc-file.l             | 42 +++++++------------
 src/timezone/pgtz.c                           |  8 +---
 8 files changed, 48 insertions(+), 68 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 9dd885d936..525039d5b2 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, ERROR) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 88a37fde72..f2b6cdf473 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -91,6 +91,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -4407,15 +4408,10 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 {
 	DIR		   *spill_dir;
 	struct dirent *spill_de;
-	struct stat statbuf;
 	char		path[MAXPGPATH * 2 + 12];
 
 	sprintf(path, "pg_replslot/%s", slotname);
 
-	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
-		return;
-
 	spill_dir = AllocateDir(path);
 	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
 	{
@@ -4463,6 +4459,7 @@ StartupReorderBuffer(void)
 {
 	DIR		   *logical_dir;
 	struct dirent *logical_de;
+	char		path[MAXPGPATH * 2 + 12];
 
 	logical_dir = AllocateDir("pg_replslot");
 	while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
@@ -4475,6 +4472,11 @@ StartupReorderBuffer(void)
 		if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
 			continue;
 
+		/* we're only handling directories here, skip if it's not one */
+		sprintf(path, "pg_replslot/%s", logical_de->d_name);
+		if (get_dirent_type(path, logical_de, false, ERROR) != PGFILETYPE_DIR)
+			continue;
+
 		/*
 		 * ok, has to be a surviving logical slot, iterate and delete
 		 * everything starting with xid-*
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 73c0f15214..8587e2de43 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1946,15 +1947,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, ERROR) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 850b74936f..bc51e816f9 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1442,7 +1443,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1452,7 +1452,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, ERROR) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..64764a287d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2708,6 +2708,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2723,6 +2727,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3159,17 +3167,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index ce5633844c..88460422dd 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1017,7 +1018,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1034,32 +1035,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1067,6 +1045,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.35.1



  [text/x-patch] v13-0002-Harden-errors-on-some-unexpected-filesystem-cond.patch (3.0K, ../../CA+hUKGKnRur_Uj6oUL1g+ffpe3-ibz0vOqYENuc0JnzWNb3htQ@mail.gmail.com/3-v13-0002-Harden-errors-on-some-unexpected-filesystem-cond.patch)
  download | inline diff:
From 3e60b8362eaa5fc50093c3fc59bc16773dda550a Mon Sep 17 00:00:00 2001
From: Thomas Munro <[email protected]>
Date: Tue, 9 Aug 2022 14:42:10 +1200
Subject: [PATCH v13 2/2] Harden errors on some unexpected filesystem
 conditions.

CheckPointSnapBuild() shouldn't fail to unlink() files.  Raise an ERROR,
there's something wrong with the filesystem.  There is also no reason to
tolerate random stray files under pg_logical/snapshots.

ReorderBufferCleanupSerializedTXNs() shouldn't be called for a
non-directory, so raise an ERROR if opendir() fails, there's something
wrong with the filesystem.

Author: Nathan Bossart <[email protected]>
Reviewed-by: Thomas Munro <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 .../replication/logical/reorderbuffer.c        |  2 +-
 src/backend/replication/logical/snapbuild.c    | 18 ++----------------
 2 files changed, 3 insertions(+), 17 deletions(-)

diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index f2b6cdf473..a80cbacd9b 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -4413,7 +4413,7 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 	sprintf(path, "pg_replslot/%s", slotname);
 
 	spill_dir = AllocateDir(path);
-	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
+	while ((spill_de = ReadDir(spill_dir, path)) != NULL)
 	{
 		/* only look at names that can be ours */
 		if (strncmp(spill_de->d_name, "xid", 3) == 0)
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 8587e2de43..bb788e1adc 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -1964,16 +1964,10 @@ CheckPointSnapBuild(void)
 		 * everything but are postfixed by .$pid.tmp. We can just remove them
 		 * the same as other files because there can be none that are
 		 * currently being written that are older than cutoff.
-		 *
-		 * We just log a message if a file doesn't fit the pattern, it's
-		 * probably some editors lock/state file or similar...
 		 */
 		if (sscanf(snap_de->d_name, "%X-%X.snap", &hi, &lo) != 2)
-		{
-			ereport(LOG,
+			ereport(ERROR,
 					(errmsg("could not parse file name \"%s\"", path)));
-			continue;
-		}
 
 		lsn = ((uint64) hi) << 32 | lo;
 
@@ -1982,19 +1976,11 @@ CheckPointSnapBuild(void)
 		{
 			elog(DEBUG1, "removing snapbuild snapshot %s", path);
 
-			/*
-			 * It's not particularly harmful, though strange, if we can't
-			 * remove the file here. Don't prevent the checkpoint from
-			 * completing, that'd be a cure worse than the disease.
-			 */
 			if (unlink(path) < 0)
-			{
-				ereport(LOG,
+				ereport(ERROR,
 						(errcode_for_file_access(),
 						 errmsg("could not remove file \"%s\": %m",
 								path)));
-				continue;
-			}
 		}
 	}
 	FreeDir(snap_dir);
-- 
2.35.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-09 03:17                           ` Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Tom Lane @ 2022-08-09 03:17 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bharath Rupireddy <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Thomas Munro <[email protected]> writes:
> The only hunk I'm having second thoughts about is the following, which
> makes unexpected stray files break checkpoints:

Sounds like a pretty bad idea.  What's the upside?

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
@ 2022-08-09 03:27                             ` Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2022-08-09 03:27 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bharath Rupireddy <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

I wrote:
> Thomas Munro <[email protected]> writes:
>> The only hunk I'm having second thoughts about is the following, which
>> makes unexpected stray files break checkpoints:

> Sounds like a pretty bad idea.  What's the upside?

Actually, having now read the patch, I don't think there is any
part of 0002 that is a good idea.  It's blithely removing the
comments that explain why the existing coding is the way it is,
and not providing a shred of justification for making checkpoints
more brittle.

I have not tried to analyze the error-handling properties of 0001,
but if it's being equally cavalier then it shouldn't be committed
either.  Most of this behavior is the result of decades of hard-won
experience; discarding it because it doesn't fit conveniently
into some refactoring plan isn't smart.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
@ 2022-08-09 03:41                               ` Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2022-08-09 03:41 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bharath Rupireddy <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 9, 2022 at 3:27 PM Tom Lane <[email protected]> wrote:
> Actually, having now read the patch, I don't think there is any
> part of 0002 that is a good idea.  It's blithely removing the
> comments that explain why the existing coding is the way it is,
> and not providing a shred of justification for making checkpoints
> more brittle.

0002 also contradicts the original $SUBJECT and goal of this thread,
which is possibly why it was kept separate.  I was only thinking of
committing 0001 myself, which is the one I'd reviewed an earlier
version of.

> I have not tried to analyze the error-handling properties of 0001,
> but if it's being equally cavalier then it shouldn't be committed
> either.  Most of this behavior is the result of decades of hard-won
> experience; discarding it because it doesn't fit conveniently
> into some refactoring plan isn't smart.

0001 does introduce new errors, as mentioned in the commit message, in
the form of elevel ERROR passed into get_dirent_type(), which might be
thrown if your OS has no d_type and lstat() fails (also if you asked
to follow symlinks, but in those cases errors were already thrown).
But in those cases, it seems at least a little fishy that we ignored
errors from the existing lstat().  I wondered if that was because they
expected that any failure meant ENOENT and they wanted to tolerate
that, but that does not seem to be the case, so I considered the error
to be an improvement.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-09 03:50                                 ` Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Tom Lane @ 2022-08-09 03:50 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bharath Rupireddy <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Thomas Munro <[email protected]> writes:
> On Tue, Aug 9, 2022 at 3:27 PM Tom Lane <[email protected]> wrote:
>> I have not tried to analyze the error-handling properties of 0001,
>> but if it's being equally cavalier then it shouldn't be committed
>> either.

> 0001 does introduce new errors, as mentioned in the commit message, in
> the form of elevel ERROR passed into get_dirent_type(), which might be
> thrown if your OS has no d_type and lstat() fails (also if you asked
> to follow symlinks, but in those cases errors were already thrown).
> But in those cases, it seems at least a little fishy that we ignored
> errors from the existing lstat().

Hmmm ... I'll grant that ignoring lstat errors altogether isn't great.
But should the replacement behavior be elog-LOG-and-press-on,
or elog-ERROR-and-fail-the-surrounding-operation?  I'm not in any
hurry to believe that the latter is more appropriate without some
analysis of what the callers are doing.

The bottom line here is that I'm distrustful of behavioral changes
introduced to simplify refactoring rather than to solve a live
problem.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
@ 2022-08-09 03:59                                   ` Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-09 03:59 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: Thomas Munro <[email protected]>; Nathan Bossart <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 9, 2022 at 9:20 AM Tom Lane <[email protected]> wrote:
>
> Thomas Munro <[email protected]> writes:
> > On Tue, Aug 9, 2022 at 3:27 PM Tom Lane <[email protected]> wrote:
> >> I have not tried to analyze the error-handling properties of 0001,
> >> but if it's being equally cavalier then it shouldn't be committed
> >> either.
>
> > 0001 does introduce new errors, as mentioned in the commit message, in
> > the form of elevel ERROR passed into get_dirent_type(), which might be
> > thrown if your OS has no d_type and lstat() fails (also if you asked
> > to follow symlinks, but in those cases errors were already thrown).
> > But in those cases, it seems at least a little fishy that we ignored
> > errors from the existing lstat().
>
> Hmmm ... I'll grant that ignoring lstat errors altogether isn't great.
> But should the replacement behavior be elog-LOG-and-press-on,
> or elog-ERROR-and-fail-the-surrounding-operation?  I'm not in any
> hurry to believe that the latter is more appropriate without some
> analysis of what the callers are doing.
>
> The bottom line here is that I'm distrustful of behavioral changes
> introduced to simplify refactoring rather than to solve a live
> problem.

+1. I agree with Tom not to change elog-LOG to elog-ERROR and fail the
checkpoint operation. Because the checkpoint is more important than
why a single snapshot file (out thousands or even million files) isn't
removed at that moment. Also, I originally proposed to change
elog-ERROR to elog-LOG in CheckPointLogicalRewriteHeap for unlink()
failures for the same reason.

--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-09 04:28                                     ` Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-08-09 04:28 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Tom Lane <[email protected]>; Thomas Munro <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 09, 2022 at 09:29:16AM +0530, Bharath Rupireddy wrote:
> On Tue, Aug 9, 2022 at 9:20 AM Tom Lane <[email protected]> wrote:
>> Hmmm ... I'll grant that ignoring lstat errors altogether isn't great.
>> But should the replacement behavior be elog-LOG-and-press-on,
>> or elog-ERROR-and-fail-the-surrounding-operation?  I'm not in any
>> hurry to believe that the latter is more appropriate without some
>> analysis of what the callers are doing.
>>
>> The bottom line here is that I'm distrustful of behavioral changes
>> introduced to simplify refactoring rather than to solve a live
>> problem.
> 
> +1. I agree with Tom not to change elog-LOG to elog-ERROR and fail the
> checkpoint operation. Because the checkpoint is more important than
> why a single snapshot file (out thousands or even million files) isn't
> removed at that moment. Also, I originally proposed to change
> elog-ERROR to elog-LOG in CheckPointLogicalRewriteHeap for unlink()
> failures for the same reason.

This was my initial instinct as well, but this thread has received
contradictory feedback during the months since.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-09 05:26                                       ` Thomas Munro <[email protected]>
  2022-08-09 18:38                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Thomas Munro @ 2022-08-09 05:26 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 9, 2022 at 4:28 PM Nathan Bossart <[email protected]> wrote:
> On Tue, Aug 09, 2022 at 09:29:16AM +0530, Bharath Rupireddy wrote:
> > On Tue, Aug 9, 2022 at 9:20 AM Tom Lane <[email protected]> wrote:
> >> Hmmm ... I'll grant that ignoring lstat errors altogether isn't great.
> >> But should the replacement behavior be elog-LOG-and-press-on,
> >> or elog-ERROR-and-fail-the-surrounding-operation?  I'm not in any
> >> hurry to believe that the latter is more appropriate without some
> >> analysis of what the callers are doing.
> >>
> >> The bottom line here is that I'm distrustful of behavioral changes
> >> introduced to simplify refactoring rather than to solve a live
> >> problem.
> >
> > +1. I agree with Tom not to change elog-LOG to elog-ERROR and fail the
> > checkpoint operation. Because the checkpoint is more important than

The changes were not from elog-LOG to elog-ERROR, they were changes
from silently eating errors, but I take your (plural) general point.

> > why a single snapshot file (out thousands or even million files) isn't
> > removed at that moment. Also, I originally proposed to change
> > elog-ERROR to elog-LOG in CheckPointLogicalRewriteHeap for unlink()
> > failures for the same reason.
>
> This was my initial instinct as well, but this thread has received
> contradictory feedback during the months since.

OK, so there aren't many places in 0001 that error out where we
previously did not.

For the one in CheckPointLogicalRewriteHeap(), if it fails, you don't
just want to skip -- it might be in the range that we need to fsync().
So what if we just deleted that get_dirent_type() != PGFILETYPE_REG
check altogether?  Depending on the name range check, we'd either
attempt to unlink() and LOG if that fails, or we'd attempt to
open()-or-ERROR and then fsync()-or-PANIC.  What functionality have we
lost without the DT_REG check?  Well, now if someone ill-advisedly
puts an important character device, socket, symlink (ie any kind of
non-DT_REG) into that directory with a name conforming to the
unlinkable range, we'll try to unlink it and possibly succeed, where
previously we'd have skipped, and if it's in the checkpointable range,
we'd try to fsync() it and likely fail, which is good because this is
a supposed to be a checkpoint.

That's already what happens if lstat() fails in master.  We fall back
to treating it like a DT_REG anyway:

               if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
                        continue;

For the one in CheckSnapBuild(), it's similar but unlink only.

For the one in StartupReplicationSlots(), you could possibly take the
same line: if it's not a directory, we'll try an rmdir() it anyway and
then emit a WARNING if that fails.  Again, that's exactly what master
would do if that lstat() failed.

In other words, if we're going to LOG and press on, maybe we should
just press on anyway.  Would the error message be any less
informative?  Would the risk of unlinking a character device that you
accidentally stored in
/clusters/pg16-main/pgdata/pg_logical/mappings/map-1234-5678 be a
problem for anyone?

Separately from that topic, it would be nice to have a comment in each
place where we tolerate unlink() failure to explain why that is
harmless except for wasted disk.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-09 18:38                                         ` Nathan Bossart <[email protected]>
  1 sibling, 0 replies; 40+ messages in thread

From: Nathan Bossart @ 2022-08-09 18:38 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 09, 2022 at 05:26:39PM +1200, Thomas Munro wrote:
> The changes were not from elog-LOG to elog-ERROR, they were changes
> from silently eating errors, but I take your (plural) general point.

I think this was in reference to 0002.  My comment was, at least.

> OK, so there aren't many places in 0001 that error out where we
> previously did not.
> 
> For the one in CheckPointLogicalRewriteHeap(), if it fails, you don't
> just want to skip -- it might be in the range that we need to fsync().
> So what if we just deleted that get_dirent_type() != PGFILETYPE_REG
> check altogether?  Depending on the name range check, we'd either
> attempt to unlink() and LOG if that fails, or we'd attempt to
> open()-or-ERROR and then fsync()-or-PANIC.

It looks like CheckPointLogicalRewriteHeap() presently ERRORs when unlink()
fails.  However, CheckPointSnapBuild() only LOGs when unlink() fails.
Since mappings files contain transaction IDs, persistent unlink() failures
might lead to wraparound, but I don't think failing checkpoints will help
improve matters.

Bharath's original patch changed CheckPointLogicalRewriteHeap() to LOG on
sscanf() and unlink() failures.  IIUC things would work the way you suggest
if that was applied.

> What functionality have we
> lost without the DT_REG check?  Well, now if someone ill-advisedly
> puts an important character device, socket, symlink (ie any kind of
> non-DT_REG) into that directory with a name conforming to the
> unlinkable range, we'll try to unlink it and possibly succeed, where
> previously we'd have skipped, and if it's in the checkpointable range,
> we'd try to fsync() it and likely fail, which is good because this is
> a supposed to be a checkpoint.

I don't know that I agree that the last part is good.  Presumably we don't
want checkpointing to fail forever because there's a random non-regular
file that can't be fsync'd.  But it's not clear why such files would exist
in the first place.  Presumably this isn't the sort of thing that Postgres
should be expected to support.

> In other words, if we're going to LOG and press on, maybe we should
> just press on anyway.  Would the error message be any less
> informative?  Would the risk of unlinking a character device that you
> accidentally stored in
> /clusters/pg16-main/pgdata/pg_logical/mappings/map-1234-5678 be a
> problem for anyone?

Probably not.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-10 07:14                                         ` Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-10 07:14 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 9, 2022 at 10:57 AM Thomas Munro <[email protected]> wrote:
>
> OK, so there aren't many places in 0001 that error out where we
> previously did not.

Well, that's not true I believe. The 0001 patch introduces
get_dirent_type() with elevel ERROR which means that lstat failures
are now reported as ERRORs with ereport(ERROR, as opposed to
continuing on the HEAD.

-        if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+        if (get_dirent_type(path, mapping_de, false, ERROR) != PGFILETYPE_REG)
             continue;

-        if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+        if (get_dirent_type(path, snap_de, false, ERROR) != PGFILETYPE_REG)
         {

so on.

The main idea of using get_dirent_type() instead of lstat or stat is
to benefit from avoiding system calls on platforms where the directory
entry type is stored in dirent structure, but not to cause errors for
lstat or stat system calls failures where we previously didn't. I
think 0001 must be just replacing lstat or stat with get_dirent_type()
with elevel ERROR if lstat or stat failure is treated as ERROR
previously, otherwise with elevel LOG. I modified it as attached. Once
this patch gets committed, then we can continue the discussion as to
whether we make elog-LOG to elog-ERROR or vice-versa.

I'm tempted to use get_dirent_type() in RemoveXlogFile() but that
requires us to pass struct dirent * from RemoveOldXlogFiles() (as
attached 0002 for now), If done, this avoids one lstat() system call
per WAL file. IMO, that's okay to pass an extra function parameter to
RemoveXlogFile() as it is a static function and there can be many
(thousands of) WAL files at times, so saving system calls (lstat() *
number of WAL files) is definitely an advantage.

Thoughts?

--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/


Attachments:

  [application/octet-stream] v14-0001-Make-more-use-of-get_dirent_type.patch (13.1K, ../../CALj2ACVd0ukptEi5pFyeEx-nw_C9jTRvtQJvQpm0BUhOCvrH0g@mail.gmail.com/2-v14-0001-Make-more-use-of-get_dirent_type.patch)
  download | inline diff:
From 4665da8af8af1d0eefeacf3626d3651443323b12 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Wed, 10 Aug 2022 07:13:02 +0000
Subject: [PATCH v14] Make more use of get_dirent_type().

We don't need to call stat() or lstat() in several ReadDir() loops, if
d_type is available, as it usually is on most systems.  If not,
get_dirent_type() will do that under the covers.

In a couple of places this will now raise an ERROR if an internal
stat/lstat fails, where previously errors were silently ignored.  ENOENT
was not expected in these locations, because the loop in question is the
one responsible for unlinking files during checkpoint, or runs at
startup.

Author: Nathan Bossart <[email protected]>
Reviewed-by: Thomas Munro <[email protected]>
Reviewed-by: Bharath Rupireddy <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 src/backend/access/heap/rewriteheap.c         |  4 +-
 .../replication/logical/reorderbuffer.c       | 12 +++---
 src/backend/replication/logical/snapbuild.c   |  5 +--
 src/backend/replication/slot.c                |  4 +-
 src/backend/storage/file/copydir.c            | 21 +++-------
 src/backend/storage/file/fd.c                 | 20 +++++----
 src/backend/utils/misc/guc-file.l             | 42 +++++++------------
 src/timezone/pgtz.c                           |  8 +---
 8 files changed, 48 insertions(+), 68 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 9dd885d936..a00874f0f7 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, LOG) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 88a37fde72..c37e970de5 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -91,6 +91,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -4407,15 +4408,10 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 {
 	DIR		   *spill_dir;
 	struct dirent *spill_de;
-	struct stat statbuf;
 	char		path[MAXPGPATH * 2 + 12];
 
 	sprintf(path, "pg_replslot/%s", slotname);
 
-	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
-		return;
-
 	spill_dir = AllocateDir(path);
 	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
 	{
@@ -4463,6 +4459,7 @@ StartupReorderBuffer(void)
 {
 	DIR		   *logical_dir;
 	struct dirent *logical_de;
+	char		path[MAXPGPATH * 2 + 12];
 
 	logical_dir = AllocateDir("pg_replslot");
 	while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
@@ -4475,6 +4472,11 @@ StartupReorderBuffer(void)
 		if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
 			continue;
 
+		/* we're only handling directories here, skip if it's not one */
+		sprintf(path, "pg_replslot/%s", logical_de->d_name);
+		if (get_dirent_type(path, logical_de, false, LOG) != PGFILETYPE_DIR)
+			continue;
+
 		/*
 		 * ok, has to be a surviving logical slot, iterate and delete
 		 * everything starting with xid-*
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 73c0f15214..5ba185fa0d 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1946,15 +1947,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, LOG) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 850b74936f..dffd87b335 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1442,7 +1443,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1452,7 +1452,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, LOG) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..64764a287d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2708,6 +2708,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2723,6 +2727,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3159,17 +3167,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index ce5633844c..88460422dd 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1017,7 +1018,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1034,32 +1035,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1067,6 +1045,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.34.1



  [application/octet-stream] v14-0002-Replace-lstat-with-get_dirent_type-in-xlog.c.patch (2.9K, ../../CALj2ACVd0ukptEi5pFyeEx-nw_C9jTRvtQJvQpm0BUhOCvrH0g@mail.gmail.com/3-v14-0002-Replace-lstat-with-get_dirent_type-in-xlog.c.patch)
  download | inline diff:
From a09666d6ca3d3071b16832033b3deec6a04fd75c Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Wed, 10 Aug 2022 07:12:15 +0000
Subject: [PATCH v14] Replace lstat() with get_dirent_type() in xlog.c

---
 src/backend/access/transam/xlog.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f0150d1e..2b39b6f3a9 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -657,8 +657,9 @@ static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
 static void RemoveTempXlogFiles(void);
 static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
 							   XLogRecPtr endptr, TimeLineID insertTLI);
-static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-						   XLogSegNo *endlogSegNo, TimeLineID insertTLI);
+static void RemoveXlogFile(const char *segname, const struct dirent *xlde,
+						   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+						   TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
@@ -3597,7 +3598,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 				/* Update the last removed location in shared memory first */
 				UpdateLastRemovedPtr(xlde->d_name);
 
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endlogSegNo,
 							   insertTLI);
 			}
 		}
@@ -3670,7 +3671,7 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
 			 * - but seems safer to let them be archived and removed later.
 			 */
 			if (!XLogArchiveIsReady(xlde->d_name))
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endLogSegNo,
 							   newTLI);
 		}
 	}
@@ -3692,14 +3693,14 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
  * should be used for this timeline.
  */
 static void
-RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-			   XLogSegNo *endlogSegNo, TimeLineID insertTLI)
+RemoveXlogFile(const char *segname, const struct dirent *xlde,
+			   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+			   TimeLineID insertTLI)
 {
 	char		path[MAXPGPATH];
 #ifdef WIN32
 	char		newpath[MAXPGPATH];
 #endif
-	struct stat statbuf;
 
 	snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);
 
@@ -3711,7 +3712,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 	if (wal_recycle &&
 		*endlogSegNo <= recycleSegNo &&
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
-		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
+		get_dirent_type(path, xlde, false, LOG) != PGFILETYPE_REG &&
 		InstallXLogFileSegment(endlogSegNo, path,
 							   true, recycleSegNo, insertTLI))
 	{
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-10 08:41                                           ` Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Thomas Munro @ 2022-08-10 08:41 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 10, 2022 at 7:15 PM Bharath Rupireddy
<[email protected]> wrote:
> The main idea of using get_dirent_type() instead of lstat or stat is
> to benefit from avoiding system calls on platforms where the directory
> entry type is stored in dirent structure, but not to cause errors for
> lstat or stat system calls failures where we previously didn't.

Yeah, I get it... I'm OK with separating mechanical changes like
lstat() -> get_dirent_type() from policy changes on error handling.  I
initially thought the ERROR was surely better than what we're doing
now (making extra system calls to avoid unlinking unexpected things,
for which our only strategy on failure is to try to unlink the thing
anyway), but it's better to discuss that separately.

> I
> think 0001 must be just replacing lstat or stat with get_dirent_type()
> with elevel ERROR if lstat or stat failure is treated as ERROR
> previously, otherwise with elevel LOG. I modified it as attached. Once
> this patch gets committed, then we can continue the discussion as to
> whether we make elog-LOG to elog-ERROR or vice-versa.

Agreed, will look at this version tomorrow.

> I'm tempted to use get_dirent_type() in RemoveXlogFile() but that
> requires us to pass struct dirent * from RemoveOldXlogFiles() (as
> attached 0002 for now), If done, this avoids one lstat() system call
> per WAL file. IMO, that's okay to pass an extra function parameter to
> RemoveXlogFile() as it is a static function and there can be many
> (thousands of) WAL files at times, so saving system calls (lstat() *
> number of WAL files) is definitely an advantage.

-        lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
+        get_dirent_type(path, xlde, false, LOG) != PGFILETYPE_REG &&

I think you wanted ==, but maybe it'd be nicer to pass in a
"recyclable" boolean to RemoveXlogFile()?

If you're looking for more, rmtree() looks like another.





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-10 09:58                                             ` Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-10 09:58 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 10, 2022 at 2:11 PM Thomas Munro <[email protected]> wrote:
>
> On Wed, Aug 10, 2022 at 7:15 PM Bharath Rupireddy
> <[email protected]> wrote:
> > The main idea of using get_dirent_type() instead of lstat or stat is
> > to benefit from avoiding system calls on platforms where the directory
> > entry type is stored in dirent structure, but not to cause errors for
> > lstat or stat system calls failures where we previously didn't.
>
> Yeah, I get it... I'm OK with separating mechanical changes like
> lstat() -> get_dirent_type() from policy changes on error handling.  I
> initially thought the ERROR was surely better than what we're doing
> now (making extra system calls to avoid unlinking unexpected things,
> for which our only strategy on failure is to try to unlink the thing
> anyway), but it's better to discuss that separately.
>
> > I
> > think 0001 must be just replacing lstat or stat with get_dirent_type()
> > with elevel ERROR if lstat or stat failure is treated as ERROR
> > previously, otherwise with elevel LOG. I modified it as attached. Once
> > this patch gets committed, then we can continue the discussion as to
> > whether we make elog-LOG to elog-ERROR or vice-versa.
>
> Agreed, will look at this version tomorrow.

Thanks.

> > I'm tempted to use get_dirent_type() in RemoveXlogFile() but that
> > requires us to pass struct dirent * from RemoveOldXlogFiles() (as
> > attached 0002 for now), If done, this avoids one lstat() system call
> > per WAL file. IMO, that's okay to pass an extra function parameter to
> > RemoveXlogFile() as it is a static function and there can be many
> > (thousands of) WAL files at times, so saving system calls (lstat() *
> > number of WAL files) is definitely an advantage.
>
> -        lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
> +        get_dirent_type(path, xlde, false, LOG) != PGFILETYPE_REG &&
>
> I think you wanted ==, but maybe it'd be nicer to pass in a
> "recyclable" boolean to RemoveXlogFile()?

Ah, my bad, I corrected it now.

If you mean to do "bool recyclable = (get_dirent_type(path, xlde,
false, LOG) == PGFILETYPE_REG)"" and pass it as parameter to
RemoveXlogFile(), then we have to do get_dirent_type calls for every
WAL file even when any of (wal_recycle && *endlogSegNo <= recycleSegNo
&& XLogCtl->InstallXLogFileSegmentActive) is false which is
unnecessary and it's better to pass dirent structure as a parameter.

> If you're looking for more, rmtree() looks like another.

Are you suggesting to not use pgfnames() in rmtree() and do
opendir()-readdir()-get_dirent_type() directly? If yes, I don't think
it's a great idea because rmtree() has recursive calls and holding
opendir()-readdir() for all rmtree() recursive calls without
closedir() may eat up dynamic memory if there are deeper level
directories. I would like to leave it that way. Do you have any other
ideas in mind?

Please find the v15 patch, I merged the RemoveXlogFile() changes into 0001.

--
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/


Attachments:

  [application/octet-stream] v15-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch (15.5K, ../../CALj2ACWSjE8AiJ4xaBS4ULQ8T6RevwsNhTD6yPBLugJBTD5LCw@mail.gmail.com/2-v15-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch)
  download | inline diff:
From a6bd634321d9b42714f4e24105fe4662d45844a4 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Wed, 10 Aug 2022 09:36:07 +0000
Subject: [PATCH v15] Make more use of get_dirent_type() in place of stat() or
 lstat().

We don't need to call stat() or lstat() in several ReadDir() loops, if
d_type is available, as it usually is on most systems.  If not,
get_dirent_type() will do that under the covers.

Authors: Nathan Bossart <[email protected]>, Bharath Rupireddy <[email protected]>
Reviewed-by: Thomas Munro <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 src/backend/access/heap/rewriteheap.c         |  4 +-
 src/backend/access/transam/xlog.c             | 17 ++++----
 .../replication/logical/reorderbuffer.c       | 12 +++---
 src/backend/replication/logical/snapbuild.c   |  5 +--
 src/backend/replication/slot.c                |  4 +-
 src/backend/storage/file/copydir.c            | 21 +++-------
 src/backend/storage/file/fd.c                 | 20 +++++----
 src/backend/utils/misc/guc-file.l             | 42 +++++++------------
 src/timezone/pgtz.c                           |  8 +---
 9 files changed, 57 insertions(+), 76 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 9dd885d936..a00874f0f7 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1227,7 +1227,7 @@ CheckPointLogicalRewriteHeap(void)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, mapping_de, false, LOG) != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 34f0150d1e..073c6f5378 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -657,8 +657,9 @@ static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
 static void RemoveTempXlogFiles(void);
 static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
 							   XLogRecPtr endptr, TimeLineID insertTLI);
-static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-						   XLogSegNo *endlogSegNo, TimeLineID insertTLI);
+static void RemoveXlogFile(const char *segname, const struct dirent *xlde,
+						   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+						   TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
@@ -3597,7 +3598,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 				/* Update the last removed location in shared memory first */
 				UpdateLastRemovedPtr(xlde->d_name);
 
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endlogSegNo,
 							   insertTLI);
 			}
 		}
@@ -3670,7 +3671,7 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
 			 * - but seems safer to let them be archived and removed later.
 			 */
 			if (!XLogArchiveIsReady(xlde->d_name))
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endLogSegNo,
 							   newTLI);
 		}
 	}
@@ -3692,14 +3693,14 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
  * should be used for this timeline.
  */
 static void
-RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-			   XLogSegNo *endlogSegNo, TimeLineID insertTLI)
+RemoveXlogFile(const char *segname, const struct dirent *xlde,
+			   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+			   TimeLineID insertTLI)
 {
 	char		path[MAXPGPATH];
 #ifdef WIN32
 	char		newpath[MAXPGPATH];
 #endif
-	struct stat statbuf;
 
 	snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);
 
@@ -3711,7 +3712,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 	if (wal_recycle &&
 		*endlogSegNo <= recycleSegNo &&
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
-		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
+		get_dirent_type(path, xlde, false, LOG) == PGFILETYPE_REG &&
 		InstallXLogFileSegment(endlogSegNo, path,
 							   true, recycleSegNo, insertTLI))
 	{
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 88a37fde72..c37e970de5 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -91,6 +91,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -4407,15 +4408,10 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 {
 	DIR		   *spill_dir;
 	struct dirent *spill_de;
-	struct stat statbuf;
 	char		path[MAXPGPATH * 2 + 12];
 
 	sprintf(path, "pg_replslot/%s", slotname);
 
-	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
-		return;
-
 	spill_dir = AllocateDir(path);
 	while ((spill_de = ReadDirExtended(spill_dir, path, INFO)) != NULL)
 	{
@@ -4463,6 +4459,7 @@ StartupReorderBuffer(void)
 {
 	DIR		   *logical_dir;
 	struct dirent *logical_de;
+	char		path[MAXPGPATH * 2 + 12];
 
 	logical_dir = AllocateDir("pg_replslot");
 	while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
@@ -4475,6 +4472,11 @@ StartupReorderBuffer(void)
 		if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
 			continue;
 
+		/* we're only handling directories here, skip if it's not one */
+		sprintf(path, "pg_replslot/%s", logical_de->d_name);
+		if (get_dirent_type(path, logical_de, false, LOG) != PGFILETYPE_DIR)
+			continue;
+
 		/*
 		 * ok, has to be a surviving logical slot, iterate and delete
 		 * everything starting with xid-*
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 73c0f15214..5ba185fa0d 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -1946,15 +1947,13 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
-
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (get_dirent_type(path, snap_de, false, LOG) != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 850b74936f..dffd87b335 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1442,7 +1443,6 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
@@ -1452,7 +1452,7 @@ StartupReplicationSlots(void)
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(path, replication_de, false, LOG) != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index efb34d4dcb..64764a287d 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2708,6 +2708,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2723,6 +2727,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3159,17 +3167,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index ce5633844c..88460422dd 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1017,7 +1018,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1034,32 +1035,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1067,6 +1045,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-16 20:32                                               ` Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-08-16 20:32 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 10, 2022 at 03:28:25PM +0530, Bharath Rupireddy wrote:
>  		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
> -		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
> +		if (get_dirent_type(path, mapping_de, false, LOG) != PGFILETYPE_REG)
>  			continue;

Previously, failure to lstat() wouldn't lead to skipping the entry.  With
this patch, a failure to determine the file type will cause the entry to be
skipped.  This might be okay in some places (e.g., CheckPointSnapBuild())
but not in others.  For example, in CheckPointLogicalRewriteHeap(), this
could cause us to skip fsync-ing a file due to a get_dirent_type() failure,
which seems bad.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-17 07:09                                                 ` Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-17 07:09 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 17, 2022 at 2:02 AM Nathan Bossart <[email protected]> wrote:
>
> On Wed, Aug 10, 2022 at 03:28:25PM +0530, Bharath Rupireddy wrote:
> >               snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
> > -             if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
> > +             if (get_dirent_type(path, mapping_de, false, LOG) != PGFILETYPE_REG)
> >                       continue;
>
> Previously, failure to lstat() wouldn't lead to skipping the entry.  With
> this patch, a failure to determine the file type will cause the entry to be
> skipped.  This might be okay in some places (e.g., CheckPointSnapBuild())
> but not in others.  For example, in CheckPointLogicalRewriteHeap(), this
> could cause us to skip fsync-ing a file due to a get_dirent_type() failure,
> which seems bad.

Hm. I corrected it in the v16 patch, please review.

-- 
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/


Attachments:

  [application/octet-stream] v16-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch (17.6K, ../../CALj2ACXZPMYXrPZ+CUE0_5DKDnxyqDGRftSgPPx--PWhWB3RtQ@mail.gmail.com/2-v16-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch)
  download | inline diff:
From d5dd101c7c360706fb28f977703ceb1a3aadf201 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Wed, 17 Aug 2022 07:05:01 +0000
Subject: [PATCH v16] Make more use of get_dirent_type() in place of stat() or
 lstat()

We don't need to call stat() or lstat() in several ReadDir() loops, if
d_type is available, as it usually is on most systems.  If not,
get_dirent_type() will do that under the covers.

Authors: Nathan Bossart, Bharath Rupireddy
Reviewed-by: Thomas Munro
Reviewed-by: Andres Freund
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 src/backend/access/heap/rewriteheap.c         |  7 +++-
 src/backend/access/transam/xlog.c             | 17 ++++----
 .../replication/logical/reorderbuffer.c       | 28 +++++++++----
 src/backend/replication/logical/snapbuild.c   |  6 ++-
 src/backend/replication/slot.c                |  6 ++-
 src/backend/storage/file/copydir.c            | 21 +++-------
 src/backend/storage/file/fd.c                 | 20 +++++----
 src/backend/utils/misc/guc-file.l             | 42 +++++++------------
 src/timezone/pgtz.c                           |  8 +---
 9 files changed, 78 insertions(+), 77 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 9dd885d936..5b20d1e1d5 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1221,13 +1221,16 @@ CheckPointLogicalRewriteHeap(void)
 		TransactionId create_xid;
 		uint32		hi,
 					lo;
+		PGFileType	de_type;
 
 		if (strcmp(mapping_de->d_name, ".") == 0 ||
 			strcmp(mapping_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		de_type = get_dirent_type(path, mapping_de, false, LOG);
+
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 87b243e0d4..16363a8f84 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -657,8 +657,9 @@ static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
 static void RemoveTempXlogFiles(void);
 static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
 							   XLogRecPtr endptr, TimeLineID insertTLI);
-static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-						   XLogSegNo *endlogSegNo, TimeLineID insertTLI);
+static void RemoveXlogFile(const char *segname, const struct dirent *xlde,
+						   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+						   TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
@@ -3597,7 +3598,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 				/* Update the last removed location in shared memory first */
 				UpdateLastRemovedPtr(xlde->d_name);
 
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endlogSegNo,
 							   insertTLI);
 			}
 		}
@@ -3670,7 +3671,7 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
 			 * - but seems safer to let them be archived and removed later.
 			 */
 			if (!XLogArchiveIsReady(xlde->d_name))
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endLogSegNo,
 							   newTLI);
 		}
 	}
@@ -3692,14 +3693,14 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
  * should be used for this timeline.
  */
 static void
-RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-			   XLogSegNo *endlogSegNo, TimeLineID insertTLI)
+RemoveXlogFile(const char *segname, const struct dirent *xlde,
+			   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+			   TimeLineID insertTLI)
 {
 	char		path[MAXPGPATH];
 #ifdef WIN32
 	char		newpath[MAXPGPATH];
 #endif
-	struct stat statbuf;
 
 	snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);
 
@@ -3711,7 +3712,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 	if (wal_recycle &&
 		*endlogSegNo <= recycleSegNo &&
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
-		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
+		get_dirent_type(path, xlde, false, LOG) == PGFILETYPE_REG &&
 		InstallXLogFileSegment(endlogSegNo, path,
 							   true, recycleSegNo, insertTLI))
 	{
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 1c21a1d14b..f61c5b747e 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -91,6 +91,7 @@
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -254,7 +255,8 @@ static void ReorderBufferRestoreChange(ReorderBuffer *rb, ReorderBufferTXN *txn,
 static void ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn);
 static void ReorderBufferTruncateTXN(ReorderBuffer *rb, ReorderBufferTXN *txn,
 									 bool txn_prepared);
-static void ReorderBufferCleanupSerializedTXNs(const char *slotname);
+static void ReorderBufferCleanupSerializedTXNs(const char *slotname,
+											   bool slot_dir_checked);
 static void ReorderBufferSerializedPath(char *path, ReplicationSlot *slot,
 										TransactionId xid, XLogSegNo segno);
 
@@ -375,7 +377,8 @@ ReorderBufferAllocate(void)
 	 * prior exit avoided calling ReorderBufferFree. Failure to do this can
 	 * produce duplicated txns, and it's very cheap if there's nothing there.
 	 */
-	ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
+	ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name),
+									   false);
 
 	return buffer;
 }
@@ -395,7 +398,8 @@ ReorderBufferFree(ReorderBuffer *rb)
 	MemoryContextDelete(context);
 
 	/* Free disk space used by unconsumed reorder buffers */
-	ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name));
+	ReorderBufferCleanupSerializedTXNs(NameStr(MyReplicationSlot->data.name),
+									   false);
 }
 
 /*
@@ -4464,7 +4468,7 @@ ReorderBufferRestoreCleanup(ReorderBuffer *rb, ReorderBufferTXN *txn)
  * prior crash or decoding session exit.
  */
 static void
-ReorderBufferCleanupSerializedTXNs(const char *slotname)
+ReorderBufferCleanupSerializedTXNs(const char *slotname, bool slot_dir_checked)
 {
 	DIR		   *spill_dir;
 	struct dirent *spill_de;
@@ -4473,8 +4477,12 @@ ReorderBufferCleanupSerializedTXNs(const char *slotname)
 
 	sprintf(path, "pg_replslot/%s", slotname);
 
-	/* we're only handling directories here, skip if it's not ours */
-	if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+	/*
+	 * We're only handling directories here, skip if it's not ours. Also, skip
+	 * if the caller has already performed this check.
+	 */
+	if (!slot_dir_checked &&
+		lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
 		return;
 
 	spill_dir = AllocateDir(path);
@@ -4524,6 +4532,7 @@ StartupReorderBuffer(void)
 {
 	DIR		   *logical_dir;
 	struct dirent *logical_de;
+	char		path[MAXPGPATH * 2 + 12];
 
 	logical_dir = AllocateDir("pg_replslot");
 	while ((logical_de = ReadDir(logical_dir, "pg_replslot")) != NULL)
@@ -4536,11 +4545,16 @@ StartupReorderBuffer(void)
 		if (!ReplicationSlotValidateName(logical_de->d_name, DEBUG2))
 			continue;
 
+		/* we're only handling directories here, skip if it's not one */
+		sprintf(path, "pg_replslot/%s", logical_de->d_name);
+		if (get_dirent_type(path, logical_de, false, LOG) != PGFILETYPE_DIR)
+			continue;
+
 		/*
 		 * ok, has to be a surviving logical slot, iterate and delete
 		 * everything starting with xid-*
 		 */
-		ReorderBufferCleanupSerializedTXNs(logical_de->d_name);
+		ReorderBufferCleanupSerializedTXNs(logical_de->d_name, true);
 	}
 	FreeDir(logical_dir);
 }
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 1ff2c12240..f90059bb4e 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -2049,15 +2050,16 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
+		PGFileType	de_type;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
+		de_type = get_dirent_type(path, snap_de, false, LOG);
 
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 850b74936f..e9b926d7de 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1442,17 +1443,18 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
+		PGFileType	de_type;
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
 			strcmp(replication_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
+		de_type = get_dirent_type(path, replication_de, false, LOG);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index e3b19ca1ed..bd6f1270f9 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2706,6 +2706,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2721,6 +2725,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3157,17 +3165,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index ce5633844c..88460422dd 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1017,7 +1018,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1034,32 +1035,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1067,6 +1045,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-23 18:07                                                   ` Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-08-23 18:07 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 17, 2022 at 12:39:26PM +0530, Bharath Rupireddy wrote:
> +	/*
> +	 * We're only handling directories here, skip if it's not ours. Also, skip
> +	 * if the caller has already performed this check.
> +	 */
> +	if (!slot_dir_checked &&
> +		lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
>  		return;

There was previous discussion about removing this stanza from
ReorderBufferCleanupSeralizedTXNs() completely [0].  If that is still
possible, I think I would choose that over having callers specify whether
to do the directory check.

> +		/* we're only handling directories here, skip if it's not one */
> +		sprintf(path, "pg_replslot/%s", logical_de->d_name);
> +		if (get_dirent_type(path, logical_de, false, LOG) != PGFILETYPE_DIR)
> +			continue;

IIUC an error in get_dirent_type() could cause slots to be skipped here,
which is a behavior change.

[0] https://postgr.es/m/20220329224832.GA560657%40nathanxps13

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-25 05:18                                                     ` Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-25 05:18 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Tue, Aug 23, 2022 at 11:37 PM Nathan Bossart
<[email protected]> wrote:
>
> On Wed, Aug 17, 2022 at 12:39:26PM +0530, Bharath Rupireddy wrote:
> > +     /*
> > +      * We're only handling directories here, skip if it's not ours. Also, skip
> > +      * if the caller has already performed this check.
> > +      */
> > +     if (!slot_dir_checked &&
> > +             lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
> >               return;
>
> There was previous discussion about removing this stanza from
> ReorderBufferCleanupSeralizedTXNs() completely [0].  If that is still
> possible, I think I would choose that over having callers specify whether
> to do the directory check.
>
> [0] https://postgr.es/m/20220329224832.GA560657%40nathanxps13



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-25 12:21                                                       ` Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Michael Paquier @ 2022-08-25 12:21 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Aug 25, 2022 at 10:48:08AM +0530, Bharath Rupireddy wrote:
> On Tue, Aug 23, 2022 at 11:37 PM Nathan Bossart <[email protected]> wrote:
>> IIUC an error in get_dirent_type() could cause slots to be skipped here,
>> which is a behavior change.
> 
> That behaviour hasn't changed, no? Currently, if lstat() fails in
> ReorderBufferCleanupSerializedTXNs() it returns to
> StartupReorderBuffer()'s while loop which is in a way continuing with
> the other slots, this patch does nothing new.

Are you sure?  FWIW, the changes in reorderbuffer.c for
ReorderBufferCleanupSerializedTXNs() reduce the code readability, in
my opinion, so that's one less argument in favor of this change.

The gain in ParseConfigDirectory() is kind of cool.
pg_tzenumerate_next(), copydir(), RemoveXlogFile()
StartupReplicationSlots(), CheckPointLogicalRewriteHeap() and
RemovePgTempFilesInDir() seem fine, as well.  At least these avoid
extra lstat() calls when the file type is unknown, which would be only
a limited number of users where some of the three DT_* are missing
(right?).
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
@ 2022-08-27 08:36                                                         ` Bharath Rupireddy <[email protected]>
  2022-08-28 21:52                                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-31 19:14                                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  0 siblings, 2 replies; 40+ messages in thread

From: Bharath Rupireddy @ 2022-08-27 08:36 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Aug 25, 2022 at 5:51 PM Michael Paquier <[email protected]> wrote:
>
> FWIW, the changes in reorderbuffer.c for
> ReorderBufferCleanupSerializedTXNs() reduce the code readability, in
> my opinion, so that's one less argument in favor of this change.

Agreed. I reverted the changes.

> The gain in ParseConfigDirectory() is kind of cool.
> pg_tzenumerate_next(), copydir(), RemoveXlogFile()
> StartupReplicationSlots(), CheckPointLogicalRewriteHeap() and
> RemovePgTempFilesInDir() seem fine, as well.  At least these avoid
> extra lstat() calls when the file type is unknown, which would be only
> a limited number of users where some of the three DT_* are missing
> (right?).

Yes, the idea is to avoid lstat() system calls when possible.

PSA v17 patch with reorderbuffer.c changes reverted.

-- 
Bharath Rupireddy
RDS Open Source Databases: https://aws.amazon.com/rds/postgresql/


Attachments:

  [application/octet-stream] v17-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch (14.1K, ../../CALj2ACV2boDSpQGYvp5Won6qfJgwdpeFMJTO-4Z7dKZy8UAH2Q@mail.gmail.com/2-v17-0001-Make-more-use-of-get_dirent_type-in-place-of-sta.patch)
  download | inline diff:
From 3ce317e9eb4b37f79e3f822fd1ae53cb5d48b12e Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sat, 27 Aug 2022 08:29:09 +0000
Subject: [PATCH v17] Make more use of get_dirent_type() in place of stat() or
 lstat()

We don't need to call stat() or lstat() in several ReadDir() loops, if
d_type is available, as it usually is on most systems.  If not,
get_dirent_type() will do that under the covers.

Authors: Nathan Bossart, Bharath Rupireddy
Reviewed-by: Thomas Munro, Andres Freund
Reviewed-by: Michael Paquier
Discussion: https://postgr.es/m/20220215231123.GA2584239%40nathanxps13
---
 src/backend/access/heap/rewriteheap.c       |  7 +++-
 src/backend/access/transam/xlog.c           | 17 +++++----
 src/backend/replication/logical/snapbuild.c |  6 ++-
 src/backend/replication/slot.c              |  6 ++-
 src/backend/storage/file/copydir.c          | 21 +++--------
 src/backend/storage/file/fd.c               | 20 +++++-----
 src/backend/utils/misc/guc-file.l           | 42 ++++++++-------------
 src/timezone/pgtz.c                         |  8 +---
 8 files changed, 57 insertions(+), 70 deletions(-)

diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c
index 9dd885d936..5b20d1e1d5 100644
--- a/src/backend/access/heap/rewriteheap.c
+++ b/src/backend/access/heap/rewriteheap.c
@@ -113,6 +113,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "common/file_utils.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1213,7 +1214,6 @@ CheckPointLogicalRewriteHeap(void)
 	mappings_dir = AllocateDir("pg_logical/mappings");
 	while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL)
 	{
-		struct stat statbuf;
 		Oid			dboid;
 		Oid			relid;
 		XLogRecPtr	lsn;
@@ -1221,13 +1221,16 @@ CheckPointLogicalRewriteHeap(void)
 		TransactionId create_xid;
 		uint32		hi,
 					lo;
+		PGFileType	de_type;
 
 		if (strcmp(mapping_de->d_name, ".") == 0 ||
 			strcmp(mapping_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/mappings/%s", mapping_de->d_name);
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		de_type = get_dirent_type(path, mapping_de, false, LOG);
+
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG)
 			continue;
 
 		/* Skip over files that cannot be ours. */
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 537845cada..901169946d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -657,8 +657,9 @@ static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
 static void RemoveTempXlogFiles(void);
 static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
 							   XLogRecPtr endptr, TimeLineID insertTLI);
-static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-						   XLogSegNo *endlogSegNo, TimeLineID insertTLI);
+static void RemoveXlogFile(const char *segname, const struct dirent *xlde,
+						   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+						   TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
@@ -3596,7 +3597,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 				/* Update the last removed location in shared memory first */
 				UpdateLastRemovedPtr(xlde->d_name);
 
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endlogSegNo,
 							   insertTLI);
 			}
 		}
@@ -3669,7 +3670,7 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
 			 * - but seems safer to let them be archived and removed later.
 			 */
 			if (!XLogArchiveIsReady(xlde->d_name))
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo,
+				RemoveXlogFile(xlde->d_name, xlde, recycleSegNo, &endLogSegNo,
 							   newTLI);
 		}
 	}
@@ -3691,14 +3692,14 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
  * should be used for this timeline.
  */
 static void
-RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-			   XLogSegNo *endlogSegNo, TimeLineID insertTLI)
+RemoveXlogFile(const char *segname, const struct dirent *xlde,
+			   XLogSegNo recycleSegNo, XLogSegNo *endlogSegNo,
+			   TimeLineID insertTLI)
 {
 	char		path[MAXPGPATH];
 #ifdef WIN32
 	char		newpath[MAXPGPATH];
 #endif
-	struct stat statbuf;
 
 	snprintf(path, MAXPGPATH, XLOGDIR "/%s", segname);
 
@@ -3710,7 +3711,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 	if (wal_recycle &&
 		*endlogSegNo <= recycleSegNo &&
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
-		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
+		get_dirent_type(path, xlde, false, LOG) == PGFILETYPE_REG &&
 		InstallXLogFileSegment(endlogSegNo, path,
 							   true, recycleSegNo, insertTLI))
 	{
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index 1ff2c12240..f90059bb4e 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -123,6 +123,7 @@
 #include "access/heapam_xlog.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
@@ -2049,15 +2050,16 @@ CheckPointSnapBuild(void)
 		uint32		hi;
 		uint32		lo;
 		XLogRecPtr	lsn;
-		struct stat statbuf;
+		PGFileType	de_type;
 
 		if (strcmp(snap_de->d_name, ".") == 0 ||
 			strcmp(snap_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_logical/snapshots/%s", snap_de->d_name);
+		de_type = get_dirent_type(path, snap_de, false, LOG);
 
-		if (lstat(path, &statbuf) == 0 && !S_ISREG(statbuf.st_mode))
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG)
 		{
 			elog(DEBUG1, "only regular files expected: %s", path);
 			continue;
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 850b74936f..e9b926d7de 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -41,6 +41,7 @@
 
 #include "access/transam.h"
 #include "access/xlog_internal.h"
+#include "common/file_utils.h"
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -1442,17 +1443,18 @@ StartupReplicationSlots(void)
 	replication_dir = AllocateDir("pg_replslot");
 	while ((replication_de = ReadDir(replication_dir, "pg_replslot")) != NULL)
 	{
-		struct stat statbuf;
 		char		path[MAXPGPATH + 12];
+		PGFileType	de_type;
 
 		if (strcmp(replication_de->d_name, ".") == 0 ||
 			strcmp(replication_de->d_name, "..") == 0)
 			continue;
 
 		snprintf(path, sizeof(path), "pg_replslot/%s", replication_de->d_name);
+		de_type = get_dirent_type(path, replication_de, false, LOG);
 
 		/* we're only creating directories here, skip if it's not our's */
-		if (lstat(path, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
+		if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_DIR)
 			continue;
 
 		/* we crashed while a slot was being setup or deleted, clean up */
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 658fd95ba9..8a866191e1 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "common/file_utils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
@@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
 	{
-		struct stat fst;
+		PGFileType	xlde_type;
 
 		/* If we got a cancel signal during the copy of the directory, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
 		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
 		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
 
-		if (lstat(fromfile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", fromfile)));
+		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
 
-		if (S_ISDIR(fst.st_mode))
+		if (xlde_type == PGFILETYPE_DIR)
 		{
 			/* recurse to handle subdirectories */
 			if (recurse)
 				copydir(fromfile, tofile, true);
 		}
-		else if (S_ISREG(fst.st_mode))
+		else if (xlde_type == PGFILETYPE_REG)
 			copy_file(fromfile, tofile);
 	}
 	FreeDir(xldir);
@@ -89,8 +87,6 @@ copydir(char *fromdir, char *todir, bool recurse)
 
 	while ((xlde = ReadDir(xldir, todir)) != NULL)
 	{
-		struct stat fst;
-
 		if (strcmp(xlde->d_name, ".") == 0 ||
 			strcmp(xlde->d_name, "..") == 0)
 			continue;
@@ -101,12 +97,7 @@ copydir(char *fromdir, char *todir, bool recurse)
 		 * We don't need to sync subdirectories here since the recursive
 		 * copydir will do it before it returns
 		 */
-		if (lstat(tofile, &fst) < 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m", tofile)));
-
-		if (S_ISREG(fst.st_mode))
+		if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
 			fsync_fname(tofile, false);
 	}
 	FreeDir(xldir);
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index e3b19ca1ed..bd6f1270f9 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -2706,6 +2706,10 @@ TryAgain:
  *
  * The pathname passed to AllocateDir must be passed to this routine too,
  * but it is only used for error reporting.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDir(DIR *dir, const char *dirname)
@@ -2721,6 +2725,10 @@ ReadDir(DIR *dir, const char *dirname)
  * If elevel < ERROR, returns NULL after any error.  With the normal coding
  * pattern, this will result in falling out of the loop immediately as
  * though the directory contained no (more) entries.
+ *
+ * If you need to discover the file type of an entry, consider using
+ * get_dirent_type() (instead of stat()/lstat()) to avoid extra system calls on
+ * many platforms.
  */
 struct dirent *
 ReadDirExtended(DIR *dir, const char *dirname, int elevel)
@@ -3157,17 +3165,11 @@ RemovePgTempFilesInDir(const char *tmpdirname, bool missing_ok, bool unlink_all)
 					PG_TEMP_FILE_PREFIX,
 					strlen(PG_TEMP_FILE_PREFIX)) == 0)
 		{
-			struct stat statbuf;
+			PGFileType	type = get_dirent_type(rm_path, temp_de, false, LOG);
 
-			if (lstat(rm_path, &statbuf) < 0)
-			{
-				ereport(LOG,
-						(errcode_for_file_access(),
-						 errmsg("could not stat file \"%s\": %m", rm_path)));
+			if (type == PGFILETYPE_ERROR)
 				continue;
-			}
-
-			if (S_ISDIR(statbuf.st_mode))
+			else if (type == PGFILETYPE_DIR)
 			{
 				/* recursively remove contents, then directory itself */
 				RemovePgTempFilesInDir(rm_path, false, true);
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index ce5633844c..88460422dd 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -14,6 +14,7 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#include "common/file_utils.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
@@ -1017,7 +1018,7 @@ ParseConfigDirectory(const char *includedir,
 
 	while ((de = ReadDir(d, directory)) != NULL)
 	{
-		struct stat st;
+		PGFileType	de_type;
 		char		filename[MAXPGPATH];
 
 		/*
@@ -1034,32 +1035,9 @@ ParseConfigDirectory(const char *includedir,
 
 		join_path_components(filename, directory, de->d_name);
 		canonicalize_path(filename);
-		if (stat(filename, &st) == 0)
+		de_type = get_dirent_type(filename, de, true, elevel);
+		if (de_type == PGFILETYPE_ERROR)
 		{
-			if (!S_ISDIR(st.st_mode))
-			{
-				/* Add file to array, increasing its size in blocks of 32 */
-				if (num_filenames >= size_filenames)
-				{
-					size_filenames += 32;
-					filenames = (char **) repalloc(filenames,
-											size_filenames * sizeof(char *));
-				}
-				filenames[num_filenames] = pstrdup(filename);
-				num_filenames++;
-			}
-		}
-		else
-		{
-			/*
-			 * stat does not care about permissions, so the most likely reason
-			 * a file can't be accessed now is if it was removed between the
-			 * directory listing and now.
-			 */
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not stat file \"%s\": %m",
-							filename)));
 			record_config_file_error(psprintf("could not stat file \"%s\"",
 											  filename),
 									 calling_file, calling_lineno,
@@ -1067,6 +1045,18 @@ ParseConfigDirectory(const char *includedir,
 			status = false;
 			goto cleanup;
 		}
+		else if (de_type != PGFILETYPE_DIR)
+		{
+			/* Add file to array, increasing its size in blocks of 32 */
+			if (num_filenames >= size_filenames)
+			{
+				size_filenames += 32;
+				filenames = (char **) repalloc(filenames,
+											   size_filenames * sizeof(char *));
+			}
+			filenames[num_filenames] = pstrdup(filename);
+			num_filenames++;
+		}
 	}
 
 	if (num_filenames > 0)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 3c278dd0f3..72f9e3d5e6 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -17,6 +17,7 @@
 #include <sys/stat.h>
 #include <time.h>
 
+#include "common/file_utils.h"
 #include "datatype/timestamp.h"
 #include "miscadmin.h"
 #include "pgtz.h"
@@ -429,7 +430,6 @@ pg_tzenumerate_next(pg_tzenum *dir)
 	{
 		struct dirent *direntry;
 		char		fullname[MAXPGPATH * 2];
-		struct stat statbuf;
 
 		direntry = ReadDir(dir->dirdesc[dir->depth], dir->dirname[dir->depth]);
 
@@ -447,12 +447,8 @@ pg_tzenumerate_next(pg_tzenum *dir)
 
 		snprintf(fullname, sizeof(fullname), "%s/%s",
 				 dir->dirname[dir->depth], direntry->d_name);
-		if (stat(fullname, &statbuf) != 0)
-			ereport(ERROR,
-					(errcode_for_file_access(),
-					 errmsg("could not stat \"%s\": %m", fullname)));
 
-		if (S_ISDIR(statbuf.st_mode))
+		if (get_dirent_type(fullname, direntry, true, ERROR) == PGFILETYPE_DIR)
 		{
 			/* Step into the subdirectory */
 			if (dir->depth >= MAX_TZDIR_DEPTH - 1)
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-28 21:52                                                           ` Nathan Bossart <[email protected]>
  2022-08-31 07:10                                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-08-28 21:52 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sat, Aug 27, 2022 at 02:06:32PM +0530, Bharath Rupireddy wrote:
> PSA v17 patch with reorderbuffer.c changes reverted.

LGTM

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-28 21:52                                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-31 07:10                                                             ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Michael Paquier @ 2022-08-31 07:10 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Andres Freund <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Sun, Aug 28, 2022 at 02:52:43PM -0700, Nathan Bossart wrote:
> On Sat, Aug 27, 2022 at 02:06:32PM +0530, Bharath Rupireddy wrote:
>> PSA v17 patch with reorderbuffer.c changes reverted.
> 
> LGTM

Yeah, what you have here looks pretty fine to me, so this is IMO
committable.  Let's wait a bit to see if there are any objections from
the others, in case.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
@ 2022-08-31 19:14                                                           ` Andres Freund <[email protected]>
  2022-08-31 19:24                                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Andres Freund @ 2022-08-31 19:14 UTC (permalink / raw)
  To: Bharath Rupireddy <[email protected]>; +Cc: Michael Paquier <[email protected]>; Nathan Bossart <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-08-27 14:06:32 +0530, Bharath Rupireddy wrote:
> @@ -50,7 +51,7 @@ copydir(char *fromdir, char *todir, bool recurse)
>  
>  	while ((xlde = ReadDir(xldir, fromdir)) != NULL)
>  	{
> -		struct stat fst;
> +		PGFileType	xlde_type;
>  
>  		/* If we got a cancel signal during the copy of the directory, quit */
>  		CHECK_FOR_INTERRUPTS();
> @@ -62,18 +63,15 @@ copydir(char *fromdir, char *todir, bool recurse)
>  		snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
>  		snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
>  
> -		if (lstat(fromfile, &fst) < 0)
> -			ereport(ERROR,
> -					(errcode_for_file_access(),
> -					 errmsg("could not stat file \"%s\": %m", fromfile)));
> +		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
>  
> -		if (S_ISDIR(fst.st_mode))
> +		if (xlde_type == PGFILETYPE_DIR)
>  		{
>  			/* recurse to handle subdirectories */
>  			if (recurse)
>  				copydir(fromfile, tofile, true);
>  		}
> -		else if (S_ISREG(fst.st_mode))
> +		else if (xlde_type == PGFILETYPE_REG)
>  			copy_file(fromfile, tofile);
>  	}
>  	FreeDir(xldir);

It continues to make no sense to me to add behaviour changes around
error-handling as part of a conversion to get_dirent_type(). I don't at all
understand why e.g. the above change to make copydir() silently skip over
files it can't stat is ok?

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-31 19:14                                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
@ 2022-08-31 19:24                                                             ` Nathan Bossart <[email protected]>
  2022-08-31 19:28                                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  0 siblings, 1 reply; 40+ messages in thread

From: Nathan Bossart @ 2022-08-31 19:24 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Wed, Aug 31, 2022 at 12:14:33PM -0700, Andres Freund wrote:
> On 2022-08-27 14:06:32 +0530, Bharath Rupireddy wrote:
>> -		if (lstat(fromfile, &fst) < 0)
>> -			ereport(ERROR,
>> -					(errcode_for_file_access(),
>> -					 errmsg("could not stat file \"%s\": %m", fromfile)));
>> +		xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
>>  
>> -		if (S_ISDIR(fst.st_mode))
>> +		if (xlde_type == PGFILETYPE_DIR)
>>  		{
>>  			/* recurse to handle subdirectories */
>>  			if (recurse)
>>  				copydir(fromfile, tofile, true);
>>  		}
>> -		else if (S_ISREG(fst.st_mode))
>> +		else if (xlde_type == PGFILETYPE_REG)
>>  			copy_file(fromfile, tofile);
>>  	}
>>  	FreeDir(xldir);
> 
> It continues to make no sense to me to add behaviour changes around
> error-handling as part of a conversion to get_dirent_type(). I don't at all
> understand why e.g. the above change to make copydir() silently skip over
> files it can't stat is ok?

In this example, the call to get_dirent_type() should ERROR if the call to
lstat() fails (the "elevel" argument is set to ERROR).
-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:17                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:27                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:41                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-09 03:50                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Tom Lane <[email protected]>
  2022-08-09 03:59                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-09 04:28                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 05:26                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 07:14                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-10 08:41                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-08-10 09:58                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-16 20:32                                               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-17 07:09                                                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-23 18:07                                                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-25 05:18                                                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-25 12:21                                                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Michael Paquier <[email protected]>
  2022-08-27 08:36                                                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-08-31 19:14                                                           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-08-31 19:24                                                             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-08-31 19:28                                                               ` Andres Freund <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Andres Freund @ 2022-08-31 19:28 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; Michael Paquier <[email protected]>; Thomas Munro <[email protected]>; Tom Lane <[email protected]>; Bossart, Nathan <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-08-31 12:24:28 -0700, Nathan Bossart wrote:
> On Wed, Aug 31, 2022 at 12:14:33PM -0700, Andres Freund wrote:
> > On 2022-08-27 14:06:32 +0530, Bharath Rupireddy wrote:
> > It continues to make no sense to me to add behaviour changes around
> > error-handling as part of a conversion to get_dirent_type(). I don't at all
> > understand why e.g. the above change to make copydir() silently skip over
> > files it can't stat is ok?
> 
> In this example, the call to get_dirent_type() should ERROR if the call to
> lstat() fails (the "elevel" argument is set to ERROR).

Oh, oops. Skimmed code too quickly...

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-08 16:09                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-08 17:14                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-18 11:23                     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Bharath Rupireddy <[email protected]>
  2022-07-18 18:18                       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-08-09 03:10                         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
@ 2022-08-09 04:07                           ` Andres Freund <[email protected]>
  1 sibling, 0 replies; 40+ messages in thread

From: Andres Freund @ 2022-08-09 04:07 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On 2022-08-09 15:10:41 +1200, Thomas Munro wrote:
> I was going to commit the first of these patches, but then I noticed Andres
> said he was planning to, so I'll wait another day.

I'd be happy for you to take this on...





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
@ 2022-07-11 20:25                 ` Andres Freund <[email protected]>
  2022-07-11 21:23                   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  1 sibling, 1 reply; 40+ messages in thread

From: Andres Freund @ 2022-07-11 20:25 UTC (permalink / raw)
  To: Nathan Bossart <[email protected]>; +Cc: Thomas Munro <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

Hi,

On 2022-04-08 13:18:57 -0700, Nathan Bossart wrote:
> @@ -1035,32 +1036,9 @@ ParseConfigDirectory(const char *includedir,
>  
>  		join_path_components(filename, directory, de->d_name);
>  		canonicalize_path(filename);
> -		if (stat(filename, &st) == 0)
> +		de_type = get_dirent_type(filename, de, true, elevel);
> +		if (de_type == PGFILETYPE_ERROR)
>  		{
> -			if (!S_ISDIR(st.st_mode))
> -			{
> -				/* Add file to array, increasing its size in blocks of 32 */
> -				if (num_filenames >= size_filenames)
> -				{
> -					size_filenames += 32;
> -					filenames = (char **) repalloc(filenames,
> -											size_filenames * sizeof(char *));
> -				}
> -				filenames[num_filenames] = pstrdup(filename);
> -				num_filenames++;
> -			}
> -		}
> -		else
> -		{
> -			/*
> -			 * stat does not care about permissions, so the most likely reason
> -			 * a file can't be accessed now is if it was removed between the
> -			 * directory listing and now.
> -			 */
> -			ereport(elevel,
> -					(errcode_for_file_access(),
> -					 errmsg("could not stat file \"%s\": %m",
> -							filename)));
>  			record_config_file_error(psprintf("could not stat file \"%s\"",
>  											  filename),
>  									 calling_file, calling_lineno,
> @@ -1068,6 +1046,18 @@ ParseConfigDirectory(const char *includedir,
>  			status = false;
>  			goto cleanup;
>  		}
> +		else if (de_type != PGFILETYPE_DIR)
> +		{
> +			/* Add file to array, increasing its size in blocks of 32 */
> +			if (num_filenames >= size_filenames)
> +			{
> +				size_filenames += 32;
> +				filenames = (char **) repalloc(filenames,
> +											   size_filenames * sizeof(char *));
> +			}
> +			filenames[num_filenames] = pstrdup(filename);
> +			num_filenames++;
> +		}
>  	}
>  
>  	if (num_filenames > 0)

Seems like the diff would be easier to read if it didn't move code around as
much?

Looks pretty reasonable, I'd be happy to commit it, I think.

Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work
  2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 17:57 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 18:10   ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
  2022-02-15 18:37     ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-02-15 23:11       ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-24 00:17         ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Thomas Munro <[email protected]>
  2022-03-29 22:48           ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-03-30 16:21             ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-04-08 20:18               ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Nathan Bossart <[email protected]>
  2022-07-11 20:25                 ` Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
@ 2022-07-11 21:23                   ` Nathan Bossart <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Nathan Bossart @ 2022-07-11 21:23 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Bharath Rupireddy <[email protected]>; Bossart, Nathan <[email protected]>; Tom Lane <[email protected]>; Julien Rouhaud <[email protected]>; PostgreSQL Hackers <[email protected]>

On Mon, Jul 11, 2022 at 01:25:33PM -0700, Andres Freund wrote:
> On 2022-04-08 13:18:57 -0700, Nathan Bossart wrote:
>> @@ -1035,32 +1036,9 @@ ParseConfigDirectory(const char *includedir,
>>  
>>  		join_path_components(filename, directory, de->d_name);
>>  		canonicalize_path(filename);
>> -		if (stat(filename, &st) == 0)
>> +		de_type = get_dirent_type(filename, de, true, elevel);
>> +		if (de_type == PGFILETYPE_ERROR)
>>  		{
>> -			if (!S_ISDIR(st.st_mode))
>> -			{
>> -				/* Add file to array, increasing its size in blocks of 32 */
>> -				if (num_filenames >= size_filenames)
>> -				{
>> -					size_filenames += 32;
>> -					filenames = (char **) repalloc(filenames,
>> -											size_filenames * sizeof(char *));
>> -				}
>> -				filenames[num_filenames] = pstrdup(filename);
>> -				num_filenames++;
>> -			}
>> -		}
>> -		else
>> -		{
>> -			/*
>> -			 * stat does not care about permissions, so the most likely reason
>> -			 * a file can't be accessed now is if it was removed between the
>> -			 * directory listing and now.
>> -			 */
>> -			ereport(elevel,
>> -					(errcode_for_file_access(),
>> -					 errmsg("could not stat file \"%s\": %m",
>> -							filename)));
>>  			record_config_file_error(psprintf("could not stat file \"%s\"",
>>  											  filename),
>>  									 calling_file, calling_lineno,
>> @@ -1068,6 +1046,18 @@ ParseConfigDirectory(const char *includedir,
>>  			status = false;
>>  			goto cleanup;
>>  		}
>> +		else if (de_type != PGFILETYPE_DIR)
>> +		{
>> +			/* Add file to array, increasing its size in blocks of 32 */
>> +			if (num_filenames >= size_filenames)
>> +			{
>> +				size_filenames += 32;
>> +				filenames = (char **) repalloc(filenames,
>> +											   size_filenames * sizeof(char *));
>> +			}
>> +			filenames[num_filenames] = pstrdup(filename);
>> +			num_filenames++;
>> +		}
>>  	}
>>  
>>  	if (num_filenames > 0)
> 
> Seems like the diff would be easier to read if it didn't move code around as
> much?

I opted to reorganize in order save an indent and simplify the conditions a
bit.  The alternative is something like this:

	if (de_type != PGFILETYPE_ERROR)
	{
		if (de_type != PGTFILETYPE_DIR)
		{
			...
		}
	}
	else
	{
		...
	}

I don't feel strongly one way or another, and I'll change it if you think
it's worth it to simplify the diff.

> Looks pretty reasonable, I'd be happy to commit it, I think.

Appreciate it.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com





^ permalink  raw  reply  [nested|flat] 40+ messages in thread

* [PATCH v22 2/2] Add REPACK command
@ 2025-07-26 17:57 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 40+ messages in thread

From: Álvaro Herrera @ 2025-07-26 17:57 UTC (permalink / raw)

REPACK absorbs the functionality of VACUUM FULL and CLUSTER in a single
command.  Because this functionality is completely different from
regular VACUUM, having it separate from VACUUM makes it easier for users
to understand; as for CLUSTER, the term is heavily overloaded in the
TI world and even in Postgres itself, so it's good that we can avoid it.

This also adds pg_repackdb, a new utility that can invoke the new
commands.  This is heavily based on vacuumdb.  We may still change the
implementation, depending on how does Windows like this one.

Author: Antonin Houska <[email protected]>
Reviewed-by: To fill in
Discussion: https://postgr.es/m/82651.1720540558@antos
Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/monitoring.sgml             | 223 ++++++-
 doc/src/sgml/ref/allfiles.sgml           |   2 +
 doc/src/sgml/ref/cluster.sgml            |  97 +--
 doc/src/sgml/ref/clusterdb.sgml          |   5 +
 doc/src/sgml/ref/pg_repackdb.sgml        | 479 ++++++++++++++
 doc/src/sgml/ref/repack.sgml             | 284 +++++++++
 doc/src/sgml/ref/vacuum.sgml             |  33 +-
 doc/src/sgml/reference.sgml              |   2 +
 src/backend/access/heap/heapam_handler.c |  32 +-
 src/backend/catalog/index.c              |   2 +-
 src/backend/catalog/system_views.sql     |  26 +
 src/backend/commands/cluster.c           | 758 +++++++++++++++--------
 src/backend/commands/vacuum.c            |   3 +-
 src/backend/parser/gram.y                |  88 ++-
 src/backend/tcop/utility.c               |  20 +-
 src/backend/utils/adt/pgstatfuncs.c      |   2 +
 src/bin/psql/tab-complete.in.c           |  33 +-
 src/bin/scripts/Makefile                 |   4 +-
 src/bin/scripts/meson.build              |   2 +
 src/bin/scripts/pg_repackdb.c            | 238 +++++++
 src/bin/scripts/t/103_repackdb.pl        |  24 +
 src/bin/scripts/vacuuming.c              |  97 ++-
 src/bin/scripts/vacuuming.h              |   9 +
 src/include/commands/cluster.h           |   8 +-
 src/include/commands/progress.h          |  61 +-
 src/include/nodes/parsenodes.h           |  20 +-
 src/include/parser/kwlist.h              |   1 +
 src/include/tcop/cmdtaglist.h            |   1 +
 src/include/utils/backend_progress.h     |   1 +
 src/test/regress/expected/cluster.out    | 125 +++-
 src/test/regress/expected/rules.out      |  23 +
 src/test/regress/sql/cluster.sql         |  59 ++
 src/tools/pgindent/typedefs.list         |   3 +
 33 files changed, 2318 insertions(+), 447 deletions(-)
 create mode 100644 doc/src/sgml/ref/pg_repackdb.sgml
 create mode 100644 doc/src/sgml/ref/repack.sgml
 create mode 100644 src/bin/scripts/pg_repackdb.c
 create mode 100644 src/bin/scripts/t/103_repackdb.pl

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3f4a27a736e..12e103d319d 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -405,6 +405,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_progress_repack</structname><indexterm><primary>pg_stat_progress_repack</primary></indexterm></entry>
+      <entry>One row for each backend running
+       <command>REPACK</command>, showing current progress.  See
+       <xref linkend="repack-progress-reporting"/>.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_progress_basebackup</structname><indexterm><primary>pg_stat_progress_basebackup</primary></indexterm></entry>
       <entry>One row for each WAL sender process streaming a base backup,
@@ -5506,7 +5514,8 @@ FROM pg_stat_get_backend_idset() AS backendid;
    certain commands during command execution.  Currently, the only commands
    which support progress reporting are <command>ANALYZE</command>,
    <command>CLUSTER</command>,
-   <command>CREATE INDEX</command>, <command>VACUUM</command>,
+   <command>CREATE INDEX</command>, <command>REPACK</command>,
+   <command>VACUUM</command>,
    <command>COPY</command>,
    and <xref linkend="protocol-replication-base-backup"/> (i.e., replication
    command that <xref linkend="app-pgbasebackup"/> issues to take
@@ -5965,6 +5974,218 @@ FROM pg_stat_get_backend_idset() AS backendid;
   </table>
  </sect2>
 
+ <sect2 id="repack-progress-reporting">
+  <title>REPACK Progress Reporting</title>
+
+  <indexterm>
+   <primary>pg_stat_progress_repack</primary>
+  </indexterm>
+
+  <para>
+   Whenever <command>REPACK</command> is running,
+   the <structname>pg_stat_progress_repack</structname> view will contain a
+   row for each backend that is currently running the command.  The tables
+   below describe the information that will be reported and provide
+   information about how to interpret it.
+  </para>
+
+  <table id="pg-stat-progress-repack-view" xreflabel="pg_stat_progress_repack">
+   <title><structname>pg_stat_progress_repack</structname> View</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pid</structfield> <type>integer</type>
+      </para>
+      <para>
+       Process ID of backend.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of the database to which this backend is connected.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>datname</structfield> <type>name</type>
+      </para>
+      <para>
+       Name of the database to which this backend is connected.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>relid</structfield> <type>oid</type>
+      </para>
+      <para>
+       OID of the table being repacked.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>phase</structfield> <type>text</type>
+      </para>
+      <para>
+       Current processing phase. See <xref linkend="repack-phases"/>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>repack_index_relid</structfield> <type>oid</type>
+      </para>
+      <para>
+       If the table is being scanned using an index, this is the OID of the
+       index being used; otherwise, it is zero.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_tuples_scanned</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of heap tuples scanned.
+       This counter only advances when the phase is
+       <literal>seq scanning heap</literal>,
+       <literal>index scanning heap</literal>
+       or <literal>writing new heap</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_tuples_written</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of heap tuples written.
+       This counter only advances when the phase is
+       <literal>seq scanning heap</literal>,
+       <literal>index scanning heap</literal>
+       or <literal>writing new heap</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_blks_total</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Total number of heap blocks in the table.  This number is reported
+       as of the beginning of <literal>seq scanning heap</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>heap_blks_scanned</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of heap blocks scanned.  This counter only advances when the
+       phase is <literal>seq scanning heap</literal>.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>index_rebuild_count</structfield> <type>bigint</type>
+      </para>
+      <para>
+       Number of indexes rebuilt.  This counter only advances when the phase
+       is <literal>rebuilding index</literal>.
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+  <table id="repack-phases">
+   <title>REPACK Phases</title>
+   <tgroup cols="2">
+    <colspec colname="col1" colwidth="1*"/>
+    <colspec colname="col2" colwidth="2*"/>
+    <thead>
+    <row>
+      <entry>Phase</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><literal>initializing</literal></entry>
+     <entry>
+       The command is preparing to begin scanning the heap.  This phase is
+       expected to be very brief.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>seq scanning heap</literal></entry>
+     <entry>
+       The command is currently scanning the table using a sequential scan.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>index scanning heap</literal></entry>
+     <entry>
+       <command>REPACK</command> is currently scanning the table using an index scan.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>sorting tuples</literal></entry>
+     <entry>
+       <command>REPACK</command> is currently sorting tuples.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>writing new heap</literal></entry>
+     <entry>
+       <command>REPACK</command> is currently writing the new heap.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>swapping relation files</literal></entry>
+     <entry>
+       The command is currently swapping newly-built files into place.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>rebuilding index</literal></entry>
+     <entry>
+       The command is currently rebuilding an index.
+     </entry>
+    </row>
+    <row>
+     <entry><literal>performing final cleanup</literal></entry>
+     <entry>
+       The command is performing final cleanup.  When this phase is
+       completed, <command>REPACK</command> will end.
+     </entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+ </sect2>
+
  <sect2 id="copy-progress-reporting">
   <title>COPY Progress Reporting</title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index f5be638867a..eabf92e3536 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -167,6 +167,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY refreshMaterializedView SYSTEM "refresh_materialized_view.sgml">
 <!ENTITY reindex            SYSTEM "reindex.sgml">
 <!ENTITY releaseSavepoint   SYSTEM "release_savepoint.sgml">
+<!ENTITY repack             SYSTEM "repack.sgml">
 <!ENTITY reset              SYSTEM "reset.sgml">
 <!ENTITY revoke             SYSTEM "revoke.sgml">
 <!ENTITY rollback           SYSTEM "rollback.sgml">
@@ -212,6 +213,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY pgIsready          SYSTEM "pg_isready.sgml">
 <!ENTITY pgReceivewal       SYSTEM "pg_receivewal.sgml">
 <!ENTITY pgRecvlogical      SYSTEM "pg_recvlogical.sgml">
+<!ENTITY pgRepackdb         SYSTEM "pg_repackdb.sgml">
 <!ENTITY pgResetwal         SYSTEM "pg_resetwal.sgml">
 <!ENTITY pgRestore          SYSTEM "pg_restore.sgml">
 <!ENTITY pgRewind           SYSTEM "pg_rewind.sgml">
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 8811f169ea0..cfcfb65e349 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -33,51 +33,13 @@ CLUSTER [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
   <title>Description</title>
 
   <para>
-   <command>CLUSTER</command> instructs <productname>PostgreSQL</productname>
-   to cluster the table specified
-   by <replaceable class="parameter">table_name</replaceable>
-   based on the index specified by
-   <replaceable class="parameter">index_name</replaceable>. The index must
-   already have been defined on
-   <replaceable class="parameter">table_name</replaceable>.
+   The <command>CLUSTER</command> command is equivalent to
+   <xref linkend="sql-repack"/> with an <literal>USING INDEX</literal>
+   clause.  See there for more details.
   </para>
 
-  <para>
-   When a table is clustered, it is physically reordered
-   based on the index information. Clustering is a one-time operation:
-   when the table is subsequently updated, the changes are
-   not clustered.  That is, no attempt is made to store new or
-   updated rows according to their index order.  (If one wishes, one can
-   periodically recluster by issuing the command again.  Also, setting
-   the table's <literal>fillfactor</literal> storage parameter to less than
-   100% can aid in preserving cluster ordering during updates, since updated
-   rows are kept on the same page if enough space is available there.)
-  </para>
+<!-- Do we need to describe exactly which options map to what?  They seem obvious to me. -->
 
-  <para>
-   When a table is clustered, <productname>PostgreSQL</productname>
-   remembers which index it was clustered by.  The form
-   <command>CLUSTER <replaceable class="parameter">table_name</replaceable></command>
-   reclusters the table using the same index as before.  You can also
-   use the <literal>CLUSTER</literal> or <literal>SET WITHOUT CLUSTER</literal>
-   forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link> to set the index to be used for
-   future cluster operations, or to clear any previous setting.
-  </para>
-
-  <para>
-   <command>CLUSTER</command> without a
-   <replaceable class="parameter">table_name</replaceable> reclusters all the
-   previously-clustered tables in the current database that the calling user
-   has privileges for.  This form of <command>CLUSTER</command> cannot be
-   executed inside a transaction block.
-  </para>
-
-  <para>
-   When a table is being clustered, 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>CLUSTER</command> is finished.
-  </para>
  </refsect1>
 
  <refsect1>
@@ -136,63 +98,12 @@ CLUSTER [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
     on the table.
    </para>
 
-   <para>
-    In cases where you are accessing single rows randomly
-    within a table, the actual order of the data in the
-    table is unimportant. However, if you tend to access some
-    data more than others, and there is an index that groups
-    them together, you will benefit from using <command>CLUSTER</command>.
-    If you are requesting a range of indexed values from a table, or a
-    single indexed value that has multiple rows that match,
-    <command>CLUSTER</command> will help because once the index identifies the
-    table page for the first row that matches, all other rows
-    that match are probably already on the same table page,
-    and so you save disk accesses and speed up the query.
-   </para>
-
-   <para>
-    <command>CLUSTER</command> can re-sort the table using either an index scan
-    on the specified index, or (if the index is a b-tree) a sequential
-    scan followed by sorting.  It will attempt to choose the method that
-    will be faster, based on planner cost parameters and available statistical
-    information.
-   </para>
-
    <para>
     While <command>CLUSTER</command> is running, the <xref
     linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
     pg_temp</literal>.
    </para>
 
-   <para>
-    When an index scan is used, a temporary copy of the table is created that
-    contains the table data in the index order.  Temporary copies of each
-    index on the table are created as well.  Therefore, you need free space on
-    disk at least equal to the sum of the table size and the index sizes.
-   </para>
-
-   <para>
-    When a sequential scan and sort is used, a temporary sort file is
-    also created, so that the peak temporary space requirement is as much
-    as double the table size, plus the index sizes.  This method is often
-    faster than the index scan method, but if the disk space requirement is
-    intolerable, you can disable this choice by temporarily setting <xref
-    linkend="guc-enable-sort"/> to <literal>off</literal>.
-   </para>
-
-   <para>
-    It is advisable to set <xref linkend="guc-maintenance-work-mem"/> to
-    a reasonably large value (but not more than the amount of RAM you can
-    dedicate to the <command>CLUSTER</command> operation) before clustering.
-   </para>
-
-   <para>
-    Because the planner records statistics about the ordering of
-    tables, it is advisable to run <link linkend="sql-analyze"><command>ANALYZE</command></link>
-    on the newly clustered table.
-    Otherwise, the planner might make poor choices of query plans.
-   </para>
-
    <para>
     Because <command>CLUSTER</command> remembers which indexes are clustered,
     one can cluster the tables one wants clustered manually the first time,
diff --git a/doc/src/sgml/ref/clusterdb.sgml b/doc/src/sgml/ref/clusterdb.sgml
index 0d2051bf6f1..546c1289c31 100644
--- a/doc/src/sgml/ref/clusterdb.sgml
+++ b/doc/src/sgml/ref/clusterdb.sgml
@@ -64,6 +64,11 @@ PostgreSQL documentation
    this utility and via other methods for accessing the server.
   </para>
 
+  <para>
+   <application>clusterdb</application> has been superceded by
+   <application>pg_repackdb</application>.
+  </para>
+
  </refsect1>
 
 
diff --git a/doc/src/sgml/ref/pg_repackdb.sgml b/doc/src/sgml/ref/pg_repackdb.sgml
new file mode 100644
index 00000000000..32570d071cb
--- /dev/null
+++ b/doc/src/sgml/ref/pg_repackdb.sgml
@@ -0,0 +1,479 @@
+<!--
+doc/src/sgml/ref/pg_repackdb.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="app-pgrepackdb">
+ <indexterm zone="app-pgrepackdb">
+  <primary>pg_repackdb</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle><application>pg_repackdb</application></refentrytitle>
+  <manvolnum>1</manvolnum>
+  <refmiscinfo>Application</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>pg_repackdb</refname>
+  <refpurpose>repack and analyze a <productname>PostgreSQL</productname>
+  database</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+  <cmdsynopsis>
+   <command>pg_repackdb</command>
+   <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
+   <arg rep="repeat"><replaceable>option</replaceable></arg>
+
+   <arg choice="plain" rep="repeat">
+    <arg choice="opt">
+     <group choice="plain">
+      <arg choice="plain"><option>-t</option></arg>
+      <arg choice="plain"><option>--table</option></arg>
+     </group>
+     <replaceable>table</replaceable>
+     <arg choice="opt">( <replaceable class="parameter">column</replaceable> [,...] )</arg>
+    </arg>
+   </arg>
+
+   <arg choice="opt">
+    <group choice="plain">
+     <arg choice="plain"><replaceable>dbname</replaceable></arg>
+     <arg choice="plain"><option>-a</option></arg>
+     <arg choice="plain"><option>--all</option></arg>
+    </group>
+   </arg>
+  </cmdsynopsis>
+
+  <cmdsynopsis>
+   <command>pg_repackdb</command>
+   <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
+   <arg rep="repeat"><replaceable>option</replaceable></arg>
+
+   <arg choice="plain" rep="repeat">
+    <arg choice="opt">
+     <group choice="plain">
+      <arg choice="plain"><option>-n</option></arg>
+      <arg choice="plain"><option>--schema</option></arg>
+     </group>
+     <replaceable>schema</replaceable>
+    </arg>
+   </arg>
+
+   <arg choice="opt">
+    <group choice="plain">
+     <arg choice="plain"><replaceable>dbname</replaceable></arg>
+     <arg choice="plain"><option>-a</option></arg>
+     <arg choice="plain"><option>--all</option></arg>
+    </group>
+   </arg>
+  </cmdsynopsis>
+
+  <cmdsynopsis>
+   <command>pg_repackdb</command>
+   <arg rep="repeat"><replaceable>connection-option</replaceable></arg>
+   <arg rep="repeat"><replaceable>option</replaceable></arg>
+
+   <arg choice="plain" rep="repeat">
+    <arg choice="opt">
+     <group choice="plain">
+      <arg choice="plain"><option>-N</option></arg>
+      <arg choice="plain"><option>--exclude-schema</option></arg>
+     </group>
+     <replaceable>schema</replaceable>
+    </arg>
+   </arg>
+
+   <arg choice="opt">
+    <group choice="plain">
+     <arg choice="plain"><replaceable>dbname</replaceable></arg>
+     <arg choice="plain"><option>-a</option></arg>
+     <arg choice="plain"><option>--all</option></arg>
+    </group>
+   </arg>
+  </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <application>pg_repackdb</application> is a utility for repacking a
+   <productname>PostgreSQL</productname> database.
+   <application>pg_repackdb</application> will also generate internal
+   statistics used by the <productname>PostgreSQL</productname> query
+   optimizer.
+  </para>
+
+  <para>
+   <application>pg_repackdb</application> is a wrapper around the SQL
+   command <link linkend="sql-repack"><command>REPACK</command></link> There
+   is no effective difference between repacking and analyzing databases via
+   this utility and via other methods for accessing the server.
+  </para>
+
+ </refsect1>
+
+
+ <refsect1>
+  <title>Options</title>
+
+   <para>
+    <application>pg_repackdb</application> accepts the following command-line arguments:
+    <variablelist>
+     <varlistentry>
+      <term><option>-a</option></term>
+      <term><option>--all</option></term>
+      <listitem>
+       <para>
+        Repack all databases.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option><optional>-d</optional> <replaceable class="parameter">dbname</replaceable></option></term>
+      <term><option><optional>--dbname=</optional><replaceable class="parameter">dbname</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the name of the database to be repacked or analyzed,
+        when <option>-a</option>/<option>--all</option> is not used.  If this
+        is not specified, the database name is read from the environment
+        variable <envar>PGDATABASE</envar>.  If that is not set, the user name
+        specified for the connection is used.
+        The <replaceable>dbname</replaceable> can be
+        a <link linkend="libpq-connstring">connection string</link>.  If so,
+        connection string parameters will override any conflicting command
+        line options.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-e</option></term>
+      <term><option>--echo</option></term>
+      <listitem>
+       <para>
+        Echo the commands that <application>pg_repackdb</application>
+        generates and sends to the server.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-j <replaceable class="parameter">njobs</replaceable></option></term>
+      <term><option>--jobs=<replaceable class="parameter">njobs</replaceable></option></term>
+      <listitem>
+       <para>
+        Execute the repack or analyze commands in parallel by running
+        <replaceable class="parameter">njobs</replaceable>
+        commands simultaneously.  This option may reduce the processing time
+        but it also increases the load on the database server.
+       </para>
+       <para>
+        <application>pg_repackdb</application> will open
+        <replaceable class="parameter">njobs</replaceable> connections to the
+        database, so make sure your <xref linkend="guc-max-connections"/>
+        setting is high enough to accommodate all connections.
+       </para>
+       <para>
+        Note that using this mode might cause deadlock failures if certain
+        system catalogs are processed in parallel.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
+      <term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
+      <listitem>
+       <para>
+        Repack or analyze all tables in
+        <replaceable class="parameter">schema</replaceable> only.  Multiple
+        schemas can be repacked by writing multiple <option>-n</option>
+        switches.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-N <replaceable class="parameter">schema</replaceable></option></term>
+      <term><option>--exclude-schema=<replaceable class="parameter">schema</replaceable></option></term>
+      <listitem>
+       <para>
+        Do not repack or analyze any tables in
+        <replaceable class="parameter">schema</replaceable>.  Multiple schemas
+        can be excluded by writing multiple <option>-N</option> switches.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-q</option></term>
+      <term><option>--quiet</option></term>
+      <listitem>
+       <para>
+        Do not display progress messages.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-t <replaceable class="parameter">table</replaceable> [ (<replaceable class="parameter">column</replaceable> [,...]) ]</option></term>
+      <term><option>--table=<replaceable class="parameter">table</replaceable> [ (<replaceable class="parameter">column</replaceable> [,...]) ]</option></term>
+      <listitem>
+       <para>
+        Repack or analyze <replaceable class="parameter">table</replaceable>
+        only.  Column names can be specified only in conjunction with
+        the <option>--analyze</option> option.  Multiple tables can be
+        repacked by writing multiple
+        <option>-t</option> switches.
+       </para>
+       <tip>
+        <para>
+         If you specify columns, you probably have to escape the parentheses
+         from the shell.  (See examples below.)
+        </para>
+       </tip>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-v</option></term>
+      <term><option>--verbose</option></term>
+      <listitem>
+       <para>
+        Print detailed information during processing.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+       <term><option>-V</option></term>
+       <term><option>--version</option></term>
+       <listitem>
+       <para>
+       Print the <application>pg_repackdb</application> version and exit.
+       </para>
+       </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-z</option></term>
+      <term><option>--analyze</option></term>
+      <listitem>
+       <para>
+        Also calculate statistics for use by the optimizer.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+       <term><option>-?</option></term>
+       <term><option>--help</option></term>
+       <listitem>
+       <para>
+       Show help about <application>pg_repackdb</application> command line
+       arguments, and exit.
+       </para>
+       </listitem>
+     </varlistentry>
+
+    </variablelist>
+   </para>
+
+   <para>
+    <application>pg_repackdb</application> also accepts
+    the following command-line arguments for connection parameters:
+    <variablelist>
+     <varlistentry>
+      <term><option>-h <replaceable class="parameter">host</replaceable></option></term>
+      <term><option>--host=<replaceable class="parameter">host</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the host name of the machine on which the server
+        is running.  If the value begins with a slash, it is used
+        as the directory for the Unix domain socket.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-p <replaceable class="parameter">port</replaceable></option></term>
+      <term><option>--port=<replaceable class="parameter">port</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the TCP port or local Unix domain socket file
+        extension on which the server
+        is listening for connections.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
+      <term><option>--username=<replaceable class="parameter">username</replaceable></option></term>
+      <listitem>
+       <para>
+        User name to connect as.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-w</option></term>
+      <term><option>--no-password</option></term>
+      <listitem>
+       <para>
+        Never issue a password prompt.  If the server requires
+        password authentication and a password is not available by
+        other means such as a <filename>.pgpass</filename> file, the
+        connection attempt will fail.  This option can be useful in
+        batch jobs and scripts where no user is present to enter a
+        password.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>-W</option></term>
+      <term><option>--password</option></term>
+      <listitem>
+       <para>
+        Force <application>pg_repackdb</application> to prompt for a
+        password before connecting to a database.
+       </para>
+
+       <para>
+        This option is never essential, since
+        <application>pg_repackdb</application> will automatically prompt
+        for a password if the server demands password authentication.
+        However, <application>pg_repackdb</application> will waste a
+        connection attempt finding out that the server wants a password.
+        In some cases it is worth typing <option>-W</option> to avoid the extra
+        connection attempt.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--maintenance-db=<replaceable class="parameter">dbname</replaceable></option></term>
+      <listitem>
+       <para>
+        When the <option>-a</option>/<option>--all</option> is used, connect
+        to this database to gather the list of databases to repack.
+        If not specified, the <literal>postgres</literal> database will be used,
+        or if that does not exist, <literal>template1</literal> will be used.
+        This can be a <link linkend="libpq-connstring">connection
+        string</link>.  If so, connection string parameters will override any
+        conflicting command line options.  Also, connection string parameters
+        other than the database name itself will be re-used when connecting
+        to other databases.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+ </refsect1>
+
+
+ <refsect1>
+  <title>Environment</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><envar>PGDATABASE</envar></term>
+    <term><envar>PGHOST</envar></term>
+    <term><envar>PGPORT</envar></term>
+    <term><envar>PGUSER</envar></term>
+
+    <listitem>
+     <para>
+      Default connection parameters
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><envar>PG_COLOR</envar></term>
+    <listitem>
+     <para>
+      Specifies whether to use color in diagnostic messages. Possible values
+      are <literal>always</literal>, <literal>auto</literal> and
+      <literal>never</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+
+  <para>
+   This utility, like most other <productname>PostgreSQL</productname> utilities,
+   also uses the environment variables supported by <application>libpq</application>
+   (see <xref linkend="libpq-envars"/>).
+  </para>
+
+ </refsect1>
+
+
+ <refsect1>
+  <title>Diagnostics</title>
+
+  <para>
+   In case of difficulty, see
+   <xref linkend="sql-repack"/> and <xref linkend="app-psql"/> for
+   discussions of potential problems and error messages.
+   The database server must be running at the
+   targeted host.  Also, any default connection settings and environment
+   variables used by the <application>libpq</application> front-end
+   library will apply.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+   <para>
+    To repack the database <literal>test</literal>:
+<screen>
+<prompt>$ </prompt><userinput>pg_repackdb test</userinput>
+</screen>
+   </para>
+
+   <para>
+    To repack and analyze for the optimizer a database named
+    <literal>bigdb</literal>:
+<screen>
+<prompt>$ </prompt><userinput>pg_repackdb --analyze bigdb</userinput>
+</screen>
+   </para>
+
+   <para>
+    To repack a single table
+    <literal>foo</literal> in a database named
+    <literal>xyzzy</literal>, and analyze a single column
+    <literal>bar</literal> of the table for the optimizer:
+<screen>
+<prompt>$ </prompt><userinput>pg_repackdb --analyze --verbose --table='foo(bar)' xyzzy</userinput>
+</screen></para>
+
+   <para>
+    To repack all tables in the <literal>foo</literal> and <literal>bar</literal> schemas
+    in a database named <literal>xyzzy</literal>:
+<screen>
+<prompt>$ </prompt><userinput>pg_repackdb --schema='foo' --schema='bar' xyzzy</userinput>
+</screen></para>
+
+
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-repack"/></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
new file mode 100644
index 00000000000..fd9d89f8aaa
--- /dev/null
+++ b/doc/src/sgml/ref/repack.sgml
@@ -0,0 +1,284 @@
+<!--
+doc/src/sgml/ref/repack.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="sql-repack">
+ <indexterm zone="sql-repack">
+  <primary>REPACK</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>REPACK</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>REPACK</refname>
+  <refpurpose>rewrite a table to reclaim disk space</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <replaceable class="parameter">table_name</replaceable> [ USING INDEX [ <replaceable class="parameter">index_name</replaceable> ] ] ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+    VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
+    ANALYSE | ANALYZE
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>REPACK</command> reclaims storage occupied by dead
+   tuples. Unlike <command>VACUUM</command>, it does so by rewriting the
+   entire contents of the table specified
+   by <replaceable class="parameter">table_name</replaceable> into a new disk
+   file with no extra space (except for the space guaranteed by
+   the <literal>fillfactor</literal> storage parameter), allowing unused space
+   to be returned to the operating system.
+  </para>
+
+  <para>
+   Without
+   a <replaceable class="parameter">table_name</replaceable>, <command>REPACK</command>
+   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.
+  </para>
+
+  <para>
+   If a <literal>USING INDEX</literal> clause is specified, the rows are
+   physically reordered based on information from an index.  Please see the
+   notes on clustering below.
+  </para>
+
+  <para>
+   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.
+  </para>
+
+  <refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering">
+   <title>Notes on Clustering</title>
+
+   <para>
+    If the <literal>USING INDEX</literal> clause is specified, the rows in
+    the table are physically reordered following an index: if an index name
+    is specified in the command, then that index is used; if no index name
+    is specified, then the index that has been configured as the index to
+    cluster on.  If no index has been configured in this way, an error is
+    thrown.  The index given in the <literal>USING INDEX</literal> clause
+    is configured as the index to cluster on, as well as an index given
+    to the <command>CLUSTER</command> command.  An index can be set
+    manually using <command>ALTER TABLE ... CLUSTER ON</command>, and reset
+    with <command>ALTER TABLE ... SET WITHOUT CLUSTER</command>.
+   </para>
+
+   <para>
+    If no table name is specified in <command>REPACK USING INDEX</command>,
+    all tables which have a clustering index defined and which the calling
+    user has privileges for are processed.
+   </para>
+
+   <para>
+    Clustering is a one-time operation: when the table is
+    subsequently updated, the changes are not clustered.  That is, no attempt
+    is made to store new or updated rows according to their index order.  (If
+    one wishes, one can periodically recluster by issuing the command again.
+    Also, setting the table's <literal>fillfactor</literal> storage parameter
+    to less than 100% can aid in preserving cluster ordering during updates,
+    since updated rows are kept on the same page if enough space is available
+    there.)
+   </para>
+
+   <para>
+    In cases where you are accessing single rows randomly within a table, the
+    actual order of the data in the table is unimportant. However, if you tend
+    to access some data more than others, and there is an index that groups
+    them together, you will benefit from using clustering.  If
+    you are requesting a range of indexed values from a table, or a single
+    indexed value that has multiple rows that match,
+    <command>REPACK</command> will help because once the index identifies the
+    table page for the first row that matches, all other rows that match are
+    probably already on the same table page, and so you save disk accesses and
+    speed up the query.
+   </para>
+
+   <para>
+    <command>REPACK</command> can re-sort the table using either an index scan
+    on the specified index (if the index is a b-tree), or a sequential scan
+    followed by sorting.  It will attempt to choose the method that will be
+    faster, based on planner cost parameters and available statistical
+    information.
+   </para>
+
+   <para>
+    Because the planner records statistics about the ordering of tables, it is
+    advisable to
+    run <link linkend="sql-analyze"><command>ANALYZE</command></link> on the
+    newly repacked table.  Otherwise, the planner might make poor choices of
+    query plans.
+   </para>
+  </refsect2>
+
+  <refsect2 id="sql-repack-notes-on-resources" xreflabel="Notes on Resources">
+   <title>Notes on Resources</title>
+
+   <para>
+    When an index scan or a sequential scan without sort is used, a temporary
+    copy of the table is created that contains the table data in the index
+    order.  Temporary copies of each index on the table are created as well.
+    Therefore, you need free space on disk at least equal to the sum of the
+    table size and the index sizes.
+   </para>
+
+   <para>
+    When a sequential scan and sort is used, a temporary sort file is also
+    created, so that the peak temporary space requirement is as much as double
+    the table size, plus the index sizes.  This method is often faster than
+    the index scan method, but if the disk space requirement is intolerable,
+    you can disable this choice by temporarily setting
+    <xref linkend="guc-enable-sort"/> to <literal>off</literal>.
+   </para>
+
+   <para>
+    It is advisable to set <xref linkend="guc-maintenance-work-mem"/> to a
+    reasonably large value (but not more than the amount of RAM you can
+    dedicate to the <command>REPACK</command> operation) before repacking.
+   </para>
+  </refsect2>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name (possibly schema-qualified) of a table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">index_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an index.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>VERBOSE</literal></term>
+    <listitem>
+     <para>
+      Prints a progress report as each table is repacked
+      at <literal>INFO</literal> level.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>ANALYZE</literal></term>
+    <term><literal>ANALYSE</literal></term>
+    <listitem>
+     <para>
+      Applies <xref linkend="sql-analyze"/> on the table after repacking.  This is
+      currently only supported when a single (non-partitioned) table is specified.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">boolean</replaceable></term>
+    <listitem>
+     <para>
+      Specifies whether the selected option should be turned on or off.
+      You can write <literal>TRUE</literal>, <literal>ON</literal>, or
+      <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
+      <literal>OFF</literal>, or <literal>0</literal> to disable it.  The
+      <replaceable class="parameter">boolean</replaceable> value can also
+      be omitted, in which case <literal>TRUE</literal> is assumed.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+   <para>
+    To repack a table, one must have the <literal>MAINTAIN</literal> privilege
+    on the table.
+   </para>
+
+   <para>
+    While <command>REPACK</command> is running, the <xref
+    linkend="guc-search-path"/> is temporarily changed to <literal>pg_catalog,
+    pg_temp</literal>.
+   </para>
+
+  <para>
+    Each backend running <command>REPACK</command> will report its progress
+    in the <structname>pg_stat_progress_repack</structname> view. See
+    <xref linkend="repack-progress-reporting"/> for details.
+  </para>
+
+   <para>
+    Repacking a partitioned table repacks each of its partitions. If an index
+    is specified, each partition is repacked using the partition of that
+    index. <command>REPACK</command> on a partitioned table cannot be executed
+    inside a transaction block.
+   </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Repack the table <literal>employees</literal>:
+<programlisting>
+REPACK employees;
+</programlisting>
+  </para>
+
+  <para>
+   Repack the table <literal>employees</literal> on the basis of its
+   index <literal>employees_ind</literal> (Since index is used here, this is
+   effectively clustering):
+<programlisting>
+REPACK employees USING INDEX employees_ind;
+</programlisting>
+  </para>
+
+  <para>
+   Repack all tables in the database on which you have
+   the <literal>MAINTAIN</literal> privilege:
+<programlisting>
+REPACK;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>REPACK</command> statement in the SQL standard.
+  </para>
+
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index bd5dcaf86a5..062b658cfcd 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -25,7 +25,6 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
-    FULL [ <replaceable class="parameter">boolean</replaceable> ]
     FREEZE [ <replaceable class="parameter">boolean</replaceable> ]
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
     ANALYZE [ <replaceable class="parameter">boolean</replaceable> ]
@@ -39,6 +38,7 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
     ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
     BUFFER_USAGE_LIMIT <replaceable class="parameter">size</replaceable>
+    FULL [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -95,20 +95,6 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
   <title>Parameters</title>
 
   <variablelist>
-   <varlistentry>
-    <term><literal>FULL</literal></term>
-    <listitem>
-     <para>
-      Selects <quote>full</quote> vacuum, which can reclaim more
-      space, but takes much longer and exclusively locks the table.
-      This method also requires extra disk space, since it writes a
-      new copy of the table and doesn't release the old copy until
-      the operation is complete.  Usually this should only be used when a
-      significant amount of space needs to be reclaimed from within the table.
-     </para>
-    </listitem>
-   </varlistentry>
-
    <varlistentry>
     <term><literal>FREEZE</literal></term>
     <listitem>
@@ -362,6 +348,23 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>FULL</literal></term>
+    <listitem>
+     <para>
+      This option, which is deprecated, makes <command>VACUUM</command>
+      behave like <command>REPACK</command> without a
+      <literal>USING INDEX</literal> clause.
+      This method of compacting the table takes much longer than
+      <command>VACUUM</command> and exclusively locks the table.
+      This method also requires extra disk space, since it writes a
+      new copy of the table and doesn't release the old copy until
+      the operation is complete.  Usually this should only be used when a
+      significant amount of space needs to be reclaimed from within the table.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">boolean</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index ff85ace83fc..2ee08e21f41 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -195,6 +195,7 @@
    &refreshMaterializedView;
    &reindex;
    &releaseSavepoint;
+   &repack;
    &reset;
    &revoke;
    &rollback;
@@ -257,6 +258,7 @@
    &pgIsready;
    &pgReceivewal;
    &pgRecvlogical;
+   &pgRepackdb;
    &pgRestore;
    &pgVerifyBackup;
    &psqlRef;
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bcbac844bb6..79f9de5d760 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -741,13 +741,13 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	if (OldIndex != NULL && !use_sort)
 	{
 		const int	ci_index[] = {
-			PROGRESS_CLUSTER_PHASE,
-			PROGRESS_CLUSTER_INDEX_RELID
+			PROGRESS_REPACK_PHASE,
+			PROGRESS_REPACK_INDEX_RELID
 		};
 		int64		ci_val[2];
 
 		/* Set phase and OIDOldIndex to columns */
-		ci_val[0] = PROGRESS_CLUSTER_PHASE_INDEX_SCAN_HEAP;
+		ci_val[0] = PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP;
 		ci_val[1] = RelationGetRelid(OldIndex);
 		pgstat_progress_update_multi_param(2, ci_index, ci_val);
 
@@ -759,15 +759,15 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 	else
 	{
 		/* In scan-and-sort mode and also VACUUM FULL, set phase */
-		pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
-									 PROGRESS_CLUSTER_PHASE_SEQ_SCAN_HEAP);
+		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+									 PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP);
 
 		tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL);
 		heapScan = (HeapScanDesc) tableScan;
 		indexScan = NULL;
 
 		/* Set total heap blocks */
-		pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS,
+		pgstat_progress_update_param(PROGRESS_REPACK_TOTAL_HEAP_BLKS,
 									 heapScan->rs_nblocks);
 	}
 
@@ -809,7 +809,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 				 * is manually updated to the correct value when the table
 				 * scan finishes.
 				 */
-				pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED,
+				pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
 											 heapScan->rs_nblocks);
 				break;
 			}
@@ -825,7 +825,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 			 */
 			if (prev_cblock != heapScan->rs_cblock)
 			{
-				pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED,
+				pgstat_progress_update_param(PROGRESS_REPACK_HEAP_BLKS_SCANNED,
 											 (heapScan->rs_cblock +
 											  heapScan->rs_nblocks -
 											  heapScan->rs_startblock
@@ -912,14 +912,14 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 			 * In scan-and-sort mode, report increase in number of tuples
 			 * scanned
 			 */
-			pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED,
+			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
 										 *num_tuples);
 		}
 		else
 		{
 			const int	ct_index[] = {
-				PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED,
-				PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN
+				PROGRESS_REPACK_HEAP_TUPLES_SCANNED,
+				PROGRESS_REPACK_HEAP_TUPLES_WRITTEN
 			};
 			int64		ct_val[2];
 
@@ -952,14 +952,14 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 		double		n_tuples = 0;
 
 		/* Report that we are now sorting tuples */
-		pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
-									 PROGRESS_CLUSTER_PHASE_SORT_TUPLES);
+		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+									 PROGRESS_REPACK_PHASE_SORT_TUPLES);
 
 		tuplesort_performsort(tuplesort);
 
 		/* Report that we are now writing new heap */
-		pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
-									 PROGRESS_CLUSTER_PHASE_WRITE_NEW_HEAP);
+		pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+									 PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP);
 
 		for (;;)
 		{
@@ -977,7 +977,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap,
 									 values, isnull,
 									 rwstate);
 			/* Report n_tuples */
-			pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN,
+			pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN,
 										 n_tuples);
 		}
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 5d9db167e59..08d4b8e44d7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -4079,7 +4079,7 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 		Assert(!ReindexIsProcessingIndex(indexOid));
 
 		/* Set index rebuild count */
-		pgstat_progress_update_param(PROGRESS_CLUSTER_INDEX_REBUILD_COUNT,
+		pgstat_progress_update_param(PROGRESS_REPACK_INDEX_REBUILD_COUNT,
 									 i);
 		i++;
 	}
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index c77fa0234bb..7924922e0fe 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1279,6 +1279,32 @@ CREATE VIEW pg_stat_progress_cluster AS
     FROM pg_stat_get_progress_info('CLUSTER') AS S
         LEFT JOIN pg_database D ON S.datid = D.oid;
 
+CREATE VIEW pg_stat_progress_repack AS
+    SELECT
+        S.pid AS pid,
+        S.datid AS datid,
+        D.datname AS datname,
+        S.relid AS relid,
+	-- param1 is currently unused
+        CASE S.param2 WHEN 0 THEN 'initializing'
+                      WHEN 1 THEN 'seq scanning heap'
+                      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'
+                      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
+    FROM pg_stat_get_progress_info('REPACK') AS S
+        LEFT JOIN pg_database D ON S.datid = D.oid;
+
+
 CREATE VIEW pg_stat_progress_create_index AS
     SELECT
         S.pid AS pid, S.datid AS datid, D.datname AS datname,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index b55221d44cd..8b64f9e6795 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -67,18 +67,41 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
-
-static void cluster_multiple_rels(List *rtcs, ClusterParams *params);
-static void rebuild_relation(Relation OldHeap, Relation index, bool verbose);
+static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
+								Oid indexOid, Oid userid, int options);
+static void rebuild_relation(RepackCommand cmd, bool usingindex,
+							 Relation OldHeap, Relation index, bool verbose);
 static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
-static List *get_tables_to_cluster(MemoryContext cluster_context);
-static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context,
-											   Oid indexOid);
-static bool cluster_is_permitted_for_relation(Oid relid, Oid userid);
+static List *get_tables_to_repack(RepackCommand cmd, bool usingindex,
+								  MemoryContext permcxt);
+static List *get_tables_to_repack_partitioned(RepackCommand cmd,
+											  MemoryContext cluster_context,
+											  Oid relid, bool rel_is_index);
+static bool cluster_is_permitted_for_relation(RepackCommand cmd,
+											  Oid relid, Oid userid);
+static Relation process_single_relation(RepackStmt *stmt,
+										ClusterParams *params);
+static Oid	determine_clustered_index(Relation rel, bool usingindex,
+									  const char *indexname);
 
 
+static const char *
+RepackCommandAsString(RepackCommand cmd)
+{
+	switch (cmd)
+	{
+		case REPACK_COMMAND_REPACK:
+			return "REPACK";
+		case REPACK_COMMAND_VACUUMFULL:
+			return "VACUUM";
+		case REPACK_COMMAND_CLUSTER:
+			return "CLUSTER";
+	}
+	return "???";
+}
+
 /*---------------------------------------------------------------------------
  * This cluster code allows for clustering multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -104,191 +127,155 @@ static bool cluster_is_permitted_for_relation(Oid relid, Oid userid);
  *---------------------------------------------------------------------------
  */
 void
-cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
+ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 {
-	ListCell   *lc;
 	ClusterParams params = {0};
-	bool		verbose = false;
 	Relation	rel = NULL;
-	Oid			indexOid = InvalidOid;
-	MemoryContext cluster_context;
+	MemoryContext repack_context;
 	List	   *rtcs;
 
 	/* Parse option list */
-	foreach(lc, stmt->params)
+	foreach_node(DefElem, opt, stmt->params)
 	{
-		DefElem    *opt = (DefElem *) lfirst(lc);
-
 		if (strcmp(opt->defname, "verbose") == 0)
-			verbose = defGetBoolean(opt);
+			params.options |= defGetBoolean(opt) ? CLUOPT_VERBOSE : 0;
+		else if (strcmp(opt->defname, "analyze") == 0 ||
+				 strcmp(opt->defname, "analyse") == 0)
+			params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0;
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("unrecognized CLUSTER option \"%s\"",
+					 errmsg("unrecognized %s option \"%s\"",
+							RepackCommandAsString(stmt->command),
 							opt->defname),
 					 parser_errposition(pstate, opt->location)));
 	}
 
-	params.options = (verbose ? CLUOPT_VERBOSE : 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)
 	{
-		/* This is the single-relation case. */
-		Oid			tableOid;
-
-		/*
-		 * Find, lock, and check permissions on the table.  We obtain
-		 * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
-		 * single-transaction case.
-		 */
-		tableOid = RangeVarGetRelidExtended(stmt->relation,
-											AccessExclusiveLock,
-											0,
-											RangeVarCallbackMaintainsTable,
-											NULL);
-		rel = table_open(tableOid, NoLock);
-
-		/*
-		 * Reject clustering a remote temp table ... their local buffer
-		 * manager is not going to cope.
-		 */
-		if (RELATION_IS_OTHER_TEMP(rel))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("cannot cluster temporary tables of other sessions")));
-
-		if (stmt->indexname == NULL)
-		{
-			ListCell   *index;
-
-			/* We need to find the index that has indisclustered set. */
-			foreach(index, RelationGetIndexList(rel))
-			{
-				indexOid = lfirst_oid(index);
-				if (get_index_isclustered(indexOid))
-					break;
-				indexOid = InvalidOid;
-			}
-
-			if (!OidIsValid(indexOid))
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_OBJECT),
-						 errmsg("there is no previously clustered index for table \"%s\"",
-								stmt->relation->relname)));
-		}
-		else
-		{
-			/*
-			 * The index is expected to be in the same namespace as the
-			 * relation.
-			 */
-			indexOid = get_relname_relid(stmt->indexname,
-										 rel->rd_rel->relnamespace);
-			if (!OidIsValid(indexOid))
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_OBJECT),
-						 errmsg("index \"%s\" for table \"%s\" does not exist",
-								stmt->indexname, stmt->relation->relname)));
-		}
-
-		/* For non-partitioned tables, do what we came here to do. */
-		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
-		{
-			cluster_rel(rel, indexOid, &params);
-			/* cluster_rel closes the relation, but keeps lock */
-
+		rel = process_single_relation(stmt, &params);
+		if (rel == NULL)
 			return;
-		}
 	}
 
+	/* Don't allow this for now.  Maybe we can add support for this later */
+	if (params.options & CLUOPT_ANALYZE)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot ANALYZE multiple tables"));
+
 	/*
 	 * 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.
 	 */
-	PreventInTransactionBlock(isTopLevel, "CLUSTER");
+	PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command));
 
 	/* Also, we need a memory context to hold our list of relations */
-	cluster_context = AllocSetContextCreate(PortalContext,
-											"Cluster",
-											ALLOCSET_DEFAULT_SIZES);
+	repack_context = AllocSetContextCreate(PortalContext,
+										   "Repack",
+										   ALLOCSET_DEFAULT_SIZES);
+
+	params.options |= CLUOPT_RECHECK;
 
 	/*
-	 * Either we're processing a partitioned table, or we were not given any
-	 * table name at all.  In either case, obtain a list of relations to
-	 * process.
-	 *
-	 * In the former case, an index name must have been given, so we don't
-	 * need to recheck its "indisclustered" bit, but we have to check that it
-	 * is an index that we can cluster on.  In the latter case, we set the
-	 * option bit to have indisclustered verified.
-	 *
-	 * Rechecking the relation itself is necessary here in all cases.
+	 * If we don't have a relation yet, determine a relation list.  If we do,
+	 * then it must be a partitioned table, and we want to process its
+	 * partitions.
 	 */
-	params.options |= CLUOPT_RECHECK;
-	if (rel != NULL)
+	if (rel == NULL)
 	{
-		Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-		check_index_is_clusterable(rel, indexOid, AccessShareLock);
-		rtcs = get_tables_to_cluster_partitioned(cluster_context, indexOid);
-
-		/* close relation, releasing lock on parent table */
-		table_close(rel, AccessExclusiveLock);
+		Assert(stmt->indexname == NULL);
+		rtcs = get_tables_to_repack(stmt->command, stmt->usingindex,
+									repack_context);
 	}
 	else
 	{
-		rtcs = get_tables_to_cluster(cluster_context);
-		params.options |= CLUOPT_RECHECK_ISCLUSTERED;
+		Oid			relid;
+		bool		rel_is_index;
+
+		Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+
+		/*
+		 * If an index name was specified, resolve it now and pass it down.
+		 */
+		if (stmt->usingindex)
+		{
+			/*
+			 * XXX how should this behave?  Passing no index to a partitioned
+			 * table could be useful to have certain partitions clustered by
+			 * some index, and other partitions by a different index.
+			 */
+			if (!stmt->indexname)
+				ereport(ERROR,
+						errmsg("there is no previously clustered index for table \"%s\"",
+							   RelationGetRelationName(rel)));
+
+			relid = determine_clustered_index(rel, true, stmt->indexname);
+			if (!OidIsValid(relid))
+				elog(ERROR, "unable to determine index to cluster on");
+			/* XXX is this the right place for this check? */
+			check_index_is_clusterable(rel, relid, AccessExclusiveLock);
+			rel_is_index = true;
+		}
+		else
+		{
+			relid = RelationGetRelid(rel);
+			rel_is_index = false;
+		}
+
+		rtcs = get_tables_to_repack_partitioned(stmt->command, repack_context,
+												relid, rel_is_index);
+
+		/* close parent relation, releasing lock on it */
+		table_close(rel, AccessExclusiveLock);
+		rel = NULL;
 	}
 
-	/* Do the job. */
-	cluster_multiple_rels(rtcs, &params);
-
-	/* Start a new transaction for the cleanup work. */
-	StartTransactionCommand();
-
-	/* Clean up working storage */
-	MemoryContextDelete(cluster_context);
-}
-
-/*
- * Given a list of relations to cluster, process each of them in a separate
- * transaction.
- *
- * We expect to be in a transaction at start, but there isn't one when we
- * return.
- */
-static void
-cluster_multiple_rels(List *rtcs, ClusterParams *params)
-{
-	ListCell   *lc;
-
 	/* Commit to get out of starting transaction */
 	PopActiveSnapshot();
 	CommitTransactionCommand();
 
 	/* Cluster the tables, each in a separate transaction */
-	foreach(lc, rtcs)
+	Assert(rel == NULL);
+	foreach_ptr(RelToCluster, rtc, rtcs)
 	{
-		RelToCluster *rtc = (RelToCluster *) lfirst(lc);
-		Relation	rel;
-
 		/* Start a new transaction for each relation. */
 		StartTransactionCommand();
 
+		/*
+		 * Open the target table, coping with the case where it has been
+		 * dropped.
+		 */
+		rel = try_table_open(rtc->tableOid, AccessExclusiveLock);
+		if (rel == NULL)
+		{
+			CommitTransactionCommand();
+			continue;
+		}
+
 		/* functions in indexes may want a snapshot set */
 		PushActiveSnapshot(GetTransactionSnapshot());
 
-		rel = table_open(rtc->tableOid, AccessExclusiveLock);
-
 		/* Process this table */
-		cluster_rel(rel, rtc->indexOid, params);
+		cluster_rel(stmt->command, stmt->usingindex,
+					rel, rtc->indexOid, &params);
 		/* cluster_rel closes the relation, but keeps lock */
 
 		PopActiveSnapshot();
 		CommitTransactionCommand();
 	}
+
+	/* Start a new transaction for the cleanup work. */
+	StartTransactionCommand();
+
+	/* Clean up working storage */
+	MemoryContextDelete(repack_context);
 }
 
 /*
@@ -304,11 +291,14 @@ cluster_multiple_rels(List *rtcs, ClusterParams *params)
  * them incrementally while we load the table.
  *
  * If indexOid is InvalidOid, the table will be rewritten in physical order
- * instead of index order.  This is the new implementation of VACUUM FULL,
- * and error messages should refer to the operation as VACUUM not CLUSTER.
+ * instead of index order.
+ *
+ * 'cmd' indicates which command is being executed, to be used for error
+ * messages.
  */
 void
-cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params)
+cluster_rel(RepackCommand cmd, bool usingindex,
+			Relation OldHeap, Oid indexOid, ClusterParams *params)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Oid			save_userid;
@@ -323,13 +313,25 @@ cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params)
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
 
-	pgstat_progress_start_command(PROGRESS_COMMAND_CLUSTER, tableOid);
-	if (OidIsValid(indexOid))
-		pgstat_progress_update_param(PROGRESS_CLUSTER_COMMAND,
-									 PROGRESS_CLUSTER_COMMAND_CLUSTER);
+	if (cmd == REPACK_COMMAND_REPACK)
+		pgstat_progress_start_command(PROGRESS_COMMAND_REPACK, tableOid);
 	else
-		pgstat_progress_update_param(PROGRESS_CLUSTER_COMMAND,
+		pgstat_progress_start_command(PROGRESS_COMMAND_CLUSTER, tableOid);
+
+	if (cmd == REPACK_COMMAND_REPACK)
+		pgstat_progress_update_param(PROGRESS_REPACK_COMMAND,
+									 PROGRESS_REPACK_COMMAND_REPACK);
+	else if (cmd == REPACK_COMMAND_CLUSTER)
+	{
+		pgstat_progress_update_param(PROGRESS_REPACK_COMMAND,
+									 PROGRESS_CLUSTER_COMMAND_CLUSTER);
+	}
+	else
+	{
+		Assert(cmd == REPACK_COMMAND_VACUUMFULL);
+		pgstat_progress_update_param(PROGRESS_REPACK_COMMAND,
 									 PROGRESS_CLUSTER_COMMAND_VACUUM_FULL);
+	}
 
 	/*
 	 * Switch to the table owner's userid, so that any index functions are run
@@ -351,63 +353,21 @@ cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params)
 	 * to cluster a not-previously-clustered index.
 	 */
 	if (recheck)
-	{
-		/* Check that the user still has privileges for the relation */
-		if (!cluster_is_permitted_for_relation(tableOid, save_userid))
-		{
-			relation_close(OldHeap, AccessExclusiveLock);
+		if (!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+								 params->options))
 			goto out;
-		}
-
-		/*
-		 * Silently skip a temp table for a remote session.  Only doing this
-		 * check in the "recheck" case is appropriate (which currently means
-		 * somebody is executing a database-wide CLUSTER or on a partitioned
-		 * table), because there is another check in cluster() which will stop
-		 * any attempt to cluster remote temp tables by name.  There is
-		 * another check in cluster_rel which is redundant, but we leave it
-		 * for extra safety.
-		 */
-		if (RELATION_IS_OTHER_TEMP(OldHeap))
-		{
-			relation_close(OldHeap, AccessExclusiveLock);
-			goto out;
-		}
-
-		if (OidIsValid(indexOid))
-		{
-			/*
-			 * Check that the index still exists
-			 */
-			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
-			{
-				relation_close(OldHeap, AccessExclusiveLock);
-				goto out;
-			}
-
-			/*
-			 * Check that the index is still the one with indisclustered set,
-			 * if needed.
-			 */
-			if ((params->options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
-				!get_index_isclustered(indexOid))
-			{
-				relation_close(OldHeap, AccessExclusiveLock);
-				goto out;
-			}
-		}
-	}
 
 	/*
-	 * We allow VACUUM FULL, but not CLUSTER, on shared catalogs.  CLUSTER
-	 * would work in most respects, but the index would only get marked as
-	 * indisclustered in the current database, leading to unexpected behavior
-	 * if CLUSTER were later invoked in another database.
+	 * We allow repacking shared catalogs only when not using an index. It
+	 * would work to use an index in most respects, but the index would only
+	 * get marked as indisclustered in the current database, leading to
+	 * unexpected behavior if CLUSTER were later invoked in another database.
 	 */
-	if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared)
+	if (usingindex && OldHeap->rd_rel->relisshared)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot cluster a shared catalog")));
+				 errmsg("cannot run \"%s\" on a shared catalog",
+						RepackCommandAsString(cmd))));
 
 	/*
 	 * Don't process temp tables of other backends ... their local buffer
@@ -415,21 +375,30 @@ cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params)
 	 */
 	if (RELATION_IS_OTHER_TEMP(OldHeap))
 	{
-		if (OidIsValid(indexOid))
+		if (cmd == REPACK_COMMAND_CLUSTER)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot cluster temporary tables of other sessions")));
+		else if (cmd == REPACK_COMMAND_REPACK)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot repack temporary tables of other sessions")));
+		}
 		else
+		{
+			Assert(cmd == REPACK_COMMAND_VACUUMFULL);
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot vacuum temporary tables of other sessions")));
+		}
 	}
 
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
 	 */
-	CheckTableNotInUse(OldHeap, OidIsValid(indexOid) ? "CLUSTER" : "VACUUM");
+	CheckTableNotInUse(OldHeap, RepackCommandAsString(cmd));
 
 	/* Check heap and index are valid to cluster on */
 	if (OidIsValid(indexOid))
@@ -469,7 +438,7 @@ cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params)
 	TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	rebuild_relation(OldHeap, index, verbose);
+	rebuild_relation(cmd, usingindex, OldHeap, index, verbose);
 	/* rebuild_relation closes OldHeap, and index if valid */
 
 out:
@@ -482,6 +451,63 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Check if the table (and its index) still meets the requirements of
+ * cluster_rel().
+ */
+static bool
+cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
+					Oid userid, int options)
+{
+	Oid			tableOid = RelationGetRelid(OldHeap);
+
+	/* Check that the user still has privileges for the relation */
+	if (!cluster_is_permitted_for_relation(cmd, tableOid, userid))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		return false;
+	}
+
+	/*
+	 * Silently skip a temp table for a remote session.  Only doing this check
+	 * in the "recheck" case is appropriate (which currently means somebody is
+	 * executing a database-wide CLUSTER or on a partitioned table), because
+	 * there is another check in cluster() which will stop any attempt to
+	 * cluster remote temp tables by name.  There is another check in
+	 * cluster_rel which is redundant, but we leave it for extra safety.
+	 */
+	if (RELATION_IS_OTHER_TEMP(OldHeap))
+	{
+		relation_close(OldHeap, AccessExclusiveLock);
+		return false;
+	}
+
+	if (OidIsValid(indexOid))
+	{
+		/*
+		 * Check that the index still exists
+		 */
+		if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
+		{
+			relation_close(OldHeap, AccessExclusiveLock);
+			return false;
+		}
+
+		/*
+		 * Check that the index is still the one with indisclustered set, if
+		 * needed.
+		 */
+		if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
+			!get_index_isclustered(indexOid))
+		{
+			relation_close(OldHeap, AccessExclusiveLock);
+			return false;
+		}
+	}
+
+	return true;
+}
+
 /*
  * Verify that the specified heap and index are valid to cluster on
  *
@@ -626,7 +652,8 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
  * On exit, they are closed, but locks on them are not released.
  */
 static void
-rebuild_relation(Relation OldHeap, Relation index, bool verbose)
+rebuild_relation(RepackCommand cmd, bool usingindex,
+				 Relation OldHeap, Relation index, bool verbose)
 {
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Oid			accessMethod = OldHeap->rd_rel->relam;
@@ -642,8 +669,8 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose)
 	Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) &&
 		   (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false)));
 
-	if (index)
-		/* Mark the correct index as clustered */
+	/* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */
+	if (usingindex)
 		mark_index_clustered(OldHeap, RelationGetRelid(index), true);
 
 	/* Remember info about rel before closing OldHeap */
@@ -1458,8 +1485,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	int			i;
 
 	/* Report that we are now swapping relation files */
-	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
-								 PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES);
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_SWAP_REL_FILES);
 
 	/* Zero out possible results from swapped_relation_files */
 	memset(mapped_tables, 0, sizeof(mapped_tables));
@@ -1509,14 +1536,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 		reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
 
 	/* Report that we are now reindexing relations */
-	pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
-								 PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);
+	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_CLUSTER_PHASE,
-								 PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP);
+	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
+								 PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
 
 	/*
 	 * If the relation being rebuilt is pg_class, swap_relation_files()
@@ -1632,69 +1659,137 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 	}
 }
 
-
 /*
- * Get a list of tables that the current user has privileges on and
- * have indisclustered set.  Return the list in a List * of RelToCluster
- * (stored in the specified memory context), each one giving the tableOid
- * and the indexOid on which the table is already clustered.
+ * Determine which relations to process, when REPACK/CLUSTER is called
+ * without specifying a table name.  The exact process depends on whether
+ * USING INDEX was given or not, and in any case we only return tables and
+ * materialized views that the current user has privileges to repack/cluster.
+ *
+ * If USING INDEX was given, we scan pg_index to find those that have
+ * indisclustered set; if it was not given, scan pg_class and return all
+ * tables.
+ *
+ * Return it as a list of RelToCluster in the given memory context.
  */
 static List *
-get_tables_to_cluster(MemoryContext cluster_context)
+get_tables_to_repack(RepackCommand command, bool usingindex,
+					 MemoryContext permcxt)
 {
-	Relation	indRelation;
+	Relation	catalog;
 	TableScanDesc scan;
-	ScanKeyData entry;
-	HeapTuple	indexTuple;
-	Form_pg_index index;
+	HeapTuple	tuple;
 	MemoryContext old_context;
 	List	   *rtcs = NIL;
 
-	/*
-	 * Get all indexes that have indisclustered set and that the current user
-	 * has the appropriate privileges for.
-	 */
-	indRelation = table_open(IndexRelationId, AccessShareLock);
-	ScanKeyInit(&entry,
-				Anum_pg_index_indisclustered,
-				BTEqualStrategyNumber, F_BOOLEQ,
-				BoolGetDatum(true));
-	scan = table_beginscan_catalog(indRelation, 1, &entry);
-	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+	if (usingindex)
 	{
-		RelToCluster *rtc;
+		ScanKeyData entry;
 
-		index = (Form_pg_index) GETSTRUCT(indexTuple);
+		catalog = table_open(IndexRelationId, AccessShareLock);
+		ScanKeyInit(&entry,
+					Anum_pg_index_indisclustered,
+					BTEqualStrategyNumber, F_BOOLEQ,
+					BoolGetDatum(true));
+		scan = table_beginscan_catalog(catalog, 1, &entry);
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			RelToCluster *rtc;
+			Form_pg_index index;
 
-		if (!cluster_is_permitted_for_relation(index->indrelid, GetUserId()))
-			continue;
+			index = (Form_pg_index) GETSTRUCT(tuple);
 
-		/* Use a permanent memory context for the result list */
-		old_context = MemoryContextSwitchTo(cluster_context);
+			/*
+			 * XXX I think the only reason there's no test failure here is
+			 * that we seldom have clustered indexes that would be affected by
+			 * concurrency.  Maybe we should also do the
+			 * ConditionalLockRelationOid+SearchSysCacheExists dance that we
+			 * do below.
+			 */
+			if (!cluster_is_permitted_for_relation(command, index->indrelid,
+												   GetUserId()))
+				continue;
 
-		rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
-		rtc->tableOid = index->indrelid;
-		rtc->indexOid = index->indexrelid;
-		rtcs = lappend(rtcs, rtc);
+			/* Use a permanent memory context for the result list */
+			old_context = MemoryContextSwitchTo(permcxt);
 
-		MemoryContextSwitchTo(old_context);
+			rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
+			rtc->tableOid = index->indrelid;
+			rtc->indexOid = index->indexrelid;
+			rtcs = lappend(rtcs, rtc);
+
+			MemoryContextSwitchTo(old_context);
+		}
 	}
-	table_endscan(scan);
+	else
+	{
+		catalog = table_open(RelationRelationId, AccessShareLock);
+		scan = table_beginscan_catalog(catalog, 0, NULL);
 
-	relation_close(indRelation, AccessShareLock);
+		while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+		{
+			RelToCluster *rtc;
+			Form_pg_class class;
+
+			class = (Form_pg_class) GETSTRUCT(tuple);
+
+			/*
+			 * Try to obtain a light lock on the table, to ensure it doesn't
+			 * go away while we collect the list.  If we cannot, just
+			 * disregard the table.  XXX we could release at the bottom of the
+			 * loop, but for now just hold it until this transaction is
+			 * finished.
+			 */
+			if (!ConditionalLockRelationOid(class->oid, AccessShareLock))
+				continue;
+
+			/* Verify that the table still exists. */
+			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(class->oid)))
+			{
+				/* Release useless lock */
+				UnlockRelationOid(class->oid, AccessShareLock);
+				continue;
+			}
+
+			/* Can only process plain tables and matviews */
+			if (class->relkind != RELKIND_RELATION &&
+				class->relkind != RELKIND_MATVIEW)
+				continue;
+
+			if (!cluster_is_permitted_for_relation(command, class->oid,
+												   GetUserId()))
+				continue;
+
+			/* Use a permanent memory context for the result list */
+			old_context = MemoryContextSwitchTo(permcxt);
+
+			rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
+			rtc->tableOid = class->oid;
+			rtc->indexOid = InvalidOid;
+			rtcs = lappend(rtcs, rtc);
+
+			MemoryContextSwitchTo(old_context);
+		}
+	}
+
+	table_endscan(scan);
+	relation_close(catalog, AccessShareLock);
 
 	return rtcs;
 }
 
 /*
- * Given an index on a partitioned table, return a list of RelToCluster for
+ * Given a partitioned table or its index, return a list of RelToCluster for
  * all the children leaves tables/indexes.
  *
  * Like expand_vacuum_rel, but here caller must hold AccessExclusiveLock
  * on the table containing the index.
+ *
+ * 'rel_is_index' tells whether 'relid' is that of an index (true) or of the
+ * owning relation.
  */
 static List *
-get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
+get_tables_to_repack_partitioned(RepackCommand cmd, MemoryContext cluster_context,
+								 Oid relid, bool rel_is_index)
 {
 	List	   *inhoids;
 	ListCell   *lc;
@@ -1702,17 +1797,33 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
 	MemoryContext old_context;
 
 	/* Do not lock the children until they're processed */
-	inhoids = find_all_inheritors(indexOid, NoLock, NULL);
+	inhoids = find_all_inheritors(relid, NoLock, NULL);
 
 	foreach(lc, inhoids)
 	{
-		Oid			indexrelid = lfirst_oid(lc);
-		Oid			relid = IndexGetRelation(indexrelid, false);
+		Oid			inhoid = lfirst_oid(lc);
+		Oid			inhrelid,
+					inhindid;
 		RelToCluster *rtc;
 
-		/* consider only leaf indexes */
-		if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
-			continue;
+		if (rel_is_index)
+		{
+			/* consider only leaf indexes */
+			if (get_rel_relkind(inhoid) != RELKIND_INDEX)
+				continue;
+
+			inhrelid = IndexGetRelation(inhoid, false);
+			inhindid = inhoid;
+		}
+		else
+		{
+			/* consider only leaf relations */
+			if (get_rel_relkind(inhoid) != RELKIND_RELATION)
+				continue;
+
+			inhrelid = inhoid;
+			inhindid = InvalidOid;
+		}
 
 		/*
 		 * It's possible that the user does not have privileges to CLUSTER the
@@ -1720,15 +1831,15 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
 		 * table.  We skip any partitions which the user is not permitted to
 		 * CLUSTER.
 		 */
-		if (!cluster_is_permitted_for_relation(relid, GetUserId()))
+		if (!cluster_is_permitted_for_relation(cmd, inhrelid, GetUserId()))
 			continue;
 
 		/* Use a permanent memory context for the result list */
 		old_context = MemoryContextSwitchTo(cluster_context);
 
 		rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
-		rtc->tableOid = relid;
-		rtc->indexOid = indexrelid;
+		rtc->tableOid = inhrelid;
+		rtc->indexOid = inhindid;
 		rtcs = lappend(rtcs, rtc);
 
 		MemoryContextSwitchTo(old_context);
@@ -1742,13 +1853,148 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
  * function emits a WARNING.
  */
 static bool
-cluster_is_permitted_for_relation(Oid relid, Oid userid)
+cluster_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK)
 		return true;
 
+	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 	ereport(WARNING,
-			(errmsg("permission denied to cluster \"%s\", skipping it",
-					get_rel_name(relid))));
+			errmsg("permission denied to execute %s on \"%s\", skipping it",
+				   cmd == REPACK_COMMAND_CLUSTER ? "CLUSTER" : "REPACK",
+				   get_rel_name(relid)));
+
 	return false;
 }
+
+
+/*
+ * Given a RepackStmt with an indicated relation name, resolve the relation
+ * name, obtain lock on it, then determine what to do based on the relation
+ * type: if it's not a partitioned table, repack it as indicated (using an
+ * existing clustered index, or following the indicated index), and return
+ * NULL.
+ *
+ * On the other hand, if the table is partitioned, do nothing further and
+ * instead return the opened relcache entry, so that caller can process the
+ * partitions using the multiple-table handling code.  The index name is not
+ * resolve in this case.
+ */
+static Relation
+process_single_relation(RepackStmt *stmt, ClusterParams *params)
+{
+	Relation	rel;
+	Oid			tableOid;
+
+	Assert(stmt->relation != NULL);
+	Assert(stmt->command == REPACK_COMMAND_CLUSTER ||
+		   stmt->command == REPACK_COMMAND_REPACK);
+
+	/*
+	 * Find, lock, and check permissions on the table.  We obtain
+	 * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
+	 * single-transaction case.
+	 */
+	tableOid = RangeVarGetRelidExtended(stmt->relation,
+										AccessExclusiveLock,
+										0,
+										RangeVarCallbackMaintainsTable,
+										NULL);
+	rel = table_open(tableOid, NoLock);
+
+	/*
+	 * Reject clustering a remote temp table ... their local buffer manager is
+	 * not going to cope.
+	 */
+	if (RELATION_IS_OTHER_TEMP(rel))
+	{
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot execute %s on temporary tables of other sessions",
+					   RepackCommandAsString(stmt->command)));
+	}
+
+	/*
+	 * For partitioned tables, let caller handle this.  Otherwise, process it
+	 * here and we're done.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		return rel;
+	else
+	{
+		Oid			indexOid;
+
+		indexOid = determine_clustered_index(rel, stmt->usingindex,
+											 stmt->indexname);
+		if (OidIsValid(indexOid))
+			check_index_is_clusterable(rel, indexOid, AccessExclusiveLock);
+		cluster_rel(stmt->command, stmt->usingindex, rel, indexOid, params);
+
+		/* Do an analyze, if requested */
+		if (params->options & CLUOPT_ANALYZE)
+		{
+			VacuumParams vac_params = {0};
+
+			vac_params.options |= VACOPT_ANALYZE;
+			if (params->options & CLUOPT_VERBOSE)
+				vac_params.options |= VACOPT_VERBOSE;
+			analyze_rel(RelationGetRelid(rel), NULL, vac_params, NIL, true,
+						NULL);
+		}
+
+		return NULL;
+	}
+}
+
+/*
+ * Given a relation and the usingindex/indexname options in a
+ * REPACK USING INDEX or CLUSTER command, return the OID of the index to use
+ * for clustering the table.
+ *
+ * Caller must hold lock on the relation so that the set of indexes doesn't
+ * change, and must call check_index_is_clusterable.
+ */
+static Oid
+determine_clustered_index(Relation rel, bool usingindex, const char *indexname)
+{
+	Oid			indexOid;
+
+	if (indexname == NULL && usingindex)
+	{
+		ListCell   *lc;
+
+		/* Find an index with indisclustered set, or report error */
+		foreach(lc, RelationGetIndexList(rel))
+		{
+			indexOid = lfirst_oid(lc);
+
+			if (get_index_isclustered(indexOid))
+				break;
+			indexOid = InvalidOid;
+		}
+
+		if (!OidIsValid(indexOid))
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("there is no previously clustered index for table \"%s\"",
+						   RelationGetRelationName(rel)));
+	}
+	else if (indexname != NULL)
+	{
+		/*
+		 * An index was specified; figure out its OID.  It must be in the same
+		 * namespace as the relation.
+		 */
+		indexOid = get_relname_relid(indexname,
+									 rel->rd_rel->relnamespace);
+		if (!OidIsValid(indexOid))
+			ereport(ERROR,
+					errcode(ERRCODE_UNDEFINED_OBJECT),
+					errmsg("index \"%s\" for table \"%s\" does not exist",
+						   indexname, RelationGetRelationName(rel)));
+	}
+	else
+		indexOid = InvalidOid;
+
+	return indexOid;
+}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..8863ad0e8bd 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2287,7 +2287,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 				cluster_params.options |= CLUOPT_VERBOSE;
 
 			/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
-			cluster_rel(rel, InvalidOid, &cluster_params);
+			cluster_rel(REPACK_COMMAND_VACUUMFULL, false, rel, InvalidOid,
+						&cluster_params);
 			/* cluster_rel closes the relation, but keeps lock */
 
 			rel = NULL;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..ab52c171b84 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -280,7 +280,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterCompositeTypeStmt AlterUserMappingStmt
 		AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt
 		AlterDefaultPrivilegesStmt DefACLAction
-		AnalyzeStmt CallStmt ClosePortalStmt ClusterStmt CommentStmt
+		AnalyzeStmt CallStmt ClosePortalStmt CommentStmt
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
 		CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
 		CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
@@ -297,7 +297,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
 		ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
 		CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
-		RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
+		RemoveFuncStmt RemoveOperStmt RenameStmt RepackStmt ReturnStmt RevokeStmt RevokeRoleStmt
 		RuleActionStmt RuleActionStmtOrEmpty RuleStmt
 		SecLabelStmt SelectStmt TransactionStmt TransactionStmtLegacy TruncateStmt
 		UnlistenStmt UpdateStmt VacuumStmt
@@ -316,7 +316,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>			opt_single_name
 %type <list>		opt_qualified_name
-%type <boolean>		opt_concurrently
+%type <boolean>		opt_concurrently opt_usingindex
 %type <dbehavior>	opt_drop_behavior
 %type <list>		opt_utility_option_list
 %type <list>		utility_option_list
@@ -763,7 +763,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	QUOTE QUOTES
 
 	RANGE READ REAL REASSIGN RECURSIVE REF_P REFERENCES REFERENCING
-	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
+	REFRESH REINDEX RELATIVE_P RELEASE RENAME REPACK REPEATABLE REPLACE REPLICA
 	RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
 	ROUTINE ROUTINES ROW ROWS RULE
 
@@ -1025,7 +1025,6 @@ stmt:
 			| CallStmt
 			| CheckPointStmt
 			| ClosePortalStmt
-			| ClusterStmt
 			| CommentStmt
 			| ConstraintsSetStmt
 			| CopyStmt
@@ -1099,6 +1098,7 @@ stmt:
 			| RemoveFuncStmt
 			| RemoveOperStmt
 			| RenameStmt
+			| RepackStmt
 			| RevokeStmt
 			| RevokeRoleStmt
 			| RuleStmt
@@ -1135,6 +1135,11 @@ opt_concurrently:
 			| /*EMPTY*/						{ $$ = false; }
 		;
 
+opt_usingindex:
+			USING INDEX						{ $$ = true; }
+			| /* EMPTY */					{ $$ = false; }
+		;
+
 opt_drop_behavior:
 			CASCADE							{ $$ = DROP_CASCADE; }
 			| RESTRICT						{ $$ = DROP_RESTRICT; }
@@ -11913,38 +11918,91 @@ CreateConversionStmt:
 /*****************************************************************************
  *
  *		QUERY:
+ *				REPACK [ (options) ] [ <qualified_name> [ USING INDEX <index_name> ] ]
+ *
+ *			obsolete variants:
  *				CLUSTER (options) [ <qualified_name> [ USING <index_name> ] ]
  *				CLUSTER [VERBOSE] [ <qualified_name> [ USING <index_name> ] ]
  *				CLUSTER [VERBOSE] <index_name> ON <qualified_name> (for pre-8.3)
  *
  *****************************************************************************/
 
-ClusterStmt:
-			CLUSTER '(' utility_option_list ')' qualified_name cluster_index_specification
+RepackStmt:
+			REPACK opt_utility_option_list qualified_name USING INDEX name
 				{
-					ClusterStmt *n = makeNode(ClusterStmt);
+					RepackStmt *n = makeNode(RepackStmt);
 
+					n->command = REPACK_COMMAND_REPACK;
+					n->relation = $3;
+					n->indexname = $6;
+					n->usingindex = true;
+					n->params = $2;
+					$$ = (Node *) n;
+				}
+			| REPACK opt_utility_option_list qualified_name opt_usingindex
+				{
+					RepackStmt *n = makeNode(RepackStmt);
+
+					n->command = REPACK_COMMAND_REPACK;
+					n->relation = $3;
+					n->indexname = NULL;
+					n->usingindex = $4;
+					n->params = $2;
+					$$ = (Node *) n;
+				}
+			| REPACK '(' utility_option_list ')'
+				{
+					RepackStmt *n = makeNode(RepackStmt);
+
+					n->command = REPACK_COMMAND_REPACK;
+					n->relation = NULL;
+					n->indexname = NULL;
+					n->usingindex = false;
+					n->params = $3;
+					$$ = (Node *) n;
+				}
+			| REPACK opt_usingindex
+				{
+					RepackStmt *n = makeNode(RepackStmt);
+
+					n->command = REPACK_COMMAND_REPACK;
+					n->relation = NULL;
+					n->indexname = NULL;
+					n->usingindex = $2;
+					n->params = NIL;
+					$$ = (Node *) n;
+				}
+			| CLUSTER '(' utility_option_list ')' qualified_name cluster_index_specification
+				{
+					RepackStmt *n = makeNode(RepackStmt);
+
+					n->command = REPACK_COMMAND_CLUSTER;
 					n->relation = $5;
 					n->indexname = $6;
+					n->usingindex = true;
 					n->params = $3;
 					$$ = (Node *) n;
 				}
 			| CLUSTER opt_utility_option_list
 				{
-					ClusterStmt *n = makeNode(ClusterStmt);
+					RepackStmt *n = makeNode(RepackStmt);
 
+					n->command = REPACK_COMMAND_CLUSTER;
 					n->relation = NULL;
 					n->indexname = NULL;
+					n->usingindex = true;
 					n->params = $2;
 					$$ = (Node *) n;
 				}
 			/* unparenthesized VERBOSE kept for pre-14 compatibility */
 			| CLUSTER opt_verbose qualified_name cluster_index_specification
 				{
-					ClusterStmt *n = makeNode(ClusterStmt);
+					RepackStmt *n = makeNode(RepackStmt);
 
+					n->command = REPACK_COMMAND_CLUSTER;
 					n->relation = $3;
 					n->indexname = $4;
+					n->usingindex = true;
 					if ($2)
 						n->params = list_make1(makeDefElem("verbose", NULL, @2));
 					$$ = (Node *) n;
@@ -11952,20 +12010,24 @@ ClusterStmt:
 			/* unparenthesized VERBOSE kept for pre-17 compatibility */
 			| CLUSTER VERBOSE
 				{
-					ClusterStmt *n = makeNode(ClusterStmt);
+					RepackStmt *n = makeNode(RepackStmt);
 
+					n->command = REPACK_COMMAND_CLUSTER;
 					n->relation = NULL;
 					n->indexname = NULL;
+					n->usingindex = true;
 					n->params = list_make1(makeDefElem("verbose", NULL, @2));
 					$$ = (Node *) n;
 				}
 			/* kept for pre-8.3 compatibility */
 			| CLUSTER opt_verbose name ON qualified_name
 				{
-					ClusterStmt *n = makeNode(ClusterStmt);
+					RepackStmt *n = makeNode(RepackStmt);
 
+					n->command = REPACK_COMMAND_CLUSTER;
 					n->relation = $5;
 					n->indexname = $3;
+					n->usingindex = true;
 					if ($2)
 						n->params = list_make1(makeDefElem("verbose", NULL, @2));
 					$$ = (Node *) n;
@@ -17961,6 +18023,7 @@ unreserved_keyword:
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
+			| REPACK
 			| REPEATABLE
 			| REPLACE
 			| REPLICA
@@ -18593,6 +18656,7 @@ bare_label_keyword:
 			| RELATIVE_P
 			| RELEASE
 			| RENAME
+			| REPACK
 			| REPEATABLE
 			| REPLACE
 			| REPLICA
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..1295dc25d02 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -277,9 +277,9 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 				return COMMAND_OK_IN_RECOVERY | COMMAND_OK_IN_READ_ONLY_TXN;
 			}
 
-		case T_ClusterStmt:
 		case T_ReindexStmt:
 		case T_VacuumStmt:
+		case T_RepackStmt:
 			{
 				/*
 				 * These commands write WAL, so they're not strictly
@@ -854,14 +854,14 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			ExecuteCallStmt(castNode(CallStmt, parsetree), params, isAtomicContext, dest);
 			break;
 
-		case T_ClusterStmt:
-			cluster(pstate, (ClusterStmt *) parsetree, isTopLevel);
-			break;
-
 		case T_VacuumStmt:
 			ExecVacuum(pstate, (VacuumStmt *) parsetree, isTopLevel);
 			break;
 
+		case T_RepackStmt:
+			ExecRepack(pstate, (RepackStmt *) parsetree, isTopLevel);
+			break;
+
 		case T_ExplainStmt:
 			ExplainQuery(pstate, (ExplainStmt *) parsetree, params, dest);
 			break;
@@ -2850,10 +2850,6 @@ CreateCommandTag(Node *parsetree)
 			tag = CMDTAG_CALL;
 			break;
 
-		case T_ClusterStmt:
-			tag = CMDTAG_CLUSTER;
-			break;
-
 		case T_VacuumStmt:
 			if (((VacuumStmt *) parsetree)->is_vacuumcmd)
 				tag = CMDTAG_VACUUM;
@@ -2861,6 +2857,10 @@ CreateCommandTag(Node *parsetree)
 				tag = CMDTAG_ANALYZE;
 			break;
 
+		case T_RepackStmt:
+			tag = CMDTAG_REPACK;
+			break;
+
 		case T_ExplainStmt:
 			tag = CMDTAG_EXPLAIN;
 			break;
@@ -3498,7 +3498,7 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_ALL;
 			break;
 
-		case T_ClusterStmt:
+		case T_RepackStmt:
 			lev = LOGSTMT_DDL;
 			break;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index c756c2bebaa..a1e10e8c2f6 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -268,6 +268,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 		cmdtype = PROGRESS_COMMAND_ANALYZE;
 	else if (pg_strcasecmp(cmd, "CLUSTER") == 0)
 		cmdtype = PROGRESS_COMMAND_CLUSTER;
+	else if (pg_strcasecmp(cmd, "REPACK") == 0)
+		cmdtype = PROGRESS_COMMAND_REPACK;
 	else if (pg_strcasecmp(cmd, "CREATE INDEX") == 0)
 		cmdtype = PROGRESS_COMMAND_CREATE_INDEX;
 	else if (pg_strcasecmp(cmd, "BASEBACKUP") == 0)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 6b20a4404b2..df6c82e6d7b 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1247,7 +1247,7 @@ static const char *const sql_commands[] = {
 	"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
 	"FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT INTO", "LISTEN", "LOAD", "LOCK",
 	"MERGE INTO", "MOVE", "NOTIFY", "PREPARE",
-	"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
+	"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE", "REPACK",
 	"RESET", "REVOKE", "ROLLBACK",
 	"SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
 	"TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
@@ -4999,6 +4999,37 @@ match_previous_words(int pattern_id,
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	}
 
+/* REPACK */
+	else if (Matches("REPACK"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables);
+	else if (Matches("REPACK", "(*)"))
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables);
+	/* If we have REPACK <sth>, then add "USING INDEX" */
+	else if (Matches("REPACK", MatchAnyExcept("(")))
+		COMPLETE_WITH("USING INDEX");
+	/* If we have REPACK (*) <sth>, then add "USING INDEX" */
+	else if (Matches("REPACK", "(*)", MatchAny))
+		COMPLETE_WITH("USING INDEX");
+	/* If we have REPACK <sth> USING, then add the index as well */
+	else if (Matches("REPACK", MatchAny, "USING", "INDEX"))
+	{
+		set_completion_reference(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
+	}
+	else if (HeadMatches("REPACK", "(*") &&
+			 !HeadMatches("REPACK", "(*)"))
+	{
+		/*
+		 * This fires if we're in an unfinished parenthesized option list.
+		 * get_previous_words treats a completed parenthesized option list as
+		 * one word, so the above test is correct.
+		 */
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("VERBOSE");
+		else if (TailMatches("VERBOSE"))
+			COMPLETE_WITH("ON", "OFF");
+	}
+
 /* SECURITY LABEL */
 	else if (Matches("SECURITY"))
 		COMPLETE_WITH("LABEL");
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index 019ca06455d..f0c1bd4175c 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -16,7 +16,7 @@ subdir = src/bin/scripts
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
-PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready
+PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready pg_repackdb
 
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
@@ -31,6 +31,7 @@ clusterdb: clusterdb.o common.o $(WIN32RES) | submake-libpq submake-libpgport su
 vacuumdb: vacuumdb.o vacuuming.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
 reindexdb: reindexdb.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
 pg_isready: pg_isready.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
+pg_repackdb: pg_repackdb.o vacuuming.o common.o $(WIN32RES) | submake-libpq submake-libpgport submake-libpgfeutils
 
 install: all installdirs
 	$(INSTALL_PROGRAM) createdb$(X)   '$(DESTDIR)$(bindir)'/createdb$(X)
@@ -41,6 +42,7 @@ install: all installdirs
 	$(INSTALL_PROGRAM) vacuumdb$(X)   '$(DESTDIR)$(bindir)'/vacuumdb$(X)
 	$(INSTALL_PROGRAM) reindexdb$(X)  '$(DESTDIR)$(bindir)'/reindexdb$(X)
 	$(INSTALL_PROGRAM) pg_isready$(X) '$(DESTDIR)$(bindir)'/pg_isready$(X)
+	$(INSTALL_PROGRAM) pg_repackdb$(X) '$(DESTDIR)$(bindir)'/pg_repackdb$(X)
 
 installdirs:
 	$(MKDIR_P) '$(DESTDIR)$(bindir)'
diff --git a/src/bin/scripts/meson.build b/src/bin/scripts/meson.build
index a4fed59d1c9..be573cae682 100644
--- a/src/bin/scripts/meson.build
+++ b/src/bin/scripts/meson.build
@@ -42,6 +42,7 @@ vacuuming_common = static_library('libvacuuming_common',
 
 binaries = [
   'vacuumdb',
+  'pg_repackdb',
 ]
 foreach binary : binaries
   binary_sources = files('@[email protected]'.format(binary))
@@ -80,6 +81,7 @@ tests += {
       't/100_vacuumdb.pl',
       't/101_vacuumdb_all.pl',
       't/102_vacuumdb_stages.pl',
+      't/103_repackdb.pl',
       't/200_connstr.pl',
     ],
   },
diff --git a/src/bin/scripts/pg_repackdb.c b/src/bin/scripts/pg_repackdb.c
new file mode 100644
index 00000000000..8b4cfca7fe1
--- /dev/null
+++ b/src/bin/scripts/pg_repackdb.c
@@ -0,0 +1,238 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_repackdb
+ *		An utility to run REPACK
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * FIXME: this is missing a way to specify the index to use to repack one
+ * table, or whether to pass a WITH INDEX clause when multiple tables are
+ * used.  Something like --index[=indexname].  Adding that bleeds into
+ * vacuuming.c as well.
+ *
+ * src/bin/scripts/pg_repackdb.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <limits.h>
+
+#include "common.h"
+#include "common/logging.h"
+#include "fe_utils/option_utils.h"
+#include "vacuuming.h"
+
+static void help(const char *progname);
+void		check_objfilter(VacObjFilter objfilter);
+
+int
+main(int argc, char *argv[])
+{
+	static struct option long_options[] = {
+		{"host", required_argument, NULL, 'h'},
+		{"port", required_argument, NULL, 'p'},
+		{"username", required_argument, NULL, 'U'},
+		{"no-password", no_argument, NULL, 'w'},
+		{"password", no_argument, NULL, 'W'},
+		{"echo", no_argument, NULL, 'e'},
+		{"quiet", no_argument, NULL, 'q'},
+		{"dbname", required_argument, NULL, 'd'},
+		{"all", no_argument, NULL, 'a'},
+		/* XXX this could be 'i', but optional_arg is messy */
+		{"index", optional_argument, NULL, 1},
+		{"table", required_argument, NULL, 't'},
+		{"verbose", no_argument, NULL, 'v'},
+		{"jobs", required_argument, NULL, 'j'},
+		{"schema", required_argument, NULL, 'n'},
+		{"exclude-schema", required_argument, NULL, 'N'},
+		{"maintenance-db", required_argument, NULL, 2},
+		{NULL, 0, NULL, 0}
+	};
+
+	const char *progname;
+	int			optindex;
+	int			c;
+	const char *dbname = NULL;
+	const char *maintenance_db = NULL;
+	ConnParams	cparams;
+	bool		echo = false;
+	bool		quiet = false;
+	vacuumingOptions vacopts;
+	VacObjFilter objfilter = OBJFILTER_NONE;
+	SimpleStringList objects = {NULL, NULL};
+	int			concurrentCons = 1;
+	int			tbl_count = 0;
+	int			ret;
+
+	/* initialize options */
+	memset(&vacopts, 0, sizeof(vacopts));
+	vacopts.mode = MODE_REPACK;
+
+	/* the same for connection parameters */
+	memset(&cparams, 0, sizeof(cparams));
+	cparams.prompt_password = TRI_DEFAULT;
+
+	pg_logging_init(argv[0]);
+	progname = get_progname(argv[0]);
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
+
+	handle_help_version_opts(argc, argv, progname, help);
+
+	while ((c = getopt_long(argc, argv, "ad:eh:j:n:N:p:qt:U:vwW",
+							long_options, &optindex)) != -1)
+	{
+		switch (c)
+		{
+			case 'a':
+				objfilter |= OBJFILTER_ALL_DBS;
+				break;
+			case 'd':
+				objfilter |= OBJFILTER_DATABASE;
+				dbname = pg_strdup(optarg);
+				break;
+			case 'e':
+				echo = true;
+				break;
+			case 'h':
+				cparams.pghost = pg_strdup(optarg);
+				break;
+			case 'j':
+				if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX,
+									  &concurrentCons))
+					exit(1);
+				break;
+			case 'n':
+				objfilter |= OBJFILTER_SCHEMA;
+				simple_string_list_append(&objects, optarg);
+				break;
+			case 'N':
+				objfilter |= OBJFILTER_SCHEMA_EXCLUDE;
+				simple_string_list_append(&objects, optarg);
+				break;
+			case 'p':
+				cparams.pgport = pg_strdup(optarg);
+				break;
+			case 'q':
+				quiet = true;
+				break;
+			case 't':
+				objfilter |= OBJFILTER_TABLE;
+				simple_string_list_append(&objects, optarg);
+				tbl_count++;
+				break;
+			case 'U':
+				cparams.pguser = pg_strdup(optarg);
+				break;
+			case 'v':
+				vacopts.verbose = true;
+				break;
+			case 'w':
+				cparams.prompt_password = TRI_NO;
+				break;
+			case 'W':
+				cparams.prompt_password = TRI_YES;
+				break;
+			case 1:
+				vacopts.using_index = true;
+				if (optarg)
+					vacopts.indexname = pg_strdup(optarg);
+				else
+					vacopts.indexname = NULL;
+				break;
+			case 2:
+				maintenance_db = pg_strdup(optarg);
+				break;
+			default:
+				/* getopt_long already emitted a complaint */
+				pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+				exit(1);
+		}
+	}
+
+	/*
+	 * Non-option argument specifies database name as long as it wasn't
+	 * already specified with -d / --dbname
+	 */
+	if (optind < argc && dbname == NULL)
+	{
+		objfilter |= OBJFILTER_DATABASE;
+		dbname = argv[optind];
+		optind++;
+	}
+
+	if (optind < argc)
+	{
+		pg_log_error("too many command-line arguments (first is \"%s\")",
+					 argv[optind]);
+		pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+		exit(1);
+	}
+
+	/*
+	 * Validate the combination of filters specified in the command-line
+	 * options.
+	 */
+	check_objfilter(objfilter);
+
+	ret = vacuuming_main(&cparams, dbname, maintenance_db, &vacopts,
+						 objfilter, &objects,
+						 tbl_count, false, concurrentCons,
+						 progname, echo, quiet);
+	exit(ret);
+}
+
+/*
+ * Verify that the filters used at command line are compatible.
+ */
+void
+check_objfilter(VacObjFilter objfilter)
+{
+	if ((objfilter & OBJFILTER_ALL_DBS) &&
+		(objfilter & OBJFILTER_DATABASE))
+		pg_fatal("cannot repack all databases and a specific one at the same time");
+
+	if ((objfilter & OBJFILTER_TABLE) &&
+		(objfilter & OBJFILTER_SCHEMA))
+		pg_fatal("cannot repack all tables in schema(s) and specific table(s) at the same time");
+
+	if ((objfilter & OBJFILTER_TABLE) &&
+		(objfilter & OBJFILTER_SCHEMA_EXCLUDE))
+		pg_fatal("cannot repack specific table(s) and exclude schema(s) at the same time");
+
+	if ((objfilter & OBJFILTER_SCHEMA) &&
+		(objfilter & OBJFILTER_SCHEMA_EXCLUDE))
+		pg_fatal("cannot repack all tables in schema(s) and exclude schema(s) at the same time");
+}
+
+static void
+help(const char *progname)
+{
+	printf(_("%s repacks a PostgreSQL database.\n\n"), progname);
+	printf(_("Usage:\n"));
+	printf(_("  %s [OPTION]... [DBNAME]\n"), progname);
+	printf(_("\nOptions:\n"));
+	printf(_("  -a, --all                       repack all databases\n"));
+	printf(_("  -d, --dbname=DBNAME             database to repack\n"));
+	printf(_("  -e, --echo                      show the commands being sent to the server\n"));
+	printf(_("  -j, --jobs=NUM                  use this many concurrent connections to repack\n"));
+	printf(_("  -n, --schema=SCHEMA             repack tables in the specified schema(s) only\n"));
+	printf(_("  -N, --exclude-schema=SCHEMA     do not repack tables in the specified schema(s)\n"));
+	printf(_("  -q, --quiet                     don't write any messages\n"));
+	printf(_("  -t, --table='TABLE'             repack specific table(s) only\n"));
+	printf(_("  -v, --verbose                   write a lot of output\n"));
+	printf(_("  -V, --version                   output version information, then exit\n"));
+	printf(_("  -?, --help                      show this help, then exit\n"));
+	printf(_("\nConnection options:\n"));
+	printf(_("  -h, --host=HOSTNAME       database server host or socket directory\n"));
+	printf(_("  -p, --port=PORT           database server port\n"));
+	printf(_("  -U, --username=USERNAME   user name to connect as\n"));
+	printf(_("  -w, --no-password         never prompt for password\n"));
+	printf(_("  -W, --password            force password prompt\n"));
+	printf(_("  --maintenance-db=DBNAME   alternate maintenance database\n"));
+	printf(_("\nRead the description of the SQL command REPACK for details.\n"));
+	printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
diff --git a/src/bin/scripts/t/103_repackdb.pl b/src/bin/scripts/t/103_repackdb.pl
new file mode 100644
index 00000000000..51de4d7ab34
--- /dev/null
+++ b/src/bin/scripts/t/103_repackdb.pl
@@ -0,0 +1,24 @@
+# Copyright (c) 2021-2025, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+program_help_ok('pg_repackdb');
+program_version_ok('pg_repackdb');
+program_options_handling_ok('pg_repackdb');
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+$node->issues_sql_like(
+	[ 'pg_repackdb', 'postgres' ],
+	qr/statement: REPACK.*;/,
+	'SQL REPACK run');
+
+
+done_testing();
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index b9f2e507557..f926cf2124a 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -1,6 +1,6 @@
 /*-------------------------------------------------------------------------
  * vacuuming.c
- *		Helper routines for vacuumdb
+ *		Helper routines for vacuumdb and pg_repackdb
  *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -46,8 +46,8 @@ static SimpleStringList *retrieve_objects(PGconn *conn,
 										  bool echo);
 static void prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 								   vacuumingOptions *vacopts, const char *table);
-static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
-							   const char *table);
+static void run_vacuum_command(PGconn *conn, vacuumingOptions *vacopts,
+							   const char *sql, bool echo, const char *table);
 
 /*
  * Executes vacuum/analyze as indicated, or dies in case of failure.
@@ -192,6 +192,14 @@ vacuum_one_database(ConnParams *cparams,
 
 	conn = connectDatabase(cparams, progname, echo, false, true);
 
+	if (vacopts->mode == MODE_REPACK && PQserverVersion(conn) < 190000)
+	{
+		/* XXX arguably, here we should use VACUUM FULL instead of failing */
+		PQfinish(conn);
+		pg_fatal("cannot use the \"%s\" command on server versions older than PostgreSQL %s",
+				 "REPACK", "19");
+	}
+
 	if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
 	{
 		PQfinish(conn);
@@ -281,12 +289,19 @@ vacuum_one_database(ConnParams *cparams,
 
 	if (!quiet)
 	{
+		/* XXX get rid of the assumption that ANALYZE_NO_STAGE means vacuum */
 		if (stage != ANALYZE_NO_STAGE)
 			printf(_("%s: processing database \"%s\": %s\n"),
 				   progname, PQdb(conn), _(stage_messages[stage]));
-		else
+		else if (vacopts->mode == MODE_VACUUM)
 			printf(_("%s: vacuuming database \"%s\"\n"),
 				   progname, PQdb(conn));
+		else
+		{
+			Assert(vacopts->mode == MODE_REPACK);
+			printf(_("%s: repacking database \"%s\"\n"),
+				   progname, PQdb(conn));
+		}
 		fflush(stdout);
 	}
 
@@ -376,7 +391,7 @@ vacuum_one_database(ConnParams *cparams,
 		 * through ParallelSlotsGetIdle.
 		 */
 		ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
-		run_vacuum_command(free_slot->connection, sql.data,
+		run_vacuum_command(free_slot->connection, vacopts, sql.data,
 						   echo, tabname);
 
 		cell = cell->next;
@@ -389,7 +404,8 @@ vacuum_one_database(ConnParams *cparams,
 	}
 
 	/* If we used SKIP_DATABASE_STATS, mop up with ONLY_DATABASE_STATS */
-	if (vacopts->skip_database_stats && stage == ANALYZE_NO_STAGE &&
+	if (vacopts->mode == MODE_VACUUM &&
+		vacopts->skip_database_stats && stage == ANALYZE_NO_STAGE &&
 		!vacopts->analyze_only)
 	{
 		const char *cmd = "VACUUM (ONLY_DATABASE_STATS);";
@@ -402,7 +418,7 @@ vacuum_one_database(ConnParams *cparams,
 		}
 
 		ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
-		run_vacuum_command(free_slot->connection, cmd, echo, NULL);
+		run_vacuum_command(free_slot->connection, vacopts, cmd, echo, NULL);
 
 		if (!ParallelSlotsWaitCompletion(sa))
 			ret = EXIT_FAILURE;
@@ -624,6 +640,23 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
 								 " AND listed_objects.object_oid IS NOT NULL\n");
 	}
 
+	/*
+	 * In REPACK mode, if the 'using_index' option was given but no index
+	 * name, filter only tables that have an index with indisclustered set.
+	 * (If an index name is given, we trust the user to pass a reasonable list
+	 * of tables.)
+	 *
+	 * XXX it may be worth printing an error if an index name is given with
+	 * no list of tables.
+	 */
+	if (vacopts->mode == MODE_REPACK &&
+		vacopts->using_index && !vacopts->indexname)
+	{
+		appendPQExpBufferStr(&catalog_query,
+							 " AND EXISTS (SELECT 1 FROM pg_catalog.pg_index\n"
+							 "    WHERE indrelid = c.oid AND indisclustered)\n");
+	}
+
 	/*
 	 * If no tables were listed, filter for the relevant relation types.  If
 	 * tables were given via --table, don't bother filtering by relation type.
@@ -811,7 +844,30 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 
 	resetPQExpBuffer(sql);
 
-	if (vacopts->analyze_only)
+	if (vacopts->mode == MODE_REPACK)
+	{
+		appendPQExpBufferStr(sql, "REPACK");
+
+		if (vacopts->verbose)
+		{
+			appendPQExpBuffer(sql, "%sVERBOSE", sep);
+			sep = comma;
+		}
+
+		if (sep != paren)
+			appendPQExpBufferChar(sql, ')');
+
+		appendPQExpBuffer(sql, " %s", table);
+
+		if (vacopts->using_index)
+		{
+			appendPQExpBuffer(sql, " USING INDEX");
+			if (vacopts->indexname)
+				appendPQExpBuffer(sql, " %s", fmtIdEnc(vacopts->indexname,
+													   PQclientEncoding(conn)));
+		}
+	}
+	else if (vacopts->analyze_only)
 	{
 		appendPQExpBufferStr(sql, "ANALYZE");
 
@@ -962,7 +1018,10 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 		}
 	}
 
-	appendPQExpBuffer(sql, " %s;", table);
+	if (vacopts->mode != MODE_REPACK)
+		appendPQExpBuffer(sql, " %s", table);
+
+	appendPQExpBufferChar(sql, ';');
 }
 
 /*
@@ -972,8 +1031,8 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
  * Any errors during command execution are reported to stderr.
  */
 static void
-run_vacuum_command(PGconn *conn, const char *sql, bool echo,
-				   const char *table)
+run_vacuum_command(PGconn *conn, vacuumingOptions *vacopts,
+				   const char *sql, bool echo, const char *table)
 {
 	bool		status;
 
@@ -986,13 +1045,21 @@ run_vacuum_command(PGconn *conn, const char *sql, bool echo,
 	{
 		if (table)
 		{
-			pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
-						 table, PQdb(conn), PQerrorMessage(conn));
+			if (vacopts->mode == MODE_VACUUM)
+				pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
+							 table, PQdb(conn), PQerrorMessage(conn));
+			else
+				pg_log_error("repacking of table \"%s\" in database \"%s\" failed: %s",
+							 table, PQdb(conn), PQerrorMessage(conn));
 		}
 		else
 		{
-			pg_log_error("vacuuming of database \"%s\" failed: %s",
-						 PQdb(conn), PQerrorMessage(conn));
+			if (vacopts->mode == MODE_VACUUM)
+				pg_log_error("vacuuming of database \"%s\" failed: %s",
+							 PQdb(conn), PQerrorMessage(conn));
+			else
+				pg_log_error("repacking of database \"%s\" failed: %s",
+							 PQdb(conn), PQerrorMessage(conn));
 		}
 	}
 }
diff --git a/src/bin/scripts/vacuuming.h b/src/bin/scripts/vacuuming.h
index 021953e153a..dde68fb4cba 100644
--- a/src/bin/scripts/vacuuming.h
+++ b/src/bin/scripts/vacuuming.h
@@ -17,6 +17,12 @@
 #include "fe_utils/connect_utils.h"
 #include "fe_utils/simple_list.h"
 
+typedef enum
+{
+	MODE_VACUUM,
+	MODE_REPACK
+} RunMode;
+
 /* For analyze-in-stages mode */
 #define ANALYZE_NO_STAGE	-1
 #define ANALYZE_NUM_STAGES	3
@@ -24,11 +30,14 @@
 /* vacuum options controlled by user flags */
 typedef struct vacuumingOptions
 {
+	RunMode		mode;
 	bool		analyze_only;
 	bool		verbose;
 	bool		and_analyze;
 	bool		full;
 	bool		freeze;
+	bool		using_index;
+	char	   *indexname;
 	bool		disable_page_skipping;
 	bool		skip_locked;
 	int			min_xid_age;
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 60088a64cbb..890998d84bb 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -24,6 +24,7 @@
 #define CLUOPT_RECHECK 0x02		/* recheck relation state */
 #define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for
 										 * indisclustered */
+#define CLUOPT_ANALYZE 0x08		/* do an ANALYZE */
 
 /* options for CLUSTER */
 typedef struct ClusterParams
@@ -31,8 +32,11 @@ typedef struct ClusterParams
 	bits32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-extern void cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Relation OldHeap, Oid indexOid, ClusterParams *params);
+
+extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
+
+extern void cluster_rel(RepackCommand command, bool usingindex,
+						Relation OldHeap, Oid indexOid, ClusterParams *params);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 1cde4bd9bcf..5b6639c114c 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -56,24 +56,51 @@
 #define PROGRESS_ANALYZE_PHASE_COMPUTE_EXT_STATS		4
 #define PROGRESS_ANALYZE_PHASE_FINALIZE_ANALYZE			5
 
-/* Progress parameters for cluster */
-#define PROGRESS_CLUSTER_COMMAND				0
-#define PROGRESS_CLUSTER_PHASE					1
-#define PROGRESS_CLUSTER_INDEX_RELID			2
-#define PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED	3
-#define PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN	4
-#define PROGRESS_CLUSTER_TOTAL_HEAP_BLKS		5
-#define PROGRESS_CLUSTER_HEAP_BLKS_SCANNED		6
-#define PROGRESS_CLUSTER_INDEX_REBUILD_COUNT	7
+/*
+ * Progress parameters for REPACK.
+ *
+ * Note: Since REPACK shares some code with CLUSTER, these values are also
+ * used by CLUSTER. (CLUSTER is now deprecated, so it makes little sense to
+ * introduce a separate set of constants.)
+ */
+#define PROGRESS_REPACK_COMMAND					0
+#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
 
-/* Phases of cluster (as advertised via PROGRESS_CLUSTER_PHASE) */
-#define PROGRESS_CLUSTER_PHASE_SEQ_SCAN_HEAP	1
-#define PROGRESS_CLUSTER_PHASE_INDEX_SCAN_HEAP	2
-#define PROGRESS_CLUSTER_PHASE_SORT_TUPLES		3
-#define PROGRESS_CLUSTER_PHASE_WRITE_NEW_HEAP	4
-#define PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES	5
-#define PROGRESS_CLUSTER_PHASE_REBUILD_INDEX	6
-#define PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP	7
+/*
+ * Phases of repack (as advertised via PROGRESS_REPACK_PHASE).
+ */
+#define PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP		1
+#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
+
+/*
+ * Commands of PROGRESS_REPACK
+ *
+ * Currently we only have one command, so the PROGRESS_REPACK_COMMAND
+ * parameter is not necessary. However it makes cluster.c simpler if we have
+ * the same set of parameters for CLUSTER and REPACK - see the note on REPACK
+ * parameters above.
+ */
+#define PROGRESS_REPACK_COMMAND_REPACK			1
+
+/*
+ * Progress parameters for cluster.
+ *
+ * Although we need to report REPACK and CLUSTER in separate views, the
+ * parameters and phases of CLUSTER are a subset of those of REPACK. Therefore
+ * we just use the appropriate values defined for REPACK above instead of
+ * defining a separate set of constants here.
+ */
 
 /* Commands of PROGRESS_CLUSTER */
 #define PROGRESS_CLUSTER_COMMAND_CLUSTER		1
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4ed14fc5b78..e4ba59f6b8f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3949,16 +3949,26 @@ typedef struct AlterSystemStmt
 } AlterSystemStmt;
 
 /* ----------------------
- *		Cluster Statement (support pbrown's cluster index implementation)
+ *		Repack Statement
  * ----------------------
  */
-typedef struct ClusterStmt
+typedef enum RepackCommand
+{
+	REPACK_COMMAND_CLUSTER,
+	REPACK_COMMAND_REPACK,
+	REPACK_COMMAND_VACUUMFULL,
+} RepackCommand;
+
+typedef struct RepackStmt
 {
 	NodeTag		type;
-	RangeVar   *relation;		/* relation being indexed, or NULL if all */
-	char	   *indexname;		/* original index defined */
+	RepackCommand command;		/* type of command being run */
+	RangeVar   *relation;		/* relation being repacked */
+	char	   *indexname;		/* order tuples by this index */
+	bool		usingindex;		/* whether USING INDEX is specified */
 	List	   *params;			/* list of DefElem nodes */
-} ClusterStmt;
+} RepackStmt;
+
 
 /* ----------------------
  *		Vacuum and Analyze Statements
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index a4af3f717a1..22559369e2c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -374,6 +374,7 @@ PG_KEYWORD("reindex", REINDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("relative", RELATIVE_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("release", RELEASE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("rename", RENAME, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("repack", REPACK, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("repeatable", REPEATABLE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replace", REPLACE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index d250a714d59..cceb312f2b3 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -196,6 +196,7 @@ PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
 PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
 PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", true, false, false)
 PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
+PG_CMDTAG(CMDTAG_REPACK, "REPACK", false, false, false)
 PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
 PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
 PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
diff --git a/src/include/utils/backend_progress.h b/src/include/utils/backend_progress.h
index dda813ab407..e69e366dcdc 100644
--- a/src/include/utils/backend_progress.h
+++ b/src/include/utils/backend_progress.h
@@ -28,6 +28,7 @@ typedef enum ProgressCommandType
 	PROGRESS_COMMAND_CREATE_INDEX,
 	PROGRESS_COMMAND_BASEBACKUP,
 	PROGRESS_COMMAND_COPY,
+	PROGRESS_COMMAND_REPACK,
 } ProgressCommandType;
 
 #define PGSTAT_NUM_PROGRESS_PARAM	20
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 4d40a6809ab..5256628b51d 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -254,6 +254,63 @@ ORDER BY 1;
  clstr_tst_pkey
 (3 rows)
 
+-- REPACK handles individual tables identically to CLUSTER, but it's worth
+-- checking if it handles table hierarchies identically as well.
+REPACK clstr_tst USING INDEX clstr_tst_c;
+-- Verify that inheritance link still works
+INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table 2');
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+ a  |  b  |        c         |           substring            | length 
+----+-----+------------------+--------------------------------+--------
+ 10 |  14 | catorce          |                                |       
+ 18 |   5 | cinco            |                                |       
+  9 |   4 | cuatro           |                                |       
+ 26 |  19 | diecinueve       |                                |       
+ 12 |  18 | dieciocho        |                                |       
+ 30 |  16 | dieciseis        |                                |       
+ 24 |  17 | diecisiete       |                                |       
+  2 |  10 | diez             |                                |       
+ 23 |  12 | doce             |                                |       
+ 11 |   2 | dos              |                                |       
+ 25 |   9 | nueve            |                                |       
+ 31 |   8 | ocho             |                                |       
+  1 |  11 | once             |                                |       
+ 28 |  15 | quince           |                                |       
+ 32 |   6 | seis             | xyzzyxyzzyxyzzyxyzzyxyzzyxyzzy | 500000
+ 29 |   7 | siete            |                                |       
+ 15 |  13 | trece            |                                |       
+ 22 |  30 | treinta          |                                |       
+ 17 |  32 | treinta y dos    |                                |       
+  3 |  31 | treinta y uno    |                                |       
+  5 |   3 | tres             |                                |       
+ 20 |   1 | uno              |                                |       
+  6 |  20 | veinte           |                                |       
+ 14 |  25 | veinticinco      |                                |       
+ 21 |  24 | veinticuatro     |                                |       
+  4 |  22 | veintidos        |                                |       
+ 19 |  29 | veintinueve      |                                |       
+ 16 |  28 | veintiocho       |                                |       
+ 27 |  26 | veintiseis       |                                |       
+ 13 |  27 | veintisiete      |                                |       
+  7 |  23 | veintitres       |                                |       
+  8 |  21 | veintiuno        |                                |       
+  0 | 100 | in child table   |                                |       
+  0 | 100 | in child table 2 |                                |       
+(34 rows)
+
+-- Verify that foreign key link still works
+INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
+ERROR:  insert or update on table "clstr_tst" violates foreign key constraint "clstr_tst_con"
+DETAIL:  Key (b)=(1111) is not present in table "clstr_tst_s".
+SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
+ORDER BY 1;
+       conname        
+----------------------
+ clstr_tst_a_not_null
+ clstr_tst_con
+ clstr_tst_pkey
+(3 rows)
+
 SELECT relname, relkind,
     EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
 FROM pg_class c WHERE relname LIKE 'clstr_tst%' ORDER BY relname;
@@ -381,6 +438,35 @@ SELECT * FROM clstr_1;
  2
 (2 rows)
 
+-- REPACK w/o argument performs no ordering, so we can only check which tables
+-- have the relfilenode changed.
+RESET SESSION AUTHORIZATION;
+CREATE TEMP TABLE relnodes_old AS
+(SELECT relname, relfilenode
+FROM pg_class
+WHERE relname IN ('clstr_1', 'clstr_2', 'clstr_3'));
+SET SESSION AUTHORIZATION regress_clstr_user;
+SET client_min_messages = ERROR;  -- order of "skipping" warnings may vary
+REPACK;
+RESET client_min_messages;
+RESET SESSION AUTHORIZATION;
+CREATE TEMP TABLE relnodes_new AS
+(SELECT relname, relfilenode
+FROM pg_class
+WHERE relname IN ('clstr_1', 'clstr_2', 'clstr_3'));
+-- Do the actual comparison. Unlike CLUSTER, clstr_3 should have been
+-- processed because there is nothing like clustering index here.
+SELECT o.relname FROM relnodes_old o
+JOIN relnodes_new n ON o.relname = n.relname
+WHERE o.relfilenode <> n.relfilenode
+ORDER BY o.relname;
+ relname 
+---------
+ clstr_1
+ clstr_3
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_clstr_user;
 -- Test MVCC-safety of cluster. There isn't much we can do to verify the
 -- results with a single backend...
 CREATE TABLE clustertest (key int PRIMARY KEY);
@@ -495,6 +581,43 @@ ALTER TABLE clstrpart SET WITHOUT CLUSTER;
 ERROR:  cannot mark index clustered in partitioned table
 ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
 ERROR:  cannot mark index clustered in partitioned table
+-- Check that REPACK sets new relfilenodes: it should process exactly the same
+-- tables as CLUSTER did.
+DROP TABLE old_cluster_info;
+DROP TABLE new_cluster_info;
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+REPACK clstrpart USING INDEX clstrpart_idx;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+   relname   | level | relkind | ?column? 
+-------------+-------+---------+----------
+ clstrpart   |     0 | p       | t
+ clstrpart1  |     1 | p       | t
+ clstrpart11 |     2 | r       | f
+ clstrpart12 |     2 | p       | t
+ clstrpart2  |     1 | r       | f
+ clstrpart3  |     1 | p       | t
+ clstrpart33 |     2 | r       | f
+(7 rows)
+
+-- And finally the same for REPACK w/o index.
+DROP TABLE old_cluster_info;
+DROP TABLE new_cluster_info;
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+REPACK clstrpart;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+   relname   | level | relkind | ?column? 
+-------------+-------+---------+----------
+ clstrpart   |     0 | p       | t
+ clstrpart1  |     1 | p       | t
+ clstrpart11 |     2 | r       | f
+ clstrpart12 |     2 | p       | t
+ clstrpart2  |     1 | r       | f
+ clstrpart3  |     1 | p       | t
+ clstrpart33 |     2 | r       | f
+(7 rows)
+
 DROP TABLE clstrpart;
 -- Ownership of partitions is checked
 CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i);
@@ -513,7 +636,7 @@ CREATE TEMP TABLE ptnowner_oldnodes AS
   JOIN pg_class AS c ON c.oid=tree.relid;
 SET SESSION AUTHORIZATION regress_ptnowner;
 CLUSTER ptnowner USING ptnowner_i_idx;
-WARNING:  permission denied to cluster "ptnowner2", skipping it
+WARNING:  permission denied to execute CLUSTER on "ptnowner2", skipping it
 RESET SESSION AUTHORIZATION;
 SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a
   JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C";
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 35e8aad7701..3a1d1d28282 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2071,6 +2071,29 @@ pg_stat_progress_create_index| SELECT s.pid,
     s.param15 AS partitions_done
    FROM (pg_stat_get_progress_info('CREATE INDEX'::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_repack| SELECT s.pid,
+    s.datid,
+    d.datname,
+    s.relid,
+        CASE s.param2
+            WHEN 0 THEN 'initializing'::text
+            WHEN 1 THEN 'seq scanning heap'::text
+            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
+            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
+   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,
     s.datid,
     d.datname,
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index b7115f86104..cfcc3dc9761 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -76,6 +76,19 @@ INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
 SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
 ORDER BY 1;
 
+-- REPACK handles individual tables identically to CLUSTER, but it's worth
+-- checking if it handles table hierarchies identically as well.
+REPACK clstr_tst USING INDEX clstr_tst_c;
+
+-- Verify that inheritance link still works
+INSERT INTO clstr_tst_inh VALUES (0, 100, 'in child table 2');
+SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
+
+-- Verify that foreign key link still works
+INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
+
+SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
+ORDER BY 1;
 
 SELECT relname, relkind,
     EXISTS(SELECT 1 FROM pg_class WHERE oid = c.reltoastrelid) AS hastoast
@@ -159,6 +172,34 @@ INSERT INTO clstr_1 VALUES (1);
 CLUSTER clstr_1;
 SELECT * FROM clstr_1;
 
+-- REPACK w/o argument performs no ordering, so we can only check which tables
+-- have the relfilenode changed.
+RESET SESSION AUTHORIZATION;
+CREATE TEMP TABLE relnodes_old AS
+(SELECT relname, relfilenode
+FROM pg_class
+WHERE relname IN ('clstr_1', 'clstr_2', 'clstr_3'));
+
+SET SESSION AUTHORIZATION regress_clstr_user;
+SET client_min_messages = ERROR;  -- order of "skipping" warnings may vary
+REPACK;
+RESET client_min_messages;
+
+RESET SESSION AUTHORIZATION;
+CREATE TEMP TABLE relnodes_new AS
+(SELECT relname, relfilenode
+FROM pg_class
+WHERE relname IN ('clstr_1', 'clstr_2', 'clstr_3'));
+
+-- Do the actual comparison. Unlike CLUSTER, clstr_3 should have been
+-- processed because there is nothing like clustering index here.
+SELECT o.relname FROM relnodes_old o
+JOIN relnodes_new n ON o.relname = n.relname
+WHERE o.relfilenode <> n.relfilenode
+ORDER BY o.relname;
+
+SET SESSION AUTHORIZATION regress_clstr_user;
+
 -- Test MVCC-safety of cluster. There isn't much we can do to verify the
 -- results with a single backend...
 
@@ -229,6 +270,24 @@ SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM o
 CLUSTER clstrpart;
 ALTER TABLE clstrpart SET WITHOUT CLUSTER;
 ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
+
+-- Check that REPACK sets new relfilenodes: it should process exactly the same
+-- tables as CLUSTER did.
+DROP TABLE old_cluster_info;
+DROP TABLE new_cluster_info;
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+REPACK clstrpart USING INDEX clstrpart_idx;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+
+-- And finally the same for REPACK w/o index.
+DROP TABLE old_cluster_info;
+DROP TABLE new_cluster_info;
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+REPACK clstrpart;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+
 DROP TABLE clstrpart;
 
 -- Ownership of partitions is checked
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3c80d49b67e..c17b5c0cadc 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2537,6 +2537,8 @@ ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
 RepOriginId
+RepackCommand
+RepackStmt
 ReparameterizeForeignPathByChild_function
 ReplaceVarsFromTargetList_context
 ReplaceVarsNoMatchOption
@@ -2604,6 +2606,7 @@ RtlNtStatusToDosError_t
 RuleInfo
 RuleLock
 RuleStmt
+RunMode
 RunningTransactions
 RunningTransactionsData
 SASLStatus
-- 
2.47.3


--52vaejy5wnlkvc5v--





^ permalink  raw  reply  [nested|flat] 40+ messages in thread


end of thread, other threads:[~2025-07-26 17:57 UTC | newest]

Thread overview: 40+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-02-15 17:09 Re: Avoid erroring out when unable to remove or parse logical rewrite files to save checkpoint work Andres Freund <[email protected]>
2022-02-15 17:57 ` Nathan Bossart <[email protected]>
2022-02-15 18:10   ` Andres Freund <[email protected]>
2022-02-15 18:37     ` Nathan Bossart <[email protected]>
2022-02-15 23:11       ` Nathan Bossart <[email protected]>
2022-03-24 00:17         ` Thomas Munro <[email protected]>
2022-03-29 22:48           ` Nathan Bossart <[email protected]>
2022-03-30 16:21             ` Nathan Bossart <[email protected]>
2022-04-08 20:18               ` Nathan Bossart <[email protected]>
2022-07-08 16:09                 ` Bharath Rupireddy <[email protected]>
2022-07-08 17:14                   ` Nathan Bossart <[email protected]>
2022-07-18 11:23                     ` Bharath Rupireddy <[email protected]>
2022-07-18 18:18                       ` Nathan Bossart <[email protected]>
2022-08-09 03:10                         ` Thomas Munro <[email protected]>
2022-08-09 03:17                           ` Tom Lane <[email protected]>
2022-08-09 03:27                             ` Tom Lane <[email protected]>
2022-08-09 03:41                               ` Thomas Munro <[email protected]>
2022-08-09 03:50                                 ` Tom Lane <[email protected]>
2022-08-09 03:59                                   ` Bharath Rupireddy <[email protected]>
2022-08-09 04:28                                     ` Nathan Bossart <[email protected]>
2022-08-09 05:26                                       ` Thomas Munro <[email protected]>
2022-08-09 18:38                                         ` Nathan Bossart <[email protected]>
2022-08-10 07:14                                         ` Bharath Rupireddy <[email protected]>
2022-08-10 08:41                                           ` Thomas Munro <[email protected]>
2022-08-10 09:58                                             ` Bharath Rupireddy <[email protected]>
2022-08-16 20:32                                               ` Nathan Bossart <[email protected]>
2022-08-17 07:09                                                 ` Bharath Rupireddy <[email protected]>
2022-08-23 18:07                                                   ` Nathan Bossart <[email protected]>
2022-08-25 05:18                                                     ` Bharath Rupireddy <[email protected]>
2022-08-25 12:21                                                       ` Michael Paquier <[email protected]>
2022-08-27 08:36                                                         ` Bharath Rupireddy <[email protected]>
2022-08-28 21:52                                                           ` Nathan Bossart <[email protected]>
2022-08-31 07:10                                                             ` Michael Paquier <[email protected]>
2022-08-31 19:14                                                           ` Andres Freund <[email protected]>
2022-08-31 19:24                                                             ` Nathan Bossart <[email protected]>
2022-08-31 19:28                                                               ` Andres Freund <[email protected]>
2022-08-09 04:07                           ` Andres Freund <[email protected]>
2022-07-11 20:25                 ` Andres Freund <[email protected]>
2022-07-11 21:23                   ` Nathan Bossart <[email protected]>
2025-07-26 17:57 [PATCH v22 2/2] Add REPACK command Álvaro Herrera <[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