start some advice storage ideas in motion
authorRobert Haas <rhaas@postgresql.org>
Tue, 1 Jul 2025 20:10:36 +0000 (16:10 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 1 Jul 2025 20:10:36 +0000 (16:10 -0400)
contrib/pg_plan_advice/Makefile
contrib/pg_plan_advice/meson.build
contrib/pg_plan_advice/pg_plan_advice.c
contrib/pg_plan_advice/pg_plan_advice.h
contrib/pg_plan_advice/pgpa_buffer.c [deleted file]
contrib/pg_plan_advice/pgpa_collector.c [new file with mode: 0644]

index e0f19dc0221761291cc2f252b69421c85b9b7258..afeea2e5dd78a2086f396ae1ac759bf7a4c17630 100644 (file)
@@ -4,7 +4,7 @@ MODULE_big = pg_plan_advice
 OBJS = \
        $(WIN32RES) \
        pg_plan_advice.o \
-       pgpa_buffer.o \
+       pgpa_collector.o \
        pgpa_identifier.o \
        pgpa_join.o \
        pgpa_output.o \
index 81d313a044be8fcd34030c259c932538e1e6c1ca..db510082d6513611cc132e5626254d71f3d4a56b 100644 (file)
@@ -2,7 +2,7 @@
 
 pg_plan_advice_sources = files(
   'pg_plan_advice.c',
-  'pgpa_buffer.c',
+  'pgpa_collector.c',
   'pgpa_identifier.c',
   'pgpa_join.c',
   'pgpa_output.c',
index 1344bcc169facccd022e018af420e68799f84208..497b69457e022cf7203ef5f76f9e48c98ccdc570 100644 (file)
  */
 #include "postgres.h"
 
-#include "funcapi.h"
 #include "pgpa_identifier.h"
+#include "pg_plan_advice.h"
 #include "pgpa_output.h"
 #include "pgpa_walker.h"
 
+#include "funcapi.h"
+#include "storage/dsm_registry.h"
+#include "utils/guc.h"
+
 PG_MODULE_MAGIC;
 
+static pgpa_shared_state *pgpa_state = NULL;
+
+/* GUC variables */
+static int     pg_plan_advice_local_collection_limit = 0;
+static int     pg_plan_advice_shared_collection_limit = 0;
+
 /* Saved hook values */
 static ExecutorStart_hook_type prev_ExecutorStart = NULL;
 
 static bool pg_plan_advice_ExecutorStart(QueryDesc *queryDesc, int eflags);
-static void pgpa_check_plan(PlannedStmt *pstmt);
+static void pgpa_collect_advice(PlannedStmt *pstmt);
 
 /*
- * Initialize this module
+ * Initialize this module.
  */
 void
 _PG_init(void)
 {
+       DefineCustomIntVariable("pg_plan_advice.local_collection_limit",
+                                                       "# of advice entries to retain in per-backend memory",
+                                                       NULL,
+                                                       &pg_plan_advice_local_collection_limit,
+                                                       0,
+                                                       0, INT_MAX,
+                                                       PGC_USERSET,
+                                                       0,
+                                                       NULL,
+                                                       NULL,
+                                                       NULL);
+
+       DefineCustomIntVariable("pg_plan_advice.shared_collection_limit",
+                                                       "# of advice entries to retain in shared memory",
+                                                       NULL,
+                                                       &pg_plan_advice_shared_collection_limit,
+                                                       0,
+                                                       0, INT_MAX,
+                                                       PGC_SUSET,
+                                                       0,
+                                                       NULL,
+                                                       NULL,
+                                                       NULL);
+
+       /* Install hooks */
        prev_ExecutorStart = ExecutorStart_hook;
        ExecutorStart_hook = pg_plan_advice_ExecutorStart;
 }
 
+/*
+ * Initialize shared state when first created.
+ */
+static void
+pgpa_init_shared_state(void *ptr)
+{
+       pgpa_shared_state *state = (pgpa_shared_state *) ptr;
+
+       state->area = DSA_HANDLE_INVALID;
+       LWLockInitialize(&state->lock, LWLockNewTrancheId());
+       state->advice_count = 0;
+       state->advice_array = InvalidDsaPointer;
+}
+
+/*
+ * Get a pointer to our shared state.
+ *
+ * If no shared state exists, create and initialize it. If it does exist but
+ * this backend has not yet accessed it, attach to it. Otherwise, just return
+ * our cached pointer.
+ *
+ * Along the way, make sure the relevant LWLock tranche is registered.
+ */
+pgpa_shared_state *
+pg_plan_advice_attach(void)
+{
+       pgpa_shared_state *state;
+       bool            found;
+
+       if (pgpa_state == NULL)
+       {
+               state = GetNamedDSMSegment("pg_plan_advice", sizeof(pgpa_shared_state),
+                                                                  pgpa_init_shared_state, &found);
+               LWLockRegisterTranche(pgpa_state->lock.tranche, "pg_plan_advice");
+               pgpa_state = state;
+       }
+
+       return pgpa_state;
+}
+
 static bool
 pg_plan_advice_ExecutorStart(QueryDesc *queryDesc, int eflags)
 {
        PlannedStmt *pstmt = queryDesc->plannedstmt;
 
-       pgpa_check_plan(pstmt);
+       if (pg_plan_advice_local_collection_limit > 0
+               || pg_plan_advice_shared_collection_limit > 0)
+               pgpa_collect_advice(pstmt);
 
        if (prev_ExecutorStart)
                return prev_ExecutorStart(queryDesc, eflags);
@@ -48,10 +125,10 @@ pg_plan_advice_ExecutorStart(QueryDesc *queryDesc, int eflags)
 }
 
 static void
-pgpa_check_plan(PlannedStmt *pstmt)
+pgpa_collect_advice(PlannedStmt *pstmt)
 {
        pgpa_plan_walker_context context;
-       StringInfoData  buf;
+       StringInfoData buf;
        ListCell   *lc;
        const char **rt_identifiers;
 
@@ -63,7 +140,7 @@ pgpa_check_plan(PlannedStmt *pstmt)
 
        foreach(lc, pstmt->subplans)
        {
-               Plan *plan = lfirst(lc);
+               Plan       *plan = lfirst(lc);
 
                if (plan != NULL)
                        pgpa_plan_walker(&context, plan, 0, NULL, NULL);
index 5fd5493b0c7a83e1879021e5916d0bc616818da3..81ac95e9f9b5ceddb7c9565dea3cb6d1b9bd1956 100644 (file)
 #ifndef PG_PLAN_ADVICE_H
 #define PG_PLAN_ADVICE_H
 
-#include "nodes/plannodes.h"
+#include "storage/lwlock.h"
+#include "utils/dsa.h"
 
-/* create_advice.c */
-extern bool append_advice_from_plan(StringInfo buf, PlannedStmt *stmt);
+typedef struct pgpa_shared_state
+{
+       dsa_handle      area;
+       LWLock          lock;
+       uint64          advice_count;
+       dsa_pointer     advice_array;
+} pgpa_shared_state;
+
+extern pgpa_shared_state *pg_plan_advice_attach(void);
 
 #endif
diff --git a/contrib/pg_plan_advice/pgpa_buffer.c b/contrib/pg_plan_advice/pgpa_buffer.c
deleted file mode 100644 (file)
index d8cbd1a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#include "postgres.h"
-
-typedef struct pgpa_collected_advice
-{
-       Oid                     userid;                 /* user OID */
-       Oid                     dbid;                   /* database OID */
-       uint64          queryid;                /* query identifier */
-       int                     nesting_level;  /* query nesting level */
-       char            advice_offset;  /* start of advice in textual data */
-       char            textual_data[FLEXIBLE_ARRAY_MEMBER];
-} pgpa_collected_advice;
-
-#if 0
-static inline const char *
-query_string(pgpa_collected_advice *ca)
-{
-       return ca->textual_data;
-}
-
-static inline const char *
-advice_string(pgpa_collected_advice *ca)
-{
-       return ca->textual_data + ca->advice_offset;
-}
-#endif
diff --git a/contrib/pg_plan_advice/pgpa_collector.c b/contrib/pg_plan_advice/pgpa_collector.c
new file mode 100644 (file)
index 0000000..927ea28
--- /dev/null
@@ -0,0 +1,63 @@
+#include "postgres.h"
+
+#include "datatype/timestamp.h"
+#include "nodes/pg_list.h"
+
+typedef struct pgpa_collected_advice
+{
+       Oid                     userid;                 /* user OID */
+       Oid                     dbid;                   /* database OID */
+       uint64          queryid;                /* query identifier */
+       int                     nesting_level;  /* query nesting level */
+       TimestampTz     timestamp;              /* query timestamp */
+       char            advice_offset;  /* start of advice in textual data */
+       char            textual_data[FLEXIBLE_ARRAY_MEMBER];
+} pgpa_collected_advice;
+
+static List *locally_collected_advice = NIL;
+
+extern void
+pgpa_save_collected_advice(Oid userid, Oid dbid, uint64 queryId,
+                                                  int nesting_level, TimestampTz timestamp,
+                                                  const char *query_string, const char *advice_string);
+
+#if 0
+static inline const char *
+query_string(pgpa_collected_advice *ca)
+{
+       return ca->textual_data;
+}
+
+static inline const char *
+advice_string(pgpa_collected_advice *ca)
+{
+       return ca->textual_data + ca->advice_offset;
+}
+#endif
+
+void
+pgpa_save_collected_advice(Oid userid, Oid dbid, uint64 queryId,
+                                                  int nesting_level, TimestampTz timestamp,
+                                                  const char *query_string, const char *advice_string)
+{
+       size_t  query_string_length = strlen(query_string) + 1;
+       size_t  advice_string_length = strlen(advice_string) + 1;
+       size_t  total_length;
+       pgpa_collected_advice *ca;
+
+       total_length = offsetof(pgpa_collected_advice, textual_data)
+               + query_string_length + advice_string_length;
+
+       ca = palloc(total_length);
+       ca->userid = userid;
+       ca->dbid = dbid;
+       ca->queryid = queryId;
+       ca->nesting_level = nesting_level;
+       ca->timestamp = timestamp;
+
+       memcpy(ca->textual_data, query_string, query_string_length);
+       memcpy(&ca->textual_data[query_string_length],
+                  advice_string, advice_string_length);
+
+       locally_collected_advice = lappend(locally_collected_advice, ca);
+}