Commit 1d654166 authored by Michael Paquier's avatar Michael Paquier

Improve handling of dropped relations for REINDEX DATABASE/SCHEMA/SYSTEM

When multiple relations are reindexed, a scan of pg_class is done first
to build the list of relations to work on.  However the REINDEX logic
has never checked if a relation listed still exists when beginning the
work on it, causing for example sudden cache lookup failures.

This commit adds safeguards against dropped relations for REINDEX,
similarly to VACUUM or CLUSTER where we try to open the relation,
ignoring it if it is missing.  A new option is added to the REINDEX
routines to control if a missed relation is OK to ignore or not.

An isolation test, based on REINDEX SCHEMA, is added for the concurrent
and non-concurrent cases.

Author: Michael Paquier
Reviewed-by: Anastasia Lubennikova
Discussion: https://postgr.es/m/20200813043805.GE11663@paquier.xyz
parent 4c51a2d1
...@@ -57,6 +57,40 @@ table_open(Oid relationId, LOCKMODE lockmode) ...@@ -57,6 +57,40 @@ table_open(Oid relationId, LOCKMODE lockmode)
return r; return r;
} }
/* ----------------
* try_table_open - open a table relation by relation OID
*
* Same as table_open, except return NULL instead of failing
* if the relation does not exist.
* ----------------
*/
Relation
try_table_open(Oid relationId, LOCKMODE lockmode)
{
Relation r;
r = try_relation_open(relationId, lockmode);
/* leave if table does not exist */
if (!r)
return NULL;
if (r->rd_rel->relkind == RELKIND_INDEX ||
r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
RelationGetRelationName(r))));
else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type",
RelationGetRelationName(r))));
return r;
}
/* ---------------- /* ----------------
* table_openrv - open a table relation specified * table_openrv - open a table relation specified
* by a RangeVar node * by a RangeVar node
......
...@@ -3437,9 +3437,21 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, ...@@ -3437,9 +3437,21 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
* Open and lock the parent heap relation. ShareLock is sufficient since * Open and lock the parent heap relation. ShareLock is sufficient since
* we only need to be sure no schema or data changes are going on. * we only need to be sure no schema or data changes are going on.
*/ */
heapId = IndexGetRelation(indexId, false); heapId = IndexGetRelation(indexId,
(options & REINDEXOPT_MISSING_OK) != 0);
/* if relation is missing, leave */
if (!OidIsValid(heapId))
return;
if ((options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
heapRelation = table_open(heapId, ShareLock); heapRelation = table_open(heapId, ShareLock);
/* if relation is gone, leave */
if (!heapRelation)
return;
if (progress) if (progress)
{ {
pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
...@@ -3672,8 +3684,15 @@ reindex_relation(Oid relid, int flags, int options) ...@@ -3672,8 +3684,15 @@ reindex_relation(Oid relid, int flags, int options)
* to prevent schema and data changes in it. The lock level used here * to prevent schema and data changes in it. The lock level used here
* should match ReindexTable(). * should match ReindexTable().
*/ */
if ((options & REINDEXOPT_MISSING_OK) != 0)
rel = try_table_open(relid, ShareLock);
else
rel = table_open(relid, ShareLock); rel = table_open(relid, ShareLock);
/* if relation is gone, leave */
if (!rel)
return false;
/* /*
* This may be useful when implemented someday; but that day is not today. * This may be useful when implemented someday; but that day is not today.
* For now, avoid erroring out when called in a multi-table context * For now, avoid erroring out when called in a multi-table context
...@@ -3771,7 +3790,14 @@ reindex_relation(Oid relid, int flags, int options) ...@@ -3771,7 +3790,14 @@ reindex_relation(Oid relid, int flags, int options)
* still hold the lock on the main table. * still hold the lock on the main table.
*/ */
if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid)) if ((flags & REINDEX_REL_PROCESS_TOAST) && OidIsValid(toast_relid))
result |= reindex_relation(toast_relid, flags, options); {
/*
* Note that this should fail if the toast relation is missing, so
* reset REINDEXOPT_MISSING_OK.
*/
result |= reindex_relation(toast_relid, flags,
options & ~(REINDEXOPT_MISSING_OK));
}
return result; return result;
} }
......
...@@ -2766,9 +2766,19 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, ...@@ -2766,9 +2766,19 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
/* functions in indexes may want a snapshot set */ /* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot()); PushActiveSnapshot(GetTransactionSnapshot());
/* check if the relation still exists */
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
{
PopActiveSnapshot();
CommitTransactionCommand();
continue;
}
if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP) if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
{ {
(void) ReindexRelationConcurrently(relid, options); (void) ReindexRelationConcurrently(relid,
options |
REINDEXOPT_MISSING_OK);
/* ReindexRelationConcurrently() does the verbose output */ /* ReindexRelationConcurrently() does the verbose output */
} }
else else
...@@ -2778,7 +2788,9 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, ...@@ -2778,7 +2788,9 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
result = reindex_relation(relid, result = reindex_relation(relid,
REINDEX_REL_PROCESS_TOAST | REINDEX_REL_PROCESS_TOAST |
REINDEX_REL_CHECK_CONSTRAINTS, REINDEX_REL_CHECK_CONSTRAINTS,
options | REINDEXOPT_REPORT_PROGRESS); options |
REINDEXOPT_REPORT_PROGRESS |
REINDEXOPT_MISSING_OK);
if (result && (options & REINDEXOPT_VERBOSE)) if (result && (options & REINDEXOPT_VERBOSE))
ereport(INFO, ereport(INFO,
...@@ -2893,7 +2905,17 @@ ReindexRelationConcurrently(Oid relationOid, int options) ...@@ -2893,7 +2905,17 @@ ReindexRelationConcurrently(Oid relationOid, int options)
errmsg("cannot reindex system catalogs concurrently"))); errmsg("cannot reindex system catalogs concurrently")));
/* Open relation to get its indexes */ /* Open relation to get its indexes */
heapRelation = table_open(relationOid, ShareUpdateExclusiveLock); if ((options & REINDEXOPT_MISSING_OK) != 0)
{
heapRelation = try_table_open(relationOid,
ShareUpdateExclusiveLock);
/* leave if relation does not exist */
if (!heapRelation)
break;
}
else
heapRelation = table_open(relationOid,
ShareUpdateExclusiveLock);
/* Add all the valid indexes of relation to list */ /* Add all the valid indexes of relation to list */
foreach(lc, RelationGetIndexList(heapRelation)) foreach(lc, RelationGetIndexList(heapRelation))
...@@ -2978,7 +3000,13 @@ ReindexRelationConcurrently(Oid relationOid, int options) ...@@ -2978,7 +3000,13 @@ ReindexRelationConcurrently(Oid relationOid, int options)
} }
case RELKIND_INDEX: case RELKIND_INDEX:
{ {
Oid heapId = IndexGetRelation(relationOid, false); Oid heapId = IndexGetRelation(relationOid,
(options & REINDEXOPT_MISSING_OK) != 0);
Relation heapRelation;
/* if relation is missing, leave */
if (!OidIsValid(heapId))
break;
if (IsCatalogRelationOid(heapId)) if (IsCatalogRelationOid(heapId))
ereport(ERROR, ereport(ERROR,
...@@ -2995,6 +3023,25 @@ ReindexRelationConcurrently(Oid relationOid, int options) ...@@ -2995,6 +3023,25 @@ ReindexRelationConcurrently(Oid relationOid, int options)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot reindex invalid index on TOAST table concurrently"))); errmsg("cannot reindex invalid index on TOAST table concurrently")));
/*
* Check if parent relation can be locked and if it exists,
* this needs to be done at this stage as the list of indexes
* to rebuild is not complete yet, and REINDEXOPT_MISSING_OK
* should not be used once all the session locks are taken.
*/
if ((options & REINDEXOPT_MISSING_OK) != 0)
{
heapRelation = try_table_open(heapId,
ShareUpdateExclusiveLock);
/* leave if relation does not exist */
if (!heapRelation)
break;
}
else
heapRelation = table_open(heapId,
ShareUpdateExclusiveLock);
table_close(heapRelation, NoLock);
/* Save the list of relation OIDs in private context */ /* Save the list of relation OIDs in private context */
oldcontext = MemoryContextSwitchTo(private_context); oldcontext = MemoryContextSwitchTo(private_context);
...@@ -3025,7 +3072,13 @@ ReindexRelationConcurrently(Oid relationOid, int options) ...@@ -3025,7 +3072,13 @@ ReindexRelationConcurrently(Oid relationOid, int options)
break; break;
} }
/* Definitely no indexes, so leave */ /*
* Definitely no indexes, so leave. Any checks based on
* REINDEXOPT_MISSING_OK should be done only while the list of indexes to
* work on is built as the session locks taken before this transaction
* commits will make sure that they cannot be dropped by a concurrent
* session until this operation completes.
*/
if (indexIds == NIL) if (indexIds == NIL)
{ {
PopActiveSnapshot(); PopActiveSnapshot();
......
...@@ -22,6 +22,7 @@ extern Relation table_open(Oid relationId, LOCKMODE lockmode); ...@@ -22,6 +22,7 @@ extern Relation table_open(Oid relationId, LOCKMODE lockmode);
extern Relation table_openrv(const RangeVar *relation, LOCKMODE lockmode); extern Relation table_openrv(const RangeVar *relation, LOCKMODE lockmode);
extern Relation table_openrv_extended(const RangeVar *relation, extern Relation table_openrv_extended(const RangeVar *relation,
LOCKMODE lockmode, bool missing_ok); LOCKMODE lockmode, bool missing_ok);
extern Relation try_table_open(Oid relationId, LOCKMODE lockmode);
extern void table_close(Relation relation, LOCKMODE lockmode); extern void table_close(Relation relation, LOCKMODE lockmode);
#endif /* TABLE_H */ #endif /* TABLE_H */
...@@ -3352,6 +3352,7 @@ typedef struct ConstraintsSetStmt ...@@ -3352,6 +3352,7 @@ typedef struct ConstraintsSetStmt
/* Reindex options */ /* Reindex options */
#define REINDEXOPT_VERBOSE (1 << 0) /* print progress info */ #define REINDEXOPT_VERBOSE (1 << 0) /* print progress info */
#define REINDEXOPT_REPORT_PROGRESS (1 << 1) /* report pgstat progress */ #define REINDEXOPT_REPORT_PROGRESS (1 << 1) /* report pgstat progress */
#define REINDEXOPT_MISSING_OK (2 << 1) /* skip missing relations */
typedef enum ReindexObjectType typedef enum ReindexObjectType
{ {
......
Parsed test spec with 3 sessions
starting permutation: begin1 lock1 reindex2 drop3 end1
step begin1: BEGIN;
step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE;
step reindex2: REINDEX SCHEMA reindex_schema; <waiting ...>
step drop3: DROP TABLE reindex_schema.tab_dropped;
step end1: COMMIT;
step reindex2: <... completed>
starting permutation: begin1 lock1 reindex_conc2 drop3 end1
step begin1: BEGIN;
step lock1: LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE;
step reindex_conc2: REINDEX SCHEMA CONCURRENTLY reindex_schema; <waiting ...>
step drop3: DROP TABLE reindex_schema.tab_dropped;
step end1: COMMIT;
step reindex_conc2: <... completed>
...@@ -49,6 +49,7 @@ test: lock-committed-update ...@@ -49,6 +49,7 @@ test: lock-committed-update
test: lock-committed-keyupdate test: lock-committed-keyupdate
test: update-locked-tuple test: update-locked-tuple
test: reindex-concurrently test: reindex-concurrently
test: reindex-schema
test: propagate-lock-delete test: propagate-lock-delete
test: tuplelock-conflict test: tuplelock-conflict
test: tuplelock-update test: tuplelock-update
......
# REINDEX with schemas
#
# Check that concurrent drop of relations while doing a REINDEX
# SCHEMA allows the command to work.
setup
{
CREATE SCHEMA reindex_schema;
CREATE TABLE reindex_schema.tab_locked (a int PRIMARY KEY);
CREATE TABLE reindex_schema.tab_dropped (a int PRIMARY KEY);
}
teardown
{
DROP SCHEMA reindex_schema CASCADE;
}
session "s1"
step "begin1" { BEGIN; }
step "lock1" { LOCK reindex_schema.tab_locked IN SHARE UPDATE EXCLUSIVE MODE; }
step "end1" { COMMIT; }
session "s2"
step "reindex2" { REINDEX SCHEMA reindex_schema; }
step "reindex_conc2" { REINDEX SCHEMA CONCURRENTLY reindex_schema; }
session "s3"
step "drop3" { DROP TABLE reindex_schema.tab_dropped; }
# The table can be dropped while reindex is waiting.
permutation "begin1" "lock1" "reindex2" "drop3" "end1"
permutation "begin1" "lock1" "reindex_conc2" "drop3" "end1"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment