add ability to store in the shared collector
authorRobert Haas <[email protected]>
Thu, 3 Jul 2025 16:05:49 +0000 (12:05 -0400)
committerRobert Haas <[email protected]>
Thu, 3 Jul 2025 16:05:49 +0000 (12:05 -0400)
contrib/pg_plan_advice/pg_plan_advice.c
contrib/pg_plan_advice/pg_plan_advice.h
contrib/pg_plan_advice/pgpa_collector.c

index d002e9d26f7f34f7d2aa55ea86dc1d4290c5cca7..27f81729f1fc240d7664758912190b71f5fe56be 100644 (file)
@@ -85,8 +85,7 @@ pgpa_init_shared_state(void *ptr)
        LWLockInitialize(&state->lock, LWLockNewTrancheId());
        state->dsa_tranche = LWLockNewTrancheId();
        state->area = DSA_HANDLE_INVALID;
-       state->advice_count = 0;
-       state->advice_array = InvalidDsaPointer;
+       state->shared_collector = InvalidDsaPointer;
 }
 
 /*
@@ -131,6 +130,9 @@ pg_plan_advice_attach(void)
        return pgpa_state;
 }
 
+/*
+ * Return a pointer to pg_plan_advice's DSA area, creating it if needed.
+ */
 dsa_area *
 pg_plan_advice_dsa_area(void)
 {
index 796921b067c0fe26a01967384326652dc983585d..cb4592a76a1006538a363b547a267503d96f23f4 100644 (file)
@@ -20,8 +20,7 @@ typedef struct pgpa_shared_state
        LWLock          lock;
        int                     dsa_tranche;
        dsa_handle      area;
-       uint64          advice_count;
-       dsa_pointer     advice_array;
+       dsa_pointer     shared_collector;
 } pgpa_shared_state;
 
 /* GUC variables */
index 9cad190337403969b5b18a8957f793e4410c0dfc..d4b6e00a2f16984f46a4723f9b03b3aad699964d 100644 (file)
@@ -29,6 +29,10 @@ PG_FUNCTION_INFO_V1(pg_get_collected_local_advice);
 
 #define        PG_GET_ADVICE_COLUMNS   7
 
+/*
+ * Advice extracted from one query plan, together with the query string
+ * and various other identifying details.
+ */
 typedef struct pgpa_collected_advice
 {
        Oid                     userid;                 /* user OID */
@@ -39,11 +43,23 @@ typedef struct pgpa_collected_advice
        char            textual_data[FLEXIBLE_ARRAY_MEMBER];
 } pgpa_collected_advice;
 
+/*
+ * A bunch of pointers to pgpa_collected_advice objects, stored in
+ * backend-local memory.
+ */
 typedef struct pgpa_local_advice_chunk
 {
        pgpa_collected_advice *entries[ADVICE_CHUNK_SIZE];
 } pgpa_local_advice_chunk;
 
+/*
+ * Information about all of the pgpa_collected_advice objects that we're
+ * storing in local memory.
+ *
+ * We assign consecutive IDs, starting from 0, to each pgpa_collected_advice
+ * object that we store. The actual storage is an array of chunks, which
+ * helps keep memcpy() overhead low when we start discarding older data.
+ */
 typedef struct pgpa_local_advice
 {
        uint64          next_id;
@@ -53,8 +69,33 @@ typedef struct pgpa_local_advice
        pgpa_local_advice_chunk **chunks;
 } pgpa_local_advice;
 
-static pgpa_local_advice *local_advice;
+/*
+ * Just like pgpa_local_advice_chunk, but stored in a dynamic shared area,
+ * so we must use dsa_pointer instead of native pointers.
+ */
+typedef struct pgpa_shared_advice_chunk
+{
+       dsa_pointer     entries[ADVICE_CHUNK_SIZE];
+} pgpa_shared_advice_chunk;
+
+/*
+ * Just like pgpa_local_advice, but stored in a dynamic shared area, so
+ * we must use dsa_pointer instead of native pointers.
+ */
+typedef struct pgpa_shared_advice
+{
+       uint64          next_id;
+       uint64          oldest_id;
+       uint64          base_id;
+       int                     chunk_array_allocated_size;
+       dsa_pointer     chunks;
+} pgpa_shared_advice;
+
+/* Pointers to local and shared collectors */
+static pgpa_local_advice *local_collector = NULL;
+static pgpa_shared_advice *shared_collector = NULL;
 
+/* Static functions */
 static pgpa_collected_advice *pgpa_make_collected_advice(Oid userid,
                                                                                                                 Oid dbid,
                                                                                                                 uint64 queryId,
@@ -65,6 +106,7 @@ static pgpa_collected_advice *pgpa_make_collected_advice(Oid userid,
                                                                                                                 dsa_pointer *result);
 static void pgpa_store_local_advice(pgpa_collected_advice *ca);
 static void pgpa_trim_local_advice(void);
+static void pgpa_store_shared_advice(dsa_pointer ca_pointer);
 
 /* Helper function to extract the query string from pgpa_collected_advice */
 static inline const char *
@@ -114,7 +156,7 @@ pgpa_collect_advice(uint64 queryId, const char *query_string,
                pgpa_make_collected_advice(userid, dbid, queryId, now,
                                                                   query_string, advice_string, area,
                                                                   &ca_pointer);
-               /* XXX do something */
+               pgpa_store_shared_advice(ca_pointer);
                /* XXX over limit? */
        }
 }
@@ -175,7 +217,7 @@ pgpa_store_local_advice(pgpa_collected_advice *ca)
 {
        uint64          chunk_number;
        uint64          chunk_offset;
-       pgpa_local_advice *la = local_advice;
+       pgpa_local_advice *la = local_collector;
 
        /* If the local advice collector isn't initialized yet, do that now. */
        if (la == NULL)
@@ -184,7 +226,7 @@ pgpa_store_local_advice(pgpa_collected_advice *ca)
                la->chunk_array_allocated_size = ADVICE_CHUNK_ARRAY_SIZE;
                la->chunks = palloc0_array(pgpa_local_advice_chunk *,
                                                                   la->chunk_array_allocated_size);
-               local_advice = la;
+               local_collector = la;
        }
 
        /* Compute chunk and offset at which to store this advice. */
@@ -220,7 +262,7 @@ pgpa_store_local_advice(pgpa_collected_advice *ca)
 static void
 pgpa_trim_local_advice(void)
 {
-       pgpa_local_advice *la = local_advice;
+       pgpa_local_advice *la = local_collector;
        uint64          current_count;
        uint64          trim_count;
        uint64          total_chunk_count;
@@ -266,6 +308,94 @@ pgpa_trim_local_advice(void)
        la->base_id += trim_chunk_count * ADVICE_CHUNK_SIZE;
 }
 
+/*
+ * Add a pg_collected_advice object to the shared advice collection.
+ *
+ * 'ca_pointer' should have been allocated from the pg_plan_advice DSA area
+ * and should point to an object of type pgpa_collected_advice.
+ */
+static void
+pgpa_store_shared_advice(dsa_pointer ca_pointer)
+{
+       uint64          chunk_number;
+       uint64          chunk_offset;
+       pgpa_shared_state *state = pg_plan_advice_attach();
+       dsa_area *area = pg_plan_advice_dsa_area();
+       pgpa_shared_advice *sa = shared_collector;
+       dsa_pointer *chunk_array;
+       pgpa_shared_advice_chunk *chunk;
+
+       /* Lock the shared state. */
+       LWLockAcquire(&state->lock, LW_EXCLUSIVE);
+
+       /*
+        * If we're not attached to the shared advice collector yet, fix that now.
+        * If we're the first ones to attach, we may need to create the object.
+        */
+       if (sa == NULL)
+       {
+               if (state->shared_collector == InvalidDsaPointer)
+                       state->shared_collector =
+                               dsa_allocate0(area, sizeof(pgpa_shared_advice));
+               shared_collector = sa = dsa_get_address(area, state->shared_collector);
+       }
+
+       /*
+        * It's possible that some other backend may have succeeded in creating
+        * the main collector object but failed to allocate an initial chunk array,
+        * so we must be prepared to allocate the chunk array here whether or not
+        * we created the collector object.
+        */
+       if (shared_collector->chunk_array_allocated_size == 0)
+       {
+               sa->chunks =
+                       dsa_allocate0(area,
+                                                 sizeof(dsa_pointer) * ADVICE_CHUNK_ARRAY_SIZE);
+               sa->chunk_array_allocated_size = ADVICE_CHUNK_ARRAY_SIZE;
+       }
+
+       /* Compute chunk and offset at which to store this advice. */
+       chunk_number = (sa->next_id - sa->base_id) / ADVICE_CHUNK_SIZE;
+       chunk_offset = (sa->next_id - sa->base_id) % ADVICE_CHUNK_SIZE;
+
+       /* Get the address of the chunk array and, if needed, extend it. */
+       if (chunk_number > sa->chunk_array_allocated_size)
+       {
+               int                     new_size;
+               dsa_pointer     new_chunks;
+
+               /*
+                * DSA can't enlarge an existing allocation, so we must make a new
+                * allocation and copy data over.
+                */
+               new_size = sa->chunk_array_allocated_size + ADVICE_CHUNK_ARRAY_SIZE;
+               new_chunks = dsa_allocate0(area, sizeof(dsa_pointer) * new_size);
+               chunk_array = dsa_get_address(area, new_chunks);
+               memcpy(chunk_array, dsa_get_address(area, sa->chunks),
+                          sizeof(dsa_pointer) * sa->chunk_array_allocated_size);
+               dsa_free(area, sa->chunks);
+               sa->chunks = new_chunks;
+               sa->chunk_array_allocated_size = new_size;
+       }
+       else
+               chunk_array = dsa_get_address(area, sa->chunks);
+
+       /* Get the address of the desired chunk, allocating it if needed. */
+       if (chunk_array[chunk_number] == InvalidDsaPointer)
+               chunk_array[chunk_number] =
+                       dsa_allocate0(area, sizeof(pgpa_shared_advice_chunk));
+       chunk = dsa_get_address(area, chunk_array[chunk_number]);
+
+       /* Save pointer and bump next-id counter. */
+       Assert(chunk->entries[chunk_offset] == InvalidDsaPointer);
+       chunk->entries[chunk_offset] = ca_pointer;
+       ++sa->next_id;
+
+       /* Release lock on shared state. */
+       LWLockRelease(&state->lock);
+
+}
+
 /*
  * SQL-callable SRF to return locally collected advice
  */
@@ -273,7 +403,7 @@ Datum
 pg_get_collected_local_advice(PG_FUNCTION_ARGS)
 {
        ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-       pgpa_local_advice *la = local_advice;
+       pgpa_local_advice *la = local_collector;
 
        /*
         * XXX. Is this the correct thing from a security point of view?