very-rough-visibility-ideas.patch
application/octet-stream
Filename: very-rough-visibility-ideas.patch
Type: application/octet-stream
Part: 0
Message:
Re: pg_amcheck contrib application
Patch
Format: unified
| File | + | − |
|---|---|---|
| contrib/amcheck/verify_heapam.c | 160 | 154 |
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 6f972e630a..066e13a1e3 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -72,6 +72,8 @@ typedef struct HeapCheckContext
TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */
FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed
* relative to next_fxid */
+ TransactionId safe_xmin; /* this XID and newer ones can't become
+ * all-visible while we're running */
/*
* Cached copy of value from MultiXactState
@@ -133,8 +135,10 @@ static void check_tuple(HeapCheckContext *ctx);
static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx);
static bool check_tuple_attribute(HeapCheckContext *ctx);
-static bool check_tuple_header_and_visibilty(HeapTupleHeader tuphdr,
- HeapCheckContext *ctx);
+static void check_tuple_header_and_visibilty(HeapTupleHeader tuphdr,
+ HeapCheckContext *ctx,
+ bool *tuple_is_readable,
+ bool *tuple_cannot_die_now);
static void report_corruption(HeapCheckContext *ctx, char *msg);
static TupleDesc verify_heapam_tupdesc(void);
@@ -248,6 +252,12 @@ verify_heapam(PG_FUNCTION_ARGS)
memset(&ctx, 0, sizeof(HeapCheckContext));
ctx.cached_xid = InvalidTransactionId;
+ /*
+ * Any xmin newer than the xmin of our snapshot can't become all-visible
+ * while we're running.
+ */
+ ctx.safe_xmin = GetTransactionSnapshot()->xmin;
+
/*
* If we report corruption when not examining some individual attribute,
* we need attnum to be reported as NULL. Set that up before any
@@ -580,27 +590,33 @@ verify_heapam_tupdesc(void)
* other words, we do not return false merely because we detected them.)
*
* For visibility determination not specifically related to corruption, what we
- * want to know is if a tuple is potentially visible to any running
- * transaction. If you are tempted to replace this function's visibility logic
- * with a call to another visibility checking function, keep in mind that this
- * function does not update hint bits, as it seems imprudent to write hint bits
- * (or anything at all) to a table during a corruption check. Nor does this
- * function bother classifying tuple visibility beyond a boolean visible vs.
- * not visible.
- *
- * The caller should already have checked that xmin and xmax are not out of
- * bounds for the relation.
+ * want to know is (1) whether the original inserter committed and (2)
+ * whether it's possible for the tuple to be pruned while we're still checking
+ * the relation. If (1) is not the case, then the tuple descriptor used to
+ * construct the table might include additional columns that are we don't
+ * know about, so we don't try to decode the tuple. If (2) is not the case,
+ * it's OK to check the tuple, but it's not safe to follow any TOAST pointers,
+ * because if this tuple can be pruned away at any time, the same is true
+ * of its TOAST chunks.
*
- * Returns whether the tuple is both visible and sufficiently sensible to
- * undergo attribute checks.
+ * Unlike other visibility-checking functions, this does not update hint bits,
+ * as it seems imprudent to write hint bits (or anything at all) to a table
+ * during a corruption check. Nor does this function bother classifying tuple
+ * visibility beyond answering the questions mentioned above.
*/
-static bool
-check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
+static void
+check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx,
+ bool *tuple_is_readable,
+ bool *tuple_cannot_die_now)
{
uint16 infomask = tuphdr->t_infomask;
bool header_garbled = false;
unsigned expected_hoff;
+ /* We haven't proven anything yet. */
+ *tuple_is_readable = false;
+ *tuple_cannot_die_now = false;
+
if (ctx->tuphdr->t_hoff > ctx->lp_len)
{
report_corruption(ctx,
@@ -649,169 +665,153 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
}
if (header_garbled)
- return false; /* checking of this tuple should not continue */
+ return; /* checking of this tuple should not continue */
+
+ /*
+ * XXX check whether raw xmin is ok by caling get_xid_status, unless
+ * HeapTupleHeaderXminFrozen in which case we should skip it
+ */
/*
* Ok, we can examine the header for tuple visibility purposes, though we
* still need to be careful about a few remaining types of header
* corruption. This logic roughly follows that of
- * HeapTupleSatisfiesVacuum. Where possible the comments indicate which
- * HTSV_Result we think that function might return for this tuple.
+ * HeapTupleSatisfiesVacuum and similar functions, but we don't need to
+ * distinguish quite as many cases.
*/
if (!HeapTupleHeaderXminCommitted(tuphdr))
{
- TransactionId raw_xmin = HeapTupleHeaderGetRawXmin(tuphdr);
-
if (HeapTupleHeaderXminInvalid(tuphdr))
- return false; /* HEAPTUPLE_DEAD */
- /* Used by pre-9.0 binary upgrades */
- else if (infomask & HEAP_MOVED_OFF ||
- infomask & HEAP_MOVED_IN)
+ return;
+ else if (tuphdr->t_infomask & HEAP_MOVED_OFF)
{
- XidCommitStatus status;
TransactionId xvac = HeapTupleHeaderGetXvac(tuphdr);
- switch (get_xid_status(xvac, ctx, &status))
+ /* XXX sanity check xvac with get_xid_status */
+
+ /*
+ * Used by pre-9.0 binary upgrades. It should be impossible for
+ * xvac to still be running, since we've removed all that code,
+ * but even if it were, it ought to be safe to read the tuple,
+ * since the original inserter must have committed. But, if the
+ * xvac transaction committed, this tuple (and its associated
+ * TOAST tuples) could be pruned at any time.
+ */
+ if (TransactionIdDidCommit(xvac))
{
- case XID_INVALID:
- report_corruption(ctx,
- pstrdup("old-style VACUUM FULL transaction ID is invalid"));
- return false; /* corrupt */
- case XID_IN_FUTURE:
- report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u equals or exceeds next valid transaction ID %u:%u",
- xvac,
- EpochFromFullTransactionId(ctx->next_fxid),
- XidFromFullTransactionId(ctx->next_fxid)));
- return false; /* corrupt */
- case XID_PRECEDES_RELMIN:
- report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u precedes relation freeze threshold %u:%u",
- xvac,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
- XidFromFullTransactionId(ctx->relfrozenfxid)));
- return false; /* corrupt */
- break;
- case XID_PRECEDES_CLUSTERMIN:
- report_corruption(ctx,
- psprintf("old-style VACUUM FULL transaction ID %u precedes oldest valid transaction ID %u:%u",
- xvac,
- EpochFromFullTransactionId(ctx->oldest_fxid),
- XidFromFullTransactionId(ctx->oldest_fxid)));
- return false; /* corrupt */
- break;
- case XID_BOUNDS_OK:
- switch (status)
- {
- case XID_IN_PROGRESS:
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
- case XID_COMMITTED:
- case XID_ABORTED:
- return false; /* HEAPTUPLE_DEAD */
- }
+ *tuple_is_readable = true;
+ return;
}
}
- else
+ else if (tuphdr->t_infomask & HEAP_MOVED_IN)
{
- XidCommitStatus status;
+ TransactionId xvac = HeapTupleHeaderGetXvac(tuphdr);
+
+ /* XXX sanity check xvac with get_xid_status */
- switch (get_xid_status(raw_xmin, ctx, &status))
+ /*
+ * Same as above, but now pruning can happen if xvac did not
+ * commit.
+ */
+ if (!TransactionIdDidCommit(xvac))
{
- case XID_INVALID:
- report_corruption(ctx,
- pstrdup("raw xmin is invalid"));
- return false;
- case XID_IN_FUTURE:
- report_corruption(ctx,
- psprintf("raw xmin %u equals or exceeds next valid transaction ID %u:%u",
- raw_xmin,
- EpochFromFullTransactionId(ctx->next_fxid),
- XidFromFullTransactionId(ctx->next_fxid)));
- return false; /* corrupt */
- case XID_PRECEDES_RELMIN:
- report_corruption(ctx,
- psprintf("raw xmin %u precedes relation freeze threshold %u:%u",
- raw_xmin,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
- XidFromFullTransactionId(ctx->relfrozenfxid)));
- return false; /* corrupt */
- case XID_PRECEDES_CLUSTERMIN:
- report_corruption(ctx,
- psprintf("raw xmin %u precedes oldest valid transaction ID %u:%u",
- raw_xmin,
- EpochFromFullTransactionId(ctx->oldest_fxid),
- XidFromFullTransactionId(ctx->oldest_fxid)));
- return false; /* corrupt */
- case XID_BOUNDS_OK:
- switch (status)
- {
- case XID_COMMITTED:
- break;
- case XID_IN_PROGRESS:
- return true; /* insert or delete in progress */
- case XID_ABORTED:
- return false; /* HEAPTUPLE_DEAD */
- }
+ *tuple_is_readable = true;
+ return;
}
}
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuphdr)))
+ {
+ /*
+ * Don't try to check tuples from uncommitted transactions, even
+ * though technically it might be safe when it's our own
+ * transaction (since we can any DDL we did ourselves).
+ */
+ return;
+ }
+ else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuphdr)))
+ {
+ /* Still running, not us, definitely can't check. */
+ return;
+ }
+ else if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuphdr)))
+ {
+ /* Inserter aborted or crashed, definitely can't check. */
+ return;
+ }
+ }
+
+ /*
+ * Okay, the inserter committed, so it was good at some point. Now what
+ * about the deleting transaction?
+ */
+ *tuple_is_readable = true;
+
+ if ((tuphdr->t_infomask & HEAP_XMAX_INVALID) ||
+ HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask))
+ {
+ /*
+ * The tuple is not deleted yet. Even if it gets deleted in the near
+ * future, it can't be pruned while we're still running because
+ * it must still be visible to our snapshot.
+ */
+ *tuple_cannot_die_now = true;
+ return;
}
- if (!(infomask & HEAP_XMAX_INVALID) && !HEAP_XMAX_IS_LOCKED_ONLY(infomask))
+ if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)
{
- if (infomask & HEAP_XMAX_IS_MULTI)
+ TransactionId xmax = HeapTupleGetUpdateXid(tuphdr);
+
+ /* XXX sanity check the update XID with get_xid_status */
+
+ if (TransactionIdIsInProgress(xmax))
{
- XidCommitStatus status;
- TransactionId xmax = HeapTupleGetUpdateXid(tuphdr);
+ /*
+ * Since the deleting transaction is still in progress, the
+ * delete can't be visible to our snapshot.
+ */
+ *tuple_cannot_die_now = true;
+ return;
+ }
+ else if (TransactionIdDidCommit(xmax))
+ {
+ /*
+ * The update XID is no longer running, and it did commit. So the
+ * tuple could be pruned if the XID is old enough.
+ */
+ if (TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuphdr),
+ ctx->safe_xmin))
+ return;
+ }
- switch (get_xid_status(xmax, ctx, &status))
- {
- /* not LOCKED_ONLY, so it has to have an xmax */
- case XID_INVALID:
- report_corruption(ctx,
- pstrdup("xmax is invalid"));
- return false; /* corrupt */
- case XID_IN_FUTURE:
- report_corruption(ctx,
- psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u",
- xmax,
- EpochFromFullTransactionId(ctx->next_fxid),
- XidFromFullTransactionId(ctx->next_fxid)));
- return false; /* corrupt */
- case XID_PRECEDES_RELMIN:
- report_corruption(ctx,
- psprintf("xmax %u precedes relation freeze threshold %u:%u",
- xmax,
- EpochFromFullTransactionId(ctx->relfrozenfxid),
- XidFromFullTransactionId(ctx->relfrozenfxid)));
- return false; /* corrupt */
- case XID_PRECEDES_CLUSTERMIN:
- report_corruption(ctx,
- psprintf("xmax %u precedes oldest valid transaction ID %u:%u",
- xmax,
- EpochFromFullTransactionId(ctx->oldest_fxid),
- XidFromFullTransactionId(ctx->oldest_fxid)));
- return false; /* corrupt */
- case XID_BOUNDS_OK:
- switch (status)
- {
- case XID_IN_PROGRESS:
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS */
- case XID_COMMITTED:
- case XID_ABORTED:
- return false; /* HEAPTUPLE_RECENTLY_DEAD or
- * HEAPTUPLE_DEAD */
- }
- }
+ *tuple_cannot_die_now = true;
+ return;
+ }
+
+ /* XXX sanity check xmax with get_xid_status */
- /* Ok, the tuple is live */
+ /*
+ * Test for cases where there's been an update or delete, but recently
+ * enough that we don't have to worry about pruning yet.
+ */
+ if (!(tuphdr->t_infomask & HEAP_XMAX_COMMITTED))
+ {
+ if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuphdr)))
+ {
+ *tuple_cannot_die_now = true;
+ return;
+ }
+ else if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuphdr)))
+ {
+ *tuple_cannot_die_now = true;
+ return;
}
- else if (!(infomask & HEAP_XMAX_COMMITTED))
- return true; /* HEAPTUPLE_DELETE_IN_PROGRESS or
- * HEAPTUPLE_LIVE */
- else
- return false; /* HEAPTUPLE_RECENTLY_DEAD or HEAPTUPLE_DEAD */
}
- return true; /* not dead */
+
+ /* The delete might be old enough that we have to worry about pruning. */
+ if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuphdr),
+ ctx->safe_xmin))
+ *tuple_cannot_die_now = true;
}
/*
@@ -1124,6 +1124,8 @@ check_tuple(HeapCheckContext *ctx)
TransactionId xmax;
bool fatal = false;
uint16 infomask = ctx->tuphdr->t_infomask;
+ bool tuple_is_readable;
+ bool tuple_cannot_die_now;
/* If xmin is normal, it should be within valid range */
xmin = HeapTupleHeaderGetXmin(ctx->tuphdr);
@@ -1244,11 +1246,15 @@ check_tuple(HeapCheckContext *ctx)
/*
* Check various forms of tuple header corruption. If the header is too
- * corrupt to continue checking, or if the tuple is not visible to anyone,
- * we cannot continue with other checks.
+ * corrupt to continue checking, or if the inserter aborted, we cannot
+ * continue with other checks.
*/
- if (!check_tuple_header_and_visibilty(ctx->tuphdr, ctx))
+ check_tuple_header_and_visibilty(ctx->tuphdr, ctx,
+ &tuple_is_readable,
+ &tuple_cannot_die_now);
+ if (!tuple_is_readable)
return;
+ /* XXX skip TOAST checks if tuple_cannot_die_now is false */
/*
* The tuple is visible, so it must be compatible with the current version