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_scantimestamptz
+
+
+ The time of the last sequential scan of this table.
+ Only populated if is enabled
+
+
+
seq_tup_readbigint
@@ -4403,6 +4413,16 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
+
+
+ last_idx_scantimestamptz
+
+
+ The time of the last index scan of this table.
+ Only populated if is enabled
+
+
+
idx_tup_fetchbigint
@@ -4654,6 +4674,16 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
+
+
+ last_idx_scantimestamptz
+
+
+ The time of the last scan of this index.
+ Only populated if is enabled
+
+
+
idx_tup_readbigint
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,