Skip to content

Commit 50a38f6

Browse files
committed
Create memory context for HashAgg with a reasonable maxBlockSize.
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/[email protected]
1 parent f0705bb commit 50a38f6

File tree

3 files changed

+58
-19
lines changed

3 files changed

+58
-19
lines changed

Diff for: src/backend/executor/execUtils.c

+56-15
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#include "executor/executor.h"
5454
#include "jit/jit.h"
5555
#include "mb/pg_wchar.h"
56+
#include "miscadmin.h"
5657
#include "nodes/nodeFuncs.h"
5758
#include "parser/parsetree.h"
5859
#include "partitioning/partdesc.h"
@@ -227,21 +228,13 @@ FreeExecutorState(EState *estate)
227228
MemoryContextDelete(estate->es_query_cxt);
228229
}
229230

230-
/* ----------------
231-
* CreateExprContext
232-
*
233-
* Create a context for expression evaluation within an EState.
234-
*
235-
* An executor run may require multiple ExprContexts (we usually make one
236-
* for each Plan node, and a separate one for per-output-tuple processing
237-
* such as constraint checking). Each ExprContext has its own "per-tuple"
238-
* memory context.
239-
*
240-
* Note we make no assumption about the caller's memory context.
241-
* ----------------
231+
/*
232+
* Internal implementation for CreateExprContext() and CreateWorkExprContext()
233+
* that allows control over the AllocSet parameters.
242234
*/
243-
ExprContext *
244-
CreateExprContext(EState *estate)
235+
static ExprContext *
236+
CreateExprContextInternal(EState *estate, Size minContextSize,
237+
Size initBlockSize, Size maxBlockSize)
245238
{
246239
ExprContext *econtext;
247240
MemoryContext oldcontext;
@@ -264,7 +257,9 @@ CreateExprContext(EState *estate)
264257
econtext->ecxt_per_tuple_memory =
265258
AllocSetContextCreate(estate->es_query_cxt,
266259
"ExprContext",
267-
ALLOCSET_DEFAULT_SIZES);
260+
minContextSize,
261+
initBlockSize,
262+
maxBlockSize);
268263

269264
econtext->ecxt_param_exec_vals = estate->es_param_exec_vals;
270265
econtext->ecxt_param_list_info = estate->es_param_list_info;
@@ -294,6 +289,52 @@ CreateExprContext(EState *estate)
294289
return econtext;
295290
}
296291

292+
/* ----------------
293+
* CreateExprContext
294+
*
295+
* Create a context for expression evaluation within an EState.
296+
*
297+
* An executor run may require multiple ExprContexts (we usually make one
298+
* for each Plan node, and a separate one for per-output-tuple processing
299+
* such as constraint checking). Each ExprContext has its own "per-tuple"
300+
* memory context.
301+
*
302+
* Note we make no assumption about the caller's memory context.
303+
* ----------------
304+
*/
305+
ExprContext *
306+
CreateExprContext(EState *estate)
307+
{
308+
return CreateExprContextInternal(estate, ALLOCSET_DEFAULT_SIZES);
309+
}
310+
311+
312+
/* ----------------
313+
* CreateWorkExprContext
314+
*
315+
* Like CreateExprContext, but specifies the AllocSet sizes to be reasonable
316+
* in proportion to work_mem. If the maximum block allocation size is too
317+
* large, it's easy to skip right past work_mem with a single allocation.
318+
* ----------------
319+
*/
320+
ExprContext *
321+
CreateWorkExprContext(EState *estate)
322+
{
323+
Size minContextSize = ALLOCSET_DEFAULT_MINSIZE;
324+
Size initBlockSize = ALLOCSET_DEFAULT_INITSIZE;
325+
Size maxBlockSize = ALLOCSET_DEFAULT_MAXSIZE;
326+
327+
/* choose the maxBlockSize to be no larger than 1/16 of work_mem */
328+
while (16 * maxBlockSize > work_mem * 1024L)
329+
maxBlockSize >>= 1;
330+
331+
if (maxBlockSize < ALLOCSET_DEFAULT_INITSIZE)
332+
maxBlockSize = ALLOCSET_DEFAULT_INITSIZE;
333+
334+
return CreateExprContextInternal(estate, minContextSize,
335+
initBlockSize, maxBlockSize);
336+
}
337+
297338
/* ----------------
298339
* CreateStandaloneExprContext
299340
*

Diff for: src/backend/executor/nodeAgg.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -3277,10 +3277,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
32773277
}
32783278

32793279
if (use_hashing)
3280-
{
3281-
ExecAssignExprContext(estate, &aggstate->ss.ps);
3282-
aggstate->hashcontext = aggstate->ss.ps.ps_ExprContext;
3283-
}
3280+
aggstate->hashcontext = CreateWorkExprContext(estate);
32843281

32853282
ExecAssignExprContext(estate, &aggstate->ss.ps);
32863283

Diff for: src/include/executor/executor.h

+1
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ extern void end_tup_output(TupOutputState *tstate);
493493
extern EState *CreateExecutorState(void);
494494
extern void FreeExecutorState(EState *estate);
495495
extern ExprContext *CreateExprContext(EState *estate);
496+
extern ExprContext *CreateWorkExprContext(EState *estate);
496497
extern ExprContext *CreateStandaloneExprContext(void);
497498
extern void FreeExprContext(ExprContext *econtext, bool isCommit);
498499
extern void ReScanExprContext(ExprContext *econtext);

0 commit comments

Comments
 (0)