diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index 3a74dbf0ed9..a2625871185 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -1282,6 +1282,9 @@ mdnblocks(SMgrRelation reln, ForkNumber forknum) * functions for this relation or handled interrupts in between. This makes * sure we have opened all active segments, so that truncate loop will get * them all! + * + * If nblocks > curnblk, the request is ignored when we are InRecovery, + * otherwise, an error is raised. */ void mdtruncate(SMgrRelation reln, ForkNumber forknum, diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index 8943528fe1c..378c2a03f39 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -910,8 +910,17 @@ smgrtruncate(SMgrRelation reln, ForkNumber *forknum, int nforks, * backends to invalidate their copies of smgr_cached_nblocks, and * these ones too at the next command boundary. But ensure they aren't * outright wrong until then. + * + * We can have nblocks > old_nblocks when a relation was truncated + * multiple times, a replica applied all the truncations, and later + * restarts from a restartpoint located before the truncations. The + * relation on disk will be the size of the last truncate. When + * replaying the first truncate, we will have nblocks > current size. + * In such cases, smgr_truncate does nothing, so set the cached size + * to the old size rather than the requested size. */ - reln->smgr_cached_nblocks[forknum[i]] = nblocks[i]; + reln->smgr_cached_nblocks[forknum[i]] = + nblocks[i] > old_nblocks[i] ? old_nblocks[i] : nblocks[i]; } }