Commit 5ddc7288 authored by Kevin Grittner's avatar Kevin Grittner

Fix an O(N^2) problem in foreign key references.

Commit 45ba424f 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.
parent aa65de04
...@@ -183,6 +183,7 @@ typedef struct RI_CompareHashEntry ...@@ -183,6 +183,7 @@ typedef struct RI_CompareHashEntry
* ---------- * ----------
*/ */
static HTAB *ri_constraint_cache = NULL; static HTAB *ri_constraint_cache = NULL;
static long ri_constraint_cache_seq_count = 0;
static HTAB *ri_query_cache = NULL; static HTAB *ri_query_cache = NULL;
static HTAB *ri_compare_cache = NULL; static HTAB *ri_compare_cache = NULL;
...@@ -215,6 +216,7 @@ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, ...@@ -215,6 +216,7 @@ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
static bool ri_AttributesEqual(Oid eq_opr, Oid typeid, static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
Datum oldvalue, Datum newvalue); Datum oldvalue, Datum newvalue);
static void ri_InitConstraintCache(void);
static void ri_InitHashTables(void); static void ri_InitHashTables(void);
static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue); static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue);
static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key); static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
...@@ -2945,6 +2947,20 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) ...@@ -2945,6 +2947,20 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
Assert(ri_constraint_cache != NULL); Assert(ri_constraint_cache != NULL);
/*
* Prevent an O(N^2) problem when creating large amounts of foreign
* key constraints with ALTER TABLE, like it happens at the end of
* a pg_dump with hundred-thousands of tables having references.
*/
ri_constraint_cache_seq_count += hash_get_num_entries(ri_constraint_cache);
if (ri_constraint_cache_seq_count > 1000000)
{
hash_destroy(ri_constraint_cache);
ri_InitConstraintCache();
ri_constraint_cache_seq_count = 0;
return;
}
hash_seq_init(&status, ri_constraint_cache); hash_seq_init(&status, ri_constraint_cache);
while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL) while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL)
{ {
...@@ -3364,13 +3380,15 @@ ri_NullCheck(HeapTuple tup, ...@@ -3364,13 +3380,15 @@ ri_NullCheck(HeapTuple tup,
/* ---------- /* ----------
* ri_InitHashTables - * ri_InitConstraintCache
* *
* Initialize our internal hash tables. * Initialize ri_constraint_cache when new or being rebuilt.
*
* This needs to be done from two places, so split it out to prevent drift.
* ---------- * ----------
*/ */
static void static void
ri_InitHashTables(void) ri_InitConstraintCache(void)
{ {
HASHCTL ctl; HASHCTL ctl;
...@@ -3380,6 +3398,20 @@ ri_InitHashTables(void) ...@@ -3380,6 +3398,20 @@ ri_InitHashTables(void)
ri_constraint_cache = hash_create("RI constraint cache", ri_constraint_cache = hash_create("RI constraint cache",
RI_INIT_CONSTRAINTHASHSIZE, RI_INIT_CONSTRAINTHASHSIZE,
&ctl, HASH_ELEM | HASH_BLOBS); &ctl, HASH_ELEM | HASH_BLOBS);
}
/* ----------
* ri_InitHashTables -
*
* Initialize our internal hash tables.
* ----------
*/
static void
ri_InitHashTables(void)
{
HASHCTL ctl;
ri_InitConstraintCache();
/* Arrange to flush cache on pg_constraint changes */ /* Arrange to flush cache on pg_constraint changes */
CacheRegisterSyscacheCallback(CONSTROID, CacheRegisterSyscacheCallback(CONSTROID,
......
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