From f7700080dc4c9226dd0fd81795214c02635e474d Mon Sep 17 00:00:00 2001 From: Melanie Plageman Date: Mon, 14 Sep 2026 11:57:08 -0400 Subject: [PATCH] Update FSM in COPY FREEZE replay d96f87332b3 folded COPY FREEZE visibility map updates into XLOG_HEAP2_MULTI_INSERT records. Unlike the former XLOG_HEAP2_VISIBLE redo path, multi-insert redo only updated the FSM when the record did not contain a heap page FPI and the heap page had less than 20% free space. This can leave all-frozen pages with missing or stale FSM entries on a standby. After promotion, vacuum will skip those pages, preventing their free space from being discovered. When a multi-insert record sets the page all-frozen, record the heap page's free space unconditionally. Reported-by: Melanie Plageman Discussion: https://postgr.es/m/CAAKRu_bS3nfMJCF2fjE%2Bceb5-t%3D2p_VPMk2PWuVomrOFxg5DGQ%40mail.gmail.com Backpatch-through: 19 --- src/backend/access/heap/heapam_xlog.c | 46 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/backend/access/heap/heapam_xlog.c b/src/backend/access/heap/heapam_xlog.c index b6179f42673..5fa1de09cfb 100644 --- a/src/backend/access/heap/heapam_xlog.c +++ b/src/backend/access/heap/heapam_xlog.c @@ -640,8 +640,6 @@ heap_xlog_multi_insert(XLogReaderState *record) if (tupdata != endptr) elog(PANIC, "total tuple length mismatch"); - freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */ - PageSetLSN(page, lsn); if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) @@ -669,9 +667,41 @@ heap_xlog_multi_insert(XLogReaderState *record) MarkBufferDirty(buffer); } + if (BufferIsValid(buffer)) + { + /* + * If we are marking the page all-frozen or the page is running low on + * free space, update the FSM as well. Arbitrarily, our definition of + * "low" is less than 20%. We can't do much better than that without + * knowing the fill-factor for the table. + * + * XXX: Unless setting the page all-frozen, we don't do this if the + * page was restored from full page image. We don't bother to update + * the FSM in that case, it doesn't need to be totally accurate + * anyway. + * + * If setting the page all-frozen, we update the FSM regardless since, + * once frozen, we lose the chance to update it during vacuum after + * promotion. See comment in heap_xlog_prune_freeze() for details. + */ + bool update_fsm = false; + + if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET || + action == BLK_NEEDS_REDO) + { + freespace = PageGetHeapFreeSpace(BufferGetPage(buffer)); + if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET || + freespace < BLCKSZ / 5) + update_fsm = true; + } + UnlockReleaseBuffer(buffer); + if (update_fsm) + XLogRecordPageWithFreeSpace(rlocator, blkno, freespace); + } + buffer = InvalidBuffer; /* @@ -714,18 +744,6 @@ heap_xlog_multi_insert(XLogReaderState *record) if (BufferIsValid(vmbuffer)) UnlockReleaseBuffer(vmbuffer); - - /* - * If the page is running low on free space, update the FSM as well. - * Arbitrarily, our definition of "low" is less than 20%. We can't do much - * better than that without knowing the fill-factor for the table. - * - * XXX: Don't do this if the page was restored from full page image. We - * don't bother to update the FSM in that case, it doesn't need to be - * totally accurate anyway. - */ - if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5) - XLogRecordPageWithFreeSpace(rlocator, blkno, freespace); } /*