aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/indexcmds.c
diff options
context:
space:
mode:
authorMichael Paquier <michael@paquier.xyz>2020-09-08 10:09:22 +0900
committerMichael Paquier <michael@paquier.xyz>2020-09-08 10:09:22 +0900
commita6642b3ae060976b42830b7dc8f29ec190ab05e4 (patch)
tree1a95529f5569ac9d87b4785a4e2b00b0f04f78df /src/backend/commands/indexcmds.c
parenta547e6867527ca16628a3fb1cf3ef6f785210a31 (diff)
downloadpostgresql-a6642b3ae060976b42830b7dc8f29ec190ab05e4.tar.gz
postgresql-a6642b3ae060976b42830b7dc8f29ec190ab05e4.zip
Add support for partitioned tables and indexes in REINDEX
Until now, REINDEX was not able to work with partitioned tables and indexes, forcing users to reindex partitions one by one. This extends REINDEX INDEX and REINDEX TABLE so as they can accept a partitioned index and table in input, respectively, to reindex all the partitions assigned to them with physical storage (foreign tables, partitioned tables and indexes are then discarded). This shares some logic with schema and database REINDEX as each partition gets processed in its own transaction after building a list of relations to work on. This choice has the advantage to minimize the number of invalid indexes to one partition with REINDEX CONCURRENTLY in the event a cancellation or failure in-flight, as the only indexes handled at once in a single REINDEX CONCURRENTLY loop are the ones from the partition being working on. Isolation tests are added to emulate some cases I bumped into while developing this feature, particularly with the concurrent drop of a leaf partition reindexed. However, this is rather limited as LOCK would cause REINDEX to block in the first transaction building the list of partitions. Per its multi-transaction nature, this new flavor cannot run in a transaction block, similarly to REINDEX SCHEMA, SYSTEM and DATABASE. Author: Justin Pryzby, Michael Paquier Reviewed-by: Anastasia Lubennikova Discussion: https://postgr.es/m/db12e897-73ff-467e-94cb-4af03705435f.adger.lj@alibaba-inc.com
Diffstat (limited to 'src/backend/commands/indexcmds.c')
-rw-r--r--src/backend/commands/indexcmds.c245
1 files changed, 193 insertions, 52 deletions
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 430e88b4c9f..f1b5f87e6a8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -88,7 +88,10 @@ static List *ChooseIndexColumnNames(List *indexElems);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
static bool ReindexRelationConcurrently(Oid relationOid, int options);
-static void ReindexPartitionedIndex(Relation parentIdx);
+
+static void ReindexPartitions(Oid relid, int options, bool isTopLevel);
+static void ReindexMultipleInternal(List *relids, int options);
+static void reindex_error_callback(void *args);
static void update_relispartition(Oid relationId, bool newval);
static bool CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts);
@@ -102,6 +105,16 @@ struct ReindexIndexCallbackState
};
/*
+ * callback arguments for reindex_error_callback()
+ */
+typedef struct ReindexErrorInfo
+{
+ char *relname;
+ char *relnamespace;
+ char relkind;
+} ReindexErrorInfo;
+
+/*
* CheckIndexCompatible
* Determine whether an existing index definition is compatible with a
* prospective index definition, such that the existing index storage
@@ -2420,12 +2433,12 @@ ChooseIndexColumnNames(List *indexElems)
* Recreate a specific index.
*/
void
-ReindexIndex(RangeVar *indexRelation, int options)
+ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
{
struct ReindexIndexCallbackState state;
Oid indOid;
- Relation irel;
char persistence;
+ char relkind;
/*
* Find and lock index, and check permissions on table; use callback to
@@ -2447,22 +2460,16 @@ ReindexIndex(RangeVar *indexRelation, int options)
&state);
/*
- * Obtain the current persistence of the existing index. We already hold
- * lock on the index.
+ * Obtain the current persistence and kind of the existing index. We
+ * already hold a lock on the index.
*/
- irel = index_open(indOid, NoLock);
-
- if (irel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
- {
- ReindexPartitionedIndex(irel);
- return;
- }
+ persistence = get_rel_persistence(indOid);
+ relkind = get_rel_relkind(indOid);
- persistence = irel->rd_rel->relpersistence;
- index_close(irel, NoLock);
-
- if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ if (relkind == RELKIND_PARTITIONED_INDEX)
+ ReindexPartitions(indOid, options, isTopLevel);
+ else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
+ persistence != RELPERSISTENCE_TEMP)
ReindexRelationConcurrently(indOid, options);
else
reindex_index(indOid, false, persistence,
@@ -2545,7 +2552,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
* Recreate all indexes of a table (and of its toast table, if any)
*/
Oid
-ReindexTable(RangeVar *relation, int options)
+ReindexTable(RangeVar *relation, int options, bool isTopLevel)
{
Oid heapOid;
bool result;
@@ -2564,8 +2571,10 @@ ReindexTable(RangeVar *relation, int options)
0,
RangeVarCallbackOwnsTable, NULL);
- if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
+ ReindexPartitions(heapOid, options, isTopLevel);
+ else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
+ get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
{
result = ReindexRelationConcurrently(heapOid, options);
@@ -2609,7 +2618,6 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
MemoryContext private_context;
MemoryContext old;
List *relids = NIL;
- ListCell *l;
int num_keys;
bool concurrent_warning = false;
@@ -2694,11 +2702,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
* Only regular tables and matviews can have indexes, so ignore any
* other kind of relation.
*
- * It is tempting to also consider partitioned tables here, but that
- * has the problem that if the children are in the same schema, they
- * would be processed twice. Maybe we could have a separate list of
- * partitioned tables, and expand that afterwards into relids,
- * ignoring any duplicates.
+ * Partitioned tables/indexes are skipped but matching leaf partitions
+ * are processed.
*/
if (classtuple->relkind != RELKIND_RELATION &&
classtuple->relkind != RELKIND_MATVIEW)
@@ -2761,14 +2766,151 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
table_endscan(scan);
table_close(relationRelation, AccessShareLock);
- /* Now reindex each rel in a separate transaction */
+ /*
+ * Process each relation listed in a separate transaction. Note that this
+ * commits and then starts a new transaction immediately.
+ */
+ ReindexMultipleInternal(relids, options);
+
+ MemoryContextDelete(private_context);
+}
+
+/*
+ * Error callback specific to ReindexPartitions().
+ */
+static void
+reindex_error_callback(void *arg)
+{
+ ReindexErrorInfo *errinfo = (ReindexErrorInfo *) arg;
+
+ Assert(errinfo->relkind == RELKIND_PARTITIONED_INDEX ||
+ errinfo->relkind == RELKIND_PARTITIONED_TABLE);
+
+ if (errinfo->relkind == RELKIND_PARTITIONED_TABLE)
+ errcontext("while reindexing partitioned table \"%s.%s\"",
+ errinfo->relnamespace, errinfo->relname);
+ else if (errinfo->relkind == RELKIND_PARTITIONED_INDEX)
+ errcontext("while reindexing partitioned index \"%s.%s\"",
+ errinfo->relnamespace, errinfo->relname);
+}
+
+/*
+ * ReindexPartitions
+ *
+ * Reindex a set of partitions, per the partitioned index or table given
+ * by the caller.
+ */
+static void
+ReindexPartitions(Oid relid, int options, bool isTopLevel)
+{
+ List *partitions = NIL;
+ char relkind = get_rel_relkind(relid);
+ char *relname = get_rel_name(relid);
+ char *relnamespace = get_namespace_name(get_rel_namespace(relid));
+ MemoryContext reindex_context;
+ List *inhoids;
+ ListCell *lc;
+ ErrorContextCallback errcallback;
+ ReindexErrorInfo errinfo;
+
+ Assert(relkind == RELKIND_PARTITIONED_INDEX ||
+ relkind == RELKIND_PARTITIONED_TABLE);
+
+ /*
+ * Check if this runs in a transaction block, with an error callback to
+ * provide more context under which a problem happens.
+ */
+ errinfo.relname = pstrdup(relname);
+ errinfo.relnamespace = pstrdup(relnamespace);
+ errinfo.relkind = relkind;
+ errcallback.callback = reindex_error_callback;
+ errcallback.arg = (void *) &errinfo;
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ PreventInTransactionBlock(isTopLevel,
+ relkind == RELKIND_PARTITIONED_TABLE ?
+ "REINDEX TABLE" : "REINDEX INDEX");
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+
+ /*
+ * Create special memory context for cross-transaction storage.
+ *
+ * Since it is a child of PortalContext, it will go away eventually even
+ * if we suffer an error so there is no need for special abort cleanup
+ * logic.
+ */
+ reindex_context = AllocSetContextCreate(PortalContext, "Reindex",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* ShareLock is enough to prevent schema modifications */
+ inhoids = find_all_inheritors(relid, ShareLock, NULL);
+
+ /*
+ * The list of relations to reindex are the physical partitions of the
+ * tree so discard any partitioned table or index.
+ */
+ foreach(lc, inhoids)
+ {
+ Oid partoid = lfirst_oid(lc);
+ char partkind = get_rel_relkind(partoid);
+ MemoryContext old_context;
+
+ /*
+ * This discards partitioned tables, partitioned indexes and foreign
+ * tables.
+ */
+ if (!RELKIND_HAS_STORAGE(partkind))
+ continue;
+
+ Assert(partkind == RELKIND_INDEX ||
+ partkind == RELKIND_RELATION);
+
+ /* Save partition OID */
+ old_context = MemoryContextSwitchTo(reindex_context);
+ partitions = lappend_oid(partitions, partoid);
+ MemoryContextSwitchTo(old_context);
+ }
+
+ /*
+ * Process each partition listed in a separate transaction. Note that
+ * this commits and then starts a new transaction immediately.
+ */
+ ReindexMultipleInternal(partitions, options);
+
+ /*
+ * Clean up working storage --- note we must do this after
+ * StartTransactionCommand, else we might be trying to delete the active
+ * context!
+ */
+ MemoryContextDelete(reindex_context);
+}
+
+/*
+ * ReindexMultipleInternal
+ *
+ * Reindex a list of relations, each one being processed in its own
+ * transaction. This commits the existing transaction immediately,
+ * and starts a new transaction when finished.
+ */
+static void
+ReindexMultipleInternal(List *relids, int options)
+{
+ ListCell *l;
+
PopActiveSnapshot();
CommitTransactionCommand();
+
foreach(l, relids)
{
Oid relid = lfirst_oid(l);
+ char relkind;
+ char relpersistence;
StartTransactionCommand();
+
/* functions in indexes may want a snapshot set */
PushActiveSnapshot(GetTransactionSnapshot());
@@ -2780,14 +2922,33 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
continue;
}
+ relkind = get_rel_relkind(relid);
+ relpersistence = get_rel_persistence(relid);
+
+ /*
+ * Partitioned tables and indexes can never be processed directly, and
+ * a list of their leaves should be built first.
+ */
+ Assert(relkind != RELKIND_PARTITIONED_INDEX &&
+ relkind != RELKIND_PARTITIONED_TABLE);
+
if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(relid) != RELPERSISTENCE_TEMP)
+ relpersistence != RELPERSISTENCE_TEMP)
{
(void) ReindexRelationConcurrently(relid,
options |
REINDEXOPT_MISSING_OK);
/* ReindexRelationConcurrently() does the verbose output */
}
+ else if (relkind == RELKIND_INDEX)
+ {
+ reindex_index(relid, false, relpersistence,
+ options |
+ REINDEXOPT_REPORT_PROGRESS |
+ REINDEXOPT_MISSING_OK);
+ PopActiveSnapshot();
+ /* reindex_index() does the verbose output */
+ }
else
{
bool result;
@@ -2810,9 +2971,8 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
CommitTransactionCommand();
}
- StartTransactionCommand();
- MemoryContextDelete(private_context);
+ StartTransactionCommand();
}
@@ -2824,8 +2984,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
* view. For tables and materialized views, all its indexes will be rebuilt,
* excluding invalid indexes and any indexes used in exclusion constraints,
* but including its associated toast table indexes. For indexes, the index
- * itself will be rebuilt. If 'relationOid' belongs to a partitioned table
- * then we issue a warning to mention these are not yet supported.
+ * itself will be rebuilt.
*
* The locks taken on parent tables and involved indexes are kept until the
* transaction is committed, at which point a session lock is taken on each
@@ -3064,13 +3223,9 @@ ReindexRelationConcurrently(Oid relationOid, int options)
MemoryContextSwitchTo(oldcontext);
break;
}
+
case RELKIND_PARTITIONED_TABLE:
- /* see reindex_relation() */
- ereport(WARNING,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
- get_rel_name(relationOid))));
- return false;
+ case RELKIND_PARTITIONED_INDEX:
default:
/* Return error if type of relation is not supported */
ereport(ERROR,
@@ -3538,20 +3693,6 @@ ReindexRelationConcurrently(Oid relationOid, int options)
}
/*
- * ReindexPartitionedIndex
- * Reindex each child of the given partitioned index.
- *
- * Not yet implemented.
- */
-static void
-ReindexPartitionedIndex(Relation parentIdx)
-{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("REINDEX is not yet implemented for partitioned indexes")));
-}
-
-/*
* Insert or delete an appropriate pg_inherits tuple to make the given index
* be a partition of the indicated parent index.
*