Skip to content

Commit 72a6dad

Browse files
committed
Move index statistics into their own stats kind
This commit splits the relation and index stats data, where the index data is now moved to a new kind called PGSTAT_KIND_INDEX. A benefit of this refactoring is a reduction of the shared memory required for each index, the new PgStat_StatIdxEntry including only the fields required for indexes. This leads to less data flushed to disk on shutdown. Another benefit is that the addition of new fields for relation-level stats is easier to think about. Note that indexes have no need for most of the xact-level fields that relations care about, clarifying the role of each counter for each relkind. System views are updated to use the index-level functions. Note this split means a possible break for tools that had the idea to call the SQL functions for relation stats (not documented), as now these would return 0 when queried for an index. These tools can use the new index functions to achieve the same result. As of this change, the pending statistics are still controlled by PgStat_TableStatus for both relation and index kinds. This is left as a follow-up piece, this one being good enough on its own. This commit is focused on the changes required for the SQL interface, shared memory management and the on-disk format. Bump catalog version. Bump PGSTAT_FILE_FORMAT_ID. Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com> Author: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/f572abe7-a1bb-e13b-48c7-2ca150546822@gmail.com
1 parent feb1536 commit 72a6dad

17 files changed

Lines changed: 547 additions & 119 deletions

File tree

doc/src/sgml/monitoring.sgml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5877,6 +5877,24 @@ description | Waiting for a newly initialized WAL file to reach durable storage
58775877
</para></entry>
58785878
</row>
58795879

5880+
<row>
5881+
<entry role="func_table_entry"><para role="func_signature">
5882+
<indexterm>
5883+
<primary>pg_stat_reset_single_index_counters</primary>
5884+
</indexterm>
5885+
<function>pg_stat_reset_single_index_counters</function> ( <type>oid</type> )
5886+
<returnvalue>void</returnvalue>
5887+
</para>
5888+
<para>
5889+
Resets statistics for a single index in the current database
5890+
or shared across all databases in the cluster to zero.
5891+
</para>
5892+
<para>
5893+
This function is restricted to superusers by default, but other users
5894+
can be granted EXECUTE to run the function.
5895+
</para></entry>
5896+
</row>
5897+
58805898
<row>
58815899
<entry role="func_table_entry"><para role="func_signature">
58825900
<indexterm>
@@ -6022,7 +6040,7 @@ description | Waiting for a newly initialized WAL file to reach durable storage
60226040
<returnvalue>void</returnvalue>
60236041
</para>
60246042
<para>
6025-
Resets statistics for a single table or index in the current database
6043+
Resets statistics for a single table in the current database
60266044
or shared across all databases in the cluster to zero.
60276045
It also resets statistics for a single sequence or materialized view
60286046
in the current database.

src/backend/catalog/system_views.sql

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -722,9 +722,9 @@ CREATE VIEW pg_stat_all_tables AS
722722
pg_stat_get_numscans(C.oid) AS seq_scan,
723723
pg_stat_get_lastscan(C.oid) AS last_seq_scan,
724724
pg_stat_get_tuples_returned(C.oid) AS seq_tup_read,
725-
sum(pg_stat_get_numscans(I.indexrelid))::bigint AS idx_scan,
726-
max(pg_stat_get_lastscan(I.indexrelid)) AS last_idx_scan,
727-
sum(pg_stat_get_tuples_fetched(I.indexrelid))::bigint +
725+
sum(pg_stat_get_idx_numscans(I.indexrelid))::bigint AS idx_scan,
726+
max(pg_stat_get_idx_lastscan(I.indexrelid)) AS last_idx_scan,
727+
sum(pg_stat_get_idx_tuples_fetched(I.indexrelid))::bigint +
728728
pg_stat_get_tuples_fetched(C.oid) AS idx_tup_fetch,
729729
pg_stat_get_tuples_inserted(C.oid) AS n_tup_ins,
730730
pg_stat_get_tuples_updated(C.oid) AS n_tup_upd,
@@ -761,8 +761,8 @@ CREATE VIEW pg_stat_xact_all_tables AS
761761
C.relname AS relname,
762762
pg_stat_get_xact_numscans(C.oid) AS seq_scan,
763763
pg_stat_get_xact_tuples_returned(C.oid) AS seq_tup_read,
764-
sum(pg_stat_get_xact_numscans(I.indexrelid))::bigint AS idx_scan,
765-
sum(pg_stat_get_xact_tuples_fetched(I.indexrelid))::bigint +
764+
sum(pg_stat_get_xact_idx_numscans(I.indexrelid))::bigint AS idx_scan,
765+
sum(pg_stat_get_xact_idx_tuples_fetched(I.indexrelid))::bigint +
766766
pg_stat_get_xact_tuples_fetched(C.oid) AS idx_tup_fetch,
767767
pg_stat_get_xact_tuples_inserted(C.oid) AS n_tup_ins,
768768
pg_stat_get_xact_tuples_updated(C.oid) AS n_tup_upd,
@@ -833,17 +833,17 @@ CREATE VIEW pg_statio_all_tables AS
833833
pg_class T ON C.reltoastrelid = T.oid
834834
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
835835
LEFT JOIN LATERAL (
836-
SELECT sum(pg_stat_get_blocks_fetched(indexrelid) -
837-
pg_stat_get_blocks_hit(indexrelid))::bigint
836+
SELECT sum(pg_stat_get_idx_blocks_fetched(indexrelid) -
837+
pg_stat_get_idx_blocks_hit(indexrelid))::bigint
838838
AS idx_blks_read,
839-
sum(pg_stat_get_blocks_hit(indexrelid))::bigint
839+
sum(pg_stat_get_idx_blocks_hit(indexrelid))::bigint
840840
AS idx_blks_hit
841841
FROM pg_index WHERE indrelid = C.oid ) I ON true
842842
LEFT JOIN LATERAL (
843-
SELECT sum(pg_stat_get_blocks_fetched(indexrelid) -
844-
pg_stat_get_blocks_hit(indexrelid))::bigint
843+
SELECT sum(pg_stat_get_idx_blocks_fetched(indexrelid) -
844+
pg_stat_get_idx_blocks_hit(indexrelid))::bigint
845845
AS idx_blks_read,
846-
sum(pg_stat_get_blocks_hit(indexrelid))::bigint
846+
sum(pg_stat_get_idx_blocks_hit(indexrelid))::bigint
847847
AS idx_blks_hit
848848
FROM pg_index WHERE indrelid = T.oid ) X ON true
849849
WHERE C.relkind IN ('r', 't', 'm');
@@ -865,11 +865,11 @@ CREATE VIEW pg_stat_all_indexes AS
865865
N.nspname AS schemaname,
866866
C.relname AS relname,
867867
I.relname AS indexrelname,
868-
pg_stat_get_numscans(I.oid) AS idx_scan,
869-
pg_stat_get_lastscan(I.oid) AS last_idx_scan,
870-
pg_stat_get_tuples_returned(I.oid) AS idx_tup_read,
871-
pg_stat_get_tuples_fetched(I.oid) AS idx_tup_fetch,
872-
pg_stat_get_stat_reset_time(I.oid) AS stats_reset
868+
pg_stat_get_idx_numscans(I.oid) AS idx_scan,
869+
pg_stat_get_idx_lastscan(I.oid) AS last_idx_scan,
870+
pg_stat_get_idx_tuples_returned(I.oid) AS idx_tup_read,
871+
pg_stat_get_idx_tuples_fetched(I.oid) AS idx_tup_fetch,
872+
pg_stat_get_idx_stat_reset_time(I.oid) AS stats_reset
873873
FROM pg_class C JOIN
874874
pg_index X ON C.oid = X.indrelid JOIN
875875
pg_class I ON I.oid = X.indexrelid
@@ -893,10 +893,10 @@ CREATE VIEW pg_statio_all_indexes AS
893893
N.nspname AS schemaname,
894894
C.relname AS relname,
895895
I.relname AS indexrelname,
896-
pg_stat_get_blocks_fetched(I.oid) -
897-
pg_stat_get_blocks_hit(I.oid) AS idx_blks_read,
898-
pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit,
899-
pg_stat_get_stat_reset_time(I.oid) AS stats_reset
896+
pg_stat_get_idx_blocks_fetched(I.oid) -
897+
pg_stat_get_idx_blocks_hit(I.oid) AS idx_blks_read,
898+
pg_stat_get_idx_blocks_hit(I.oid) AS idx_blks_hit,
899+
pg_stat_get_idx_stat_reset_time(I.oid) AS stats_reset
900900
FROM pg_class C JOIN
901901
pg_index X ON C.oid = X.indrelid JOIN
902902
pg_class I ON I.oid = X.indexrelid

src/backend/utils/activity/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ OBJS = \
2525
pgstat_checkpointer.o \
2626
pgstat_database.o \
2727
pgstat_function.o \
28+
pgstat_index.o \
2829
pgstat_io.o \
2930
pgstat_kind.o \
3031
pgstat_lock.o \

src/backend/utils/activity/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ backend_sources += files(
1010
'pgstat_checkpointer.c',
1111
'pgstat_database.c',
1212
'pgstat_function.c',
13+
'pgstat_index.c',
1314
'pgstat_io.c',
1415
'pgstat_kind.c',
1516
'pgstat_lock.c',

src/backend/utils/activity/pgstat.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,22 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
317317
.reset_timestamp_cb = pgstat_relation_reset_timestamp_cb,
318318
},
319319

320+
[PGSTAT_KIND_INDEX] = {
321+
.name = "index",
322+
323+
.fixed_amount = false,
324+
.write_to_file = true,
325+
326+
.shared_size = sizeof(PgStatShared_Index),
327+
.shared_data_off = offsetof(PgStatShared_Index, stats),
328+
.shared_data_len = sizeof(((PgStatShared_Index *) 0)->stats),
329+
.pending_size = sizeof(PgStat_TableStatus),
330+
331+
.flush_pending_cb = pgstat_index_flush_cb,
332+
.delete_pending_cb = pgstat_index_delete_pending_cb,
333+
.reset_timestamp_cb = pgstat_index_reset_timestamp_cb,
334+
},
335+
320336
[PGSTAT_KIND_FUNCTION] = {
321337
.name = "function",
322338

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/* -------------------------------------------------------------------------
2+
*
3+
* pgstat_index.c
4+
* Implementation of index statistics.
5+
*
6+
* This file contains the implementation of index statistics.
7+
*
8+
* Copyright (c) 2001-2026, PostgreSQL Global Development Group
9+
*
10+
* IDENTIFICATION
11+
* src/backend/utils/activity/pgstat_index.c
12+
* -------------------------------------------------------------------------
13+
*/
14+
15+
#include "postgres.h"
16+
17+
#include "access/xact.h"
18+
#include "catalog/catalog.h"
19+
#include "utils/memutils.h"
20+
#include "utils/pgstat_internal.h"
21+
#include "utils/rel.h"
22+
#include "utils/timestamp.h"
23+
24+
25+
/*
26+
* Flush out pending stats for an index entry.
27+
*
28+
* If nowait is true and the lock could not be immediately acquired, returns
29+
* false without flushing the entry. Otherwise returns true.
30+
*
31+
* Some of the stats are copied to the corresponding pending database stats
32+
* entry when successfully flushing.
33+
*/
34+
bool
35+
pgstat_index_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
36+
{
37+
Oid dboid;
38+
PgStat_TableStatus *lstats; /* pending stats entry */
39+
PgStatShared_Index *shidxstats;
40+
PgStat_StatIdxEntry *idxentry; /* index entry of shared stats */
41+
PgStat_StatDBEntry *dbentry; /* pending database entry */
42+
43+
dboid = entry_ref->shared_entry->key.dboid;
44+
lstats = (PgStat_TableStatus *) entry_ref->pending;
45+
shidxstats = (PgStatShared_Index *) entry_ref->shared_stats;
46+
47+
/*
48+
* Ignore entries that didn't accumulate any actual counts, such as
49+
* indexes that were opened by the planner but not used.
50+
*/
51+
if (pg_memory_is_all_zeros(&lstats->counts,
52+
sizeof(struct PgStat_TableCounts)))
53+
return true;
54+
55+
if (!pgstat_lock_entry(entry_ref, nowait))
56+
return false;
57+
58+
/* Add the values to the shared entry. */
59+
idxentry = &shidxstats->stats;
60+
61+
idxentry->numscans += lstats->counts.numscans;
62+
if (lstats->counts.numscans)
63+
{
64+
TimestampTz t = GetCurrentTransactionStopTimestamp();
65+
66+
if (t > idxentry->lastscan)
67+
idxentry->lastscan = t;
68+
}
69+
idxentry->tuples_returned += lstats->counts.tuples_returned;
70+
idxentry->tuples_fetched += lstats->counts.tuples_fetched;
71+
idxentry->blocks_fetched += lstats->counts.blocks_fetched;
72+
idxentry->blocks_hit += lstats->counts.blocks_hit;
73+
74+
pgstat_unlock_entry(entry_ref);
75+
76+
/* The entry was successfully flushed, add the same to database stats */
77+
dbentry = pgstat_prep_database_pending(dboid);
78+
dbentry->tuples_returned += lstats->counts.tuples_returned;
79+
dbentry->tuples_fetched += lstats->counts.tuples_fetched;
80+
dbentry->blocks_fetched += lstats->counts.blocks_fetched;
81+
dbentry->blocks_hit += lstats->counts.blocks_hit;
82+
83+
return true;
84+
}
85+
86+
/*
87+
* Callback to delete pending index stats.
88+
*/
89+
void
90+
pgstat_index_delete_pending_cb(PgStat_EntryRef *entry_ref)
91+
{
92+
PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
93+
94+
if (pending->relation)
95+
pgstat_unlink_relation(pending->relation);
96+
}
97+
98+
/*
99+
* Callback to reset the timestamp on an index stats entry.
100+
*/
101+
void
102+
pgstat_index_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
103+
{
104+
((PgStatShared_Index *) header)->stats.stat_reset_time = ts;
105+
}
106+
107+
/*
108+
* Support function for the SQL-callable pgstat* functions. Returns
109+
* the collected statistics for one index or NULL. NULL doesn't mean
110+
* that the index doesn't exist, just that there are no statistics, so the
111+
* caller is better off to report ZERO instead.
112+
*/
113+
PgStat_StatIdxEntry *
114+
pgstat_fetch_stat_idxentry(Oid relid)
115+
{
116+
return pgstat_fetch_stat_idxentry_ext(IsSharedRelation(relid), relid, NULL);
117+
}
118+
119+
/*
120+
* More efficient version of pgstat_fetch_stat_idxentry(), allowing to specify
121+
* whether the to-be-accessed index is a shared relation or not. This version
122+
* also returns whether the caller can pfree() the result if desired.
123+
*/
124+
PgStat_StatIdxEntry *
125+
pgstat_fetch_stat_idxentry_ext(bool shared, Oid reloid, bool *may_free)
126+
{
127+
Oid dboid = (shared ? InvalidOid : MyDatabaseId);
128+
129+
return (PgStat_StatIdxEntry *)
130+
pgstat_fetch_entry(PGSTAT_KIND_INDEX, dboid, reloid, may_free);
131+
}

0 commit comments

Comments
 (0)