1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

Add a function pg_stat_clear_snapshot() that discards any statistics snapshot

already collected in the current transaction; this allows plpgsql functions to
watch for stats updates even though they are confined to a single transaction.
Use this instead of the previous kluge involving pg_stat_file() to wait for
the stats collector to update in the stats regression test.  Internally,
decouple storage of stats snapshots from transaction boundaries; they'll
now stick around until someone calls pgstat_clear_snapshot --- which xact.c
still does at transaction end, to maintain the previous behavior.  This makes
the logic a lot cleaner, at the price of a couple dozen cycles per transaction
exit.
This commit is contained in:
Tom Lane
2007-02-07 23:11:30 +00:00
parent d9ce68872f
commit aec4cf1c8c
9 changed files with 192 additions and 170 deletions

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.232 2007/02/01 19:10:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.233 2007/02/07 23:11:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1628,6 +1628,7 @@ CommitTransaction(void)
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
pgstat_clear_snapshot();
pgstat_count_xact_commit();
pgstat_report_txn_timestamp(0);
@ -1844,6 +1845,7 @@ PrepareTransaction(void)
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
pgstat_clear_snapshot();
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
@ -1995,6 +1997,7 @@ AbortTransaction(void)
AtEOXact_Namespace(false);
smgrabort();
AtEOXact_Files();
pgstat_clear_snapshot();
pgstat_count_xact_rollback();
pgstat_report_txn_timestamp(0);

View File

@ -13,7 +13,7 @@
*
* Copyright (c) 2001-2007, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.144 2007/01/26 20:06:52 tgl Exp $
* $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.145 2007/02/07 23:11:29 tgl Exp $
* ----------
*/
#include "postgres.h"
@ -130,9 +130,8 @@ static TabStatArray SharedTabStat = {0, 0, NULL};
static int pgStatXactCommit = 0;
static int pgStatXactRollback = 0;
static TransactionId pgStatDBHashXact = InvalidTransactionId;
static MemoryContext pgStatLocalContext = NULL;
static HTAB *pgStatDBHash = NULL;
static TransactionId pgStatLocalStatusXact = InvalidTransactionId;
static PgBackendStatus *localBackendStatusTable = NULL;
static int localNumBackends = 0;
@ -156,11 +155,13 @@ static void pgstat_beshutdown_hook(int code, Datum arg);
static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create);
static void pgstat_drop_database(Oid databaseid);
static void pgstat_write_statsfile(void);
static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb);
static HTAB *pgstat_read_statsfile(Oid onlydb);
static void backend_read_statsfile(void);
static void pgstat_read_current_status(void);
static HTAB *pgstat_collect_oids(Oid catalogid);
static void pgstat_setup_memcxt(void);
static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype);
static void pgstat_send(void *msg, int len);
@ -1535,22 +1536,24 @@ pgstat_report_waiting(bool waiting)
static void
pgstat_read_current_status(void)
{
TransactionId topXid = GetTopTransactionId();
volatile PgBackendStatus *beentry;
PgBackendStatus *localtable;
PgBackendStatus *localentry;
int i;
Assert(!pgStatRunningInCollector);
if (TransactionIdEquals(pgStatLocalStatusXact, topXid))
if (localBackendStatusTable)
return; /* already done */
localBackendStatusTable = (PgBackendStatus *)
MemoryContextAlloc(TopTransactionContext,
pgstat_setup_memcxt();
localtable = (PgBackendStatus *)
MemoryContextAlloc(pgStatLocalContext,
sizeof(PgBackendStatus) * MaxBackends);
localNumBackends = 0;
beentry = BackendStatusArray;
localentry = localBackendStatusTable;
localentry = localtable;
for (i = 1; i <= MaxBackends; i++)
{
/*
@ -1587,7 +1590,8 @@ pgstat_read_current_status(void)
}
}
pgStatLocalStatusXact = topXid;
/* Set the pointer only after completion of a valid table */
localBackendStatusTable = localtable;
}
@ -1720,7 +1724,7 @@ PgstatCollectorMain(int argc, char *argv[])
* zero.
*/
pgStatRunningInCollector = true;
pgstat_read_statsfile(&pgStatDBHash, InvalidOid);
pgStatDBHash = pgstat_read_statsfile(InvalidOid);
/*
* Setup the descriptor set for select(2). Since only one bit in the set
@ -2090,38 +2094,24 @@ pgstat_write_statsfile(void)
* databases' hash table (whose entries point to the tables' hash tables).
* ----------
*/
static void
pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
static HTAB *
pgstat_read_statsfile(Oid onlydb)
{
PgStat_StatDBEntry *dbentry;
PgStat_StatDBEntry dbbuf;
PgStat_StatTabEntry *tabentry;
PgStat_StatTabEntry tabbuf;
HASHCTL hash_ctl;
HTAB *dbhash;
HTAB *tabhash = NULL;
FILE *fpin;
int32 format_id;
bool found;
MemoryContext use_mcxt;
int mcxt_flags;
/*
* If running in the collector or the autovacuum process, we use the
* DynaHashCxt memory context. If running in a backend, we use the
* TopTransactionContext instead, so the caller must only know the last
* XactId when this call happened to know if his tables are still valid or
* already gone!
* The tables will live in pgStatLocalContext.
*/
if (pgStatRunningInCollector || IsAutoVacuumProcess())
{
use_mcxt = NULL;
mcxt_flags = 0;
}
else
{
use_mcxt = TopTransactionContext;
mcxt_flags = HASH_CONTEXT;
}
pgstat_setup_memcxt();
/*
* Create the DB hashtable
@ -2130,9 +2120,9 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatDBEntry);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = use_mcxt;
*dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_FUNCTION | mcxt_flags);
hash_ctl.hcxt = pgStatLocalContext;
dbhash = hash_create("Databases hash", PGSTAT_DB_HASH_SIZE, &hash_ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
/*
* Try to open the status file. If it doesn't exist, the backends simply
@ -2140,7 +2130,7 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
* with empty counters.
*/
if ((fpin = AllocateFile(PGSTAT_STAT_FILENAME, PG_BINARY_R)) == NULL)
return;
return dbhash;
/*
* Verify it's of the expected format.
@ -2178,7 +2168,7 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
/*
* Add to the DB hash
*/
dbentry = (PgStat_StatDBEntry *) hash_search(*dbhash,
dbentry = (PgStat_StatDBEntry *) hash_search(dbhash,
(void *) &dbbuf.databaseid,
HASH_ENTER,
&found);
@ -2207,11 +2197,11 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(PgStat_StatTabEntry);
hash_ctl.hash = oid_hash;
hash_ctl.hcxt = use_mcxt;
hash_ctl.hcxt = pgStatLocalContext;
dbentry->tables = hash_create("Per-database table",
PGSTAT_TAB_HASH_SIZE,
&hash_ctl,
HASH_ELEM | HASH_FUNCTION | mcxt_flags);
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
/*
* Arrange that following 'T's add entries to this database's
@ -2274,44 +2264,78 @@ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb)
done:
FreeFile(fpin);
return dbhash;
}
/*
* If not done for this transaction, read the statistics collector
* stats file into some hash tables.
*
* Because we store the tables in TopTransactionContext, the result
* is good for the entire current main transaction.
*
* Inside the autovacuum process, the statfile is assumed to be valid
* "forever", that is one iteration, within one database. This means
* we only consider the statistics as they were when the autovacuum
* iteration started.
* If not already done, read the statistics collector stats file into
* some hash tables. The results will be kept until pgstat_clear_snapshot()
* is called (typically, at end of transaction).
*/
static void
backend_read_statsfile(void)
{
if (IsAutoVacuumProcess())
{
/* already read it? */
if (pgStatDBHash)
return;
Assert(!pgStatRunningInCollector);
pgstat_read_statsfile(&pgStatDBHash, InvalidOid);
}
else
{
TransactionId topXid = GetTopTransactionId();
/* already read it? */
if (pgStatDBHash)
return;
Assert(!pgStatRunningInCollector);
if (!TransactionIdEquals(pgStatDBHashXact, topXid))
{
Assert(!pgStatRunningInCollector);
pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId);
pgStatDBHashXact = topXid;
}
}
/* Autovacuum wants stats about all databases */
if (IsAutoVacuumProcess())
pgStatDBHash = pgstat_read_statsfile(InvalidOid);
else
pgStatDBHash = pgstat_read_statsfile(MyDatabaseId);
}
/* ----------
* pgstat_setup_memcxt() -
*
* Create pgStatLocalContext, if not already done.
* ----------
*/
static void
pgstat_setup_memcxt(void)
{
if (!pgStatLocalContext)
pgStatLocalContext = AllocSetContextCreate(TopMemoryContext,
"Statistics snapshot",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
}
/* ----------
* pgstat_clear_snapshot() -
*
* Discard any data collected in the current transaction. Any subsequent
* request will cause new snapshots to be read.
*
* This is also invoked during transaction commit or abort to discard
* the no-longer-wanted snapshot.
* ----------
*/
void
pgstat_clear_snapshot(void)
{
/* In an autovacuum process we keep the stats forever */
if (IsAutoVacuumProcess())
return;
/* Release memory, if any was allocated */
if (pgStatLocalContext)
MemoryContextDelete(pgStatLocalContext);
/* Reset variables */
pgStatLocalContext = NULL;
pgStatDBHash = NULL;
localBackendStatusTable = NULL;
localNumBackends = 0;
}
/* ----------
* pgstat_recv_tabstat() -
*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/pgstatfuncs.c,v 1.37 2007/01/05 22:19:41 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/pgstatfuncs.c,v 1.38 2007/02/07 23:11:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -39,7 +39,6 @@ extern Datum pg_stat_get_last_autoanalyze_time(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
extern Datum pg_backend_pid(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_backend_userid(PG_FUNCTION_ARGS);
@ -57,6 +56,9 @@ extern Datum pg_stat_get_db_xact_rollback(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_db_blocks_fetched(PG_FUNCTION_ARGS);
extern Datum pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS);
extern Datum pg_stat_clear_snapshot(PG_FUNCTION_ARGS);
extern Datum pg_stat_reset(PG_FUNCTION_ARGS);
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
@ -336,16 +338,6 @@ pg_backend_pid(PG_FUNCTION_ARGS)
PG_RETURN_INT32(MyProcPid);
}
/*
* Built-in function for resetting the counters
*/
Datum
pg_stat_reset(PG_FUNCTION_ARGS)
{
pgstat_reset_counters();
PG_RETURN_BOOL(true);
}
Datum
pg_stat_get_backend_pid(PG_FUNCTION_ARGS)
@ -678,3 +670,23 @@ pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS)
PG_RETURN_INT64(result);
}
/* Discard the active statistics snapshot */
Datum
pg_stat_clear_snapshot(PG_FUNCTION_ARGS)
{
pgstat_clear_snapshot();
PG_RETURN_VOID();
}
/* Reset all counters for the current database */
Datum
pg_stat_reset(PG_FUNCTION_ARGS)
{
pgstat_reset_counters();
PG_RETURN_VOID();
}

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.381 2007/02/06 02:59:12 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.382 2007/02/07 23:11:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200702051
#define CATALOG_VERSION_NO 200702071
#endif

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.442 2007/02/03 14:06:55 petere Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.443 2007/02/07 23:11:30 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
@ -2938,8 +2938,6 @@ DATA(insert OID = 1936 ( pg_stat_get_backend_idset PGNSP PGUID 12 1 100 f f t
DESCR("Statistics: Currently active backend IDs");
DATA(insert OID = 2026 ( pg_backend_pid PGNSP PGUID 12 1 0 f f t f s 0 23 "" _null_ _null_ _null_ pg_backend_pid - _null_ ));
DESCR("Statistics: Current backend PID");
DATA(insert OID = 2274 ( pg_stat_reset PGNSP PGUID 12 1 0 f f f f v 0 16 "" _null_ _null_ _null_ pg_stat_reset - _null_ ));
DESCR("Statistics: Reset collected statistics");
DATA(insert OID = 1937 ( pg_stat_get_backend_pid PGNSP PGUID 12 1 0 f f t f s 1 23 "23" _null_ _null_ _null_ pg_stat_get_backend_pid - _null_ ));
DESCR("Statistics: PID of backend");
DATA(insert OID = 1938 ( pg_stat_get_backend_dbid PGNSP PGUID 12 1 0 f f t f s 1 26 "23" _null_ _null_ _null_ pg_stat_get_backend_dbid - _null_ ));
@ -2970,6 +2968,10 @@ DATA(insert OID = 1944 ( pg_stat_get_db_blocks_fetched PGNSP PGUID 12 1 0 f f t
DESCR("Statistics: Blocks fetched for database");
DATA(insert OID = 1945 ( pg_stat_get_db_blocks_hit PGNSP PGUID 12 1 0 f f t f s 1 20 "26" _null_ _null_ _null_ pg_stat_get_db_blocks_hit - _null_ ));
DESCR("Statistics: Blocks found in cache for database");
DATA(insert OID = 2230 ( pg_stat_clear_snapshot PGNSP PGUID 12 1 0 f f f f v 0 2278 "" _null_ _null_ _null_ pg_stat_clear_snapshot - _null_ ));
DESCR("Statistics: Discard current transaction's statistics snapshot");
DATA(insert OID = 2274 ( pg_stat_reset PGNSP PGUID 12 1 0 f f f f v 0 2278 "" _null_ _null_ _null_ pg_stat_reset - _null_ ));
DESCR("Statistics: Reset collected statistics for current database");
DATA(insert OID = 1946 ( encode PGNSP PGUID 12 1 0 f f t f i 2 25 "17 25" _null_ _null_ _null_ binary_encode - _null_ ));
DESCR("Convert bytea value into some ascii-only text string");

View File

@ -5,7 +5,7 @@
*
* Copyright (c) 2001-2007, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/pgstat.h,v 1.52 2007/01/05 22:19:50 momjian Exp $
* $PostgreSQL: pgsql/src/include/pgstat.h,v 1.53 2007/02/07 23:11:30 tgl Exp $
* ----------
*/
#ifndef PGSTAT_H
@ -380,6 +380,7 @@ extern void pgstat_report_tabstat(void);
extern void pgstat_vacuum_tabstat(void);
extern void pgstat_drop_relation(Oid relid);
extern void pgstat_clear_snapshot(void);
extern void pgstat_reset_counters(void);
extern void pgstat_report_autovac(Oid dboid);

View File

@ -27,6 +27,35 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
FROM pg_catalog.pg_stat_user_tables AS t,
pg_catalog.pg_statio_user_tables AS b
WHERE t.relname='tenk2' AND b.relname='tenk2';
-- function to wait for counters to advance
create function wait_for_stats() returns void as $$
declare
start_time timestamptz := clock_timestamp();
updated bool;
begin
-- we don't want to wait forever; loop will exit after 30 seconds
for i in 1 .. 300 loop
-- check to see if indexscan has been sensed
SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated
FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
WHERE st.relname='tenk2' AND cl.relname='tenk2';
exit when updated;
-- wait a little
perform pg_sleep(0.1);
-- reset stats snapshot so we can test again
perform pg_stat_clear_snapshot();
end loop;
-- report time waited in postmaster log (where it won't change test output)
raise log 'wait_for_stats delayed % seconds',
extract(epoch from clock_timestamp() - start_time);
end
$$ language plpgsql;
-- enable statistics
SET stats_block_level = on;
SET stats_row_level = on;
@ -44,58 +73,13 @@ SELECT count(*) FROM tenk2 WHERE unique1 = 1;
1
(1 row)
-- All of the thrashing here is to wait for the stats collector to update,
-- without waiting too long (in fact, we'd like to try to measure how long
-- we wait). Watching for change in the stats themselves wouldn't work
-- because the backend only reads them once per transaction. The stats file
-- mod timestamp isn't too helpful because it may have resolution of only one
-- second, or even worse. So, we touch a new table and then watch for change
-- in the size of the stats file. Ugh.
-- save current stats-file size
CREATE TEMP TABLE prevfilesize AS
SELECT size FROM pg_stat_file('global/pgstat.stat');
-- make and touch a previously nonexistent table
CREATE TABLE stats_hack (f1 int);
SELECT * FROM stats_hack;
f1
----
(0 rows)
-- wait for stats collector to update
create function wait_for_stats() returns void as $$
declare
start_time timestamptz := clock_timestamp();
oldsize bigint;
newsize bigint;
begin
-- fetch previous stats-file size
select size into oldsize from prevfilesize;
-- we don't want to wait forever; loop will exit after 30 seconds
for i in 1 .. 300 loop
-- look for update of stats file
select size into newsize from pg_stat_file('global/pgstat.stat');
exit when newsize != oldsize;
-- wait a little
perform pg_sleep(0.1);
end loop;
-- report time waited in postmaster log (where it won't change test output)
raise log 'wait_for_stats delayed % seconds',
extract(epoch from clock_timestamp() - start_time);
end
$$ language plpgsql;
SELECT wait_for_stats();
wait_for_stats
----------------
(1 row)
DROP TABLE stats_hack;
-- check effects
SELECT st.seq_scan >= pr.seq_scan + 1,
st.seq_tup_read >= pr.seq_tup_read + cl.reltuples,

View File

@ -21,52 +21,28 @@ SELECT t.seq_scan, t.seq_tup_read, t.idx_scan, t.idx_tup_fetch,
pg_catalog.pg_statio_user_tables AS b
WHERE t.relname='tenk2' AND b.relname='tenk2';
-- enable statistics
SET stats_block_level = on;
SET stats_row_level = on;
-- do a seqscan
SELECT count(*) FROM tenk2;
-- do an indexscan
SELECT count(*) FROM tenk2 WHERE unique1 = 1;
-- All of the thrashing here is to wait for the stats collector to update,
-- without waiting too long (in fact, we'd like to try to measure how long
-- we wait). Watching for change in the stats themselves wouldn't work
-- because the backend only reads them once per transaction. The stats file
-- mod timestamp isn't too helpful because it may have resolution of only one
-- second, or even worse. So, we touch a new table and then watch for change
-- in the size of the stats file. Ugh.
-- save current stats-file size
CREATE TEMP TABLE prevfilesize AS
SELECT size FROM pg_stat_file('global/pgstat.stat');
-- make and touch a previously nonexistent table
CREATE TABLE stats_hack (f1 int);
SELECT * FROM stats_hack;
-- wait for stats collector to update
-- function to wait for counters to advance
create function wait_for_stats() returns void as $$
declare
start_time timestamptz := clock_timestamp();
oldsize bigint;
newsize bigint;
updated bool;
begin
-- fetch previous stats-file size
select size into oldsize from prevfilesize;
-- we don't want to wait forever; loop will exit after 30 seconds
for i in 1 .. 300 loop
-- look for update of stats file
select size into newsize from pg_stat_file('global/pgstat.stat');
-- check to see if indexscan has been sensed
SELECT (st.idx_scan >= pr.idx_scan + 1) INTO updated
FROM pg_stat_user_tables AS st, pg_class AS cl, prevstats AS pr
WHERE st.relname='tenk2' AND cl.relname='tenk2';
exit when newsize != oldsize;
exit when updated;
-- wait a little
perform pg_sleep(0.1);
-- reset stats snapshot so we can test again
perform pg_stat_clear_snapshot();
end loop;
-- report time waited in postmaster log (where it won't change test output)
@ -75,9 +51,17 @@ begin
end
$$ language plpgsql;
SELECT wait_for_stats();
-- enable statistics
SET stats_block_level = on;
SET stats_row_level = on;
DROP TABLE stats_hack;
-- do a seqscan
SELECT count(*) FROM tenk2;
-- do an indexscan
SELECT count(*) FROM tenk2 WHERE unique1 = 1;
-- wait for stats collector to update
SELECT wait_for_stats();
-- check effects
SELECT st.seq_scan >= pr.seq_scan + 1,