From 87ba918b9020ce9fa0d4852f222221f521d8701f Mon Sep 17 00:00:00 2001 From: Zhijie Hou Date: Fri, 10 Apr 2026 16:24:55 +0800 Subject: [PATCH v2] Allow old WAL recycling during REPACK CONCURRENTLY During REPACK CONCURRENTLY, logical decoding can keep replication slot.restart_lsn pinned behind the oldest running transaction, which is often the long-lived REPACK transaction itself. As a result, old WAL segments are retained longer than necessary. This commit advances the replication slot each time WAL insertion crosses a segment boundary, so obsolete WAL files can be recycled while REPACK is still running. This change does not advance catalog_xmin. REPACK already holds a snapshot that prevents catalog dead tuple removal, so catalog_xmin handling can be addressed independently. --- src/backend/commands/repack_worker.c | 14 +++++++++++++- src/backend/replication/logical/logical.c | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index b84041372b8..eed69d36508 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -397,12 +397,24 @@ decode_concurrent_changes(LogicalDecodingContext *ctx, /* * If WAL segment boundary has been crossed, inform the decoding - * system that the catalog_xmin can advance. + * system that the slot can advance. + * + * Once REPACK begins copying data to the new table, the logical + * decoding machinery prevents the slot from advancing beyond the + * oldest running transaction (which is the REPACK transaction + * itself). As a result, restart_lsn and catalog_xmin can no + * longer advance automatically. + * + * To allow old WAL files to be recycled, we manually advance the + * slot each time a WAL segment boundary is crossed. We do not + * advance catalog_xmin here because the REPACK transaction anyway + * holds a snapshot that prevents catalog dead tuple removal. */ end_lsn = ctx->reader->EndRecPtr; XLByteToSeg(end_lsn, segno_new, wal_segment_size); if (segno_new != repack_current_segment) { + LogicalIncreaseRestartDecodingForSlot(end_lsn, end_lsn); LogicalConfirmReceivedLocation(end_lsn); elog(DEBUG1, "REPACK: confirmed receive location %X/%X", (uint32) (end_lsn >> 32), (uint32) end_lsn); diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index a33a685dcc6..11b64de3d90 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -1913,8 +1913,14 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) SpinLockRelease(&MyReplicationSlot->mutex); ReplicationSlotsComputeRequiredXmin(false); - ReplicationSlotsComputeRequiredLSN(); } + + /* + * Now the new restart_lsn is safely on disk, recompute the global WAL + * retention requirement. + */ + if (updated_restart) + ReplicationSlotsComputeRequiredLSN(); } else { -- 2.43.0