public inbox for [email protected]
help / color / mirror / Atom feedFrom: Sami Imseih <[email protected]>
To: Michael Paquier <[email protected]>
Cc: Lukas Fittl <[email protected]>
Cc: PostgreSQL Hackers <[email protected]>
Cc: Marko M <[email protected]>
Subject: Re: [PATCH] Optionally record Plan IDs to track plan changes for a query
Date: Thu, 23 Jan 2025 19:25:35 -0600
Message-ID: <CAA5RZ0sUPPOpkRZD=Za83op2ngcPC7dp249vcHA-X5YS7p3n8Q@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <CAP53Pkyow59ajFMHGpmb1BK9WHDypaWtUsS_5DoYUEfsa_Hktg@mail.gmail.com>
<[email protected]>
Thanks for starting this thread. This is an important feature.
I am still reviewing, but wanted to share some initial comments.
== pg_stat_plans extension (0004)
1. pg_stat_plans_1_0 should not call pgstat_fetch_entry.l
This is not needed since we already have the entry with a shared lock
and it could lead to an assertion error when pgstat_fetch_entry
may conditionally call dshash_find. dshash_find asserts that the lock
is not already held. Calling pgstat_get_entry_data should be
enough here.
2. a "toplevel" bool field is missing in pg_stat_plans to indicate the
plan is for a nested query.
3. I think we should add cumulative planning_time. This
Probably should be controlled with a separate GUC as well.
4. For deallocation, I wonder if it makes more sense to zap the
plans with the lowest total execution time rather than calls; or make
this configurable. In fact, I think choosing the eviction strategy
should be done in pg_stat_statements as well ( but that belongs
in a separate discussion ). The idea is to give more priority to
plans that have the most overall database time.
5. What are your thoughts about controlling the memory by
size rather than .max and .max_size ? if a typical plan
is 2KB, a user can fit 10k plans with 20MB. A typical
user can probably allocate much more memory for this
purpose.
Also, pgstat_gc_plans is doing a loop over the
hash to get the # of entries. I don't think this
is a good idea for performance and it may not be possible to
actually enforce the .max on a dshash since
the lock is taken on a partition level.
6. I do like the idea of showing an in-flight plan.
This is so useful for a rare plan, especially on the
first capture of the plan ( calls = 0), and the planId
can be joined with pg_stat_activity to get the query
text.
/* Record initial entry now, so plan text is available for currently
running queries */
pgstat_report_plan_stats(queryDesc,
0, /* executions are counted in
pgsp_ExecutorEnd */
0.0);
We will need to be clear in the documentation
that calls being 0 is a valid scenario.
== core plan id computation (0003)
1. compute_plan_id should do exactly what compute_query_id
does. It should have an "auto" as the default which automatically
computes a plan id when pg_stat_plans is enabled.
2.
> Mixed feelings about the choices of JumblePlanNode() in 0003 based on
> its complexity as implemented. When it comes to such things, we
> should keep the custom node functions short, applying node_attr
> instead to the elements of the nodes so as the assumptions behind the
> jumbling are documented within the structure definitions in the
> headers, not the jumbling code itself.
+1
we should be able to control which node is considered for plan_id
computation using a node attribute such as plan_jumble_ignore.
I played around with this idea by building on top of your proposal
and attached my experiment code for this. The tricky part will be finalizing
which nodes and node fields to use for plan computation.
3. We may want to combine all the jumbling code into
a single jumble.c since the query and plan jumble will
share a lot of the same code, i.e. JumbleState.
_JumbleNode, etc.
Regards,
Sami Imseih
Amazon Web Services (AWS)
Attachments:
[application/octet-stream] Experiment-with-plan-identifier-computation.patch (52.8K, 2-Experiment-with-plan-identifier-computation.patch)
download | inline diff:
From be688ab9e3625c1b3ea855d20fbe0b4f68d9b43d Mon Sep 17 00:00:00 2001
From: Sami Imseih <[email protected]>
Date: Thu, 23 Jan 2025 13:06:42 -0600
Subject: [PATCH 1/1] Experiment with plan identifier computation
---
.../pg_stat_statements/pg_stat_statements.c | 2 +-
src/backend/access/brin/brin.c | 5 +
src/backend/access/nbtree/nbtsort.c | 7 +
src/backend/catalog/system_views.sql | 1 +
src/backend/commands/createas.c | 2 +-
src/backend/commands/explain.c | 16 ++
src/backend/commands/extension.c | 2 +-
src/backend/commands/portalcmds.c | 2 +-
src/backend/commands/vacuumparallel.c | 2 +
src/backend/executor/execMain.c | 2 +-
src/backend/executor/execParallel.c | 1 +
src/backend/nodes/Makefile | 4 +-
src/backend/nodes/gen_node_support.pl | 95 ++++++++
src/backend/nodes/planjumblefuncs.c | 220 ++++++++++++++++++
src/backend/nodes/queryjumblefuncs.c | 2 +-
src/backend/optimizer/plan/planner.c | 5 +
src/backend/parser/analyze.c | 2 +-
src/backend/postmaster/launch_backend.c | 5 +-
src/backend/tcop/postgres.c | 2 +
src/backend/utils/activity/backend_status.c | 63 +++++
src/backend/utils/adt/pgstatfuncs.c | 8 +-
src/backend/utils/error/csvlog.c | 3 +
src/backend/utils/error/elog.c | 8 +
src/backend/utils/error/jsonlog.c | 3 +
src/backend/utils/misc/guc_tables.c | 30 ++-
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/catalog/pg_proc.dat | 6 +-
src/include/nodes/bitmapset.h | 2 +-
src/include/nodes/{queryjumble.h => jumble.h} | 23 +-
src/include/nodes/meson.build | 1 +
src/include/nodes/parsenodes.h | 4 +-
src/include/nodes/plannodes.h | 54 ++---
src/include/nodes/primnodes.h | 8 +-
src/include/parser/analyze.h | 2 +-
src/include/utils/backend_status.h | 5 +
src/test/regress/expected/rules.out | 9 +-
36 files changed, 553 insertions(+), 54 deletions(-)
create mode 100644 src/backend/nodes/planjumblefuncs.c
rename src/include/nodes/{queryjumble.h => jumble.h} (80%)
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index bebf8134eb..26ef7f3e03 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -55,7 +55,7 @@
#include "jit/jit.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/scanner.h"
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 4289142e20..33137862a1 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -70,6 +70,9 @@ typedef struct BrinShared
/* Query ID, for report in worker processes */
uint64 queryid;
+ /* Plan ID, for report in worker processes */
+ uint64 planid;
+
/*
* workersdonecv is used to monitor the progress of workers. All parallel
* participants must indicate that they are done before leader can use
@@ -2452,6 +2455,7 @@ _brin_begin_parallel(BrinBuildState *buildstate, Relation heap, Relation index,
brinshared->scantuplesortstates = scantuplesortstates;
brinshared->pagesPerRange = buildstate->bs_pagesPerRange;
brinshared->queryid = pgstat_get_my_query_id();
+ brinshared->planid = pgstat_get_my_plan_id();
ConditionVariableInit(&brinshared->workersdonecv);
SpinLockInit(&brinshared->mutex);
@@ -2897,6 +2901,7 @@ _brin_parallel_build_main(dsm_segment *seg, shm_toc *toc)
/* Track query ID */
pgstat_report_query_id(brinshared->queryid, false);
+ pgstat_report_plan_id(brinshared->planid, false);
/* Open relations within worker */
heapRel = table_open(brinshared->heaprelid, heapLockmode);
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 7aba852db9..7d90b2a94f 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -107,6 +107,9 @@ typedef struct BTShared
/* Query ID, for report in worker processes */
uint64 queryid;
+ /* Plan ID, for report in worker processes */
+ uint64 planid;
+
/*
* workersdonecv is used to monitor the progress of workers. All parallel
* participants must indicate that they are done before leader can use
@@ -1508,6 +1511,7 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request)
btshared->isconcurrent = isconcurrent;
btshared->scantuplesortstates = scantuplesortstates;
btshared->queryid = pgstat_get_my_query_id();
+ btshared->planid = pgstat_get_my_plan_id();
ConditionVariableInit(&btshared->workersdonecv);
SpinLockInit(&btshared->mutex);
/* Initialize mutable state */
@@ -1793,6 +1797,9 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc)
/* Track query ID */
pgstat_report_query_id(btshared->queryid, false);
+ /* Track plan ID */
+ pgstat_report_plan_id(btshared->planid, false);
+
/* Open relations within worker */
heapRel = table_open(btshared->heaprelid, heapLockmode);
indexRel = index_open(btshared->indexrelid, indexLockmode);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 46868bf7e8..a49efc6332 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -885,6 +885,7 @@ CREATE VIEW pg_stat_activity AS
S.backend_xid,
s.backend_xmin,
S.query_id,
+ S.plan_id,
S.query,
S.backend_type
FROM pg_stat_get_activity(NULL) AS S
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 23cecd99c9..a8498e370c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -37,7 +37,7 @@
#include "commands/view.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "parser/analyze.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/tcopprot.h"
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c24e66f82e..d722b7048c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -966,6 +966,22 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
ExplainPropertyInteger("Query Identifier", NULL, (int64)
queryDesc->plannedstmt->queryId, es);
}
+
+ /*
+ * COMPUTE_PLAN_ID_REGRESS means COMPUTE_PLAN_ID_AUTO, but we don't show
+ * the planid in any of the EXPLAIN plans to keep stable the results
+ * generated by regression test suites.
+ */
+ if (es->verbose && queryDesc->plannedstmt->planId != UINT64CONST(0) &&
+ compute_plan_id != COMPUTE_PLAN_ID_REGRESS)
+ {
+ /*
+ * Output the queryid as an int64 rather than a uint64 so we match
+ * what would be seen in the BIGINT pg_stat_plans.planid column.
+ */
+ ExplainPropertyInteger("Plan Identifier", NULL, (int64)
+ queryDesc->plannedstmt->planId, es);
+ }
}
/*
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index ba540e3de5..3a462d708b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -54,7 +54,7 @@
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "storage/fd.h"
#include "tcop/utility.h"
#include "utils/acl.h"
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index e7c8171c10..926ec2af36 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -28,7 +28,7 @@
#include "executor/executor.h"
#include "executor/tstoreReceiver.h"
#include "miscadmin.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "parser/analyze.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 0d92e694d6..0de51d1a4a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -64,6 +64,7 @@ typedef struct PVShared
Oid relid;
int elevel;
uint64 queryid;
+ uint64 planid;
/*
* Fields for both index vacuum and cleanup.
@@ -371,6 +372,7 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
shared->relid = RelationGetRelid(rel);
shared->elevel = elevel;
shared->queryid = pgstat_get_my_query_id();
+ shared->planid = pgstat_get_my_plan_id();
shared->maintenance_work_mem_worker =
(nindexes_mwm > 0) ?
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fb8dba3ab2..9ddfa5d867 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -50,7 +50,7 @@
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "parser/parse_relation.h"
#include "pgstat.h"
#include "rewrite/rewriteHandler.h"
diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c
index ff4d9dd1bb..20ebc23b00 100644
--- a/src/backend/executor/execParallel.c
+++ b/src/backend/executor/execParallel.c
@@ -174,6 +174,7 @@ ExecSerializePlan(Plan *plan, EState *estate)
pstmt = makeNode(PlannedStmt);
pstmt->commandType = CMD_SELECT;
pstmt->queryId = pgstat_get_my_query_id();
+ pstmt->planId = pgstat_get_my_plan_id();
pstmt->hasReturning = false;
pstmt->hasModifyingCTE = false;
pstmt->canSetTag = true;
diff --git a/src/backend/nodes/Makefile b/src/backend/nodes/Makefile
index 66bbad8e6e..55902bee29 100644
--- a/src/backend/nodes/Makefile
+++ b/src/backend/nodes/Makefile
@@ -27,6 +27,7 @@ OBJS = \
params.o \
print.o \
queryjumblefuncs.o \
+ planjumblefuncs.o \
read.o \
readfuncs.o \
tidbitmap.o \
@@ -91,7 +92,8 @@ copyfuncs.o: copyfuncs.c copyfuncs.funcs.c copyfuncs.switch.c | node-support-sta
equalfuncs.o: equalfuncs.c equalfuncs.funcs.c equalfuncs.switch.c | node-support-stamp
outfuncs.o: outfuncs.c outfuncs.funcs.c outfuncs.switch.c | node-support-stamp
queryjumblefuncs.o: queryjumblefuncs.c queryjumblefuncs.funcs.c queryjumblefuncs.switch.c | node-support-stamp
+planjumblefuncs.o: planjumblefuncs.c planjumblefuncs.funcs.c planjumblefuncs.switch.c | node-support-stamp
readfuncs.o: readfuncs.c readfuncs.funcs.c readfuncs.switch.c | node-support-stamp
clean:
- rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out queryjumble read) $(addsuffix funcs.switch.c,copy equal out queryjumble read) nodetags.h
+ rm -f node-support-stamp $(addsuffix funcs.funcs.c,copy equal out queryjumble planjumble read) $(addsuffix funcs.switch.c,copy equal out queryjumble read) nodetags.h
diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index 7c012c27f8..77acb24e8b 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -131,6 +131,8 @@ my @no_read_write;
my @special_read_write;
# node types we don't want any support functions for, just node tags
my @nodetag_only;
+# node types we don't want plan jumble support for
+my @no_plan_jumble;
# types that are copied by straight assignment
my @scalar_types = qw(
@@ -164,6 +166,7 @@ push @node_types, qw(List);
push @no_copy, qw(List);
push @no_equal, qw(List);
push @no_query_jumble, qw(List);
+push @no_plan_jumble, qw(List);
push @special_read_write, qw(List);
# Nodes with custom copy/equal implementations are skipped from
@@ -176,6 +179,9 @@ my @custom_read_write;
# Similarly for custom query jumble implementation.
my @custom_query_jumble;
+# Similarly for custom plan jumble implementation.
+my @custom_plan_jumble;
+
# Track node types with manually assigned NodeTag numbers.
my %manual_nodetag_number;
@@ -346,6 +352,10 @@ foreach my $infile (@ARGV)
{
push @no_query_jumble, $in_struct;
}
+ elsif ($attr eq 'no_plan_jumble')
+ {
+ push @no_plan_jumble, $in_struct;
+ }
elsif ($attr eq 'no_read')
{
push @no_read, $in_struct;
@@ -424,6 +434,8 @@ foreach my $infile (@ARGV)
if elem $supertype, @no_read;
push @no_query_jumble, $in_struct
if elem $supertype, @no_query_jumble;
+ push @no_plan_jumble, $in_struct
+ if elem $supertype, @no_plan_jumble;
}
}
@@ -475,6 +487,7 @@ foreach my $infile (@ARGV)
equal_ignore_if_zero
query_jumble_ignore
query_jumble_location
+ plan_jumble_ignore
read_write_ignore
write_only_relids
write_only_nondefault_pathtarget
@@ -1339,6 +1352,88 @@ _jumble${n}(JumbleState *jstate, Node *node)
close $jff;
close $jfs;
+# planjumblefuncs.c
+
+push @output_files, 'planjumblefuncs.funcs.c';
+open my $pjff, '>', "$output_path/planjumblefuncs.funcs.c$tmpext" or die $!;
+push @output_files, 'planjumblefuncs.switch.c';
+open my $pjfs, '>', "$output_path/planjumblefuncs.switch.c$tmpext" or die $!;
+
+printf $pjff $header_comment, 'planjumblefuncs.funcs.c';
+printf $pjfs $header_comment, 'planjumblefuncs.switch.c';
+
+print $pjff $node_includes;
+
+foreach my $n (@node_types)
+{
+ next if elem $n, @abstract_types;
+ next if elem $n, @nodetag_only;
+ my $struct_no_plan_jumble = (elem $n, @no_plan_jumble);
+
+ print $pjfs "\t\t\tcase T_${n}:\n"
+ . "\t\t\t\t_jumble${n}(jstate, expr);\n"
+ . "\t\t\t\tbreak;\n"
+ unless $struct_no_plan_jumble;
+
+ next if elem $n, @custom_plan_jumble;
+
+ print $pjff "
+static void
+_jumble${n}(JumbleState *jstate, Node *node)
+{
+\t${n} *expr = (${n} *) node;\n
+" unless $struct_no_plan_jumble;
+
+ # print instructions for each field
+ foreach my $f (@{ $node_type_info{$n}->{fields} })
+ {
+ my $t = $node_type_info{$n}->{field_types}{$f};
+ my @a = @{ $node_type_info{$n}->{field_attrs}{$f} };
+ my $plan_jumble_ignore = $struct_no_plan_jumble;
+
+ # extract per-field attributes
+ foreach my $a (@a)
+ {
+ if ($a eq 'plan_jumble_ignore')
+ {
+ $plan_jumble_ignore = 1;
+ }
+ }
+
+ # node type
+ if (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/)
+ and elem $1, @node_types)
+ {
+ print $pjff "\tJUMBLE_NODE($f);\n"
+ unless $plan_jumble_ignore;
+ }
+ elsif ($t eq 'char*')
+ {
+ print $pjff "\tJUMBLE_STRING($f);\n"
+ unless $plan_jumble_ignore;
+ }
+ else
+ {
+ print $pjff "\tJUMBLE_FIELD($f);\n"
+ unless $plan_jumble_ignore;
+ }
+ }
+
+ # Some nodes have no attributes like CheckPointStmt,
+ # so tweak things for empty contents.
+ if (scalar(@{ $node_type_info{$n}->{fields} }) == 0)
+ {
+ print $pjff "\t(void) expr;\n"
+ unless $struct_no_plan_jumble;
+ }
+
+ print $pjff "}
+" unless $struct_no_plan_jumble;
+}
+
+close $pjff;
+close $pjfs;
+
# now rename the temporary files to their final names
foreach my $file (@output_files)
{
diff --git a/src/backend/nodes/planjumblefuncs.c b/src/backend/nodes/planjumblefuncs.c
new file mode 100644
index 0000000000..6080382ce7
--- /dev/null
+++ b/src/backend/nodes/planjumblefuncs.c
@@ -0,0 +1,220 @@
+/*-------------------------------------------------------------------------
+ *
+ * planjumblefuncs.c
+ * Plan fingerprinting.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/nodes/planjumblefuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "common/hashfn.h"
+#include "miscadmin.h"
+#include "nodes/jumble.h"
+#include "parser/scansup.h"
+#include "utils/backend_status.h"
+
+#define JUMBLE_SIZE 1024 /* query serialization buffer size */
+
+/* GUC parameters */
+int compute_plan_id = COMPUTE_PLAN_ID_AUTO;
+
+/*
+ * True when compute_plan_id is ON or AUTO, and a module requests them.
+ *
+ * Note that IsPlanIdEnabled() should be used instead of checking
+ * plan_id_enabled or compute_plan_id directly when we want to know
+ * whether plan identifiers are computed in the core or not.
+ */
+bool plan_id_enabled = false;
+
+static void AppendJumble(JumbleState *jstate,
+ const unsigned char *item, Size size);
+static void _jumbleNode(JumbleState *jstate, Node *node);
+static void _jumbleList(JumbleState *jstate, Node *node);
+
+JumbleState *
+JumblePlan(PlannedStmt *ps)
+{
+ JumbleState *jstate = NULL;
+
+ Assert(IsPlanIdEnabled());
+
+ jstate = (JumbleState *) palloc(sizeof(JumbleState));
+
+ /* Set up workspace for query jumbling */
+ jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+ jstate->jumble_len = 0;
+ jstate->clocations_buf_size = 32;
+ jstate->clocations = (LocationLen *)
+ palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+ jstate->clocations_count = 0;
+ jstate->highest_extern_param_id = 0;
+
+ /* Compute query ID and mark the Query node with it */
+ _jumbleNode(jstate, (Node *) ps->planTree);
+ ps->planId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+ jstate->jumble_len,
+ 0));
+ pgstat_report_plan_id(ps->planId, false);
+
+ return jstate;
+}
+
+/*
+ * Enables plan identifier computation.
+ *
+ * Third-party plugins can use this function to inform core that they require
+ * a plan identifier to be computed.
+ */
+void
+EnablePlanId(void)
+{
+ if (compute_plan_id != COMPUTE_PLAN_ID_OFF)
+ plan_id_enabled = true;
+}
+
+/*
+ * AppendJumble: Append a value that is substantive in a given query to
+ * the current jumble.
+ */
+static void
+AppendJumble(JumbleState *jstate, const unsigned char *item, Size size)
+{
+ unsigned char *jumble = jstate->jumble;
+ Size jumble_len = jstate->jumble_len;
+
+ /*
+ * Whenever the jumble buffer is full, we hash the current contents and
+ * reset the buffer to contain just that hash value, thus relying on the
+ * hash to summarize everything so far.
+ */
+ while (size > 0)
+ {
+ Size part_size;
+
+ if (jumble_len >= JUMBLE_SIZE)
+ {
+ uint64 start_hash;
+
+ start_hash = DatumGetUInt64(hash_any_extended(jumble,
+ JUMBLE_SIZE, 0));
+ memcpy(jumble, &start_hash, sizeof(start_hash));
+ jumble_len = sizeof(start_hash);
+ }
+ part_size = Min(size, JUMBLE_SIZE - jumble_len);
+ memcpy(jumble + jumble_len, item, part_size);
+ jumble_len += part_size;
+ item += part_size;
+ size -= part_size;
+ }
+ jstate->jumble_len = jumble_len;
+}
+
+#define JUMBLE_NODE(item) \
+ _jumbleNode(jstate, (Node *) expr->item)
+#define JUMBLE_LOCATION(location) \
+ RecordConstLocation(jstate, expr->location)
+#define JUMBLE_FIELD(item) \
+ AppendJumble(jstate, (const unsigned char *) &(expr->item), sizeof(expr->item))
+#define JUMBLE_FIELD_SINGLE(item) \
+ AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
+#define JUMBLE_STRING(str) \
+do { \
+ if (expr->str) \
+ AppendJumble(jstate, (const unsigned char *) (expr->str), strlen(expr->str) + 1); \
+} while(0)
+
+#include "planjumblefuncs.funcs.c"
+
+static void
+_jumbleNode(JumbleState *jstate, Node *node)
+{
+ Node *expr = node;
+
+ if (expr == NULL)
+ return;
+
+ /* Guard against stack overflow due to overly complex expressions */
+ check_stack_depth();
+
+ /*
+ * We always emit the node's NodeTag, then any additional fields that are
+ * considered significant, and then we recurse to any child nodes.
+ */
+ JUMBLE_FIELD(type);
+
+ switch (nodeTag(expr))
+ {
+#include "planjumblefuncs.switch.c"
+
+ case T_List:
+ case T_IntList:
+ case T_OidList:
+ case T_XidList:
+ _jumbleList(jstate, expr);
+ break;
+ default:
+ /* Only a warning, since we can stumble along anyway */
+ elog(WARNING, "unrecognized node type: %d",
+ (int) nodeTag(expr));
+ break;
+ }
+
+ /* Special cases to handle outside the automated code */
+ switch (nodeTag(expr))
+ {
+ case T_Param:
+ {
+ Param *p = (Param *) node;
+
+ /*
+ * Update the highest Param id seen, in order to start
+ * normalization correctly.
+ */
+ if (p->paramkind == PARAM_EXTERN &&
+ p->paramid > jstate->highest_extern_param_id)
+ jstate->highest_extern_param_id = p->paramid;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+_jumbleList(JumbleState *jstate, Node *node)
+{
+ List *expr = (List *) node;
+ ListCell *l;
+
+ switch (expr->type)
+ {
+ case T_List:
+ foreach(l, expr)
+ _jumbleNode(jstate, lfirst(l));
+ break;
+ case T_IntList:
+ foreach(l, expr)
+ JUMBLE_FIELD_SINGLE(lfirst_int(l));
+ break;
+ case T_OidList:
+ foreach(l, expr)
+ JUMBLE_FIELD_SINGLE(lfirst_oid(l));
+ break;
+ case T_XidList:
+ foreach(l, expr)
+ JUMBLE_FIELD_SINGLE(lfirst_xid(l));
+ break;
+ default:
+ elog(ERROR, "unrecognized list node type: %d",
+ (int) expr->type);
+ return;
+ }
+}
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index b103a28193..9a3fc0478d 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -34,7 +34,7 @@
#include "common/hashfn.h"
#include "miscadmin.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "parser/scansup.h"
#define JUMBLE_SIZE 1024 /* query serialization buffer size */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 6803edd085..e0df15fe0d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -37,6 +37,7 @@
#ifdef OPTIMIZER_DEBUG
#include "nodes/print.h"
#endif
+#include "nodes/jumble.h"
#include "nodes/supportnodes.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
@@ -569,6 +570,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
result->utilityStmt = parse->utilityStmt;
result->stmt_location = parse->stmt_location;
result->stmt_len = parse->stmt_len;
+ result->planId = UINT64CONST(0);
result->jitFlags = PGJIT_NONE;
if (jit_enabled && jit_above_cost >= 0 &&
@@ -598,6 +600,9 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions,
if (glob->partition_directory != NULL)
DestroyPartitionDirectory(glob->partition_directory);
+ if (IsPlanIdEnabled())
+ JumblePlan(result);
+
return result;
}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 76f58b3aca..cd235921b4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -31,7 +31,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index a97a1eda6d..3d34784c04 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -53,7 +53,7 @@
#include "utils/memutils.h"
#ifdef EXEC_BACKEND
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "storage/pg_shmem.h"
#include "storage/spin.h"
#endif
@@ -115,6 +115,7 @@ typedef struct
bool redirection_done;
bool IsBinaryUpgrade;
bool query_id_enabled;
+ bool plan_id_enabled;
int max_safe_fds;
int MaxBackends;
int num_pmchild_slots;
@@ -744,6 +745,7 @@ save_backend_variables(BackendParameters *param,
param->redirection_done = redirection_done;
param->IsBinaryUpgrade = IsBinaryUpgrade;
param->query_id_enabled = query_id_enabled;
+ param->plan_id_enabled = plan_id_enabled;
param->max_safe_fds = max_safe_fds;
param->MaxBackends = MaxBackends;
@@ -1004,6 +1006,7 @@ restore_backend_variables(BackendParameters *param)
redirection_done = param->redirection_done;
IsBinaryUpgrade = param->IsBinaryUpgrade;
query_id_enabled = param->query_id_enabled;
+ plan_id_enabled = param->plan_id_enabled;
max_safe_fds = param->max_safe_fds;
MaxBackends = param->MaxBackends;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5655348a2e..587f164b55 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1106,6 +1106,7 @@ exec_simple_query(const char *query_string)
size_t cmdtaglen;
pgstat_report_query_id(0, true);
+ pgstat_report_plan_id(0, true);
/*
* Get the command name for use in status display (it also becomes the
@@ -2163,6 +2164,7 @@ exec_execute_message(const char *portal_name, long max_rows)
if (stmt->queryId != UINT64CONST(0))
{
pgstat_report_query_id(stmt->queryId, false);
+ pgstat_report_plan_id(stmt->planId, false);
break;
}
}
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index 731342799a..7ccb2c6c6c 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -379,6 +379,7 @@ pgstat_bestart(void)
lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
lbeentry.st_progress_command_target = InvalidOid;
lbeentry.st_query_id = UINT64CONST(0);
+ lbeentry.st_plan_id = UINT64CONST(0);
/*
* we don't zero st_progress_param here to save cycles; nobody should
@@ -533,6 +534,7 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
/* st_xact_start_timestamp and wait_event_info are also disabled */
beentry->st_xact_start_timestamp = 0;
beentry->st_query_id = UINT64CONST(0);
+ beentry->st_plan_id = UINT64CONST(0);
proc->wait_event_info = 0;
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
@@ -593,7 +595,10 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
* identifier.
*/
if (state == STATE_RUNNING)
+ {
beentry->st_query_id = UINT64CONST(0);
+ beentry->st_plan_id = UINT64CONST(0);
+ }
if (cmd_str != NULL)
{
@@ -644,6 +649,44 @@ pgstat_report_query_id(uint64 query_id, bool force)
PGSTAT_END_WRITE_ACTIVITY(beentry);
}
+/* --------
+ * pgstat_report_plan_id() -
+ *
+ * Called to update top-level plan identifier.
+ * --------
+ */
+void
+pgstat_report_plan_id(uint64 plan_id, bool force)
+{
+ volatile PgBackendStatus *beentry = MyBEEntry;
+
+ /*
+ * if track_activities is disabled, st_plan_id should already have been
+ * reset
+ */
+ if (!beentry || !pgstat_track_activities)
+ return;
+
+ /*
+ * We only report the top-level plan identifiers. The stored plan_id is
+ * reset when a backend calls pgstat_report_activity(STATE_RUNNING), or
+ * with an explicit call to this function using the force flag. If the
+ * saved plan identifier is not zero it means that it's not a top-level
+ * command, so ignore the one provided unless it's an explicit call to
+ * reset the identifier.
+ */
+ if (beentry->st_plan_id != 0 && !force)
+ return;
+
+ /*
+ * Update my status entry, following the protocol of bumping
+ * st_changecount before and after. We use a volatile pointer here to
+ * ensure the compiler doesn't try to get cute.
+ */
+ PGSTAT_BEGIN_WRITE_ACTIVITY(beentry);
+ beentry->st_plan_id = plan_id;
+ PGSTAT_END_WRITE_ACTIVITY(beentry);
+}
/* ----------
* pgstat_report_appname() -
@@ -1040,6 +1083,26 @@ pgstat_get_my_query_id(void)
return MyBEEntry->st_query_id;
}
+/* ----------
+ * pgstat_get_my_plan_id() -
+ *
+ * Return current backend's query identifier.
+ */
+uint64
+pgstat_get_my_plan_id(void)
+{
+ if (!MyBEEntry)
+ return 0;
+
+ /*
+ * There's no need for a lock around pgstat_begin_read_activity /
+ * pgstat_end_read_activity here as it's only called from
+ * pg_stat_get_activity which is already protected, or from the same
+ * backend which means that there won't be concurrent writes.
+ */
+ return MyBEEntry->st_plan_id;
+}
+
/* ----------
* pgstat_get_backend_type_by_proc_number() -
*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 0f5e0a9778..c033dd0778 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -302,7 +302,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
Datum
pg_stat_get_activity(PG_FUNCTION_ARGS)
{
-#define PG_STAT_GET_ACTIVITY_COLS 31
+#define PG_STAT_GET_ACTIVITY_COLS 32
int num_backends = pgstat_fetch_stat_numbackends();
int curr_backend;
int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -613,6 +613,11 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[30] = true;
else
values[30] = UInt64GetDatum(beentry->st_query_id);
+
+ if (beentry->st_plan_id == 0)
+ nulls[31] = true;
+ else
+ values[31] = UInt64GetDatum(beentry->st_plan_id);
}
else
{
@@ -642,6 +647,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[28] = true;
nulls[29] = true;
nulls[30] = true;
+ nulls[31] = true;
}
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
diff --git a/src/backend/utils/error/csvlog.c b/src/backend/utils/error/csvlog.c
index 8e9fbcb999..86650b02ff 100644
--- a/src/backend/utils/error/csvlog.c
+++ b/src/backend/utils/error/csvlog.c
@@ -250,6 +250,9 @@ write_csvlog(ErrorData *edata)
/* query id */
appendStringInfo(&buf, "%lld", (long long) pgstat_get_my_query_id());
+ /* plan id */
+ appendStringInfo(&buf, "%lld", (long long) pgstat_get_my_plan_id());
+
appendStringInfoChar(&buf, '\n');
/* If in the syslogger process, try to write messages direct to file */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 860bbd40d4..c8861b5090 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -3154,6 +3154,14 @@ log_status_format(StringInfo buf, const char *format, ErrorData *edata)
appendStringInfo(buf, "%lld",
(long long) pgstat_get_my_query_id());
break;
+ case 'X':
+ if (padding != 0)
+ appendStringInfo(buf, "%*lld", padding,
+ (long long) pgstat_get_my_plan_id());
+ else
+ appendStringInfo(buf, "%lld",
+ (long long) pgstat_get_my_plan_id());
+ break;
default:
/* format error - ignore it */
break;
diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c
index 6533f1d688..a88bd4339e 100644
--- a/src/backend/utils/error/jsonlog.c
+++ b/src/backend/utils/error/jsonlog.c
@@ -287,6 +287,9 @@ write_jsonlog(ErrorData *edata)
appendJSONKeyValueFmt(&buf, "query_id", false, "%lld",
(long long) pgstat_get_my_query_id());
+ appendJSONKeyValueFmt(&buf, "plan_id", false, "%lld",
+ (long long) pgstat_get_my_plan_id());
+
/* Finish string */
appendStringInfoChar(&buf, '}');
appendStringInfoChar(&buf, '\n');
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 38cb9e970d..bc02e7c103 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -50,7 +50,7 @@
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/scram.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "optimizer/cost.h"
#include "optimizer/geqo.h"
#include "optimizer/optimizer.h"
@@ -309,6 +309,24 @@ static const struct config_enum_entry compute_query_id_options[] = {
{NULL, 0, false}
};
+/*
+ * Although only "on", "off", and "auto" are documented, we accept
+ * all the likely variants of "on" and "off".
+ */
+static const struct config_enum_entry compute_plan_id_options[] = {
+ {"auto", COMPUTE_PLAN_ID_AUTO, false},
+ {"regress", COMPUTE_PLAN_ID_REGRESS, false},
+ {"on", COMPUTE_PLAN_ID_ON, false},
+ {"off", COMPUTE_PLAN_ID_OFF, false},
+ {"true", COMPUTE_PLAN_ID_ON, true},
+ {"false", COMPUTE_PLAN_ID_OFF, true},
+ {"yes", COMPUTE_PLAN_ID_ON, true},
+ {"no", COMPUTE_PLAN_ID_OFF, true},
+ {"1", COMPUTE_PLAN_ID_ON, true},
+ {"0", COMPUTE_PLAN_ID_OFF, true},
+ {NULL, 0, false}
+};
+
/*
* Although only "on", "off", and "partition" are documented, we
* accept all the likely variants of "on" and "off".
@@ -4873,6 +4891,16 @@ struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"compute_plan_id", PGC_SUSET, STATS_MONITORING,
+ gettext_noop("Enables in-core computation of the plan tree."),
+ NULL
+ },
+ &compute_plan_id,
+ COMPUTE_PLAN_ID_AUTO, compute_plan_id_options,
+ NULL, NULL, NULL
+ },
+
{
{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
gettext_noop("Enables the planner to use constraints to optimize queries."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 079efa1baa..0634ae90dd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -641,6 +641,7 @@
# - Monitoring -
#compute_query_id = auto
+#compute_plan_id = auto
#log_statement_stats = off
#log_parser_stats = off
#log_planner_stats = off
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 18560755d2..de341e2d02 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5549,9 +5549,9 @@
proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
proretset => 't', provolatile => 's', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
- proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,bool,text,bool,bool,int4,int8}',
- proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
- proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id}',
+ proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,text,numeric,text,bool,text,bool,bool,int4,int8,int8}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc,gss_delegation,leader_pid,query_id,plan_id}',
prosrc => 'pg_stat_get_activity' },
{ oid => '6318', descr => 'describe wait events',
proname => 'pg_get_wait_events', procost => '10', prorows => '250',
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 03faca9308..a8694d646a 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -48,7 +48,7 @@ typedef int32 signedbitmapword; /* must be the matching signed type */
typedef struct Bitmapset
{
- pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble)
+ pg_node_attr(custom_copy_equal, special_read_write, no_query_jumble, no_plan_jumble)
NodeTag type;
int nwords; /* number of words in array */
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/jumble.h
similarity index 80%
rename from src/include/nodes/queryjumble.h
rename to src/include/nodes/jumble.h
index 50eb956658..3cd35f145f 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/jumble.h
@@ -1,13 +1,13 @@
/*-------------------------------------------------------------------------
*
- * queryjumble.h
+ * jumble.h
* Query normalization and fingerprinting.
*
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * src/include/nodes/queryjumble.h
+ * src/include/nodes/jumble.h
*
*-------------------------------------------------------------------------
*/
@@ -15,6 +15,7 @@
#define QUERYJUMBLE_H
#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
/*
* Struct for tracking locations/lengths of constants during normalization
@@ -57,17 +58,25 @@ enum ComputeQueryIdType
COMPUTE_QUERY_ID_ON,
COMPUTE_QUERY_ID_AUTO,
COMPUTE_QUERY_ID_REGRESS,
+ COMPUTE_PLAN_ID_OFF,
+ COMPUTE_PLAN_ID_ON,
+ COMPUTE_PLAN_ID_AUTO,
+ COMPUTE_PLAN_ID_REGRESS,
};
/* GUC parameters */
extern PGDLLIMPORT int compute_query_id;
+extern PGDLLIMPORT int compute_plan_id;
extern const char *CleanQuerytext(const char *query, int *location, int *len);
extern JumbleState *JumbleQuery(Query *query);
+extern JumbleState *JumblePlan(PlannedStmt *ps);
extern void EnableQueryId(void);
+extern void EnablePlanId(void);
extern PGDLLIMPORT bool query_id_enabled;
+extern PGDLLIMPORT bool plan_id_enabled;
/*
* Returns whether query identifier computation has been enabled, either
@@ -83,4 +92,14 @@ IsQueryIdEnabled(void)
return query_id_enabled;
}
+static inline bool
+IsPlanIdEnabled(void)
+{
+ if (compute_plan_id == COMPUTE_PLAN_ID_OFF)
+ return false;
+ if (compute_plan_id == COMPUTE_PLAN_ID_ON)
+ return true;
+ return plan_id_enabled;
+}
+
#endif /* QUERYJUMBLE_H */
diff --git a/src/include/nodes/meson.build b/src/include/nodes/meson.build
index f3dd5461fe..2e73b39640 100644
--- a/src/include/nodes/meson.build
+++ b/src/include/nodes/meson.build
@@ -37,6 +37,7 @@ node_support_output = [
'copyfuncs.funcs.c', 'copyfuncs.switch.c',
'equalfuncs.funcs.c', 'equalfuncs.switch.c',
'queryjumblefuncs.funcs.c', 'queryjumblefuncs.switch.c',
+ 'planjumblefuncs.funcs.c', 'planjumblefuncs.switch.c',
]
node_support_install = [
dir_include_server / 'nodes',
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ffe155ee20..8bf2b11f8b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -367,7 +367,7 @@ union ValUnion
typedef struct A_Const
{
- pg_node_attr(custom_copy_equal, custom_read_write, custom_query_jumble)
+ pg_node_attr(custom_copy_equal, custom_read_write, custom_query_jumble, no_plan_jumble)
NodeTag type;
union ValUnion val;
@@ -2670,7 +2670,7 @@ typedef enum VariableSetKind
typedef struct VariableSetStmt
{
- pg_node_attr(custom_query_jumble)
+ pg_node_attr(custom_query_jumble, no_plan_jumble)
NodeTag type;
VariableSetKind kind;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 9e19cdd284..d3eb40e5ea 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -53,6 +53,8 @@ typedef struct PlannedStmt
uint64 queryId; /* query identifier (copied from Query) */
+ uint64 planId; /* plan identifier (copied from PlannedStmt) */
+
bool hasReturning; /* is it insert|update|delete|merge RETURNING? */
bool hasModifyingCTE; /* has insert|update|delete|merge in WITH? */
@@ -125,33 +127,33 @@ typedef struct Plan
/*
* estimated execution costs for plan (see costsize.c for more info)
*/
- int disabled_nodes; /* count of disabled nodes */
- Cost startup_cost; /* cost expended before fetching any tuples */
- Cost total_cost; /* total cost (assuming all tuples fetched) */
+ int disabled_nodes pg_node_attr(plan_jumble_ignore); /* count of disabled nodes */
+ Cost startup_cost pg_node_attr(plan_jumble_ignore); /* cost expended before fetching any tuples */
+ Cost total_cost pg_node_attr(plan_jumble_ignore); /* total cost (assuming all tuples fetched) */
/*
* planner's estimate of result size of this plan step
*/
- Cardinality plan_rows; /* number of rows plan is expected to emit */
- int plan_width; /* average row width in bytes */
+ Cardinality plan_rows pg_node_attr(plan_jumble_ignore); /* number of rows plan is expected to emit */
+ int plan_width pg_node_attr(plan_jumble_ignore); /* average row width in bytes */
/*
* information needed for parallel query
*/
- bool parallel_aware; /* engage parallel-aware logic? */
- bool parallel_safe; /* OK to use as part of parallel plan? */
+ bool parallel_aware pg_node_attr(plan_jumble_ignore); /* engage parallel-aware logic? */
+ bool parallel_safe pg_node_attr(plan_jumble_ignore); /* OK to use as part of parallel plan? */
/*
* information needed for asynchronous execution
*/
- bool async_capable; /* engage asynchronous-capable logic? */
+ bool async_capable pg_node_attr(plan_jumble_ignore); /* engage asynchronous-capable logic? */
/*
* Common structural data for all Plan types.
*/
- int plan_node_id; /* unique across entire final plan tree */
+ int plan_node_id pg_node_attr(plan_jumble_ignore); /* unique across entire final plan tree */
List *targetlist; /* target list to be computed at this node */
- List *qual; /* implicitly-ANDed qual conditions */
+ List *qual pg_node_attr(plan_jumble_ignore); /* implicitly-ANDed qual conditions */
struct Plan *lefttree; /* input plan tree(s) */
struct Plan *righttree;
List *initPlan; /* Init Plan nodes (un-correlated expr
@@ -168,8 +170,8 @@ typedef struct Plan
* params that affect the node (i.e., the setParams of its initplans).
* These are _all_ the PARAM_EXEC params that affect this node.
*/
- Bitmapset *extParam;
- Bitmapset *allParam;
+ Bitmapset *extParam pg_node_attr(plan_jumble_ignore);
+ Bitmapset *allParam pg_node_attr(plan_jumble_ignore);
} Plan;
/* ----------------
@@ -268,7 +270,7 @@ struct PartitionPruneInfo; /* forward reference to struct below */
typedef struct Append
{
Plan plan;
- Bitmapset *apprelids; /* RTIs of appendrel(s) formed by this node */
+ Bitmapset *apprelids pg_node_attr(plan_jumble_ignore); /* RTIs of appendrel(s) formed by this node */
List *appendplans;
int nasyncplans; /* # of asynchronous plans */
@@ -292,7 +294,7 @@ typedef struct MergeAppend
Plan plan;
/* RTIs of appendrel(s) formed by this node */
- Bitmapset *apprelids;
+ Bitmapset *apprelids pg_node_attr(plan_jumble_ignore);
List *mergeplans;
@@ -389,7 +391,7 @@ typedef struct Scan
pg_node_attr(abstract)
Plan plan;
- Index scanrelid; /* relid is index into the range table */
+ Index scanrelid pg_node_attr(plan_jumble_ignore); /* relid is index into the range table */
} Scan;
/* ----------------
@@ -719,8 +721,8 @@ typedef struct ForeignScan
List *fdw_private; /* private data for FDW */
List *fdw_scan_tlist; /* optional tlist describing scan tuple */
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
- Bitmapset *fs_relids; /* base+OJ RTIs generated by this scan */
- Bitmapset *fs_base_relids; /* base RTIs generated by this scan */
+ Bitmapset *fs_relids pg_node_attr(plan_jumble_ignore); /* base+OJ RTIs generated by this scan */
+ Bitmapset *fs_base_relids pg_node_attr(plan_jumble_ignore); /* base RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan;
@@ -748,7 +750,7 @@ typedef struct CustomScan
List *custom_exprs; /* expressions that custom code may evaluate */
List *custom_private; /* private data for custom code */
List *custom_scan_tlist; /* optional tlist describing scan tuple */
- Bitmapset *custom_relids; /* RTIs generated by this scan */
+ Bitmapset *custom_relids pg_node_attr(plan_jumble_ignore); /* RTIs generated by this scan */
/*
* NOTE: The method field of CustomScan is required to be a pointer to a
@@ -924,7 +926,7 @@ typedef struct Memoize
uint32 est_entries;
/* paramids from param_exprs */
- Bitmapset *keyparamids;
+ Bitmapset *keyparamids pg_node_attr(plan_jumble_ignore);
} Memoize;
/* ----------------
@@ -1023,7 +1025,7 @@ typedef struct Agg
uint64 transitionSpace;
/* IDs of Params used in Aggref inputs */
- Bitmapset *aggParams;
+ Bitmapset *aggParams pg_node_attr(plan_jumble_ignore);
/* Note: planner provides numGroups & aggParams only in HASHED/MIXED case */
@@ -1147,7 +1149,7 @@ typedef struct Gather
int rescan_param; /* ID of Param that signals a rescan, or -1 */
bool single_copy; /* don't execute plan more than once */
bool invisible; /* suppress EXPLAIN display (for testing)? */
- Bitmapset *initParam; /* param id's of initplans which are referred
+ Bitmapset *initParam pg_node_attr(plan_jumble_ignore); /* param id's of initplans which are referred
* at gather or one of it's child node */
} Gather;
@@ -1186,7 +1188,7 @@ typedef struct GatherMerge
* param id's of initplans which are referred at gather merge or one of
* it's child node
*/
- Bitmapset *initParam;
+ Bitmapset *initParam pg_node_attr(plan_jumble_ignore);
} GatherMerge;
/* ----------------
@@ -1426,7 +1428,7 @@ typedef struct PartitionPruneInfo
NodeTag type;
List *prune_infos;
- Bitmapset *other_subplans;
+ Bitmapset *other_subplans pg_node_attr(plan_jumble_ignore);
} PartitionPruneInfo;
/*
@@ -1456,7 +1458,7 @@ typedef struct PartitionedRelPruneInfo
Index rtindex;
/* Indexes of all partitions which subplans or subparts are present for */
- Bitmapset *present_parts;
+ Bitmapset *present_parts pg_node_attr(plan_jumble_ignore);
/* Length of the following arrays: */
int nparts;
@@ -1480,7 +1482,7 @@ typedef struct PartitionedRelPruneInfo
List *exec_pruning_steps; /* List of PartitionPruneStep */
/* All PARAM_EXEC Param IDs in exec_pruning_steps */
- Bitmapset *execparamids;
+ Bitmapset *execparamids pg_node_attr(plan_jumble_ignore);
} PartitionedRelPruneInfo;
/*
@@ -1531,7 +1533,7 @@ typedef struct PartitionPruneStepOp
StrategyNumber opstrategy;
List *exprs;
List *cmpfns;
- Bitmapset *nullkeys;
+ Bitmapset *nullkeys pg_node_attr(plan_jumble_ignore);
} PartitionPruneStepOp;
/*
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 59e7bb26bb..8eb9959496 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -2246,15 +2246,15 @@ typedef struct TargetEntry
/* attribute number (see notes above) */
AttrNumber resno;
/* name of the column (could be NULL) */
- char *resname pg_node_attr(query_jumble_ignore);
+ char *resname pg_node_attr(query_jumble_ignore, plan_jumble_ignore);
/* nonzero if referenced by a sort/group clause */
Index ressortgroupref;
/* OID of column's source table */
- Oid resorigtbl pg_node_attr(query_jumble_ignore);
+ Oid resorigtbl pg_node_attr(query_jumble_ignore, plan_jumble_ignore);
/* column's number in source table */
- AttrNumber resorigcol pg_node_attr(query_jumble_ignore);
+ AttrNumber resorigcol pg_node_attr(query_jumble_ignore, plan_jumble_ignore);
/* set to true to eliminate the attribute from final target list */
- bool resjunk pg_node_attr(query_jumble_ignore);
+ bool resjunk pg_node_attr(query_jumble_ignore, plan_jumble_ignore);
} TargetEntry;
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index f1bd18c49f..e414b630bc 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -15,7 +15,7 @@
#define ANALYZE_H
#include "nodes/params.h"
-#include "nodes/queryjumble.h"
+#include "nodes/jumble.h"
#include "parser/parse_node.h"
/* Hook for plugins to get control at end of parse analysis */
diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h
index d3d4ff6c5c..e074bc5b45 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -170,6 +170,9 @@ typedef struct PgBackendStatus
/* query identifier, optionally computed using post_parse_analyze_hook */
uint64 st_query_id;
+
+ /* plan identifier, optionally computed using planner_hook */
+ uint64 st_plan_id;
} PgBackendStatus;
@@ -316,6 +319,7 @@ extern void pgstat_clear_backend_activity_snapshot(void);
/* Activity reporting functions */
extern void pgstat_report_activity(BackendState state, const char *cmd_str);
extern void pgstat_report_query_id(uint64 query_id, bool force);
+extern void pgstat_report_plan_id(uint64 query_id, bool force);
extern void pgstat_report_tempfile(size_t filesize);
extern void pgstat_report_appname(const char *appname);
extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
@@ -323,6 +327,7 @@ extern const char *pgstat_get_backend_current_activity(int pid, bool checkUser);
extern const char *pgstat_get_crashed_backend_activity(int pid, char *buffer,
int buflen);
extern uint64 pgstat_get_my_query_id(void);
+extern uint64 pgstat_get_my_plan_id(void);
extern BackendType pgstat_get_backend_type_by_proc_number(ProcNumber procNumber);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 856a8349c5..e20cc7d28c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1760,9 +1760,10 @@ pg_stat_activity| SELECT s.datid,
s.backend_xid,
s.backend_xmin,
s.query_id,
+ s.plan_id,
s.query,
s.backend_type
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
LEFT JOIN pg_database d ON ((s.datid = d.oid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_all_indexes| SELECT c.oid AS relid,
@@ -1886,7 +1887,7 @@ pg_stat_gssapi| SELECT pid,
gss_princ AS principal,
gss_enc AS encrypted,
gss_delegation AS credentials_delegated
- FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
WHERE (client_port IS NOT NULL);
pg_stat_io| SELECT backend_type,
object,
@@ -2092,7 +2093,7 @@ pg_stat_replication| SELECT s.pid,
w.sync_priority,
w.sync_state,
w.reply_time
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_replication_slots| SELECT s.slot_name,
@@ -2126,7 +2127,7 @@ pg_stat_ssl| SELECT pid,
ssl_client_dn AS client_dn,
ssl_client_serial AS client_serial,
ssl_issuer_dn AS issuer_dn
- FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id)
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc, gss_delegation, leader_pid, query_id, plan_id)
WHERE (client_port IS NOT NULL);
pg_stat_subscription| SELECT su.oid AS subid,
su.subname,
--
2.39.5 (Apple Git-154)
view thread (34+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: [PATCH] Optionally record Plan IDs to track plan changes for a query
In-Reply-To: <CAA5RZ0sUPPOpkRZD=Za83op2ngcPC7dp249vcHA-X5YS7p3n8Q@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox