Improve performance of find_tabstat_entry()/get_tabstat_entry()

Patch introduces a hash map reloid -> PgStat_TableStatus which improves
performance in case of large number of tables/partitions.

Author: Aleksander Alekseev
Reviewed-by: Andres Freund, Anastasia Lubennikova, Tels, me

https://commitfest.postgresql.org/13/1058/
This commit is contained in:
Teodor Sigaev 2017-03-27 18:34:42 +03:00
parent d65561464f
commit 090010f2ec
1 changed files with 93 additions and 46 deletions

View File

@ -173,6 +173,20 @@ typedef struct TabStatusArray
static TabStatusArray *pgStatTabList = NULL; static TabStatusArray *pgStatTabList = NULL;
/*
* pgStatTabHash entry
*/
typedef struct TabStatHashEntry
{
Oid t_id;
PgStat_TableStatus* tsa_entry;
} TabStatHashEntry;
/*
* Hash table for O(1) t_id -> tsa_entry lookup
*/
static HTAB *pgStatTabHash = NULL;
/* /*
* Backends store per-function info that's waiting to be sent to the collector * Backends store per-function info that's waiting to be sent to the collector
* in this hash table (indexed by function OID). * in this hash table (indexed by function OID).
@ -840,6 +854,14 @@ pgstat_report_stat(bool force)
tsa->tsa_used = 0; tsa->tsa_used = 0;
} }
/*
* pgStatTabHash is outdated on this point so we have to clean it,
* hash_destroy() will remove hash memory context, allocated in
* make_sure_stat_tab_initialized()
*/
hash_destroy(pgStatTabHash);
pgStatTabHash = NULL;
/* /*
* Send partial messages. Make sure that any pending xact commit/abort * Send partial messages. Make sure that any pending xact commit/abort
* gets counted, even if there are no table stats to send. * gets counted, even if there are no table stats to send.
@ -1684,60 +1706,88 @@ pgstat_initstats(Relation rel)
rel->pgstat_info = get_tabstat_entry(rel_id, rel->rd_rel->relisshared); rel->pgstat_info = get_tabstat_entry(rel_id, rel->rd_rel->relisshared);
} }
/*
* Make sure pgStatTabList and pgStatTabHash are initialized.
*/
static void
make_sure_stat_tab_initialized()
{
HASHCTL ctl;
MemoryContext new_ctx;
if(!pgStatTabList)
{
/* This is first time procedure is called */
pgStatTabList = (TabStatusArray *) MemoryContextAllocZero(TopMemoryContext,
sizeof(TabStatusArray));
}
if(pgStatTabHash)
return;
/* Hash table was freed or never existed. */
new_ctx = AllocSetContextCreate(
TopMemoryContext,
"PGStatLookupHashTableContext",
ALLOCSET_DEFAULT_SIZES);
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(TabStatHashEntry);
ctl.hcxt = new_ctx;
pgStatTabHash = hash_create("pgstat t_id to tsa_entry lookup hash table",
TABSTAT_QUANTUM, &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
}
/* /*
* get_tabstat_entry - find or create a PgStat_TableStatus entry for rel * get_tabstat_entry - find or create a PgStat_TableStatus entry for rel
*/ */
static PgStat_TableStatus * static PgStat_TableStatus *
get_tabstat_entry(Oid rel_id, bool isshared) get_tabstat_entry(Oid rel_id, bool isshared)
{ {
TabStatHashEntry* hash_entry;
PgStat_TableStatus *entry; PgStat_TableStatus *entry;
TabStatusArray *tsa; TabStatusArray *tsa;
TabStatusArray *prev_tsa; bool found;
int i;
make_sure_stat_tab_initialized();
/* /*
* Search the already-used tabstat slots for this relation. * Find an entry or create a new one.
*/ */
prev_tsa = NULL; hash_entry = hash_search(pgStatTabHash, &rel_id, HASH_ENTER, &found);
for (tsa = pgStatTabList; tsa != NULL; prev_tsa = tsa, tsa = tsa->tsa_next) if(found)
return hash_entry->tsa_entry;
/*
* `hash_entry` was just created and now we have to fill it.
* First make sure there is a free space in a last element of pgStatTabList.
*/
tsa = pgStatTabList;
while(tsa->tsa_used == TABSTAT_QUANTUM)
{ {
for (i = 0; i < tsa->tsa_used; i++) if(tsa->tsa_next == NULL)
{ {
entry = &tsa->tsa_entries[i]; tsa->tsa_next = (TabStatusArray *) MemoryContextAllocZero(TopMemoryContext,
if (entry->t_id == rel_id) sizeof(TabStatusArray));
return entry;
} }
if (tsa->tsa_used < TABSTAT_QUANTUM) tsa = tsa->tsa_next;
{
/*
* It must not be present, but we found a free slot instead. Fine,
* let's use this one. We assume the entry was already zeroed,
* either at creation or after last use.
*/
entry = &tsa->tsa_entries[tsa->tsa_used++];
entry->t_id = rel_id;
entry->t_shared = isshared;
return entry;
}
} }
/* /*
* We ran out of tabstat slots, so allocate more. Be sure they're zeroed. * Add an entry.
*/
tsa = (TabStatusArray *) MemoryContextAllocZero(TopMemoryContext,
sizeof(TabStatusArray));
if (prev_tsa)
prev_tsa->tsa_next = tsa;
else
pgStatTabList = tsa;
/*
* Use the first entry of the new TabStatusArray.
*/ */
entry = &tsa->tsa_entries[tsa->tsa_used++]; entry = &tsa->tsa_entries[tsa->tsa_used++];
entry->t_id = rel_id; entry->t_id = rel_id;
entry->t_shared = isshared; entry->t_shared = isshared;
/*
* Add a corresponding entry to pgStatTabHash.
*/
hash_entry->tsa_entry = entry;
return entry; return entry;
} }
@ -1749,22 +1799,19 @@ get_tabstat_entry(Oid rel_id, bool isshared)
PgStat_TableStatus * PgStat_TableStatus *
find_tabstat_entry(Oid rel_id) find_tabstat_entry(Oid rel_id)
{ {
PgStat_TableStatus *entry; TabStatHashEntry* hash_entry;
TabStatusArray *tsa;
int i;
for (tsa = pgStatTabList; tsa != NULL; tsa = tsa->tsa_next) /*
{ * There are no entries at all.
for (i = 0; i < tsa->tsa_used; i++) */
{ if(!pgStatTabHash)
entry = &tsa->tsa_entries[i]; return NULL;
if (entry->t_id == rel_id)
return entry;
}
}
/* Not present */ hash_entry = hash_search(pgStatTabHash, &rel_id, HASH_FIND, NULL);
return NULL; if(!hash_entry)
return NULL;
return hash_entry->tsa_entry;
} }
/* /*