Create memory context for HashAgg with a reasonable maxBlockSize.
authorJeff Davis <jdavis@postgresql.org>
Wed, 8 Apr 2020 03:42:04 +0000 (20:42 -0700)
committerJeff Davis <jdavis@postgresql.org>
Wed, 8 Apr 2020 04:25:28 +0000 (21:25 -0700)
If the memory context's maxBlockSize is too big, a single block
allocation can suddenly exceed work_mem. For Hash Aggregation, this
can mean spilling to disk too early or reporting a confusing memory
usage number for EXPLAN ANALYZE.

Introduce CreateWorkExprContext(), which is like CreateExprContext(),
except that it creates the AllocSet with a maxBlockSize that is
reasonable in proportion to work_mem.

Right now, CreateWorkExprContext() is only used by Hash Aggregation,
but it may be generally useful in the future.

Discussion: https://postgr.es/m/412a3fbf306f84d8d78c4009e11791867e62b87c.camel@j-davis.com

src/backend/executor/execUtils.c
src/backend/executor/nodeAgg.c
src/include/executor/executor.h

index cc5177cc2b9f13b0a65c6b1fbb5649457eb451ca..ca973882d0175f97c5e9fc533f9d747b1f86bb5f 100644 (file)
@@ -53,6 +53,7 @@
 #include "executor/executor.h"
 #include "jit/jit.h"
 #include "mb/pg_wchar.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -227,21 +228,13 @@ FreeExecutorState(EState *estate)
    MemoryContextDelete(estate->es_query_cxt);
 }
 
-/* ----------------
- *     CreateExprContext
- *
- *     Create a context for expression evaluation within an EState.
- *
- * An executor run may require multiple ExprContexts (we usually make one
- * for each Plan node, and a separate one for per-output-tuple processing
- * such as constraint checking).  Each ExprContext has its own "per-tuple"
- * memory context.
- *
- * Note we make no assumption about the caller's memory context.
- * ----------------
+/*
+ * Internal implementation for CreateExprContext() and CreateWorkExprContext()
+ * that allows control over the AllocSet parameters.
  */
-ExprContext *
-CreateExprContext(EState *estate)
+static ExprContext *
+CreateExprContextInternal(EState *estate, Size minContextSize,
+                         Size initBlockSize, Size maxBlockSize)
 {
    ExprContext *econtext;
    MemoryContext oldcontext;
@@ -264,7 +257,9 @@ CreateExprContext(EState *estate)
    econtext->ecxt_per_tuple_memory =
        AllocSetContextCreate(estate->es_query_cxt,
                              "ExprContext",
-                             ALLOCSET_DEFAULT_SIZES);
+                             minContextSize,
+                             initBlockSize,
+                             maxBlockSize);
 
    econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
    econtext->ecxt_param_list_info = estate->es_param_list_info;
@@ -294,6 +289,52 @@ CreateExprContext(EState *estate)
    return econtext;
 }
 
+/* ----------------
+ *     CreateExprContext
+ *
+ *     Create a context for expression evaluation within an EState.
+ *
+ * An executor run may require multiple ExprContexts (we usually make one
+ * for each Plan node, and a separate one for per-output-tuple processing
+ * such as constraint checking).  Each ExprContext has its own "per-tuple"
+ * memory context.
+ *
+ * Note we make no assumption about the caller's memory context.
+ * ----------------
+ */
+ExprContext *
+CreateExprContext(EState *estate)
+{
+   return CreateExprContextInternal(estate, ALLOCSET_DEFAULT_SIZES);
+}
+
+
+/* ----------------
+ *     CreateWorkExprContext
+ *
+ * Like CreateExprContext, but specifies the AllocSet sizes to be reasonable
+ * in proportion to work_mem. If the maximum block allocation size is too
+ * large, it's easy to skip right past work_mem with a single allocation.
+ * ----------------
+ */
+ExprContext *
+CreateWorkExprContext(EState *estate)
+{
+   Size minContextSize = ALLOCSET_DEFAULT_MINSIZE;
+   Size initBlockSize = ALLOCSET_DEFAULT_INITSIZE;
+   Size maxBlockSize = ALLOCSET_DEFAULT_MAXSIZE;
+
+   /* choose the maxBlockSize to be no larger than 1/16 of work_mem */
+   while (16 * maxBlockSize > work_mem * 1024L)
+       maxBlockSize >>= 1;
+
+   if (maxBlockSize < ALLOCSET_DEFAULT_INITSIZE)
+       maxBlockSize = ALLOCSET_DEFAULT_INITSIZE;
+
+   return CreateExprContextInternal(estate, minContextSize,
+                                    initBlockSize, maxBlockSize);
+}
+
 /* ----------------
  *     CreateStandaloneExprContext
  *
index 4e100e5755f1c562b2f90c3aa399794da5a9788e..44587a84bae24c3ac970c3487b72995338e46ef6 100644 (file)
@@ -3277,10 +3277,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
    }
 
    if (use_hashing)
-   {
-       ExecAssignExprContext(estate, &aggstate->ss.ps);
-       aggstate->hashcontext = aggstate->ss.ps.ps_ExprContext;
-   }
+       aggstate->hashcontext = CreateWorkExprContext(estate);
 
    ExecAssignExprContext(estate, &aggstate->ss.ps);
 
index 94890512dc881806aa31c4c2dc9670a3d46314bd..c7deeac662f6aa17c7cf42bccb605e01d11df412 100644 (file)
@@ -493,6 +493,7 @@ extern void end_tup_output(TupOutputState *tstate);
 extern EState *CreateExecutorState(void);
 extern void FreeExecutorState(EState *estate);
 extern ExprContext *CreateExprContext(EState *estate);
+extern ExprContext *CreateWorkExprContext(EState *estate);
 extern ExprContext *CreateStandaloneExprContext(void);
 extern void FreeExprContext(ExprContext *econtext, bool isCommit);
 extern void ReScanExprContext(ExprContext *econtext);