Skip to content

Commit 7d07128

Browse files
committed
Fix an O(N^2) problem in foreign key references.
Commit 45ba424 improved foreign key lookups during bulk updates when the FK value does not change. When restoring a schema dump from a database with many (say 100,000) foreign keys, this cache would grow very big and every ALTER TABLE command was causing an InvalidateConstraintCacheCallBack(), which uses a sequential hash table scan. This could cause a severe performance regression in restoring a schema dump (including during pg_upgrade). The patch uses a heuristic method of detecting when the hash table should be destroyed and recreated. InvalidateConstraintCacheCallBack() adds the current size of the hash table to a counter. When that sum reaches 1,000,000, the hash table is flushed. This fixes the regression without noticeable harm to the bulk update use case. Jan Wieck Backpatch to 9.3 where the performance regression was introduced.
1 parent 907f3a9 commit 7d07128

File tree

1 file changed

+35
-3
lines changed

1 file changed

+35
-3
lines changed

src/backend/utils/adt/ri_triggers.c

+35-3
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ typedef struct RI_CompareHashEntry
182182
* ----------
183183
*/
184184
static HTAB *ri_constraint_cache = NULL;
185+
static long ri_constraint_cache_seq_count = 0;
185186
static HTAB *ri_query_cache = NULL;
186187
static HTAB *ri_compare_cache = NULL;
187188

@@ -214,6 +215,7 @@ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
214215
static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
215216
Datum oldvalue, Datum newvalue);
216217

218+
static void ri_InitConstraintCache(void);
217219
static void ri_InitHashTables(void);
218220
static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue);
219221
static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
@@ -2932,6 +2934,20 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
29322934

29332935
Assert(ri_constraint_cache != NULL);
29342936

2937+
/*
2938+
* Prevent an O(N^2) problem when creating large amounts of foreign
2939+
* key constraints with ALTER TABLE, like it happens at the end of
2940+
* a pg_dump with hundred-thousands of tables having references.
2941+
*/
2942+
ri_constraint_cache_seq_count += hash_get_num_entries(ri_constraint_cache);
2943+
if (ri_constraint_cache_seq_count > 1000000)
2944+
{
2945+
hash_destroy(ri_constraint_cache);
2946+
ri_InitConstraintCache();
2947+
ri_constraint_cache_seq_count = 0;
2948+
return;
2949+
}
2950+
29352951
hash_seq_init(&status, ri_constraint_cache);
29362952
while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL)
29372953
{
@@ -3327,13 +3343,15 @@ ri_NullCheck(HeapTuple tup,
33273343

33283344

33293345
/* ----------
3330-
* ri_InitHashTables -
3346+
* ri_InitConstraintCache
33313347
*
3332-
* Initialize our internal hash tables.
3348+
* Initialize ri_constraint_cache when new or being rebuilt.
3349+
*
3350+
* This needs to be done from two places, so split it out to prevent drift.
33333351
* ----------
33343352
*/
33353353
static void
3336-
ri_InitHashTables(void)
3354+
ri_InitConstraintCache(void)
33373355
{
33383356
HASHCTL ctl;
33393357

@@ -3344,6 +3362,20 @@ ri_InitHashTables(void)
33443362
ri_constraint_cache = hash_create("RI constraint cache",
33453363
RI_INIT_CONSTRAINTHASHSIZE,
33463364
&ctl, HASH_ELEM | HASH_FUNCTION);
3365+
}
3366+
3367+
/* ----------
3368+
* ri_InitHashTables -
3369+
*
3370+
* Initialize our internal hash tables.
3371+
* ----------
3372+
*/
3373+
static void
3374+
ri_InitHashTables(void)
3375+
{
3376+
HASHCTL ctl;
3377+
3378+
ri_InitConstraintCache();
33473379

33483380
/* Arrange to flush cache on pg_constraint changes */
33493381
CacheRegisterSyscacheCallback(CONSTROID,

0 commit comments

Comments
 (0)