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

Commit d5b31cc

Browse files
committed
Fix an O(N^2) performance issue for sessions modifying many relations.
AtEOXact_RelationCache() scanned the entire relation cache at the end of any transaction that created a new relation or assigned a new relfilenode. Thus, clients such as pg_restore had an O(N^2) performance problem that would start to be noticeable after creating 10000 or so tables. Since typically only a small number of relcache entries need any cleanup, we can fix this by keeping a small list of their OIDs and doing hash_searches for them. We fall back to the full-table scan if the list overflows. Ideally, the maximum list length would be set at the point where N hash_searches would cost just less than the full-table scan. Some quick experimentation says that point might be around 50-100; I (tgl) conservatively set MAX_EOXACT_LIST = 32. For the case that we're worried about here, which is short single-statement transactions, it's unlikely there would ever be more than about a dozen list entries anyway; so it's probably not worth being too tense about the value. We could avoid the hash_searches by instead keeping the target relcache entries linked into a list, but that would be noticeably more complicated and bug-prone because of the need to maintain such a list in the face of relcache entry drops. Since a relcache entry can only need such cleanup after a somewhat-heavyweight filesystem operation, trying to save a hash_search per cleanup doesn't seem very useful anyway --- it's the scan over all the not-needing-cleanup entries that we wish to avoid here. Jeff Janes, reviewed and tweaked a bit by Tom Lane
1 parent 0a2da52 commit d5b31cc

File tree

1 file changed

+128
-46
lines changed

1 file changed

+128
-46
lines changed

src/backend/utils/cache/relcache.c

+128-46
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,27 @@ static long relcacheInvalsReceived = 0L;
137137
static List *initFileRelationIds = NIL;
138138

139139
/*
140-
* This flag lets us optimize away work in AtEO(Sub)Xact_RelationCache().
140+
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
141+
* cleanup work. This list intentionally has limited size; if it overflows,
142+
* we fall back to scanning the whole hashtable. There is no value in a very
143+
* large list because (1) at some point, a hash_seq_search scan is faster than
144+
* retail lookups, and (2) the value of this is to reduce EOXact work for
145+
* short transactions, which can't have dirtied all that many tables anyway.
146+
* EOXactListAdd() does not bother to prevent duplicate list entries, so the
147+
* cleanup processing must be idempotent.
141148
*/
142-
static bool need_eoxact_work = false;
149+
#define MAX_EOXACT_LIST 32
150+
static Oid eoxact_list[MAX_EOXACT_LIST];
151+
static int eoxact_list_len = 0;
152+
static bool eoxact_list_overflowed = false;
153+
154+
#define EOXactListAdd(rel) \
155+
do { \
156+
if (eoxact_list_len < MAX_EOXACT_LIST) \
157+
eoxact_list[eoxact_list_len++] = (rel)->rd_id; \
158+
else \
159+
eoxact_list_overflowed = true; \
160+
} while (0)
143161

144162

145163
/*
@@ -204,6 +222,9 @@ static void RelationClearRelation(Relation relation, bool rebuild);
204222

205223
static void RelationReloadIndexInfo(Relation relation);
206224
static void RelationFlushRelation(Relation relation);
225+
static void AtEOXact_cleanup(Relation relation, bool isCommit);
226+
static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
227+
SubTransactionId mySubid, SubTransactionId parentSubid);
207228
static bool load_relcache_init_file(bool shared);
208229
static void write_relcache_init_file(bool shared);
209230
static void write_item(const void *data, Size len, FILE *fp);
@@ -2275,38 +2296,69 @@ AtEOXact_RelationCache(bool isCommit)
22752296
{
22762297
HASH_SEQ_STATUS status;
22772298
RelIdCacheEnt *idhentry;
2299+
int i;
22782300

22792301
/*
2280-
* To speed up transaction exit, we want to avoid scanning the relcache
2281-
* unless there is actually something for this routine to do. Other than
2282-
* the debug-only Assert checks, most transactions don't create any work
2283-
* for us to do here, so we keep a static flag that gets set if there is
2284-
* anything to do. (Currently, this means either a relation is created in
2285-
* the current xact, or one is given a new relfilenode, or an index list
2286-
* is forced.) For simplicity, the flag remains set till end of top-level
2287-
* transaction, even though we could clear it at subtransaction end in
2288-
* some cases.
2289-
*/
2290-
if (!need_eoxact_work
2291-
#ifdef USE_ASSERT_CHECKING
2292-
&& !assert_enabled
2293-
#endif
2294-
)
2295-
return;
2296-
2297-
hash_seq_init(&status, RelationIdCache);
2298-
2299-
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
2302+
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2303+
* listed in it. Otherwise fall back on a hash_seq_search scan.
2304+
*
2305+
* For simplicity, eoxact_list[] entries are not deleted till end of
2306+
* top-level transaction, even though we could remove them at
2307+
* subtransaction end in some cases, or remove relations from the list if
2308+
* they are cleared for other reasons. Therefore we should expect the
2309+
* case that list entries are not found in the hashtable; if not, there's
2310+
* nothing to do for them.
2311+
*/
2312+
if (eoxact_list_overflowed)
23002313
{
2301-
Relation relation = idhentry->reldesc;
2314+
hash_seq_init(&status, RelationIdCache);
2315+
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
2316+
{
2317+
AtEOXact_cleanup(idhentry->reldesc, isCommit);
2318+
}
2319+
}
2320+
else
2321+
{
2322+
for (i = 0; i < eoxact_list_len; i++)
2323+
{
2324+
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
2325+
(void *) &eoxact_list[i],
2326+
HASH_FIND,
2327+
NULL);
2328+
if (idhentry != NULL)
2329+
AtEOXact_cleanup(idhentry->reldesc, isCommit);
2330+
}
2331+
}
23022332

2333+
/* Now we're out of the transaction and can clear the list */
2334+
eoxact_list_len = 0;
2335+
eoxact_list_overflowed = false;
2336+
}
2337+
2338+
/*
2339+
* AtEOXact_cleanup
2340+
*
2341+
* Clean up a single rel at main-transaction commit or abort
2342+
*
2343+
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
2344+
* bother to prevent duplicate entries in eoxact_list[].
2345+
*/
2346+
static void
2347+
AtEOXact_cleanup(Relation relation, bool isCommit)
2348+
{
23032349
/*
23042350
* The relcache entry's ref count should be back to its normal
23052351
* not-in-a-transaction state: 0 unless it's nailed in cache.
23062352
*
23072353
* In bootstrap mode, this is NOT true, so don't check it --- the
23082354
* bootstrap code expects relations to stay open across start/commit
23092355
* transaction calls. (That seems bogus, but it's not worth fixing.)
2356+
*
2357+
* Note: ideally this check would be applied to every relcache entry,
2358+
* not just those that have eoxact work to do. But it's not worth
2359+
* forcing a scan of the whole relcache just for this. (Moreover,
2360+
* doing so would mean that assert-enabled testing never tests the
2361+
* hash_search code path above, which seems a bad idea.)
23102362
*/
23112363
#ifdef USE_ASSERT_CHECKING
23122364
if (!IsBootstrapProcessingMode())
@@ -2335,7 +2387,7 @@ AtEOXact_RelationCache(bool isCommit)
23352387
else
23362388
{
23372389
RelationClearRelation(relation, false);
2338-
continue;
2390+
return;
23392391
}
23402392
}
23412393

@@ -2354,10 +2406,6 @@ AtEOXact_RelationCache(bool isCommit)
23542406
relation->rd_oidindex = InvalidOid;
23552407
relation->rd_indexvalid = 0;
23562408
}
2357-
}
2358-
2359-
/* Once done with the transaction, we can reset need_eoxact_work */
2360-
need_eoxact_work = false;
23612409
}
23622410

23632411
/*
@@ -2373,20 +2421,51 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
23732421
{
23742422
HASH_SEQ_STATUS status;
23752423
RelIdCacheEnt *idhentry;
2424+
int i;
23762425

23772426
/*
2378-
* Skip the relcache scan if nothing to do --- see notes for
2379-
* AtEOXact_RelationCache.
2427+
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2428+
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
2429+
* logic as in AtEOXact_RelationCache.
23802430
*/
2381-
if (!need_eoxact_work)
2382-
return;
2383-
2384-
hash_seq_init(&status, RelationIdCache);
2385-
2386-
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
2431+
if (eoxact_list_overflowed)
23872432
{
2388-
Relation relation = idhentry->reldesc;
2433+
hash_seq_init(&status, RelationIdCache);
2434+
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
2435+
{
2436+
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
2437+
mySubid, parentSubid);
2438+
}
2439+
}
2440+
else
2441+
{
2442+
for (i = 0; i < eoxact_list_len; i++)
2443+
{
2444+
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
2445+
(void *) &eoxact_list[i],
2446+
HASH_FIND,
2447+
NULL);
2448+
if (idhentry != NULL)
2449+
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
2450+
mySubid, parentSubid);
2451+
}
2452+
}
23892453

2454+
/* Don't reset the list; we still need more cleanup later */
2455+
}
2456+
2457+
/*
2458+
* AtEOSubXact_cleanup
2459+
*
2460+
* Clean up a single rel at subtransaction commit or abort
2461+
*
2462+
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
2463+
* bother to prevent duplicate entries in eoxact_list[].
2464+
*/
2465+
static void
2466+
AtEOSubXact_cleanup(Relation relation, bool isCommit,
2467+
SubTransactionId mySubid, SubTransactionId parentSubid)
2468+
{
23902469
/*
23912470
* Is it a relation created in the current subtransaction?
23922471
*
@@ -2400,7 +2479,7 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
24002479
else
24012480
{
24022481
RelationClearRelation(relation, false);
2403-
continue;
2482+
return;
24042483
}
24052484
}
24062485

@@ -2426,7 +2505,6 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
24262505
relation->rd_oidindex = InvalidOid;
24272506
relation->rd_indexvalid = 0;
24282507
}
2429-
}
24302508
}
24312509

24322510

@@ -2516,9 +2594,6 @@ RelationBuildLocalRelation(const char *relname,
25162594
rel->rd_createSubid = GetCurrentSubTransactionId();
25172595
rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
25182596

2519-
/* must flag that we have rels created in this transaction */
2520-
need_eoxact_work = true;
2521-
25222597
/*
25232598
* create a new tuple descriptor from the one passed in. We do this
25242599
* partly to copy it into the cache context, and partly because the new
@@ -2609,6 +2684,12 @@ RelationBuildLocalRelation(const char *relname,
26092684
*/
26102685
RelationCacheInsert(rel);
26112686

2687+
/*
2688+
* Flag relation as needing eoxact cleanup (to clear rd_createSubid).
2689+
* We can't do this before storing relid in it.
2690+
*/
2691+
EOXactListAdd(rel);
2692+
26122693
/*
26132694
* done building relcache entry.
26142695
*/
@@ -2732,8 +2813,9 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
27322813
* operations on the rel in the same transaction.
27332814
*/
27342815
relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId();
2735-
/* ... and now we have eoxact cleanup work to do */
2736-
need_eoxact_work = true;
2816+
2817+
/* Flag relation as needing eoxact cleanup (to remove the hint) */
2818+
EOXactListAdd(relation);
27372819
}
27382820

27392821

@@ -3513,8 +3595,8 @@ RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex)
35133595
relation->rd_indexlist = indexIds;
35143596
relation->rd_oidindex = oidIndex;
35153597
relation->rd_indexvalid = 2; /* mark list as forced */
3516-
/* must flag that we have a forced index list */
3517-
need_eoxact_work = true;
3598+
/* Flag relation as needing eoxact cleanup (to reset the list) */
3599+
EOXactListAdd(relation);
35183600
}
35193601

35203602
/*

0 commit comments

Comments
 (0)