mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
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
This commit is contained in:
parent
0a2da5282a
commit
d5b31cc32b
164
src/backend/utils/cache/relcache.c
vendored
164
src/backend/utils/cache/relcache.c
vendored
@ -137,9 +137,27 @@ static long relcacheInvalsReceived = 0L;
|
||||
static List *initFileRelationIds = NIL;
|
||||
|
||||
/*
|
||||
* This flag lets us optimize away work in AtEO(Sub)Xact_RelationCache().
|
||||
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
|
||||
* cleanup work. This list intentionally has limited size; if it overflows,
|
||||
* we fall back to scanning the whole hashtable. There is no value in a very
|
||||
* large list because (1) at some point, a hash_seq_search scan is faster than
|
||||
* retail lookups, and (2) the value of this is to reduce EOXact work for
|
||||
* short transactions, which can't have dirtied all that many tables anyway.
|
||||
* EOXactListAdd() does not bother to prevent duplicate list entries, so the
|
||||
* cleanup processing must be idempotent.
|
||||
*/
|
||||
static bool need_eoxact_work = false;
|
||||
#define MAX_EOXACT_LIST 32
|
||||
static Oid eoxact_list[MAX_EOXACT_LIST];
|
||||
static int eoxact_list_len = 0;
|
||||
static bool eoxact_list_overflowed = false;
|
||||
|
||||
#define EOXactListAdd(rel) \
|
||||
do { \
|
||||
if (eoxact_list_len < MAX_EOXACT_LIST) \
|
||||
eoxact_list[eoxact_list_len++] = (rel)->rd_id; \
|
||||
else \
|
||||
eoxact_list_overflowed = true; \
|
||||
} while (0)
|
||||
|
||||
|
||||
/*
|
||||
@ -204,6 +222,9 @@ static void RelationClearRelation(Relation relation, bool rebuild);
|
||||
|
||||
static void RelationReloadIndexInfo(Relation relation);
|
||||
static void RelationFlushRelation(Relation relation);
|
||||
static void AtEOXact_cleanup(Relation relation, bool isCommit);
|
||||
static void AtEOSubXact_cleanup(Relation relation, bool isCommit,
|
||||
SubTransactionId mySubid, SubTransactionId parentSubid);
|
||||
static bool load_relcache_init_file(bool shared);
|
||||
static void write_relcache_init_file(bool shared);
|
||||
static void write_item(const void *data, Size len, FILE *fp);
|
||||
@ -2275,31 +2296,56 @@ AtEOXact_RelationCache(bool isCommit)
|
||||
{
|
||||
HASH_SEQ_STATUS status;
|
||||
RelIdCacheEnt *idhentry;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* To speed up transaction exit, we want to avoid scanning the relcache
|
||||
* unless there is actually something for this routine to do. Other than
|
||||
* the debug-only Assert checks, most transactions don't create any work
|
||||
* for us to do here, so we keep a static flag that gets set if there is
|
||||
* anything to do. (Currently, this means either a relation is created in
|
||||
* the current xact, or one is given a new relfilenode, or an index list
|
||||
* is forced.) For simplicity, the flag remains set till end of top-level
|
||||
* transaction, even though we could clear it at subtransaction end in
|
||||
* some cases.
|
||||
* Unless the eoxact_list[] overflowed, we only need to examine the rels
|
||||
* listed in it. Otherwise fall back on a hash_seq_search scan.
|
||||
*
|
||||
* For simplicity, eoxact_list[] entries are not deleted till end of
|
||||
* top-level transaction, even though we could remove them at
|
||||
* subtransaction end in some cases, or remove relations from the list if
|
||||
* they are cleared for other reasons. Therefore we should expect the
|
||||
* case that list entries are not found in the hashtable; if not, there's
|
||||
* nothing to do for them.
|
||||
*/
|
||||
if (!need_eoxact_work
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
&& !assert_enabled
|
||||
#endif
|
||||
)
|
||||
return;
|
||||
|
||||
if (eoxact_list_overflowed)
|
||||
{
|
||||
hash_seq_init(&status, RelationIdCache);
|
||||
|
||||
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
|
||||
{
|
||||
Relation relation = idhentry->reldesc;
|
||||
AtEOXact_cleanup(idhentry->reldesc, isCommit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < eoxact_list_len; i++)
|
||||
{
|
||||
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
|
||||
(void *) &eoxact_list[i],
|
||||
HASH_FIND,
|
||||
NULL);
|
||||
if (idhentry != NULL)
|
||||
AtEOXact_cleanup(idhentry->reldesc, isCommit);
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we're out of the transaction and can clear the list */
|
||||
eoxact_list_len = 0;
|
||||
eoxact_list_overflowed = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* AtEOXact_cleanup
|
||||
*
|
||||
* Clean up a single rel at main-transaction commit or abort
|
||||
*
|
||||
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
|
||||
* bother to prevent duplicate entries in eoxact_list[].
|
||||
*/
|
||||
static void
|
||||
AtEOXact_cleanup(Relation relation, bool isCommit)
|
||||
{
|
||||
/*
|
||||
* The relcache entry's ref count should be back to its normal
|
||||
* not-in-a-transaction state: 0 unless it's nailed in cache.
|
||||
@ -2307,6 +2353,12 @@ AtEOXact_RelationCache(bool isCommit)
|
||||
* In bootstrap mode, this is NOT true, so don't check it --- the
|
||||
* bootstrap code expects relations to stay open across start/commit
|
||||
* transaction calls. (That seems bogus, but it's not worth fixing.)
|
||||
*
|
||||
* Note: ideally this check would be applied to every relcache entry,
|
||||
* not just those that have eoxact work to do. But it's not worth
|
||||
* forcing a scan of the whole relcache just for this. (Moreover,
|
||||
* doing so would mean that assert-enabled testing never tests the
|
||||
* hash_search code path above, which seems a bad idea.)
|
||||
*/
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
if (!IsBootstrapProcessingMode())
|
||||
@ -2335,7 +2387,7 @@ AtEOXact_RelationCache(bool isCommit)
|
||||
else
|
||||
{
|
||||
RelationClearRelation(relation, false);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2356,10 +2408,6 @@ AtEOXact_RelationCache(bool isCommit)
|
||||
}
|
||||
}
|
||||
|
||||
/* Once done with the transaction, we can reset need_eoxact_work */
|
||||
need_eoxact_work = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* AtEOSubXact_RelationCache
|
||||
*
|
||||
@ -2373,20 +2421,51 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
|
||||
{
|
||||
HASH_SEQ_STATUS status;
|
||||
RelIdCacheEnt *idhentry;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Skip the relcache scan if nothing to do --- see notes for
|
||||
* AtEOXact_RelationCache.
|
||||
* Unless the eoxact_list[] overflowed, we only need to examine the rels
|
||||
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
|
||||
* logic as in AtEOXact_RelationCache.
|
||||
*/
|
||||
if (!need_eoxact_work)
|
||||
return;
|
||||
|
||||
if (eoxact_list_overflowed)
|
||||
{
|
||||
hash_seq_init(&status, RelationIdCache);
|
||||
|
||||
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
|
||||
{
|
||||
Relation relation = idhentry->reldesc;
|
||||
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
|
||||
mySubid, parentSubid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < eoxact_list_len; i++)
|
||||
{
|
||||
idhentry = (RelIdCacheEnt *) hash_search(RelationIdCache,
|
||||
(void *) &eoxact_list[i],
|
||||
HASH_FIND,
|
||||
NULL);
|
||||
if (idhentry != NULL)
|
||||
AtEOSubXact_cleanup(idhentry->reldesc, isCommit,
|
||||
mySubid, parentSubid);
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't reset the list; we still need more cleanup later */
|
||||
}
|
||||
|
||||
/*
|
||||
* AtEOSubXact_cleanup
|
||||
*
|
||||
* Clean up a single rel at subtransaction commit or abort
|
||||
*
|
||||
* NB: this processing must be idempotent, because EOXactListAdd() doesn't
|
||||
* bother to prevent duplicate entries in eoxact_list[].
|
||||
*/
|
||||
static void
|
||||
AtEOSubXact_cleanup(Relation relation, bool isCommit,
|
||||
SubTransactionId mySubid, SubTransactionId parentSubid)
|
||||
{
|
||||
/*
|
||||
* Is it a relation created in the current subtransaction?
|
||||
*
|
||||
@ -2400,7 +2479,7 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
|
||||
else
|
||||
{
|
||||
RelationClearRelation(relation, false);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2427,7 +2506,6 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
|
||||
relation->rd_indexvalid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@ -2516,9 +2594,6 @@ RelationBuildLocalRelation(const char *relname,
|
||||
rel->rd_createSubid = GetCurrentSubTransactionId();
|
||||
rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
|
||||
|
||||
/* must flag that we have rels created in this transaction */
|
||||
need_eoxact_work = true;
|
||||
|
||||
/*
|
||||
* create a new tuple descriptor from the one passed in. We do this
|
||||
* partly to copy it into the cache context, and partly because the new
|
||||
@ -2609,6 +2684,12 @@ RelationBuildLocalRelation(const char *relname,
|
||||
*/
|
||||
RelationCacheInsert(rel);
|
||||
|
||||
/*
|
||||
* Flag relation as needing eoxact cleanup (to clear rd_createSubid).
|
||||
* We can't do this before storing relid in it.
|
||||
*/
|
||||
EOXactListAdd(rel);
|
||||
|
||||
/*
|
||||
* done building relcache entry.
|
||||
*/
|
||||
@ -2732,8 +2813,9 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
|
||||
* operations on the rel in the same transaction.
|
||||
*/
|
||||
relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId();
|
||||
/* ... and now we have eoxact cleanup work to do */
|
||||
need_eoxact_work = true;
|
||||
|
||||
/* Flag relation as needing eoxact cleanup (to remove the hint) */
|
||||
EOXactListAdd(relation);
|
||||
}
|
||||
|
||||
|
||||
@ -3513,8 +3595,8 @@ RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex)
|
||||
relation->rd_indexlist = indexIds;
|
||||
relation->rd_oidindex = oidIndex;
|
||||
relation->rd_indexvalid = 2; /* mark list as forced */
|
||||
/* must flag that we have a forced index list */
|
||||
need_eoxact_work = true;
|
||||
/* Flag relation as needing eoxact cleanup (to reset the list) */
|
||||
EOXactListAdd(relation);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Loading…
x
Reference in New Issue
Block a user