Fix COPY FREEZE with CLOBBER_CACHE_ALWAYS
authorTomas Vondra <tomas.vondra@postgresql.org>
Sat, 23 Jan 2021 23:24:50 +0000 (00:24 +0100)
committerTomas Vondra <tomas.vondra@postgresql.org>
Sun, 24 Jan 2021 00:08:11 +0000 (01:08 +0100)
This adds code omitted from commit 7db0cd2145 by accident, which had
two consequences. Firstly, only rows inserted by heap_multi_insert were
frozen as expected when running COPY FREEZE, while heap_insert left
rows unfrozen. That however includes rows in TOAST tables, so a lot of
data might have been left unfrozen. Secondly, page might have been left
partially empty after relcache invalidation.

This addresses both of those issues.

Discussion: https://postgr.es/m/CABOikdN-ptGv0mZntrK2Q8OtfUuAjqaYMGmkdU1dCKFtUxVLrg@mail.gmail.com

src/backend/access/heap/heapam.c
src/backend/access/heap/hio.c

index faffbb18658d7a7b2fe1929686352271a3d52137..d107e14690c7e50fcd52fdc07c1b6bfd6170c63b 100644 (file)
@@ -1880,8 +1880,12 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
        TransactionId xid = GetCurrentTransactionId();
        HeapTuple       heaptup;
        Buffer          buffer;
+       Page            page = NULL;
        Buffer          vmbuffer = InvalidBuffer;
+       bool            starting_with_empty_page;
        bool            all_visible_cleared = false;
+       bool            all_frozen_set = false;
+       uint8           vmstatus = 0;
 
        /*
         * Fill in tuple header fields and toast the tuple if necessary.
@@ -1894,11 +1898,36 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
        /*
         * Find buffer to insert this tuple into.  If the page is all visible,
         * this will also pin the requisite visibility map page.
+        *
+        * Also pin visibility map page if COPY FREEZE inserts tuples into an
+        * empty page. See all_frozen_set below.
         */
        buffer = RelationGetBufferForTuple(relation, heaptup->t_len,
                                                                           InvalidBuffer, options, bistate,
                                                                           &vmbuffer, NULL);
 
+
+       /*
+        * If we're inserting frozen entry into an empty page,
+        * set visibility map bits and PageAllVisible() hint.
+        *
+        * If we're inserting frozen entry into already all_frozen page,
+        * preserve this state.
+        */
+       if (options & HEAP_INSERT_FROZEN)
+       {
+               page = BufferGetPage(buffer);
+
+               starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
+
+               if (visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer))
+                       vmstatus = visibilitymap_get_status(relation,
+                                                                BufferGetBlockNumber(buffer), &vmbuffer);
+
+               if ((starting_with_empty_page || vmstatus & VISIBILITYMAP_ALL_FROZEN))
+                       all_frozen_set = true;
+       }
+
        /*
         * We're about to do the actual insert -- but check for conflict first, to
         * avoid possibly having to roll back work we've just done.
@@ -1922,7 +1951,14 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
        RelationPutHeapTuple(relation, buffer, heaptup,
                                                 (options & HEAP_INSERT_SPECULATIVE) != 0);
 
-       if (PageIsAllVisible(BufferGetPage(buffer)))
+       /*
+        * If the page is all visible, need to clear that, unless we're only
+        * going to add further frozen rows to it.
+        *
+        * If we're only adding already frozen rows to a page that was empty or
+        * marked as all visible, mark it as all-visible.
+        */
+       if (PageIsAllVisible(BufferGetPage(buffer)) && !(options & HEAP_INSERT_FROZEN))
        {
                all_visible_cleared = true;
                PageClearAllVisible(BufferGetPage(buffer));
@@ -1930,6 +1966,13 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
                                                        ItemPointerGetBlockNumber(&(heaptup->t_self)),
                                                        vmbuffer, VISIBILITYMAP_VALID_BITS);
        }
+       else if (all_frozen_set)
+       {
+               /* We only ever set all_frozen_set after reading the page. */
+               Assert(page);
+
+               PageSetAllVisible(page);
+       }
 
        /*
         * XXX Should we set PageSetPrunable on this page ?
@@ -1977,6 +2020,8 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
                xlrec.flags = 0;
                if (all_visible_cleared)
                        xlrec.flags |= XLH_INSERT_ALL_VISIBLE_CLEARED;
+               if (all_frozen_set)
+                       xlrec.flags = XLH_INSERT_ALL_FROZEN_SET;
                if (options & HEAP_INSERT_SPECULATIVE)
                        xlrec.flags |= XLH_INSERT_IS_SPECULATIVE;
                Assert(ItemPointerGetBlockNumber(&heaptup->t_self) == BufferGetBlockNumber(buffer));
@@ -2025,6 +2070,29 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
 
        END_CRIT_SECTION();
 
+       /*
+        * If we've frozen everything on the page, update the visibilitymap.
+        * We're already holding pin on the vmbuffer.
+        *
+        * No need to update the visibilitymap if it had all_frozen bit set
+        * before this insertion.
+        */
+       if (all_frozen_set && ((vmstatus & VISIBILITYMAP_ALL_FROZEN) == 0))
+       {
+               Assert(PageIsAllVisible(page));
+               Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer));
+
+               /*
+                * It's fine to use InvalidTransactionId here - this is only used
+                * when HEAP_INSERT_FROZEN is specified, which intentionally
+                * violates visibility rules.
+                */
+               visibilitymap_set(relation, BufferGetBlockNumber(buffer), buffer,
+                                                       InvalidXLogRecPtr, vmbuffer,
+                                                       InvalidTransactionId,
+                                                       VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN);
+       }
+
        UnlockReleaseBuffer(buffer);
        if (vmbuffer != InvalidBuffer)
                ReleaseBuffer(vmbuffer);
@@ -8708,6 +8776,10 @@ heap_xlog_insert(XLogReaderState *record)
        ItemPointerSetBlockNumber(&target_tid, blkno);
        ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum);
 
+       /* check that the mutually exclusive flags are not both set */
+       Assert (!((xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) &&
+                         (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)));
+
        /*
         * The visibility map may need to be fixed even if the heap page is
         * already up-to-date.
@@ -8925,6 +8997,10 @@ heap_xlog_multi_insert(XLogReaderState *record)
                if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
                        PageClearAllVisible(page);
 
+               /* XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible */
+               if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
+                       PageSetAllVisible(page);
+
                MarkBufferDirty(buffer);
        }
        if (BufferIsValid(buffer))
index 2d23b3ef7194516d5494e62e0aae595f67da6b45..fb7ad0bab47ac4049586bf92a787410f5dea7e39 100644 (file)
@@ -396,19 +396,19 @@ RelationGetBufferForTuple(Relation relation, Size len,
                 * target.
                 */
                targetBlock = GetPageWithFreeSpace(relation, len + saveFreeSpace);
+       }
 
-               /*
-                * If the FSM knows nothing of the rel, try the last page before we
-                * give up and extend.  This avoids one-tuple-per-page syndrome during
-                * bootstrapping or in a recently-started system.
-                */
-               if (targetBlock == InvalidBlockNumber)
-               {
-                       BlockNumber nblocks = RelationGetNumberOfBlocks(relation);
+       /*
+        * If the FSM knows nothing of the rel, try the last page before we
+        * give up and extend.  This avoids one-tuple-per-page syndrome during
+        * bootstrapping or in a recently-started system.
+        */
+       if (targetBlock == InvalidBlockNumber)
+       {
+               BlockNumber nblocks = RelationGetNumberOfBlocks(relation);
 
-                       if (nblocks > 0)
-                               targetBlock = nblocks - 1;
-               }
+               if (nblocks > 0)
+                       targetBlock = nblocks - 1;
        }
 
 loop: