postgres.git / summary / log / commit / refs

commit    1681a70df3d68b6f9dc82645f97f8d4668edc42f
Author:   Tomas Vondra <tomas.vondra@postgresql.org>
Date:     Fri May 02 21:04:39 2025 +0000
First released in: 18.0

    Fix memory leak in _gin_parallel_merge
    
    To insert the merged GIN entries in _gin_parallel_merge, the leader
    calls ginEntryInsert(). This may allocate memory, e.g. for a new leaf
    tuple. This was allocated in the PortalContext, and kept until the end
    of the index build. For most GIN indexes the amount of leaked memory is
    negligible, but for custom opclasses with large keys it may cause OOMs.
    
    Fixed by calling ginEntryInsert() in a temporary memory context, reset
    after each insert. Other ginEntryInsert() callers do this too, except
    that the context is reset after batches of inserts. More frequent resets
    don't seem to hurt performance, it may even help it a bit.
    
    Report and fix by Vinod Sridharan.
    
    Author: Vinod Sridharan <vsridh90@gmail.com>
    Reviewed-by: Tomas Vondra <tomas@vondra.me>
    Discussion: https://postgr.es/m/CAFMdLD4p0VBd8JG=Nbi=BKv6rzFAiGJ_sXSFrw-2tNmNZFO5Kg@mail.gmail.com


src/backend/access/gin/gininsert.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index a7b7b5996e3..a65acd89104 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -1669,6 +1669,8 @@ _gin_parallel_merge(GinBuildState *state) */ while ((tup = tuplesort_getgintuple(state->bs_sortstate, &tuplen, true)) != NULL) { + MemoryContext oldCtx; + CHECK_FOR_INTERRUPTS(); /* @@ -1685,10 +1687,15 @@ _gin_parallel_merge(GinBuildState *state) */ AssertCheckItemPointers(buffer); + oldCtx = MemoryContextSwitchTo(state->tmpCtx); + ginEntryInsert(&state->ginstate, buffer->attnum, buffer->key, buffer->category, buffer->items, buffer->nitems, &state->buildStats); + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(state->tmpCtx); + /* discard the existing data */ GinBufferReset(buffer); } @@ -1711,10 +1718,15 @@ _gin_parallel_merge(GinBuildState *state) */ AssertCheckItemPointers(buffer); + oldCtx = MemoryContextSwitchTo(state->tmpCtx); + ginEntryInsert(&state->ginstate, buffer->attnum, buffer->key, buffer->category, buffer->items, buffer->nfrozen, &state->buildStats); + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(state->tmpCtx); + /* truncate the data we've just discarded */ GinBufferTrim(buffer); } [parent: e83a8ae44729]