diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index a5cd4e44c7..0753dbb45c 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7983,6 +7983,38 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; + + track_scan_timestamps (boolean) + + track_scan_timestamps configuration parameter + + + + + Enables the tracking of the last scan time for tables and indexes. + This parameter is off by default, as it will repeatedly query the + operating system for the current time, which may cause significant + overhead on some platforms. You can use the + tool to measure the overhead of + timing on your system. + Last sequential and index scan times for tables are displayed in + + pg_stat_all_tables system view as + well as the pg_stat_sys_tables and + pg_stat_user_tables views. + Similarly, the last scan times for indexes are shown in the + + pg_stat_all_indexes view as well + as the pg_stat_sys_indexes and + pg_stat_user_indexes views. + The last scan times are useful in determining when tables and indexes + were last used. In the case of indexes, this can be particularly + useful when reviewing the schema with the aim of removing unnecessary + indexes. + + + + track_io_timing (boolean) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 1d9509a2f6..8e5ca789fe 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4385,6 +4385,16 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i + + + last_seq_scan timestamptz + + + The time of the last sequential scan of this table. + Only populated if is enabled + + + seq_tup_read bigint @@ -4403,6 +4413,16 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i + + + last_idx_scan timestamptz + + + The time of the last index scan of this table. + Only populated if is enabled + + + idx_tup_fetch bigint @@ -4654,6 +4674,16 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i + + + last_idx_scan timestamptz + + + The time of the last scan of this index. + Only populated if is enabled + + + idx_tup_read bigint diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 5a844b63a1..801ba03ba4 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -657,8 +657,10 @@ CREATE VIEW pg_stat_all_tables AS N.nspname AS schemaname, C.relname AS relname, pg_stat_get_numscans(C.oid) AS seq_scan, + pg_stat_get_lastscan(C.oid) AS last_seq_scan, pg_stat_get_tuples_returned(C.oid) AS seq_tup_read, sum(pg_stat_get_numscans(I.indexrelid))::bigint AS idx_scan, + max(pg_stat_get_lastscan(I.indexrelid)) AS last_idx_scan, sum(pg_stat_get_tuples_fetched(I.indexrelid))::bigint + pg_stat_get_tuples_fetched(C.oid) AS idx_tup_fetch, pg_stat_get_tuples_inserted(C.oid) AS n_tup_ins, @@ -775,6 +777,7 @@ CREATE VIEW pg_stat_all_indexes AS C.relname AS relname, I.relname AS indexrelname, pg_stat_get_numscans(I.oid) AS idx_scan, + pg_stat_get_lastscan(I.oid) AS last_idx_scan, pg_stat_get_tuples_returned(I.oid) AS idx_tup_read, pg_stat_get_tuples_fetched(I.oid) AS idx_tup_fetch FROM pg_class C JOIN diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index a846d9ffb6..ea3ceb0792 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -50,6 +50,10 @@ static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop) static void restore_truncdrop_counters(PgStat_TableXactStatus *trans); +/* GUC variables */ +bool pgstat_track_scan_timestamps = false; + + /* * Copy stats between relations. This is used for things like REINDEX * CONCURRENTLY. @@ -789,6 +793,9 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) tabentry = &shtabstats->stats; tabentry->numscans += lstats->t_counts.t_numscans; + if (pgstat_track_scan_timestamps && lstats->t_counts.t_numscans && + tabentry->lastscan + USECS_PER_SEC < GetCurrentTransactionStopTimestamp()) + tabentry->lastscan = GetCurrentTimestamp(); tabentry->tuples_returned += lstats->t_counts.t_tuples_returned; tabentry->tuples_fetched += lstats->t_counts.t_tuples_fetched; tabentry->tuples_inserted += lstats->t_counts.t_tuples_inserted; diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index d9e2a79382..7eb8660989 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -52,6 +52,25 @@ pg_stat_get_numscans(PG_FUNCTION_ARGS) } +Datum +pg_stat_get_lastscan(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + TimestampTz result; + PgStat_StatTabEntry *tabentry; + + if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL) + result = 0; + else + result = tabentry->lastscan; + + if (result == 0) + PG_RETURN_NULL(); + else + PG_RETURN_TIMESTAMPTZ(result); +} + + Datum pg_stat_get_tuples_returned(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 9fbbfb1be5..5de7c1e0f9 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1600,6 +1600,15 @@ static struct config_bool ConfigureNamesBool[] = false, NULL, NULL, NULL }, + { + {"track_scan_timestamps", PGC_SUSET, STATS_CUMULATIVE, + gettext_noop("Records the last scan time for relations."), + NULL + }, + &pgstat_track_scan_timestamps, + false, + NULL, NULL, NULL + }, { {"track_wal_io_timing", PGC_SUSET, STATS_CUMULATIVE, gettext_noop("Collects timing statistics for WAL I/O activity."), diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index be47583122..f5320fb39e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5249,6 +5249,10 @@ proname => 'pg_stat_get_numscans', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', prosrc => 'pg_stat_get_numscans' }, +{ oid => '2173', descr => 'statistics: time of the last scan for table/index', + proname => 'pg_stat_get_lastscan', provolatile => 's', proparallel => 'r', + prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_lastscan' }, { oid => '1929', descr => 'statistics: number of tuples read by seqscan', proname => 'pg_stat_get_tuples_returned', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index ac28f813b4..18ad9af978 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -242,7 +242,7 @@ typedef struct PgStat_TableXactStatus * ------------------------------------------------------------ */ -#define PGSTAT_FILE_FORMAT_ID 0x01A5BCA7 +#define PGSTAT_FILE_FORMAT_ID 0x01A5BCA8 typedef struct PgStat_ArchiverStats { @@ -355,6 +355,7 @@ typedef struct PgStat_StatSubEntry typedef struct PgStat_StatTabEntry { PgStat_Counter numscans; + TimestampTz lastscan; PgStat_Counter tuples_returned; PgStat_Counter tuples_fetched; @@ -643,6 +644,7 @@ extern PgStat_WalStats *pgstat_fetch_stat_wal(void); /* GUC parameters */ extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; +extern PGDLLIMPORT bool pgstat_track_scan_timestamps; extern PGDLLIMPORT int pgstat_fetch_consistency; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 7ec3d2688f..d9287ef0c9 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1768,6 +1768,7 @@ pg_stat_all_indexes| SELECT c.oid AS relid, c.relname, i.relname AS indexrelname, pg_stat_get_numscans(i.oid) AS idx_scan, + pg_stat_get_lastscan(i.oid) AS last_idx_scan, pg_stat_get_tuples_returned(i.oid) AS idx_tup_read, pg_stat_get_tuples_fetched(i.oid) AS idx_tup_fetch FROM (((pg_class c @@ -1779,8 +1780,10 @@ pg_stat_all_tables| SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, pg_stat_get_numscans(c.oid) AS seq_scan, + pg_stat_get_lastscan(c.oid) AS last_seq_scan, pg_stat_get_tuples_returned(c.oid) AS seq_tup_read, (sum(pg_stat_get_numscans(i.indexrelid)))::bigint AS idx_scan, + max(pg_stat_get_lastscan(i.indexrelid)) AS last_idx_scan, ((sum(pg_stat_get_tuples_fetched(i.indexrelid)))::bigint + pg_stat_get_tuples_fetched(c.oid)) AS idx_tup_fetch, pg_stat_get_tuples_inserted(c.oid) AS n_tup_ins, pg_stat_get_tuples_updated(c.oid) AS n_tup_upd, @@ -2112,6 +2115,7 @@ pg_stat_sys_indexes| SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, + pg_stat_all_indexes.last_idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes @@ -2120,8 +2124,10 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, + pg_stat_all_tables.last_seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, + pg_stat_all_tables.last_idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, @@ -2156,6 +2162,7 @@ pg_stat_user_indexes| SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, + pg_stat_all_indexes.last_idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes @@ -2164,8 +2171,10 @@ pg_stat_user_tables| SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, + pg_stat_all_tables.last_seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, + pg_stat_all_tables.last_idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd,