Re: [BUG] [PATCH] pg_basebackup produces wrong incremental files after relation truncation in segmented tables

Oleg Tkachenko <oatkachenko@gmail.com>

From: Oleg Tkachenko <oatkachenko@gmail.com>
To: pgsql-hackers@lists.postgresql.org
Date: 2025-12-15T11:09:03Z
Lists: pgsql-hackers

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Don't set the truncation block length greater than RELSEG_SIZE.

Hello,

I am following up on my report with a patch, which did not receive any responses. I suspect the issue only manifests under specific conditions, so I am sending additional details along with a reliable reproducer.



For context:

Incremental backups cannot be restored when a relation larger than 1 GB (multiple segments) is vacuum-truncated between the base backup and the incremental backup (pg_basebackup itself completes successfully, but the resulting incremental backup is not restorable)

For segmented relations, the WAL summarizer records limit_block in the WAL summary. During incremental backup, the truncation length is computed incorrectly because a relation-wide limit is compared against a segment-local size.



Reproducer

I am attaching a bash script that reliably reproduces the issue on my system. The script:

Creates a table large enough to span multiple segments.

Takes a full base backup.

Deletes rows at the end of the relation.

Runs VACUUM (TRUNCATE) to remove blocks.

Takes an incremental backup.

Fails during pg_combinebackup.

The script is fully automated and intended to be run as-is.

A patch itself in the previous message. 

I would appreciate feedback on the approach and am happy to revise it if needed.



Best regards,

Oleg Tkachenko



> On Nov 14, 2025, at 20:43, Oleg Tkachenko <oatkachenko@gmail.com> wrote:
> 
> Hello PostgreSQL developers,
> 
> I’ve encountered a bug in the incremental backup feature that prevents restoration of backups containing relations larger than 1 GB that were vacuum-truncated.
> 
> 
> 
> Problem Description
> 
> When taking incremental backups of relations that span multiple segments, if the relation is truncated during VACUUM (after the base backup but before the incremental one), pg_combinebackup fails with:
> 
> ```
> 
> file "%s" has truncation block length %u in excess of segment size %u
> 
> ```
> 
> pg_basebackup itself completes without errors, but the resulting incremental backup cannot be restored.
> 
> 
> 
> Root Cause
> 
> In segmented relations, a VACUUM that truncates blocks sets a limit_block in the WAL summary. The incremental restore logic miscalculates truncation_block_length when processing segment 0…N, because it compares the segment-local size with a relation-wide limit.
> 
> In src/backend/backup/basebackup_incremental.c:
> 
> ```
> 
> *truncation_block_length = size / BLCKSZ;
> 
> if (BlockNumberIsValid(limit_block))
> 
> {
> 
>     unsigned relative_limit = limit_block - segno * RELSEG_SIZE;
> 
>     if (*truncation_block_length < relative_limit)   /* ← problematic */
> 
>         *truncation_block_length = relative_limit;
> 
> }
> 
> ```
> 
> For example, if limit_block lies in segment 10, then relative_limit will be roughly 9 * RELSEG_SIZE while processing segment 0. This forces truncation_block_length far beyond the actual segment size, leading to a segment length larger than RELSEG_SIZE and eventually the restore error.
> 
> 
> 
> Reproduction Steps
> 
> 
> Create a table larger than 1 GB (multiple segments).
> 
> Take a full base backup.
> 
> Delete rows that occupy the end of the relation.
> 
> Run VACUUM (VERBOSE, TRUNCATE) to ensure blocks are removed.
> 
> (optional) Confirm that the WAL summary includes a limit entry for the relation.
> 
> Take an incremental backup with pg_basebackup.
> 
> Attempt to restore using pg_combinebackup.
> 
> Observe the truncation block length error.
> 
> 
> 
> 
> Patch
> 
> A patch correcting this logic is attached, and I’m happy to provide additional details or revisions if helpful.
> 
> 
> 
> Best regards,
> 
> Oleg Tkachenko
> 
> 
> <bug_truncation_block_length.patch>
> 
> 
> 
> 
>