]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Delay build of Memoize hash table until executor run
authorDavid Rowley <drowley@postgresql.org>
Mon, 29 Jan 2024 23:37:03 +0000 (12:37 +1300)
committerDavid Rowley <drowley@postgresql.org>
Mon, 29 Jan 2024 23:37:03 +0000 (12:37 +1300)
Previously this hash table was built during executor startup.  This
could cause long delays in EXPLAIN (without ANALYZE) when the planner
opts to use a large Memoize hash table.

No backpatch for now due to lack of complaints.

Author: David Rowley
Discussion: https://postgr.es/m/CAApHDvoJktJ5XL=Kjh2a2TFr64R-7eQZV-+jcJrUwoES2GLiWg@mail.gmail.com

src/backend/executor/nodeMemoize.c

index 0722e47777b6235b7af41445d8cbf76e07e673e1..18870f10e1134e256cba2d2c68ac55752a017c58 100644 (file)
@@ -278,11 +278,14 @@ MemoizeHash_equal(struct memoize_hash *tb, const MemoizeKey *key1,
 }
 
 /*
- * Initialize the hash table to empty.
+ * Initialize the hash table to empty.  The MemoizeState's hashtable field
+ * must point to NULL.
  */
 static void
 build_hash_table(MemoizeState *mstate, uint32 size)
 {
+       Assert(mstate->hashtable == NULL);
+
        /* Make a guess at a good size when we're not given a valid size. */
        if (size == 0)
                size = 1024;
@@ -400,8 +403,10 @@ remove_cache_entry(MemoizeState *mstate, MemoizeEntry *entry)
 static void
 cache_purge_all(MemoizeState *mstate)
 {
-       uint64          evictions = mstate->hashtable->members;
-       PlanState  *pstate = (PlanState *) mstate;
+       uint64          evictions = 0;
+
+       if (mstate->hashtable != NULL)
+               evictions = mstate->hashtable->members;
 
        /*
         * Likely the most efficient way to remove all items is to just reset the
@@ -410,8 +415,8 @@ cache_purge_all(MemoizeState *mstate)
         */
        MemoryContextReset(mstate->tableContext);
 
-       /* Make the hash table the same size as the original size */
-       build_hash_table(mstate, ((Memoize *) pstate->plan)->est_entries);
+       /* NULLify so we recreate the table on the next call */
+       mstate->hashtable = NULL;
 
        /* reset the LRU list */
        dlist_init(&mstate->lru_list);
@@ -707,6 +712,10 @@ ExecMemoize(PlanState *pstate)
 
                                Assert(node->entry == NULL);
 
+                               /* first call? we'll need a hash table. */
+                               if (unlikely(node->hashtable == NULL))
+                                       build_hash_table(node, ((Memoize *) pstate->plan)->est_entries);
+
                                /*
                                 * We're only ever in this state for the first call of the
                                 * scan.  Here we have a look to see if we've already seen the
@@ -1051,8 +1060,11 @@ ExecInitMemoize(Memoize *node, EState *estate, int eflags)
        /* Zero the statistics counters */
        memset(&mstate->stats, 0, sizeof(MemoizeInstrumentation));
 
-       /* Allocate and set up the actual cache */
-       build_hash_table(mstate, node->est_entries);
+       /*
+        * Because it may require a large allocation, we delay building of the
+        * hash table until executor run.
+        */
+       mstate->hashtable = NULL;
 
        return mstate;
 }
@@ -1062,6 +1074,7 @@ ExecEndMemoize(MemoizeState *node)
 {
 #ifdef USE_ASSERT_CHECKING
        /* Validate the memory accounting code is correct in assert builds. */
+       if (node->hashtable != NULL)
        {
                int                     count;
                uint64          mem = 0;