Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 67d0f7a

Browse files
committed
Second try at fixing O(N^2) problem in foreign key references.
This replaces ill-fated commit 5ddc728, which was reverted because it broke active uses of FK cache entries. In this patch, we still do nothing more to invalidatable cache entries than mark them as needing revalidation, so we won't break active uses. To keep down the overhead of InvalidateConstraintCacheCallBack(), keep a list of just the currently-valid cache entries. (The entries are large enough that some added space for list links doesn't seem like a big problem.) This would still be O(N^2) when there are many valid entries, though, so when the list gets too long, just force the "sinval reset" behavior to remove everything from the list. I set the threshold at 1000 entries, somewhat arbitrarily. Possibly that could be fine-tuned later. Another item for future study is whether it's worth adding reference counting so that we could safely remove invalidated entries. As-is, problem cases are likely to end up with large and mostly invalid FK caches. Like the previous attempt, backpatch to 9.3. Jan Wieck and Tom Lane
1 parent c961f40 commit 67d0f7a

File tree

1 file changed

+38
-7
lines changed

1 file changed

+38
-7
lines changed

src/backend/utils/adt/ri_triggers.c

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "commands/trigger.h"
4141
#include "executor/executor.h"
4242
#include "executor/spi.h"
43+
#include "lib/ilist.h"
4344
#include "parser/parse_coerce.h"
4445
#include "parser/parse_relation.h"
4546
#include "miscadmin.h"
@@ -124,6 +125,7 @@ typedef struct RI_ConstraintInfo
124125
* PK) */
125126
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK =
126127
* FK) */
128+
dlist_node valid_link; /* Link in list of valid entries */
127129
} RI_ConstraintInfo;
128130

129131

@@ -184,6 +186,8 @@ typedef struct RI_CompareHashEntry
184186
static HTAB *ri_constraint_cache = NULL;
185187
static HTAB *ri_query_cache = NULL;
186188
static HTAB *ri_compare_cache = NULL;
189+
static dlist_head ri_constraint_cache_valid_list;
190+
static int ri_constraint_cache_valid_count = 0;
187191

188192

189193
/* ----------
@@ -2911,6 +2915,13 @@ ri_LoadConstraintInfo(Oid constraintOid)
29112915

29122916
ReleaseSysCache(tup);
29132917

2918+
/*
2919+
* For efficient processing of invalidation messages below, we keep a
2920+
* doubly-linked list, and a count, of all currently valid entries.
2921+
*/
2922+
dlist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link);
2923+
ri_constraint_cache_valid_count++;
2924+
29142925
riinfo->valid = true;
29152926

29162927
return riinfo;
@@ -2923,21 +2934,41 @@ ri_LoadConstraintInfo(Oid constraintOid)
29232934
* gets enough update traffic that it's probably worth being smarter.
29242935
* Invalidate any ri_constraint_cache entry associated with the syscache
29252936
* entry with the specified hash value, or all entries if hashvalue == 0.
2937+
*
2938+
* Note: at the time a cache invalidation message is processed there may be
2939+
* active references to the cache. Because of this we never remove entries
2940+
* from the cache, but only mark them invalid, which is harmless to active
2941+
* uses. (Any query using an entry should hold a lock sufficient to keep that
2942+
* data from changing under it --- but we may get cache flushes anyway.)
29262943
*/
29272944
static void
29282945
InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
29292946
{
2930-
HASH_SEQ_STATUS status;
2931-
RI_ConstraintInfo *hentry;
2947+
dlist_mutable_iter iter;
29322948

29332949
Assert(ri_constraint_cache != NULL);
29342950

2935-
hash_seq_init(&status, ri_constraint_cache);
2936-
while ((hentry = (RI_ConstraintInfo *) hash_seq_search(&status)) != NULL)
2951+
/*
2952+
* If the list of currently valid entries gets excessively large, we mark
2953+
* them all invalid so we can empty the list. This arrangement avoids
2954+
* O(N^2) behavior in situations where a session touches many foreign keys
2955+
* and also does many ALTER TABLEs, such as a restore from pg_dump.
2956+
*/
2957+
if (ri_constraint_cache_valid_count > 1000)
2958+
hashvalue = 0; /* pretend it's a cache reset */
2959+
2960+
dlist_foreach_modify(iter, &ri_constraint_cache_valid_list)
29372961
{
2938-
if (hentry->valid &&
2939-
(hashvalue == 0 || hentry->oidHashValue == hashvalue))
2940-
hentry->valid = false;
2962+
RI_ConstraintInfo *riinfo = dlist_container(RI_ConstraintInfo,
2963+
valid_link, iter.cur);
2964+
2965+
if (hashvalue == 0 || riinfo->oidHashValue == hashvalue)
2966+
{
2967+
riinfo->valid = false;
2968+
/* Remove invalidated entries from the list, too */
2969+
dlist_delete(iter.cur);
2970+
ri_constraint_cache_valid_count--;
2971+
}
29412972
}
29422973
}
29432974

0 commit comments

Comments
 (0)