Speed up GetForeignKeyOids (#7578)

DESCRIPTION: Fix performance issue in GetForeignKeyOids on systems with
many constraints

GetForeignKeyOids was showing up in CPU profiles when distributing
schemas on systems with 100k+ constraints. The reason was that this
function was doing a sequence scan of pg_constraint to get the foreign
keys that referenced the requested table.

This fixes that by finding the constraints referencing the table through
pg_depend instead of pg_constraint. We're doing this indirection,
because pg_constraint doesn't have an index that we can use, but
pg_depend does.
pull/7582/head^2
Jelte Fennema-Nio 2024-04-16 10:16:40 +02:00 committed by GitHub
parent 110b4192b2
commit a263ac6f5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 122 additions and 65 deletions

View File

@ -20,6 +20,7 @@
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@ -36,6 +37,7 @@
#include "distributed/commands.h"
#include "distributed/commands/sequence.h"
#include "distributed/coordinator_protocol.h"
#include "distributed/hash_helpers.h"
#include "distributed/listutils.h"
#include "distributed/multi_join_order.h"
#include "distributed/namespace_utils.h"
@ -1199,64 +1201,16 @@ TableHasExternalForeignKeys(Oid relationId)
/*
* GetForeignKeyOids takes in a relationId, and returns a list of OIDs for
* foreign constraints that the relation with relationId is involved according
* to "flags" argument. See ExtractForeignKeyConstraintsMode enum definition
* for usage of the flags.
* ForeignConstraintMatchesFlags is a function with logic that's very specific
* to GetForeignKeyOids. There's no reason to use it in any other context.
*/
List *
GetForeignKeyOids(Oid relationId, int flags)
static bool
ForeignConstraintMatchesFlags(Form_pg_constraint constraintForm,
int flags)
{
AttrNumber pgConstraintTargetAttrNumber = InvalidAttrNumber;
bool extractReferencing = (flags & INCLUDE_REFERENCING_CONSTRAINTS);
bool extractReferenced = (flags & INCLUDE_REFERENCED_CONSTRAINTS);
/*
* Only one of them should be passed at a time since the way we scan
* pg_constraint differs for those columns. Anum_pg_constraint_conrelid
* supports index scan while Anum_pg_constraint_confrelid does not.
*/
Assert(!(extractReferencing && extractReferenced));
Assert(extractReferencing || extractReferenced);
bool useIndex = false;
Oid indexOid = InvalidOid;
if (extractReferencing)
{
pgConstraintTargetAttrNumber = Anum_pg_constraint_conrelid;
useIndex = true;
indexOid = ConstraintRelidTypidNameIndexId;
}
else if (extractReferenced)
{
pgConstraintTargetAttrNumber = Anum_pg_constraint_confrelid;
}
bool excludeSelfReference = (flags & EXCLUDE_SELF_REFERENCES);
List *foreignKeyOids = NIL;
ScanKeyData scanKey[1];
int scanKeyCount = 1;
Relation pgConstraint = table_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&scanKey[0], pgConstraintTargetAttrNumber,
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint, indexOid, useIndex,
NULL, scanKeyCount, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
while (HeapTupleIsValid(heapTuple))
{
Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple);
if (constraintForm->contype != CONSTRAINT_FOREIGN)
{
heapTuple = systable_getnext(scanDescriptor);
continue;
return false;
}
bool inheritedConstraint = OidIsValid(constraintForm->conparentid);
@ -1267,38 +1221,141 @@ GetForeignKeyOids(Oid relationId, int flags)
* the table as we already process the constraints from parent tables
* implicitly when a command is issued
*/
heapTuple = systable_getnext(scanDescriptor);
continue;
return false;
}
Oid constraintId = constraintForm->oid;
bool excludeSelfReference = (flags & EXCLUDE_SELF_REFERENCES);
bool isSelfReference = (constraintForm->conrelid == constraintForm->confrelid);
if (excludeSelfReference && isSelfReference)
{
heapTuple = systable_getnext(scanDescriptor);
continue;
return false;
}
Oid otherTableId = InvalidOid;
if (extractReferencing)
if (flags & INCLUDE_REFERENCING_CONSTRAINTS)
{
otherTableId = constraintForm->confrelid;
}
else if (extractReferenced)
else
{
otherTableId = constraintForm->conrelid;
}
if (!IsTableTypeIncluded(otherTableId, flags))
return IsTableTypeIncluded(otherTableId, flags);
}
/*
* GetForeignKeyOidsForReferencedTable returns a list of foreign key OIDs that
* reference the relationId and match the given flags.
*
* This is separated from GetForeignKeyOids because we need to scan pg_depend
* instead of pg_constraint directly. The reason for this is that there is no
* index on the confrelid of pg_constraint, so searching by that column
* requires a seqscan.
*/
static List *
GetForeignKeyOidsForReferencedTable(Oid relationId, int flags)
{
HTAB *foreignKeyOidsSet = CreateSimpleHashSetWithName(
Oid, "ReferencingForeignKeyOidsSet");
List *foreignKeyOidsList = NIL;
ScanKeyData key[2];
HeapTuple dependTup;
Relation depRel = table_open(DependRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_depend_refclassid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(RelationRelationId));
ScanKeyInit(&key[1],
Anum_pg_depend_refobjid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relationId));
SysScanDesc scan = systable_beginscan(depRel, DependReferenceIndexId, true,
NULL, lengthof(key), key);
while (HeapTupleIsValid(dependTup = systable_getnext(scan)))
{
Form_pg_depend deprec = (Form_pg_depend) GETSTRUCT(dependTup);
if (deprec->classid != ConstraintRelationId ||
deprec->deptype != DEPENDENCY_NORMAL ||
hash_search(foreignKeyOidsSet, &deprec->objid, HASH_FIND, NULL))
{
heapTuple = systable_getnext(scanDescriptor);
continue;
}
foreignKeyOids = lappend_oid(foreignKeyOids, constraintId);
heapTuple = systable_getnext(scanDescriptor);
HeapTuple constraintTup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(
deprec->objid));
if (!HeapTupleIsValid(constraintTup)) /* can happen during DROP TABLE */
{
continue;
}
Form_pg_constraint constraint = (Form_pg_constraint) GETSTRUCT(constraintTup);
if (constraint->confrelid == relationId &&
ForeignConstraintMatchesFlags(constraint, flags))
{
foreignKeyOidsList = lappend_oid(foreignKeyOidsList, constraint->oid);
hash_search(foreignKeyOidsSet, &constraint->oid, HASH_ENTER, NULL);
}
ReleaseSysCache(constraintTup);
}
systable_endscan(scan);
table_close(depRel, AccessShareLock);
return foreignKeyOidsList;
}
/*
* GetForeignKeyOids takes in a relationId, and returns a list of OIDs for
* foreign constraints that the relation with relationId is involved according
* to "flags" argument. See ExtractForeignKeyConstraintsMode enum definition
* for usage of the flags.
*/
List *
GetForeignKeyOids(Oid relationId, int flags)
{
bool extractReferencing PG_USED_FOR_ASSERTS_ONLY = (flags &
INCLUDE_REFERENCING_CONSTRAINTS);
bool extractReferenced = (flags & INCLUDE_REFERENCED_CONSTRAINTS);
/*
* Only one of them should be passed at a time since the way we scan
* pg_constraint differs for those columns. Anum_pg_constraint_conrelid
* supports index scan while Anum_pg_constraint_confrelid does not.
*/
Assert(!(extractReferencing && extractReferenced));
Assert(extractReferencing || extractReferenced);
if (extractReferenced)
{
return GetForeignKeyOidsForReferencedTable(relationId, flags);
}
List *foreignKeyOids = NIL;
ScanKeyData scanKey[1];
int scanKeyCount = 1;
Relation pgConstraint = table_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint,
ConstraintRelidTypidNameIndexId, true,
NULL, scanKeyCount, scanKey);
HeapTuple heapTuple;
while (HeapTupleIsValid(heapTuple = systable_getnext(scanDescriptor)))
{
Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple);
if (ForeignConstraintMatchesFlags(constraintForm, flags))
{
foreignKeyOids = lappend_oid(foreignKeyOids, constraintForm->oid);
}
}
systable_endscan(scanDescriptor);