Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 21b446d

Browse files
committed
Fix CLUSTER/VACUUM FULL for toast values owned by recently-updated rows.
In commit 7b0d0e9, I made CLUSTER and VACUUM FULL try to preserve toast value OIDs from the original toast table to the new one. However, if we have to copy both live and recently-dead versions of a row that has a toasted column, those versions may well reference the same toast value with the same OID. The patch then led to duplicate-key failures as we tried to insert the toast value twice with the same OID. (The previous behavior was not very desirable either, since it would have silently inserted the same value twice with different OIDs. That wastes space, but what's worse is that the toast values inserted for already-dead heap rows would not be reclaimed by subsequent ordinary VACUUMs, since they go into the new toast table marked live not deleted.) To fix, check if the copied OID already exists in the new toast table, and if so, assume that it stores the desired value. This is reasonably safe since the only case where we will copy an OID from a previous toast pointer is when toast_insert_or_update was given that toast pointer and so we just pulled the data from the old table; if we got two different values that way then we have big problems anyway. We do have to assume that no other backend is inserting items into the new toast table concurrently, but that's surely safe for CLUSTER and VACUUM FULL. Per bug #6393 from Maxim Boguk. Back-patch to 9.0, same as the previous patch.
1 parent de5a08c commit 21b446d

File tree

3 files changed

+62
-19
lines changed

3 files changed

+62
-19
lines changed

src/backend/access/heap/tuptoaster.c

+53-14
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ do { \
7676
static void toast_delete_datum(Relation rel, Datum value);
7777
static Datum toast_save_datum(Relation rel, Datum value,
7878
struct varlena *oldexternal, int options);
79-
static bool toast_valueid_exists(Oid toastrelid, Oid valueid);
79+
static bool toastrel_valueid_exists(Relation toastrel, Oid valueid);
80+
static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
8081
static struct varlena *toast_fetch_datum(struct varlena * attr);
8182
static struct varlena *toast_fetch_datum_slice(struct varlena * attr,
8283
int32 sliceoffset, int32 length);
@@ -1342,7 +1343,34 @@ toast_save_datum(Relation rel, Datum value,
13421343
/* Must copy to access aligned fields */
13431344
VARATT_EXTERNAL_GET_POINTER(old_toast_pointer, oldexternal);
13441345
if (old_toast_pointer.va_toastrelid == rel->rd_toastoid)
1346+
{
1347+
/* This value came from the old toast table; reuse its OID */
13451348
toast_pointer.va_valueid = old_toast_pointer.va_valueid;
1349+
1350+
/*
1351+
* There is a corner case here: the table rewrite might have
1352+
* to copy both live and recently-dead versions of a row, and
1353+
* those versions could easily reference the same toast value.
1354+
* When we copy the second or later version of such a row,
1355+
* reusing the OID will mean we select an OID that's already
1356+
* in the new toast table. Check for that, and if so, just
1357+
* fall through without writing the data again.
1358+
*
1359+
* While annoying and ugly-looking, this is a good thing
1360+
* because it ensures that we wind up with only one copy of
1361+
* the toast value when there is only one copy in the old
1362+
* toast table. Before we detected this case, we'd have made
1363+
* multiple copies, wasting space; and what's worse, the
1364+
* copies belonging to already-deleted heap tuples would not
1365+
* be reclaimed by VACUUM.
1366+
*/
1367+
if (toastrel_valueid_exists(toastrel,
1368+
toast_pointer.va_valueid))
1369+
{
1370+
/* Match, so short-circuit the data storage loop below */
1371+
data_todo = 0;
1372+
}
1373+
}
13461374
}
13471375
if (toast_pointer.va_valueid == InvalidOid)
13481376
{
@@ -1356,8 +1384,8 @@ toast_save_datum(Relation rel, Datum value,
13561384
GetNewOidWithIndex(toastrel,
13571385
RelationGetRelid(toastidx),
13581386
(AttrNumber) 1);
1359-
} while (toast_valueid_exists(rel->rd_toastoid,
1360-
toast_pointer.va_valueid));
1387+
} while (toastid_valueid_exists(rel->rd_toastoid,
1388+
toast_pointer.va_valueid));
13611389
}
13621390
}
13631391

@@ -1495,24 +1523,18 @@ toast_delete_datum(Relation rel, Datum value)
14951523

14961524

14971525
/* ----------
1498-
* toast_valueid_exists -
1526+
* toastrel_valueid_exists -
14991527
*
15001528
* Test whether a toast value with the given ID exists in the toast relation
15011529
* ----------
15021530
*/
15031531
static bool
1504-
toast_valueid_exists(Oid toastrelid, Oid valueid)
1532+
toastrel_valueid_exists(Relation toastrel, Oid valueid)
15051533
{
15061534
bool result = false;
1507-
Relation toastrel;
15081535
ScanKeyData toastkey;
15091536
SysScanDesc toastscan;
15101537

1511-
/*
1512-
* Open the toast relation
1513-
*/
1514-
toastrel = heap_open(toastrelid, AccessShareLock);
1515-
15161538
/*
15171539
* Setup a scan key to find chunks with matching va_valueid
15181540
*/
@@ -1530,10 +1552,27 @@ toast_valueid_exists(Oid toastrelid, Oid valueid)
15301552
if (systable_getnext(toastscan) != NULL)
15311553
result = true;
15321554

1533-
/*
1534-
* End scan and close relations
1535-
*/
15361555
systable_endscan(toastscan);
1556+
1557+
return result;
1558+
}
1559+
1560+
/* ----------
1561+
* toastid_valueid_exists -
1562+
*
1563+
* As above, but work from toast rel's OID not an open relation
1564+
* ----------
1565+
*/
1566+
static bool
1567+
toastid_valueid_exists(Oid toastrelid, Oid valueid)
1568+
{
1569+
bool result;
1570+
Relation toastrel;
1571+
1572+
toastrel = heap_open(toastrelid, AccessShareLock);
1573+
1574+
result = toastrel_valueid_exists(toastrel, valueid);
1575+
15371576
heap_close(toastrel, AccessShareLock);
15381577

15391578
return result;

src/backend/commands/cluster.c

+7-4
Original file line numberDiff line numberDiff line change
@@ -787,16 +787,19 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
787787
* When doing swap by content, any toast pointers written into NewHeap
788788
* must use the old toast table's OID, because that's where the toast
789789
* data will eventually be found. Set this up by setting rd_toastoid.
790-
* This also tells tuptoaster.c to preserve the toast value OIDs,
791-
* which we want so as not to invalidate toast pointers in system
792-
* catalog caches.
790+
* This also tells toast_save_datum() to preserve the toast value
791+
* OIDs, which we want so as not to invalidate toast pointers in
792+
* system catalog caches, and to avoid making multiple copies of a
793+
* single toast value.
793794
*
794795
* Note that we must hold NewHeap open until we are done writing data,
795796
* since the relcache will not guarantee to remember this setting once
796797
* the relation is closed. Also, this technique depends on the fact
797798
* that no one will try to read from the NewHeap until after we've
798799
* finished writing it and swapping the rels --- otherwise they could
799-
* follow the toast pointers to the wrong place.
800+
* follow the toast pointers to the wrong place. (It would actually
801+
* work for values copied over from the old toast table, but not for
802+
* any values that we toast which were previously not toasted.)
800803
*/
801804
NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;
802805
}

src/include/utils/rel.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ typedef struct RelationData
159159
* have the existing toast table's OID, not the OID of the transient toast
160160
* table. If rd_toastoid isn't InvalidOid, it is the OID to place in
161161
* toast pointers inserted into this rel. (Note it's set on the new
162-
* version of the main heap, not the toast table itself.)
162+
* version of the main heap, not the toast table itself.) This also
163+
* causes toast_save_datum() to try to preserve toast value OIDs.
163164
*/
164165
Oid rd_toastoid; /* Real TOAST table's OID, or InvalidOid */
165166

0 commit comments

Comments
 (0)