Skip to content

Commit dd58194

Browse files
pg_amcheck: avoid unhelpful verification attempts.
Avoid calling contrib/amcheck functions with relations that are unsuitable for checking. Specifically, don't attempt verification of temporary relations, or indexes whose pg_index entry indicates that the index is invalid, or not ready. These relations are not supported by any of the contrib/amcheck functions, for reasons that are pretty fundamental. For example, the implementation of REINDEX CONCURRENTLY can add its own "transient" pg_index entries, which has rather unclear implications for the B-Tree verification functions, at least in the general case -- so they just treat it as an error. It falls to the amcheck caller (in this case pg_amcheck) to deal with the situation at a higher level. pg_amcheck now simply treats these conditions as additional "visibility concerns" when it queries system catalogs. This is a little arbitrary. It seems to have the least problems among any of the available alternatives. Author: Mark Dilger <mark.dilger@enterprisedb.com> Reported-By: Alexander Lakhin <exclusion@gmail.com> Reviewed-By: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Robert Haas <robertmhaas@gmail.com> Bug: #17212 Discussion: https://postgr.es/m/17212-34dd4a1d6bba98bf@postgresql.org Backpatch: 14-, where pg_amcheck was introduced.
1 parent 419d27b commit dd58194

File tree

3 files changed

+299
-29
lines changed

3 files changed

+299
-29
lines changed

doc/src/sgml/ref/pg_amcheck.sgml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,18 @@ PostgreSQL documentation
432432
</variablelist>
433433
</para>
434434

435+
<warning>
436+
<para>
437+
The extra checks performed against B-tree indexes when the
438+
<option>--parent-check</option> option or the
439+
<option>--rootdescend</option> option is specified require
440+
relatively strong relation-level locks. These checks are the only
441+
checks that will block concurrent data modification from
442+
<command>INSERT</command>, <command>UPDATE</command>, and
443+
<command>DELETE</command> commands.
444+
</para>
445+
</warning>
446+
435447
<para>
436448
The following command-line options control the connection to the server:
437449

src/bin/pg_amcheck/pg_amcheck.c

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,9 @@ main(int argc, char *argv[])
818818
* names matching the expectations of verify_heap_slot_handler, which will
819819
* receive and handle each row returned from the verify_heapam() function.
820820
*
821+
* The constructed SQL command will silently skip temporary tables, as checking
822+
* them would needlessly draw errors from the underlying amcheck function.
823+
*
821824
* sql: buffer into which the heap table checking command will be written
822825
* rel: relation information for the heap table to be checked
823826
* conn: the connection to be used, for string escaping purposes
@@ -827,10 +830,10 @@ prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
827830
{
828831
resetPQExpBuffer(sql);
829832
appendPQExpBuffer(sql,
830-
"SELECT blkno, offnum, attnum, msg FROM %s.verify_heapam("
831-
"\nrelation := %u, on_error_stop := %s, check_toast := %s, skip := '%s'",
833+
"SELECT v.blkno, v.offnum, v.attnum, v.msg "
834+
"FROM pg_catalog.pg_class c, %s.verify_heapam("
835+
"\nrelation := c.oid, on_error_stop := %s, check_toast := %s, skip := '%s'",
832836
rel->datinfo->amcheck_schema,
833-
rel->reloid,
834837
opts.on_error_stop ? "true" : "false",
835838
opts.reconcile_toast ? "true" : "false",
836839
opts.skip);
@@ -840,7 +843,10 @@ prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
840843
if (opts.endblock >= 0)
841844
appendPQExpBuffer(sql, ", endblock := " INT64_FORMAT, opts.endblock);
842845

843-
appendPQExpBufferChar(sql, ')');
846+
appendPQExpBuffer(sql,
847+
"\n) v WHERE c.oid = %u "
848+
"AND c.relpersistence != 't'",
849+
rel->reloid);
844850
}
845851

846852
/*
@@ -851,6 +857,10 @@ prepare_heap_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
851857
* functions do not return any, but rather return corruption information by
852858
* raising errors, which verify_btree_slot_handler expects.
853859
*
860+
* The constructed SQL command will silently skip temporary indexes, and
861+
* indexes being reindexed concurrently, as checking them would needlessly draw
862+
* errors from the underlying amcheck functions.
863+
*
854864
* sql: buffer into which the heap table checking command will be written
855865
* rel: relation information for the index to be checked
856866
* conn: the connection to be used, for string escaping purposes
@@ -860,27 +870,31 @@ prepare_btree_command(PQExpBuffer sql, RelationInfo *rel, PGconn *conn)
860870
{
861871
resetPQExpBuffer(sql);
862872

863-
/*
864-
* Embed the database, schema, and relation name in the query, so if the
865-
* check throws an error, the user knows which relation the error came
866-
* from.
867-
*/
868873
if (opts.parent_check)
869874
appendPQExpBuffer(sql,
870-
"SELECT * FROM %s.bt_index_parent_check("
871-
"index := '%u'::regclass, heapallindexed := %s, "
872-
"rootdescend := %s)",
875+
"SELECT %s.bt_index_parent_check("
876+
"index := c.oid, heapallindexed := %s, rootdescend := %s)"
877+
"\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
878+
"WHERE c.oid = %u "
879+
"AND c.oid = i.indexrelid "
880+
"AND c.relpersistence != 't' "
881+
"AND i.indisready AND i.indisvalid AND i.indislive",
873882
rel->datinfo->amcheck_schema,
874-
rel->reloid,
875883
(opts.heapallindexed ? "true" : "false"),
876-
(opts.rootdescend ? "true" : "false"));
884+
(opts.rootdescend ? "true" : "false"),
885+
rel->reloid);
877886
else
878887
appendPQExpBuffer(sql,
879-
"SELECT * FROM %s.bt_index_check("
880-
"index := '%u'::regclass, heapallindexed := %s)",
888+
"SELECT %s.bt_index_check("
889+
"index := c.oid, heapallindexed := %s)"
890+
"\nFROM pg_catalog.pg_class c, pg_catalog.pg_index i "
891+
"WHERE c.oid = %u "
892+
"AND c.oid = i.indexrelid "
893+
"AND c.relpersistence != 't' "
894+
"AND i.indisready AND i.indisvalid AND i.indislive",
881895
rel->datinfo->amcheck_schema,
882-
rel->reloid,
883-
(opts.heapallindexed ? "true" : "false"));
896+
(opts.heapallindexed ? "true" : "false"),
897+
rel->reloid);
884898
}
885899

886900
/*
@@ -1088,15 +1102,17 @@ verify_btree_slot_handler(PGresult *res, PGconn *conn, void *context)
10881102

10891103
if (PQresultStatus(res) == PGRES_TUPLES_OK)
10901104
{
1091-
int ntups = PQntuples(res);
1105+
int ntups = PQntuples(res);
10921106

1093-
if (ntups != 1)
1107+
if (ntups > 1)
10941108
{
10951109
/*
10961110
* We expect the btree checking functions to return one void row
1097-
* each, so we should output some sort of warning if we get
1098-
* anything else, not because it indicates corruption, but because
1099-
* it suggests a mismatch between amcheck and pg_amcheck versions.
1111+
* each, or zero rows if the check was skipped due to the object
1112+
* being in the wrong state to be checked, so we should output some
1113+
* sort of warning if we get anything more, not because it
1114+
* indicates corruption, but because it suggests a mismatch between
1115+
* amcheck and pg_amcheck versions.
11001116
*
11011117
* In conjunction with --progress, anything written to stderr at
11021118
* this time would present strangely to the user without an extra
@@ -1896,10 +1912,16 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
18961912
"\nAND (c.relam = %u OR NOT ep.btree_only OR ep.rel_regex IS NULL)",
18971913
HEAP_TABLE_AM_OID, BTREE_AM_OID);
18981914

1915+
/*
1916+
* Exclude temporary tables and indexes, which must necessarily belong to
1917+
* other sessions. (We don't create any ourselves.) We must ultimately
1918+
* exclude indexes marked invalid or not ready, but we delay that decision
1919+
* until firing off the amcheck command, as the state of an index may
1920+
* change by then.
1921+
*/
1922+
appendPQExpBufferStr(&sql, "\nWHERE c.relpersistence != 't'");
18991923
if (opts.excludetbl || opts.excludeidx || opts.excludensp)
1900-
appendPQExpBufferStr(&sql, "\nWHERE ep.pattern_id IS NULL");
1901-
else
1902-
appendPQExpBufferStr(&sql, "\nWHERE true");
1924+
appendPQExpBufferStr(&sql, "\nAND ep.pattern_id IS NULL");
19031925

19041926
/*
19051927
* We need to be careful not to break the --no-dependent-toast and
@@ -1951,7 +1973,8 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
19511973
"\nON ('pg_toast' ~ ep.nsp_regex OR ep.nsp_regex IS NULL)"
19521974
"\nAND (t.relname ~ ep.rel_regex OR ep.rel_regex IS NULL)"
19531975
"\nAND ep.heap_only"
1954-
"\nWHERE ep.pattern_id IS NULL");
1976+
"\nWHERE ep.pattern_id IS NULL"
1977+
"\nAND t.relpersistence != 't'");
19551978
appendPQExpBufferStr(&sql,
19561979
"\n)");
19571980
}
@@ -1969,7 +1992,8 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
19691992
"\nINNER JOIN pg_catalog.pg_index i "
19701993
"ON r.oid = i.indrelid "
19711994
"INNER JOIN pg_catalog.pg_class c "
1972-
"ON i.indexrelid = c.oid");
1995+
"ON i.indexrelid = c.oid "
1996+
"AND c.relpersistence != 't'");
19731997
if (opts.excludeidx || opts.excludensp)
19741998
appendPQExpBufferStr(&sql,
19751999
"\nINNER JOIN pg_catalog.pg_namespace n "
@@ -2007,7 +2031,8 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
20072031
"INNER JOIN pg_catalog.pg_index i "
20082032
"ON t.oid = i.indrelid"
20092033
"\nINNER JOIN pg_catalog.pg_class c "
2010-
"ON i.indexrelid = c.oid");
2034+
"ON i.indexrelid = c.oid "
2035+
"AND c.relpersistence != 't'");
20112036
if (opts.excludeidx)
20122037
appendPQExpBufferStr(&sql,
20132038
"\nLEFT OUTER JOIN exclude_pat ep "

0 commit comments

Comments
 (0)