[PATCH v1 05/12] prune_xid-hist: core functions
Renan Alves Fonseca <renanfonseca@gmail.com>
From: "Renan A. Fonseca" <renanfonseca@gmail.com>
To:
Date: 2025-05-27T14:39:57Z
Lists: pgsql-hackers
---
src/backend/utils/activity/pgstat_relation.c | 251 +++++++++++++++++++
1 file changed, 251 insertions(+)
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index 28587e2916b..33d6a384929 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -20,6 +20,7 @@
#include "access/twophase_rmgr.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+#include "storage/procarray.h"
#include "utils/memutils.h"
#include "utils/pgstat_internal.h"
#include "utils/rel.h"
@@ -460,6 +461,256 @@ pgstat_update_heap_dead_tuples(Relation rel, int delta)
}
}
+// TODO: use TransactionIdPrecedes and TransactionIdFollows in all xact comparisons
+static int
+find_last_containing_bin(TransactionId *bounds, TransactionId xid)
+{
+ int bin;
+
+ for (bin = PRUNE_XID_HIST_NBINS - 1; bin > 0; bin--)
+ if (bounds[bin - 1] == InvalidTransactionId || xid > bounds[bin - 1])
+ break;
+ return bin;
+}
+
+static int
+find_first_containing_bin(TransactionId *bounds, TransactionId xid)
+{
+ int bin;
+
+ for (bin = 0; bin < PRUNE_XID_HIST_NBINS - 1; bin++)
+ if (bounds[bin] != InvalidTransactionId && xid < bounds[bin])
+ break;
+ return bin;
+}
+
+
+static void
+hist_prune_xid_update(PruneXidHistogramTransient *hist, TransactionId xid, PgStat_Counter delta)
+{
+ int bin;
+
+ if (xid == InvalidTransactionId)
+ return;
+
+ bin = find_last_containing_bin(hist->bounds, xid);
+
+ if (bin == PRUNE_XID_HIST_NBINS - 1)
+ {
+ /* handle open bin */
+ if (delta > 0)
+ hist->freqs[bin] += delta;
+ else
+ hist->neg_freq += delta;
+ hist->bounds[bin] = Max(hist->bounds[bin], xid);
+ }
+ else
+ {
+ hist->freqs[bin] += delta;
+ }
+}
+
+
+/*
+ * At certain moments, we know that freqs cannot be negative. Then, we can
+ * rectify the histogram by moving the negative counts forward. Both
+ * histograms, before and after this operation, respect the lower bound
+ * guarantees. But the resultant is a tighter bound.
+ */
+static void
+hist_prune_xid_rectify(PruneXidHistogram *hist)
+{
+ PgStat_Counter excess = 0;
+ int bin;
+
+ for (bin = 0; bin < PRUNE_XID_HIST_NBINS - 1; bin++)
+ {
+ hist->freqs[bin] += excess;
+ excess = 0;
+
+ if (hist->freqs[bin] < 0)
+ {
+ excess = hist->freqs[bin];
+ hist->freqs[bin] = 0;
+ }
+ }
+ hist->freqs[bin] += excess;
+}
+
+static void
+hist_shift_left(PruneXidHistogram *hist, int from_bin)
+{
+ PgStat_Counter *freqs = hist->freqs;
+ TransactionId *bounds = hist->bounds;
+ int bin;
+
+ Assert(from_bin >= 0);
+
+ for (bin = from_bin; bin < PRUNE_XID_HIST_NBINS - 1; bin++)
+ {
+ bounds[bin] = bounds[bin + 1];
+ freqs[bin] = freqs[bin + 1];
+ }
+ bounds[bin] = 0;
+ freqs[bin] = 0;
+ return;
+}
+
+/*
+ * Merge intermediate adjacent bins in order to make space for a new open bin.
+ */
+static void
+hist_prune_xid_balance(PruneXidHistogram *hist)
+{
+ PgStat_Counter *freqs = hist->freqs;
+ TransactionId *bounds = hist->bounds;
+ int bin;
+
+ /* do nothing if open bin is empty */
+ if (freqs[PRUNE_XID_HIST_NBINS - 1] == 0)
+ return;
+ if (freqs[PRUNE_XID_HIST_NBINS - 1] == freqs[PRUNE_XID_HIST_NBINS - 2])
+ return;
+
+ /* if histogram is partially fulfilled, just shift bins */
+ if (bounds[0] == 0)
+ {
+ hist_shift_left(hist, 0);
+ return;
+ }
+
+ /*
+ * if second bin upper bound xid is smaller than oldest non removable xid,
+ * merge first two bins
+ */
+ if (freqs[1] < GetOldestNonRemovableTransactionId(NULL))
+ {
+ freqs[0] += freqs[1];
+ bounds[0] = bounds[1];
+ hist_shift_left(hist, 1);
+ return;
+ }
+
+ /*
+ * if open bin is too "big", merge two "small" adjacent bins.
+ */
+ {
+ PgStat_Counter open_bin_freq = freqs[PRUNE_XID_HIST_NBINS - 1];
+ PgStat_Counter smallest_pair_freq = 0;
+ int smallest_bin = 0;
+
+ for (bin = 0; bin < PRUNE_XID_HIST_NBINS - 1; bin++)
+ if (freqs[bin] + freqs[bin + 1] > smallest_pair_freq)
+ {
+ smallest_pair_freq = freqs[bin] + freqs[bin + 1];
+ smallest_bin = bin;
+ }
+ if (open_bin_freq > smallest_pair_freq)
+ {
+ freqs[smallest_bin] += freqs[smallest_bin + 1];
+ bounds[smallest_bin] = bounds[smallest_bin + 1];
+ hist_shift_left(hist, smallest_bin + 1);
+ }
+ return;
+ }
+}
+
+/* update transient histogram */
+void
+pgstat_update_transient_prune_xid_histogram(PruneXidHistogramTransient *hist, TransactionId old_xid, TransactionId new_xid)
+{
+ if (old_xid == new_xid)
+ return;
+ hist_prune_xid_update(hist, old_xid, -1);
+ hist_prune_xid_update(hist, new_xid, 1);
+}
+
+/* update per-backend prune_xid_histogram */
+void
+pgstat_update_relation_prune_xid_histogram(Relation rel, TransactionId old_xid, TransactionId new_xid)
+{
+ if (pgstat_should_count_relation(rel))
+ {
+ PgStat_TableStatus *pgstat_info = rel->pgstat_info;
+ PruneXidHistogramTransient *hist = &pgstat_info->counts.prune_xid_hist;
+
+ Assert(old_xid != new_xid);
+ hist_prune_xid_update(hist, old_xid, -1);
+ hist_prune_xid_update(hist, new_xid, 1);
+
+ }
+}
+
+
+/*
+ * This function updates prune_xid histogram of respective table in
+ * shared PgStat_StatTabEntry using information from auxiliary histogram.
+ */
+void
+pgstat_update_shared_prune_xid_histogram(PgStat_StatTabEntry *tabentry, const PruneXidHistogramTransient *source)
+{
+ int bin_source,
+ bin_target;
+ PgStat_Counter delta;
+ TransactionId xid;
+ PruneXidHistogram *target = &tabentry->prune_xid_hist;
+
+ /* process main, positive, freqs */
+ for (bin_source = 0; bin_source < PRUNE_XID_HIST_NBINS; bin_source++)
+ {
+ delta = source->freqs[bin_source];
+ xid = source->bounds[bin_source];
+
+ if (xid == InvalidTransactionId || delta == 0)
+ /* skip bins in partially filled histograms */
+ continue;
+
+ bin_target = find_last_containing_bin(target->bounds, xid);
+ target->freqs[bin_target] += delta;
+
+ if (bin_target == PRUNE_XID_HIST_NBINS - 1)
+ target->bounds[bin_target] = Max(target->bounds[bin_target], xid);
+ }
+
+ /* process negative freq */
+ xid = source->bounds[PRUNE_XID_HIST_NBINS - 2] + 1;
+ bin_target = find_first_containing_bin(target->bounds, xid);
+ target->freqs[bin_target] += source->neg_freq;
+
+ /* finally, balance histogram */
+ hist_prune_xid_balance(target);
+ hist_prune_xid_rectify(target);
+}
+
+static void
+pgstat_init_local_prune_xid_histogram(PgStat_TableStatus *lstats, PgStat_EntryRef *entry_ref)
+{
+ int bin;
+ PgStatShared_Relation *shtabstats;
+
+ shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
+
+ for (bin = 0; bin < PRUNE_XID_HIST_NBINS; bin++)
+ {
+ lstats->counts.prune_xid_hist.bounds[bin] = shtabstats->stats.prune_xid_hist.bounds[bin];
+ lstats->counts.prune_xid_hist.freqs[bin] = 0;
+ }
+ lstats->counts.prune_xid_hist.neg_freq = 0;
+}
+
+const PgStat_Counter *
+pgstat_get_prune_xid_histogram_freqs(PgStat_StatTabEntry *tabentry)
+{
+ return tabentry->prune_xid_hist.freqs;
+}
+
+const TransactionId *
+pgstat_get_prune_xid_histogram_bounds(PgStat_StatTabEntry *tabentry)
+{
+ return tabentry->prune_xid_hist.bounds;
+}
+
+
/*
* Support function for the SQL-callable pgstat* functions. Returns
* the collected statistics for one table or NULL. NULL doesn't mean
--
2.47.0
--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
filename=v1-0006-prune_xid-hist-initialize-shared-histogram-on-TRU.patch