Fix memory leak in _gin_parallel_merge
authorTomas Vondra <tomas.vondra@postgresql.org>
Fri, 2 May 2025 21:04:39 +0000 (23:04 +0200)
committerTomas Vondra <tomas.vondra@postgresql.org>
Fri, 2 May 2025 21:05:18 +0000 (23:05 +0200)
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

index a7b7b5996e3248b564e0e3d9536cd2c707f51fb2..a65acd89104931747391563def39c6b7759ae4f0 100644 (file)
@@ -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);
        }