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);