From 0b5454f2dc77a403e28b10a6d193332fed7439fa Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Tue, 27 Oct 2026 20:13:23 +0000 Subject: [PATCH v2 1/1] pgstat: Add helper to drop entries from a stats kind's hash pgstat_drop_matching_entries() scans all variable-numbered stats hash tables. Some callers already know which stats kind they want to scan, and after 44539786b82 such a kind may have its own dshash. This fills in a piece left by that work for callers that drop matching entries of a known stats kind. Add pgstat_drop_matching_entries_of_kind(), which scans the hash table mapped to one stats kind. When that mapping points to the shared variable stats hash, filter entries by kind before invoking the match callback. pgstat_drop_matching_entries() keeps the same behavior by using the same per-hash helper without a kind filter. An immediate motivation is pg_stat_statements, where follow-up work can use this to drop matching entries from its stats kind. Other stats kinds that know their kind can use it as well. Add test_custom_stats coverage for pgstat_drop_matching_entries_of_kind(). --- src/backend/utils/activity/pgstat_shmem.c | 92 ++++++++++++++----- src/include/utils/pgstat_internal.h | 13 +++ .../test_custom_stats/t/001_custom_stats.pl | 29 +++++- .../test_custom_var_stats--1.0.sql | 5 + .../test_custom_stats/test_custom_var_stats.c | 35 +++++++ 5 files changed, 151 insertions(+), 23 deletions(-) diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c index 8500927a68b..beee3e66e13 100644 --- a/src/backend/utils/activity/pgstat_shmem.c +++ b/src/backend/utils/activity/pgstat_shmem.c @@ -48,6 +48,10 @@ typedef struct PgStat_EntryRefHashEntry static void pgstat_drop_database_and_contents(Oid dboid); static void pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat); +static uint64 pgstat_drop_matching_entries_in_hash(dshash_table *hash, + PgStat_Kind filter_kind, + bool (*do_drop) (PgStatShared_HashEntry *, Datum), + Datum match_data); static void pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, bool discard_pending); static bool pgstat_need_entry_refs_gc(void); @@ -1234,39 +1238,85 @@ pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid, void pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum), Datum match_data) +{ + uint64 not_freed_count = 0; + + for (int h = 0; h < pgStatLocal.num_var_hashes; h++) + not_freed_count += + pgstat_drop_matching_entries_in_hash(pgStatLocal.var_hashes[h], + PGSTAT_KIND_INVALID, + do_drop, match_data); + + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); +} + +static uint64 +pgstat_drop_matching_entries_in_hash(dshash_table *hash, + PgStat_Kind filter_kind, + bool (*do_drop) (PgStatShared_HashEntry *, Datum), + Datum match_data) { dshash_seq_status hstat; PgStatShared_HashEntry *ps; uint64 not_freed_count = 0; - for (int h = 0; h < pgStatLocal.num_var_hashes; h++) + /* entries are removed, take an exclusive lock */ + dshash_seq_init(&hstat, hash, true); + while ((ps = dshash_seq_next(&hstat)) != NULL) { - /* entries are removed, take an exclusive lock */ - dshash_seq_init(&hstat, pgStatLocal.var_hashes[h], true); - while ((ps = dshash_seq_next(&hstat)) != NULL) - { - if (ps->dropped) - continue; + if (ps->dropped) + continue; - if (do_drop != NULL && !do_drop(ps, match_data)) - continue; + /* + * For filtered scans, the requested kind may map to the shared + * variable stats hash, where this scan can see entries for other + * kinds. Dedicated hashes contain only entries of the requested + * kind. + */ + if (filter_kind != PGSTAT_KIND_INVALID && + ps->key.kind != filter_kind) + continue; - /* delete local reference */ - if (pgStatEntryRefHash) - { - PgStat_EntryRefHashEntry *lohashent = - pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key); + if (do_drop != NULL && !do_drop(ps, match_data)) + continue; - if (lohashent) - pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref, - true); - } + /* delete local reference */ + if (pgStatEntryRefHash) + { + PgStat_EntryRefHashEntry *lohashent = + pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key); - if (!pgstat_drop_entry_internal(ps, &hstat)) - not_freed_count++; + if (lohashent) + pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref, + true); } - dshash_seq_term(&hstat); + + if (!pgstat_drop_entry_internal(ps, &hstat)) + not_freed_count++; } + dshash_seq_term(&hstat); + + return not_freed_count; +} + +/* + * Scan through the hashtable containing the variable-numbered stats kind, + * dropping entries of that kind if approved by the optional do_drop() function. + */ +void +pgstat_drop_matching_entries_of_kind(PgStat_Kind kind, + bool (*do_drop) (PgStatShared_HashEntry *, Datum), + Datum match_data) +{ + uint64 not_freed_count; + + Assert(kind >= PGSTAT_KIND_MIN && kind <= PGSTAT_KIND_MAX); + Assert(pgStatLocal.kind_hash[kind] != NULL); + + not_freed_count = + pgstat_drop_matching_entries_in_hash(pgStatLocal.kind_hash[kind], + kind, do_drop, match_data); if (not_freed_count > 0) pgstat_request_entry_refs_gc(); diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index 21403516e28..bee029c0de0 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -843,8 +843,21 @@ extern void pgstat_unlock_entry(PgStat_EntryRef *entry_ref); extern bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool missing_ok); extern void pgstat_drop_all_entries(void); + +/* + * Drop variable-numbered stats entries for which do_drop returns true. If + * do_drop is NULL, drop all variable-numbered stats entries. + */ extern void pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum), Datum match_data); + +/* + * Like pgstat_drop_matching_entries(), limited to entries of the specified + * stats kind. + */ +extern void pgstat_drop_matching_entries_of_kind(PgStat_Kind kind, + bool (*do_drop) (PgStatShared_HashEntry *, Datum), + Datum match_data); extern PgStat_EntryRef *pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, bool nowait); extern void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts); diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index ccda129c4ff..5174815fab9 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -114,17 +114,42 @@ foreach my $stats_kind (@var_stats_kinds) "entry4|3|Test entry 4", "variable-sized stats of entry4 for $kind_name"); - # Test drop of variable-sized stats. + # Test drop of variable-sized stats via pgstat_drop_entry(). $node->safe_psql('postgres', q(select * from test_custom_stats_var_drop('entry3'))); $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry3'))); is($result, "", "entry3 not found after drop for $kind_name"); + + # In the shared hash case, keep a relation stats entry around to exercise + # filtering by kind before the callback is called. + if ($own_hash eq 'f') + { + $node->safe_psql('postgres', + q(CREATE TABLE test_custom_stats_shared_hash_rel(a int); + INSERT INTO test_custom_stats_shared_hash_rel VALUES (1); + SELECT pg_stat_force_next_flush())); + $result = $node->safe_psql('postgres', + q(SELECT n_tup_ins FROM pg_stat_user_tables + WHERE relname = 'test_custom_stats_shared_hash_rel')); + is($result, "1", "relation stats entry exists in shared hash for $kind_name"); + } + + # Test drop of variable-sized stats via pgstat_drop_matching_entries_of_kind(). $node->safe_psql('postgres', - q(select * from test_custom_stats_var_drop('entry4'))); + q(select * from test_custom_stats_var_drop_matching('entry4'))); $result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry4'))); is($result, "", "entry4 not found after drop for $kind_name"); + if ($own_hash eq 'f') + { + # The relation stats entry should survive the custom stats drop. + $result = $node->safe_psql('postgres', + q(SELECT n_tup_ins FROM pg_stat_user_tables + WHERE relname = 'test_custom_stats_shared_hash_rel')); + is($result, "1", + "relation stats entry remains after custom stats drop for $kind_name"); + } # Test persistence across clean restart. $node->stop(); diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql index 2b92316679f..42377767918 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql +++ b/src/test/modules/test_custom_stats/test_custom_var_stats--1.0.sql @@ -18,6 +18,11 @@ RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_var_drop' LANGUAGE C STRICT PARALLEL UNSAFE; +CREATE FUNCTION test_custom_stats_var_drop_matching(IN name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'test_custom_stats_var_drop_matching' +LANGUAGE C STRICT PARALLEL UNSAFE; + CREATE FUNCTION test_custom_stats_var_reset() RETURNS void AS 'MODULE_PATHNAME', 'test_custom_stats_var_reset' diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index ba6486fff6a..1b476876824 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -635,6 +635,21 @@ test_custom_stats_var_update(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +static bool +test_custom_stats_var_match_entry(PgStatShared_HashEntry *p, Datum match_data) +{ + /* + * pgstat_drop_matching_entries_of_kind() should filter by kind before + * invoking the callback. + */ + if (p->key.kind != PGSTAT_KIND_TEST_CUSTOM_VAR_STATS) + elog(ERROR, + "unexpected stats kind for custom stats match callback: %u", + p->key.kind); + + return p->key.objid == DatumGetUInt64(match_data); +} + /* * test_custom_stats_var_drop * Remove custom statistic entry @@ -655,6 +670,26 @@ test_custom_stats_var_drop(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* + * test_custom_stats_var_drop_matching + * Remove custom statistic entry using a matching callback + * + * Drops the named statistic from shared memory. + */ +PG_FUNCTION_INFO_V1(test_custom_stats_var_drop_matching); +Datum +test_custom_stats_var_drop_matching(PG_FUNCTION_ARGS) +{ + char *stat_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + uint64 stat_idx = PGSTAT_CUSTOM_VAR_STATS_IDX(stat_name); + + pgstat_drop_matching_entries_of_kind(PGSTAT_KIND_TEST_CUSTOM_VAR_STATS, + test_custom_stats_var_match_entry, + UInt64GetDatum(stat_idx)); + + PG_RETURN_VOID(); +} + /* * test_custom_stats_var_reset * Reset all custom statistic entries -- 2.50.1