From 3c95abf32b870fd2424d6326d95034cebe2d5f27 Mon Sep 17 00:00:00 2001 From: "yangboyu.yby" Date: Wed, 29 Jul 2026 14:10:31 +0800 Subject: [PATCH] Fix incremental backup of relations truncated and regrown by bulk insert When a relation is truncated by vacuum and then regrown by a bulk insert (e.g. COPY), bulk extension in RelationAddBlocks() extends the file past the last used page. The trailing pages are pure zero pages written by mdzeroextend() and are never WAL-logged, so they are absent from the WAL summary and from the incremental backup file. GetFileBackupMethod() nevertheless sets truncation_block_length to the current file size. During pg_combinebackup, any page below truncation_block_length that is missing from the incremental backup file is sourced from the prior backup; if that not WAL-logged page is at or above limit_block, the prior backup's version of it is stale, so the reconstructed data directory is inconsistent The existing guard only forces a full backup of a segment when limit_block is at or before the segment start. Widen the guard by one segment so that the segment containing limit_block is always backed up fully; then no block at or above limit_block can ever be sourced from an older backup. --- src/backend/backup/basebackup_incremental.c | 31 +++++---------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/backend/backup/basebackup_incremental.c b/src/backend/backup/basebackup_incremental.c index 0fc4042693f..33c95541ddf 100644 --- a/src/backend/backup/basebackup_incremental.c +++ b/src/backend/backup/basebackup_incremental.c @@ -775,10 +775,14 @@ GetFileBackupMethod(IncrementalBackupInfo *ib, const char *path, } /* - * If the limit_block is less than or equal to the point where this - * segment starts, send the whole file. + * If the limit_block falls within this segment or an earlier one, + * send the whole file. Blocks at or above limit_block from older + * backups must never be used: they may have been truncated away and + * later recreated without WAL logging (e.g. bulk-insert extension + * overshoot zero pages) */ - if (limit_block <= segno * RELSEG_SIZE) + if (BlockNumberIsValid(limit_block) && + limit_block / RELSEG_SIZE <= segno) return BACK_UP_FILE_FULLY; /* @@ -846,27 +850,6 @@ GetFileBackupMethod(IncrementalBackupInfo *ib, const char *path, * blocks included in the backup are non-consecutive.) */ *truncation_block_length = size / BLCKSZ; - if (BlockNumberIsValid(limit_block)) - { - unsigned relative_limit = limit_block - segno * RELSEG_SIZE; - - /* - * We can't set a truncation_block_length in excess of the limit block - * number (relativized to the current segment). To do so would be to - * treat blocks from older backups as valid current contents even if - * they were subsequently truncated away. - */ - if (*truncation_block_length < relative_limit) - *truncation_block_length = relative_limit; - - /* - * We also can't set a truncation_block_length in excess of the - * segment size, since the reconstructed file can't be larger than - * that. - */ - if (*truncation_block_length > RELSEG_SIZE) - *truncation_block_length = RELSEG_SIZE; - } /* Send it incrementally. */ return BACK_UP_FILE_INCREMENTALLY; -- 2.43.7