public inbox for [email protected]help / color / mirror / Atom feed
[PATCH v11 6/6] Move removal of old logical rewrite mapping files to custodian. 24+ messages / 4 participants [nested] [flat]
* [PATCH v11 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2f08fbe8d3..a01edf8a1f 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -117,6 +117,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -124,6 +125,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1183,7 +1185,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1211,6 +1214,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1243,15 +1251,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1289,3 +1289,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --envbJBWh7q8WU6mo-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v15 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..ff4cd8cef9 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index d0fd955d4b..c4d0a22451 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -382,3 +388,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --HcAYCG3uE/tztfnV Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v15-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v16 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- contrib/test_decoding/expected/rewrite.out | 21 ++++++ contrib/test_decoding/sql/rewrite.sql | 17 +++++ src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++--- src/backend/postmaster/custodian.c | 43 ++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 6 files changed, 154 insertions(+), 10 deletions(-) diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out index b30999c436..00b505ef67 100644 --- a/contrib/test_decoding/expected/rewrite.out +++ b/contrib/test_decoding/expected/rewrite.out @@ -152,6 +152,27 @@ SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'inc COMMIT (6 rows) +-- make sure custodian cleans up files +CHECKPOINT; +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + ?column? +---------- + t +(1 row) + SELECT pg_drop_replication_slot('regression_slot'); pg_drop_replication_slot -------------------------- diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql index 62dead3a9b..767eccbed4 100644 --- a/contrib/test_decoding/sql/rewrite.sql +++ b/contrib/test_decoding/sql/rewrite.sql @@ -100,6 +100,23 @@ VACUUM FULL pg_proc; VACUUM FULL pg_description; VACUUM FULL pg_shdescription; V INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (9, 7, 1); SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +-- make sure custodian cleans up files +CHECKPOINT; +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + SELECT pg_drop_replication_slot('regression_slot'); DROP TABLE IF EXISTS replication_example; DROP FUNCTION iamalongfunction(); diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..ff4cd8cef9 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index d0fd955d4b..c4d0a22451 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -382,3 +388,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --lrZ03NoBR/3+SXJZ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v16-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v17 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- contrib/test_decoding/expected/rewrite.out | 19 ++++++ contrib/test_decoding/sql/rewrite.sql | 14 ++++ src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++--- src/backend/postmaster/custodian.c | 43 ++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 6 files changed, 149 insertions(+), 10 deletions(-) diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out index 8b97f15f6f..214a514a0a 100644 --- a/contrib/test_decoding/expected/rewrite.out +++ b/contrib/test_decoding/expected/rewrite.out @@ -183,3 +183,22 @@ SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); t (1 row) +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql index d268fa559a..d66f70f837 100644 --- a/contrib/test_decoding/sql/rewrite.sql +++ b/contrib/test_decoding/sql/rewrite.sql @@ -122,3 +122,17 @@ BEGIN END $$; SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..ff4cd8cef9 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index d0fd955d4b..c4d0a22451 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -382,3 +388,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --ew6BAiZeqk4r7MaW Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v17-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v4 6/8] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. --- src/backend/access/heap/rewriteheap.c | 83 +++++++++++++++++++++++---- src/backend/postmaster/checkpointer.c | 33 +++++++++++ src/backend/postmaster/custodian.c | 10 ++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/bgwriter.h | 3 + 5 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2a53826736..c5a1103687 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,10 +116,13 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/bgwriter.h" +#include "postmaster/interrupt.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" #include "storage/fd.h" +#include "storage/proc.h" #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" @@ -1182,7 +1185,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1214,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + CheckPointSetLogicalRewriteCutoff(cutoff); + if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1249,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1287,65 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't interfere with an + * ongoing call to CheckPointLogicalRewriteHeap() that is flushing mappings to + * disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + bool value_set = false; + + cutoff = CheckPointGetLogicalRewriteCutoff(&value_set); + if (!value_set) + return; + + mappings_dir = AllocateDir("pg_logical/mappings"); + while (!ShutdownRequestPending && + (mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + struct stat statbuf; + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 23f691cd47..fe0934e5a6 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -127,6 +127,9 @@ typedef struct uint32 num_backend_writes; /* counts user backend buffer writes */ uint32 num_backend_fsync; /* counts user backend fsync calls */ + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ + bool logical_rewrite_mappings_cutoff_set; + int num_requests; /* current # of requests */ int max_requests; /* allocated array size */ CheckpointerRequest requests[FLEXIBLE_ARRAY_MEMBER]; @@ -1341,3 +1344,33 @@ FirstCallSinceLastCheckpoint(void) return FirstCall; } + +/* + * Used by CheckPointLogicalRewriteHeap() to tell the custodian which logical + * rewrite mapping files it can remove. + */ +void +CheckPointSetLogicalRewriteCutoff(XLogRecPtr cutoff) +{ + SpinLockAcquire(&CheckpointerShmem->ckpt_lck); + CheckpointerShmem->logical_rewrite_mappings_cutoff = cutoff; + CheckpointerShmem->logical_rewrite_mappings_cutoff_set = true; + SpinLockRelease(&CheckpointerShmem->ckpt_lck); +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CheckPointGetLogicalRewriteCutoff(bool *value_set) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CheckpointerShmem->ckpt_lck); + cutoff = CheckpointerShmem->logical_rewrite_mappings_cutoff; + *value_set = CheckpointerShmem->logical_rewrite_mappings_cutoff_set; + SpinLockRelease(&CheckpointerShmem->ckpt_lck); + + return cutoff; +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 0f4dbdd669..9c5479b5cf 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -36,6 +36,7 @@ #include <time.h> +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -218,6 +219,15 @@ CustodianMain(void) */ RemoveOldSerializedSnapshots(); + /* + * Remove logical rewrite mapping files that are no longer needed. + * + * It is not important for these to be removed in single-user mode, so + * we don't need any extra handling outside of the custodian process for + * this. + */ + RemoveOldLogicalRewriteMappings(); + /* Calculate how long to sleep */ end_time = (pg_time_t) time(NULL); elapsed_secs = end_time - start_time; diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index aa5c48f219..f493094557 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" void CheckPointLogicalRewriteHeap(void); +void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h index 2882efd67b..051e6732cb 100644 --- a/src/include/postmaster/bgwriter.h +++ b/src/include/postmaster/bgwriter.h @@ -42,4 +42,7 @@ extern void CheckpointerShmemInit(void); extern bool FirstCallSinceLastCheckpoint(void); +extern void CheckPointSetLogicalRewriteCutoff(XLogRecPtr cutoff); +extern XLogRecPtr CheckPointGetLogicalRewriteCutoff(bool *value_set); + #endif /* _BGWRITER_H */ -- 2.25.1 --BXVAT5kNtrzKuDFl Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0007-Use-syncfs-in-CheckPointLogicalRewriteHeap-for-sh.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v5 6/8] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. --- src/backend/access/heap/rewriteheap.c | 83 +++++++++++++++++++++++---- src/backend/postmaster/checkpointer.c | 33 +++++++++++ src/backend/postmaster/custodian.c | 10 ++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/bgwriter.h | 3 + 5 files changed, 120 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2a53826736..c5a1103687 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,10 +116,13 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/bgwriter.h" +#include "postmaster/interrupt.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" #include "storage/fd.h" +#include "storage/proc.h" #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" @@ -1182,7 +1185,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1214,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + CheckPointSetLogicalRewriteCutoff(cutoff); + if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1249,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1287,65 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't interfere with an + * ongoing call to CheckPointLogicalRewriteHeap() that is flushing mappings to + * disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + bool value_set = false; + + cutoff = CheckPointGetLogicalRewriteCutoff(&value_set); + if (!value_set) + return; + + mappings_dir = AllocateDir("pg_logical/mappings"); + while (!ShutdownRequestPending && + (mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + struct stat statbuf; + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 4488e3a443..666f2a0368 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -128,6 +128,9 @@ typedef struct uint32 num_backend_writes; /* counts user backend buffer writes */ uint32 num_backend_fsync; /* counts user backend fsync calls */ + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ + bool logical_rewrite_mappings_cutoff_set; + int num_requests; /* current # of requests */ int max_requests; /* allocated array size */ CheckpointerRequest requests[FLEXIBLE_ARRAY_MEMBER]; @@ -1342,3 +1345,33 @@ FirstCallSinceLastCheckpoint(void) return FirstCall; } + +/* + * Used by CheckPointLogicalRewriteHeap() to tell the custodian which logical + * rewrite mapping files it can remove. + */ +void +CheckPointSetLogicalRewriteCutoff(XLogRecPtr cutoff) +{ + SpinLockAcquire(&CheckpointerShmem->ckpt_lck); + CheckpointerShmem->logical_rewrite_mappings_cutoff = cutoff; + CheckpointerShmem->logical_rewrite_mappings_cutoff_set = true; + SpinLockRelease(&CheckpointerShmem->ckpt_lck); +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CheckPointGetLogicalRewriteCutoff(bool *value_set) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CheckpointerShmem->ckpt_lck); + cutoff = CheckpointerShmem->logical_rewrite_mappings_cutoff; + *value_set = CheckpointerShmem->logical_rewrite_mappings_cutoff_set; + SpinLockRelease(&CheckpointerShmem->ckpt_lck); + + return cutoff; +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 8591c5db9b..7f914a617f 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -36,6 +36,7 @@ #include <time.h> +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -219,6 +220,15 @@ CustodianMain(void) */ RemoveOldSerializedSnapshots(); + /* + * Remove logical rewrite mapping files that are no longer needed. + * + * It is not important for these to be removed in single-user mode, so + * we don't need any extra handling outside of the custodian process for + * this. + */ + RemoveOldLogicalRewriteMappings(); + /* Calculate how long to sleep */ end_time = (pg_time_t) time(NULL); elapsed_secs = end_time - start_time; diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index aa5c48f219..f493094557 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" void CheckPointLogicalRewriteHeap(void); +void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h index 2882efd67b..051e6732cb 100644 --- a/src/include/postmaster/bgwriter.h +++ b/src/include/postmaster/bgwriter.h @@ -42,4 +42,7 @@ extern void CheckpointerShmemInit(void); extern bool FirstCallSinceLastCheckpoint(void); +extern void CheckPointSetLogicalRewriteCutoff(XLogRecPtr cutoff); +extern XLogRecPtr CheckPointGetLogicalRewriteCutoff(bool *value_set); + #endif /* _BGWRITER_H */ -- 2.25.1 --2oS5YaxWCcQjTEyO Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v5-0007-Use-syncfs-in-CheckPointLogicalRewriteHeap-for-sh.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v10 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2f08fbe8d3..a01edf8a1f 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -117,6 +117,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -124,6 +125,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1183,7 +1185,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1211,6 +1214,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1243,15 +1251,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1289,3 +1289,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 353cbb2924..965372b5ff 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --+QahgC5+KEYLbs62-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v18 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- contrib/test_decoding/expected/rewrite.out | 19 ++++++ contrib/test_decoding/sql/rewrite.sql | 14 ++++ src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++--- src/backend/postmaster/custodian.c | 43 ++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 6 files changed, 149 insertions(+), 10 deletions(-) diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out index 8b97f15f6f..214a514a0a 100644 --- a/contrib/test_decoding/expected/rewrite.out +++ b/contrib/test_decoding/expected/rewrite.out @@ -183,3 +183,22 @@ SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); t (1 row) +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql index d268fa559a..d66f70f837 100644 --- a/contrib/test_decoding/sql/rewrite.sql +++ b/contrib/test_decoding/sql/rewrite.sql @@ -122,3 +122,17 @@ BEGIN END $$; SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..ff4cd8cef9 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 9382d524a6..33185e9913 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -377,3 +383,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --LZvS9be/3tNcYl/X Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v18-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v19 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- contrib/test_decoding/expected/rewrite.out | 19 ++++++ contrib/test_decoding/sql/rewrite.sql | 14 ++++ src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++--- src/backend/postmaster/custodian.c | 43 ++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 6 files changed, 149 insertions(+), 10 deletions(-) diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out index 8b97f15f6f..214a514a0a 100644 --- a/contrib/test_decoding/expected/rewrite.out +++ b/contrib/test_decoding/expected/rewrite.out @@ -183,3 +183,22 @@ SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); t (1 row) +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql index d268fa559a..d66f70f837 100644 --- a/contrib/test_decoding/sql/rewrite.sql +++ b/contrib/test_decoding/sql/rewrite.sql @@ -122,3 +122,17 @@ BEGIN END $$; SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 8993c1ed5a..9ea0f81ac3 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 4e0ce1f7b3..4cbd89fae9 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -377,3 +383,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 1125457053..dc3eb3e308 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --x+6KMIRAuhnl3hBn Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v19-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v20 3/4] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- contrib/test_decoding/expected/rewrite.out | 19 ++++++ contrib/test_decoding/sql/rewrite.sql | 14 ++++ src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++--- src/backend/postmaster/custodian.c | 43 ++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 6 files changed, 149 insertions(+), 10 deletions(-) diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out index 8b97f15f6f..214a514a0a 100644 --- a/contrib/test_decoding/expected/rewrite.out +++ b/contrib/test_decoding/expected/rewrite.out @@ -183,3 +183,22 @@ SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); t (1 row) +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql index d268fa559a..d66f70f837 100644 --- a/contrib/test_decoding/sql/rewrite.sql +++ b/contrib/test_decoding/sql/rewrite.sql @@ -122,3 +122,17 @@ BEGIN END $$; SELECT count(*) = 0 FROM pg_ls_logicalsnapdir(); +DO $$ +DECLARE + mappings_removed bool; + loops int := 0; +BEGIN + LOOP + mappings_removed := count(*) = 0 FROM pg_ls_logicalmapdir(); + IF mappings_removed OR loops > 120 * 100 THEN EXIT; END IF; + PERFORM pg_sleep(0.01); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) = 0 FROM pg_ls_logicalmapdir(); diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 8993c1ed5a..9ea0f81ac3 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 4e0ce1f7b3..4cbd89fae9 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -377,3 +383,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 1125457053..dc3eb3e308 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --CE+1k2dSO48ffgeK Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v20-0004-Do-not-delay-shutdown-due-to-long-running-custod.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v10 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2f08fbe8d3..a01edf8a1f 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -117,6 +117,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -124,6 +125,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1183,7 +1185,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1211,6 +1214,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1243,15 +1251,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1289,3 +1289,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 353cbb2924..965372b5ff 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --+QahgC5+KEYLbs62-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v6 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. --- src/backend/access/heap/rewriteheap.c | 79 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 44 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 5 ++ 4 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2a53826736..edeab65e60 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -1182,7 +1183,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1212,10 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + CustodianSetLogicalRewriteCutoff(cutoff); + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1246,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1284,64 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't interfere with an + * ongoing call to CheckPointLogicalRewriteHeap() that is flushing mappings to + * disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + bool value_set = false; + + cutoff = CustodianGetLogicalRewriteCutoff(&value_set); + if (!value_set) + return; + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 861de882c6..0ce4edcf61 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -27,6 +27,7 @@ #include <time.h> +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -46,6 +47,9 @@ typedef struct { slock_t cust_lck; int cust_flags; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ + bool logical_rewrite_mappings_cutoff_set; } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -222,6 +226,16 @@ CustodianMain(void) if (flags & CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS) RemoveOldSerializedSnapshots(); + /* + * Remove logical rewrite mapping files that are no longer needed. + * + * It is not important for these to be removed in single-user mode, so + * we don't need any extra handling outside of the custodian process for + * this. + */ + if (flags & CUSTODIAN_REMOVE_REWRITE_MAPPINGS) + RemoveOldLogicalRewriteMappings(); + /* Calculate how long to sleep */ end_time = (pg_time_t) time(NULL); elapsed_secs = end_time - start_time; @@ -274,3 +288,33 @@ RequestCustodian(int flags) if (ProcGlobal->custodianLatch) SetLatch(ProcGlobal->custodianLatch); } + +/* + * Used by CheckPointLogicalRewriteHeap() to tell the custodian which logical + * rewrite mapping files it can remove. + */ +void +CustodianSetLogicalRewriteCutoff(XLogRecPtr cutoff) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = cutoff; + CustodianShmem->logical_rewrite_mappings_cutoff_set = true; + SpinLockRelease(&CustodianShmem->cust_lck); +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(bool *value_set) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + *value_set = CustodianShmem->logical_rewrite_mappings_cutoff_set; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 3e27790b3f..61d7aa8ed8 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 769c07f2c9..1af96ebd02 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,13 +12,18 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(int flags); +extern void CustodianSetLogicalRewriteCutoff(XLogRecPtr cutoff); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(bool *value_set); /* flags for RequestCustodian() */ #define CUSTODIAN_REMOVE_TEMP_FILES 0x0001 #define CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS 0x0002 +#define CUSTODIAN_REMOVE_REWRITE_MAPPINGS 0x0004 #endif /* _CUSTODIAN_H */ -- 2.25.1 --liOOAslEiF7prFVr-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v7 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 197f06b5ec..6de1fb19de 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1182,7 +1184,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1213,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1248,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1286,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 353cbb2924..965372b5ff 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --/04w6evG8XlLl3ft-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v8 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 9dd885d936..a08dd4a524 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1182,7 +1184,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1213,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1248,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1286,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 353cbb2924..965372b5ff 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --X1bOJ3K7DJ5YkBrT-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v9 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 9dd885d936..a08dd4a524 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1182,7 +1184,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1210,6 +1213,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1240,15 +1248,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1286,3 +1286,61 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + 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; + TransactionId rewrite_xid; + TransactionId create_xid; + uint32 hi, + lo; + + 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)) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 353cbb2924..965372b5ff 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --y0ulUmNC+osPPQO6-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v12 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 80 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 ++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 118 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..07976504cc 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1247,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1285,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --UlVJffcvxoiEqYs2-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v13 6/6] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 80 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 ++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 118 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..07976504cc 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,11 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, + !IsUnderPostmaster, + LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1247,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1285,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index 855a756ca0..d4be19e5de 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(bool retry); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -73,6 +78,7 @@ struct cust_task_funcs_entry static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_TEMP_FILES, RemovePgTempFiles, NULL}, {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -384,3 +390,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index 37334941cc..f177d55159 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -20,6 +22,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_TEMP_FILES, CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -29,5 +32,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --CE+1k2dSO48ffgeK-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v14 3/3] Move removal of old logical rewrite mapping files to custodian. @ 2021-12-13 06:07 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2021-12-13 06:07 UTC (permalink / raw) If there are many such files to remove, checkpoints can take much longer. To avoid this, move this work to the newly-introduced custodian process. Since the mapping files include 32-bit transaction IDs, there is a risk of wraparound if the files are not cleaned up fast enough. Removing these files in checkpoints offered decent wraparound protection simply due to the relatively high frequency of checkpointing. With this change, servers should still clean up mappings files with decently high frequency, but in theory the wraparound risk might worsen for some (e.g., if the custodian is spending a lot of time on a different task). Given this is an existing problem, this change makes no effort to handle the wraparound risk, and it is left as a future exercise. --- src/backend/access/heap/rewriteheap.c | 78 +++++++++++++++++++++++---- src/backend/postmaster/custodian.c | 43 +++++++++++++++ src/include/access/rewriteheap.h | 1 + src/include/postmaster/custodian.h | 4 ++ 4 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index 2fe9e48e50..ff4cd8cef9 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -116,6 +116,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "pgstat.h" +#include "postmaster/custodian.h" #include "replication/logical.h" #include "replication/slot.h" #include "storage/bufmgr.h" @@ -123,6 +124,7 @@ #include "storage/procarray.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" #include "utils/rel.h" /* @@ -1179,7 +1181,8 @@ heap_xlog_logical_rewrite(XLogReaderState *r) * Perform a checkpoint for logical rewrite mappings * * This serves two tasks: - * 1) Remove all mappings not needed anymore based on the logical restart LSN + * 1) Alert the custodian to remove all mappings not needed anymore based on the + * logical restart LSN * 2) Flush all remaining mappings to disk, so that replay after a checkpoint * only has to deal with the parts of a mapping that have been written out * after the checkpoint started. @@ -1207,6 +1210,9 @@ CheckPointLogicalRewriteHeap(void) if (cutoff != InvalidXLogRecPtr && redo < cutoff) cutoff = redo; + /* let the custodian know what it can remove */ + RequestCustodian(CUSTODIAN_REMOVE_REWRITE_MAPPINGS, LSNGetDatum(cutoff)); + mappings_dir = AllocateDir("pg_logical/mappings"); while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) { @@ -1239,15 +1245,7 @@ CheckPointLogicalRewriteHeap(void) lsn = ((uint64) hi) << 32 | lo; - if (lsn < cutoff || cutoff == InvalidXLogRecPtr) - { - elog(DEBUG1, "removing logical rewrite file \"%s\"", path); - if (unlink(path) < 0) - ereport(ERROR, - (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); - } - else + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) { /* on some operating systems fsyncing a file requires O_RDWR */ int fd = OpenTransientFile(path, O_RDWR | PG_BINARY); @@ -1285,3 +1283,63 @@ CheckPointLogicalRewriteHeap(void) /* persist directory entries to disk */ fsync_fname("pg_logical/mappings", true); } + +/* + * Remove all mappings not needed anymore based on the logical restart LSN saved + * by the checkpointer. We use this saved value instead of calling + * ReplicationSlotsComputeLogicalRestartLSN() so that we don't try to remove + * files that a concurrent call to CheckPointLogicalRewriteHeap() is trying to + * flush to disk. + */ +void +RemoveOldLogicalRewriteMappings(void) +{ + XLogRecPtr cutoff; + DIR *mappings_dir; + struct dirent *mapping_de; + char path[MAXPGPATH + 20]; + + cutoff = CustodianGetLogicalRewriteCutoff(); + + mappings_dir = AllocateDir("pg_logical/mappings"); + while ((mapping_de = ReadDir(mappings_dir, "pg_logical/mappings")) != NULL) + { + Oid dboid; + Oid relid; + XLogRecPtr lsn; + TransactionId rewrite_xid; + 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); + de_type = get_dirent_type(path, mapping_de, false, DEBUG1); + + if (de_type != PGFILETYPE_ERROR && de_type != PGFILETYPE_REG) + continue; + + /* Skip over files that cannot be ours. */ + if (strncmp(mapping_de->d_name, "map-", 4) != 0) + continue; + + if (sscanf(mapping_de->d_name, LOGICAL_REWRITE_FORMAT, + &dboid, &relid, &hi, &lo, &rewrite_xid, &create_xid) != 6) + elog(ERROR, "could not parse filename \"%s\"", mapping_de->d_name); + + lsn = ((uint64) hi) << 32 | lo; + if (lsn >= cutoff && cutoff != InvalidXLogRecPtr) + continue; + + elog(DEBUG1, "removing logical rewrite file \"%s\"", path); + if (unlink(path) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not remove file \"%s\": %m", path))); + } + FreeDir(mappings_dir); +} diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c index d0fd955d4b..c4d0a22451 100644 --- a/src/backend/postmaster/custodian.c +++ b/src/backend/postmaster/custodian.c @@ -21,6 +21,7 @@ */ #include "postgres.h" +#include "access/rewriteheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" #include "postmaster/custodian.h" @@ -33,11 +34,13 @@ #include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/memutils.h" +#include "utils/pg_lsn.h" static void DoCustodianTasks(void); static CustodianTask CustodianGetNextTask(void); static void CustodianEnqueueTask(CustodianTask task); static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); +static void CustodianSetLogicalRewriteCutoff(Datum arg); typedef struct { @@ -45,6 +48,8 @@ typedef struct CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; int task_queue_head; + + XLogRecPtr logical_rewrite_mappings_cutoff; /* can remove older mappings */ } CustodianShmemStruct; static CustodianShmemStruct *CustodianShmem; @@ -72,6 +77,7 @@ struct cust_task_funcs_entry */ static const struct cust_task_funcs_entry cust_task_functions[] = { {CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, RemoveOldSerializedSnapshots, NULL}, + {CUSTODIAN_REMOVE_REWRITE_MAPPINGS, RemoveOldLogicalRewriteMappings, CustodianSetLogicalRewriteCutoff}, {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ }; @@ -382,3 +388,40 @@ LookupCustodianFunctions(CustodianTask task) elog(ERROR, "could not lookup functions for custodian task %d", task); pg_unreachable(); } + +/* + * Stores the provided cutoff LSN in the custodian's shared memory. + * + * It's okay if the cutoff LSN is updated before a previously set cutoff has + * been used for cleaning up files. If that happens, it just means that the + * next invocation of RemoveOldLogicalRewriteMappings() will use a more accurate + * cutoff. + */ +static void +CustodianSetLogicalRewriteCutoff(Datum arg) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->logical_rewrite_mappings_cutoff = DatumGetLSN(arg); + SpinLockRelease(&CustodianShmem->cust_lck); + + /* if pass-by-ref, free Datum memory */ +#ifndef USE_FLOAT8_BYVAL + pfree(DatumGetPointer(arg)); +#endif +} + +/* + * Used by the custodian to determine which logical rewrite mapping files it can + * remove. + */ +XLogRecPtr +CustodianGetLogicalRewriteCutoff(void) +{ + XLogRecPtr cutoff; + + SpinLockAcquire(&CustodianShmem->cust_lck); + cutoff = CustodianShmem->logical_rewrite_mappings_cutoff; + SpinLockRelease(&CustodianShmem->cust_lck); + + return cutoff; +} diff --git a/src/include/access/rewriteheap.h b/src/include/access/rewriteheap.h index 5cc04756a5..bc875330d7 100644 --- a/src/include/access/rewriteheap.h +++ b/src/include/access/rewriteheap.h @@ -53,5 +53,6 @@ typedef struct LogicalRewriteMappingData */ #define LOGICAL_REWRITE_FORMAT "map-%x-%x-%X_%X-%x-%x" extern void CheckPointLogicalRewriteHeap(void); +extern void RemoveOldLogicalRewriteMappings(void); #endif /* REWRITE_HEAP_H */ diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h index ab6d4283b9..00280c203b 100644 --- a/src/include/postmaster/custodian.h +++ b/src/include/postmaster/custodian.h @@ -12,6 +12,8 @@ #ifndef _CUSTODIAN_H #define _CUSTODIAN_H +#include "access/xlogdefs.h" + /* * If you add a new task here, be sure to add its corresponding function * pointers to cust_task_functions in custodian.c. @@ -19,6 +21,7 @@ typedef enum CustodianTask { CUSTODIAN_REMOVE_SERIALIZED_SNAPSHOTS, + CUSTODIAN_REMOVE_REWRITE_MAPPINGS, NUM_CUSTODIAN_TASKS, /* new tasks go above */ INVALID_CUSTODIAN_TASK @@ -28,5 +31,6 @@ extern void CustodianMain(void) pg_attribute_noreturn(); extern Size CustodianShmemSize(void); extern void CustodianShmemInit(void); extern void RequestCustodian(CustodianTask task, Datum arg); +extern XLogRecPtr CustodianGetLogicalRewriteCutoff(void); #endif /* _CUSTODIAN_H */ -- 2.25.1 --BXVAT5kNtrzKuDFl-- ^ permalink raw reply [nested|flat] 24+ messages in thread
* Re: pgsql: Move snowball_create.sql creation into perl file @ 2023-05-23 18:51 Tom Lane <[email protected]> 0 siblings, 1 reply; 24+ messages in thread From: Tom Lane @ 2023-05-23 18:51 UTC (permalink / raw) To: Andres Freund <[email protected]>; +Cc: Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> [ dropping -packagers ] Andres Freund <[email protected]> writes: > I guess I need to go and check how long the "release" tarball generation > takes... It's quick except for the documentation-generating steps. Maybe we could test that part only once? regards, tom lane ^ permalink raw reply [nested|flat] 24+ messages in thread
* testing dist tarballs @ 2023-05-24 21:24 Andres Freund <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 2 replies; 24+ messages in thread From: Andres Freund @ 2023-05-24 21:24 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> Hi, On 2023-05-23 14:51:03 -0400, Tom Lane wrote: > Andres Freund <[email protected]> writes: > > I guess I need to go and check how long the "release" tarball generation > > takes... > > It's quick except for the documentation-generating steps. Maybe > we could test that part only once? First thing I noticed that 'make dist' doesn't work in a vpath, failing in a somewhat obscure way (likely because in a vpath build the the copy from the source dir doesn't include GNUMakefile). Do we expect it to work? Besides docs, the slowest part appears to be gzip --best and then bzip2, as those runs serially and takes 11 and 13 seconds respectively here... The first thing I tried was: make -j8 dist GZIP=pigz BZIP2=pbzip2 unfortunately that results in pigz: abort: cannot provide files in GZIP environment variable echo GZIP=pigz >> src/Makefile.custom echo BZIP2=pbzip2 >> src/Makefile.custom reduces that to real 1m6.472s user 1m28.316s sys 0m5.340s real 0m54.811s user 1m42.078s sys 0m6.183s still not great... OTOH, we currently already build the docs as part of the CompilerWarnings test. I don't think there's a reason to test that twice? For me make distcheck currently fails: In file included from ../../src/include/postgres.h:46, from hashfn.c:24: ../../src/include/utils/elog.h:79:10: fatal error: utils/errcodes.h: No such file or directory 79 | #include "utils/errcodes.h" | ^~~~~~~~~~~~~~~~~~ compilation terminated. make[3]: *** [<builtin>: hashfn.o] Error 1 at first I thought it was due to my use of -j8 - but it doesn't even work without that. That's due to MAKELEVEL: submake-generated-headers: ifndef NO_GENERATED_HEADERS ifeq ($(MAKELEVEL),0) $(MAKE) -C $(top_builddir)/src/backend generated-headers endif endif So the distcheck target needs to reset MAKELEVEL=0 - unless somebody has a better idea? Separately, it's somewhat confusing that we include errcodes.h etc in src/backend/utils, rather than its final location, in src/include/utils. It works, even without perl, because copying the file doesn't require perl, it's just generating it... Greetings, Andres Freund ^ permalink raw reply [nested|flat] 24+ messages in thread
* Re: testing dist tarballs @ 2023-05-24 21:35 Tom Lane <[email protected]> parent: Andres Freund <[email protected]> 1 sibling, 0 replies; 24+ messages in thread From: Tom Lane @ 2023-05-24 21:35 UTC (permalink / raw) To: Andres Freund <[email protected]>; +Cc: Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> Andres Freund <[email protected]> writes: > First thing I noticed that 'make dist' doesn't work in a vpath, failing in a > somewhat obscure way (likely because in a vpath build the the copy from the > source dir doesn't include GNUMakefile). Do we expect it to work? Don't see how it could possibly be useful in a vpath, because you'd have the real source files and the generated files in different trees. regards, tom lane ^ permalink raw reply [nested|flat] 24+ messages in thread
* Re: testing dist tarballs @ 2023-05-26 07:02 Peter Eisentraut <[email protected]> parent: Andres Freund <[email protected]> 1 sibling, 1 reply; 24+ messages in thread From: Peter Eisentraut @ 2023-05-26 07:02 UTC (permalink / raw) To: Andres Freund <[email protected]>; Tom Lane <[email protected]>; +Cc: Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> On 24.05.23 23:24, Andres Freund wrote: > First thing I noticed that 'make dist' doesn't work in a vpath, failing in a > somewhat obscure way (likely because in a vpath build the the copy from the > source dir doesn't include GNUMakefile). Do we expect it to work? I don't think so. > Separately, it's somewhat confusing that we include errcodes.h etc in > src/backend/utils, rather than its final location, in src/include/utils. It > works, even without perl, because copying the file doesn't require perl, it's > just generating it... The "copying" is actually a symlink, right? I don't think we want to ship symlinks in the tarball? ^ permalink raw reply [nested|flat] 24+ messages in thread
* Re: testing dist tarballs @ 2023-05-27 18:47 Andres Freund <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 24+ messages in thread From: Andres Freund @ 2023-05-27 18:47 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Tom Lane <[email protected]>; Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> Hi, On 2023-05-26 09:02:33 +0200, Peter Eisentraut wrote: > On 24.05.23 23:24, Andres Freund wrote: > > First thing I noticed that 'make dist' doesn't work in a vpath, failing in a > > somewhat obscure way (likely because in a vpath build the the copy from the > > source dir doesn't include GNUMakefile). Do we expect it to work? > > I don't think so. Maybe we should just error out in that case, instead of failing in an obscure way down the line? > > Separately, it's somewhat confusing that we include errcodes.h etc in > > src/backend/utils, rather than its final location, in src/include/utils. It > > works, even without perl, because copying the file doesn't require perl, it's > > just generating it... > > The "copying" is actually a symlink, right? I don't think we want to ship > symlinks in the tarball? Fair point - still seems we should just create the files in the right directory instead of doing it in the wrong place and then creating symlinks to make them accessible... Greetings, Andres Freund ^ permalink raw reply [nested|flat] 24+ messages in thread
* Re: testing dist tarballs @ 2023-05-31 10:52 Peter Eisentraut <[email protected]> parent: Andres Freund <[email protected]> 0 siblings, 0 replies; 24+ messages in thread From: Peter Eisentraut @ 2023-05-31 10:52 UTC (permalink / raw) To: Andres Freund <[email protected]>; +Cc: Tom Lane <[email protected]>; Christoph Berg <[email protected]>; PostgreSQL Hackers <[email protected]> On 27.05.23 14:47, Andres Freund wrote: >>> Separately, it's somewhat confusing that we include errcodes.h etc in >>> src/backend/utils, rather than its final location, in src/include/utils. It >>> works, even without perl, because copying the file doesn't require perl, it's >>> just generating it... >> >> The "copying" is actually a symlink, right? I don't think we want to ship >> symlinks in the tarball? > > Fair point - still seems we should just create the files in the right > directory instead of doing it in the wrong place and then creating symlinks to > make them accessible... Right. I think the reason this was set up this way is that with make it is generally dubious to create target files outside of the current directory. ^ permalink raw reply [nested|flat] 24+ messages in thread
end of thread, other threads:[~2023-05-31 10:52 UTC | newest] Thread overview: 24+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-12-13 06:07 [PATCH v19 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v12 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v6 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v5 6/8] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v10 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v15 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v16 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v17 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v13 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v4 6/8] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v14 3/3] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v18 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v10 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v20 3/4] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v8 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v11 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v7 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2021-12-13 06:07 [PATCH v9 6/6] Move removal of old logical rewrite mapping files to custodian. Nathan Bossart <[email protected]> 2023-05-23 18:51 Re: pgsql: Move snowball_create.sql creation into perl file Tom Lane <[email protected]> 2023-05-24 21:24 ` testing dist tarballs Andres Freund <[email protected]> 2023-05-24 21:35 ` Re: testing dist tarballs Tom Lane <[email protected]> 2023-05-26 07:02 ` Re: testing dist tarballs Peter Eisentraut <[email protected]> 2023-05-27 18:47 ` Re: testing dist tarballs Andres Freund <[email protected]> 2023-05-31 10:52 ` Re: testing dist tarballs Peter Eisentraut <[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