mirror of
https://github.com/postgres/postgres.git
synced 2025-07-21 16:02:15 +03:00
Fix a performance problem in databases with large numbers of tables
(or other types of pg_class entry): the function pgstat_vacuum_tabstat, invoked during VACUUM startup, had runtime proportional to the number of stats table entries times the number of pg_class rows; in other words O(N^2) if the stats collector's information is reasonably complete. Replace list searching with a hash table to bring it back to O(N) behavior. Per report from kim at myemma.com. Back-patch as far as 8.1; 8.0 and before use different coding here.
This commit is contained in:
@ -13,7 +13,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2001-2005, PostgreSQL Global Development Group
|
* Copyright (c) 2001-2005, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.111.2.6 2006/07/16 18:17:23 tgl Exp $
|
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.111.2.7 2007/01/11 23:06:16 tgl Exp $
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
@ -178,6 +178,7 @@ static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb,
|
|||||||
PgStat_StatBeEntry **betab,
|
PgStat_StatBeEntry **betab,
|
||||||
int *numbackends);
|
int *numbackends);
|
||||||
static void backend_read_statsfile(void);
|
static void backend_read_statsfile(void);
|
||||||
|
static HTAB *pgstat_collect_oids(Oid catalogid);
|
||||||
|
|
||||||
static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype);
|
static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype);
|
||||||
static void pgstat_send(void *msg, int len);
|
static void pgstat_send(void *msg, int len);
|
||||||
@ -886,10 +887,7 @@ pgstat_report_tabstat(void)
|
|||||||
void
|
void
|
||||||
pgstat_vacuum_tabstat(void)
|
pgstat_vacuum_tabstat(void)
|
||||||
{
|
{
|
||||||
List *oidlist;
|
HTAB *htab;
|
||||||
Relation rel;
|
|
||||||
HeapScanDesc scan;
|
|
||||||
HeapTuple tup;
|
|
||||||
PgStat_MsgTabpurge msg;
|
PgStat_MsgTabpurge msg;
|
||||||
HASH_SEQ_STATUS hstat;
|
HASH_SEQ_STATUS hstat;
|
||||||
PgStat_StatDBEntry *dbentry;
|
PgStat_StatDBEntry *dbentry;
|
||||||
@ -908,15 +906,7 @@ pgstat_vacuum_tabstat(void)
|
|||||||
/*
|
/*
|
||||||
* Read pg_database and make a list of OIDs of all existing databases
|
* Read pg_database and make a list of OIDs of all existing databases
|
||||||
*/
|
*/
|
||||||
oidlist = NIL;
|
htab = pgstat_collect_oids(DatabaseRelationId);
|
||||||
rel = heap_open(DatabaseRelationId, AccessShareLock);
|
|
||||||
scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
|
|
||||||
while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
|
||||||
{
|
|
||||||
oidlist = lappend_oid(oidlist, HeapTupleGetOid(tup));
|
|
||||||
}
|
|
||||||
heap_endscan(scan);
|
|
||||||
heap_close(rel, AccessShareLock);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Search the database hash table for dead databases and tell the
|
* Search the database hash table for dead databases and tell the
|
||||||
@ -927,12 +917,14 @@ pgstat_vacuum_tabstat(void)
|
|||||||
{
|
{
|
||||||
Oid dbid = dbentry->databaseid;
|
Oid dbid = dbentry->databaseid;
|
||||||
|
|
||||||
if (!list_member_oid(oidlist, dbid))
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
|
if (hash_search(htab, (void *) &dbid, HASH_FIND, NULL) == NULL)
|
||||||
pgstat_drop_database(dbid);
|
pgstat_drop_database(dbid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
list_free(oidlist);
|
hash_destroy(htab);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lookup our own database entry; if not found, nothing more to do.
|
* Lookup our own database entry; if not found, nothing more to do.
|
||||||
@ -946,15 +938,7 @@ pgstat_vacuum_tabstat(void)
|
|||||||
/*
|
/*
|
||||||
* Similarly to above, make a list of all known relations in this DB.
|
* Similarly to above, make a list of all known relations in this DB.
|
||||||
*/
|
*/
|
||||||
oidlist = NIL;
|
htab = pgstat_collect_oids(RelationRelationId);
|
||||||
rel = heap_open(RelationRelationId, AccessShareLock);
|
|
||||||
scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
|
|
||||||
while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
|
||||||
{
|
|
||||||
oidlist = lappend_oid(oidlist, HeapTupleGetOid(tup));
|
|
||||||
}
|
|
||||||
heap_endscan(scan);
|
|
||||||
heap_close(rel, AccessShareLock);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize our messages table counter to zero
|
* Initialize our messages table counter to zero
|
||||||
@ -967,13 +951,17 @@ pgstat_vacuum_tabstat(void)
|
|||||||
hash_seq_init(&hstat, dbentry->tables);
|
hash_seq_init(&hstat, dbentry->tables);
|
||||||
while ((tabentry = (PgStat_StatTabEntry *) hash_seq_search(&hstat)) != NULL)
|
while ((tabentry = (PgStat_StatTabEntry *) hash_seq_search(&hstat)) != NULL)
|
||||||
{
|
{
|
||||||
if (list_member_oid(oidlist, tabentry->tableid))
|
Oid tabid = tabentry->tableid;
|
||||||
|
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
|
if (hash_search(htab, (void *) &tabid, HASH_FIND, NULL) != NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Not there, so add this table's Oid to the message
|
* Not there, so add this table's Oid to the message
|
||||||
*/
|
*/
|
||||||
msg.m_tableid[msg.m_nentries++] = tabentry->tableid;
|
msg.m_tableid[msg.m_nentries++] = tabid;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the message is full, send it out and reinitialize to empty
|
* If the message is full, send it out and reinitialize to empty
|
||||||
@ -1005,7 +993,50 @@ pgstat_vacuum_tabstat(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Clean up */
|
/* Clean up */
|
||||||
list_free(oidlist);
|
hash_destroy(htab);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ----------
|
||||||
|
* pgstat_collect_oids() -
|
||||||
|
*
|
||||||
|
* Collect the OIDs of either all databases or all tables, according to
|
||||||
|
* the parameter, into a temporary hash table. Caller should hash_destroy
|
||||||
|
* the result when done with it.
|
||||||
|
* ----------
|
||||||
|
*/
|
||||||
|
static HTAB *
|
||||||
|
pgstat_collect_oids(Oid catalogid)
|
||||||
|
{
|
||||||
|
HTAB *htab;
|
||||||
|
HASHCTL hash_ctl;
|
||||||
|
Relation rel;
|
||||||
|
HeapScanDesc scan;
|
||||||
|
HeapTuple tup;
|
||||||
|
|
||||||
|
memset(&hash_ctl, 0, sizeof(hash_ctl));
|
||||||
|
hash_ctl.keysize = sizeof(Oid);
|
||||||
|
hash_ctl.entrysize = sizeof(Oid);
|
||||||
|
hash_ctl.hash = oid_hash;
|
||||||
|
htab = hash_create("Temporary table of OIDs",
|
||||||
|
PGSTAT_TAB_HASH_SIZE,
|
||||||
|
&hash_ctl,
|
||||||
|
HASH_ELEM | HASH_FUNCTION);
|
||||||
|
|
||||||
|
rel = heap_open(catalogid, AccessShareLock);
|
||||||
|
scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
|
||||||
|
while ((tup = heap_getnext(scan, ForwardScanDirection)) != NULL)
|
||||||
|
{
|
||||||
|
Oid thisoid = HeapTupleGetOid(tup);
|
||||||
|
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
|
(void) hash_search(htab, (void *) &thisoid, HASH_ENTER, NULL);
|
||||||
|
}
|
||||||
|
heap_endscan(scan);
|
||||||
|
heap_close(rel, AccessShareLock);
|
||||||
|
|
||||||
|
return htab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user