Skip to content

Commit

Permalink
Reuse BrinDesc and BrinRevmap in brininsert
Browse files Browse the repository at this point in the history
The brininsert code used to initialize (and destroy) BrinDesc and
BrinRevmap for each tuple, which is not free. This patch initializes
these structures only once, and reuses them for all inserts in the same
command. The data is passed through indexInfo->ii_AmCache.

This also introduces an optional AM callback "aminsertcleanup" that
allows performing custom cleanup in case simply pfree-ing ii_AmCache is
not sufficient (which is the case when the cache contains TupleDesc,
Buffers, and so on).

Author: Soumyadeep Chakraborty
Reviewed-by: Alvaro Herrera, Matthias van de Meent, Tomas Vondra
Discussion: https://postgr.es/m/CAE-ML%2B9r2%3DaO1wwji1sBN9gvPz2xRAtFUGfnffpd0ZqyuzjamA%40mail.gmail.com
  • Loading branch information
tvondra committed Nov 25, 2023
1 parent 6ec8262 commit c1ec02b
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 12 deletions.
1 change: 1 addition & 0 deletions contrib/bloom/blutils.c
Expand Up @@ -131,6 +131,7 @@ blhandler(PG_FUNCTION_ARGS)
amroutine->ambuild = blbuild;
amroutine->ambuildempty = blbuildempty;
amroutine->aminsert = blinsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = blbulkdelete;
amroutine->amvacuumcleanup = blvacuumcleanup;
amroutine->amcanreturn = NULL;
Expand Down
17 changes: 16 additions & 1 deletion doc/src/sgml/indexam.sgml
Expand Up @@ -139,6 +139,7 @@ typedef struct IndexAmRoutine
ambuild_function ambuild;
ambuildempty_function ambuildempty;
aminsert_function aminsert;
aminsertcleanup_function aminsertcleanup;
ambulkdelete_function ambulkdelete;
amvacuumcleanup_function amvacuumcleanup;
amcanreturn_function amcanreturn; /* can be NULL */
Expand Down Expand Up @@ -359,7 +360,21 @@ aminsert (Relation indexRelation,
within an SQL statement, it can allocate space
in <literal>indexInfo-&gt;ii_Context</literal> and store a pointer to the
data in <literal>indexInfo-&gt;ii_AmCache</literal> (which will be NULL
initially).
initially). After the index insertions complete, the memory will be freed
automatically. If additional cleanup is required (e.g. if the cache contains
buffers and tuple descriptors), the AM may define an optional function
<literal>indexinsertcleanup</literal>, called before the memory is released.
</para>

<para>
<programlisting>
void
aminsertcleanup (IndexInfo *indexInfo);
</programlisting>
Clean up state that was maintained across successive inserts in
<literal>indexInfo-&gt;ii_AmCache</literal>. This is useful if the data
requires additional cleanup steps, and simply releasing the memory is not
sufficient.
</para>

<para>
Expand Down
75 changes: 64 additions & 11 deletions src/backend/access/brin/brin.c
Expand Up @@ -58,6 +58,17 @@ typedef struct BrinBuildState
BrinMemTuple *bs_dtuple;
} BrinBuildState;

/*
* We use a BrinInsertState to capture running state spanning multiple
* brininsert invocations, within the same command.
*/
typedef struct BrinInsertState
{
BrinRevmap *bis_rmAccess;
BrinDesc *bis_desc;
BlockNumber bis_pages_per_range;
} BrinInsertState;

/*
* Struct used as "opaque" during index scans
*/
Expand All @@ -72,6 +83,7 @@ typedef struct BrinOpaque

static BrinBuildState *initialize_brin_buildstate(Relation idxRel,
BrinRevmap *revmap, BlockNumber pagesPerRange);
static BrinInsertState *initialize_brin_insertstate(Relation idxRel, IndexInfo *indexInfo);
static void terminate_brin_buildstate(BrinBuildState *state);
static void brinsummarize(Relation index, Relation heapRel, BlockNumber pageRange,
bool include_partial, double *numSummarized, double *numExisting);
Expand Down Expand Up @@ -117,6 +129,7 @@ brinhandler(PG_FUNCTION_ARGS)
amroutine->ambuild = brinbuild;
amroutine->ambuildempty = brinbuildempty;
amroutine->aminsert = brininsert;
amroutine->aminsertcleanup = brininsertcleanup;
amroutine->ambulkdelete = brinbulkdelete;
amroutine->amvacuumcleanup = brinvacuumcleanup;
amroutine->amcanreturn = NULL;
Expand All @@ -140,6 +153,27 @@ brinhandler(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(amroutine);
}

/*
* Initialize a BrinInsertState to maintain state to be used across multiple
* tuple inserts, within the same command.
*/
static BrinInsertState *
initialize_brin_insertstate(Relation idxRel, IndexInfo *indexInfo)
{
BrinInsertState *bistate;
MemoryContext oldcxt;

oldcxt = MemoryContextSwitchTo(indexInfo->ii_Context);
bistate = palloc0(sizeof(BrinInsertState));
bistate->bis_desc = brin_build_desc(idxRel);
bistate->bis_rmAccess = brinRevmapInitialize(idxRel,
&bistate->bis_pages_per_range);
indexInfo->ii_AmCache = bistate;
MemoryContextSwitchTo(oldcxt);

return bistate;
}

/*
* A tuple in the heap is being inserted. To keep a brin index up to date,
* we need to obtain the relevant index tuple and compare its stored values
Expand All @@ -162,14 +196,24 @@ brininsert(Relation idxRel, Datum *values, bool *nulls,
BlockNumber pagesPerRange;
BlockNumber origHeapBlk;
BlockNumber heapBlk;
BrinDesc *bdesc = (BrinDesc *) indexInfo->ii_AmCache;
BrinInsertState *bistate = (BrinInsertState *) indexInfo->ii_AmCache;
BrinRevmap *revmap;
BrinDesc *bdesc;
Buffer buf = InvalidBuffer;
MemoryContext tupcxt = NULL;
MemoryContext oldcxt = CurrentMemoryContext;
bool autosummarize = BrinGetAutoSummarize(idxRel);

revmap = brinRevmapInitialize(idxRel, &pagesPerRange);
/*
* If firt time through in this statement, initialize the insert state
* that we keep for all the inserts in the command.
*/
if (!bistate)
bistate = initialize_brin_insertstate(idxRel, indexInfo);

revmap = bistate->bis_rmAccess;
bdesc = bistate->bis_desc;
pagesPerRange = bistate->bis_pages_per_range;

/*
* origHeapBlk is the block number where the insertion occurred. heapBlk
Expand Down Expand Up @@ -228,14 +272,6 @@ brininsert(Relation idxRel, Datum *values, bool *nulls,
if (!brtup)
break;

/* First time through in this statement? */
if (bdesc == NULL)
{
MemoryContextSwitchTo(indexInfo->ii_Context);
bdesc = brin_build_desc(idxRel);
indexInfo->ii_AmCache = (void *) bdesc;
MemoryContextSwitchTo(oldcxt);
}
/* First time through in this brininsert call? */
if (tupcxt == NULL)
{
Expand Down Expand Up @@ -306,7 +342,6 @@ brininsert(Relation idxRel, Datum *values, bool *nulls,
break;
}

brinRevmapTerminate(revmap);
if (BufferIsValid(buf))
ReleaseBuffer(buf);
MemoryContextSwitchTo(oldcxt);
Expand All @@ -316,6 +351,24 @@ brininsert(Relation idxRel, Datum *values, bool *nulls,
return false;
}

/*
* Callback to clean up the BrinInsertState once all tuple inserts are done.
*/
void
brininsertcleanup(IndexInfo *indexInfo)
{
BrinInsertState *bistate = (BrinInsertState *) indexInfo->ii_AmCache;

Assert(bistate);
/*
* Clean up the revmap. Note that the brinDesc has already been cleaned up
* as part of its own memory context.
*/
brinRevmapTerminate(bistate->bis_rmAccess);
bistate->bis_rmAccess = NULL;
bistate->bis_desc = NULL;
}

/*
* Initialize state for a BRIN index scan.
*
Expand Down
1 change: 1 addition & 0 deletions src/backend/access/gin/ginutil.c
Expand Up @@ -64,6 +64,7 @@ ginhandler(PG_FUNCTION_ARGS)
amroutine->ambuild = ginbuild;
amroutine->ambuildempty = ginbuildempty;
amroutine->aminsert = gininsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = ginbulkdelete;
amroutine->amvacuumcleanup = ginvacuumcleanup;
amroutine->amcanreturn = NULL;
Expand Down
1 change: 1 addition & 0 deletions src/backend/access/gist/gist.c
Expand Up @@ -86,6 +86,7 @@ gisthandler(PG_FUNCTION_ARGS)
amroutine->ambuild = gistbuild;
amroutine->ambuildempty = gistbuildempty;
amroutine->aminsert = gistinsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = gistbulkdelete;
amroutine->amvacuumcleanup = gistvacuumcleanup;
amroutine->amcanreturn = gistcanreturn;
Expand Down
1 change: 1 addition & 0 deletions src/backend/access/hash/hash.c
Expand Up @@ -83,6 +83,7 @@ hashhandler(PG_FUNCTION_ARGS)
amroutine->ambuild = hashbuild;
amroutine->ambuildempty = hashbuildempty;
amroutine->aminsert = hashinsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = hashbulkdelete;
amroutine->amvacuumcleanup = hashvacuumcleanup;
amroutine->amcanreturn = NULL;
Expand Down
15 changes: 15 additions & 0 deletions src/backend/access/index/indexam.c
Expand Up @@ -196,6 +196,21 @@ index_insert(Relation indexRelation,
indexInfo);
}

/* -------------------------
* index_insert_cleanup - clean up after all index inserts are done
* -------------------------
*/
void
index_insert_cleanup(Relation indexRelation,
IndexInfo *indexInfo)
{
RELATION_CHECKS;
Assert(indexInfo);

if (indexRelation->rd_indam->aminsertcleanup)
indexRelation->rd_indam->aminsertcleanup(indexInfo);
}

/*
* index_beginscan - start a scan of an index with amgettuple
*
Expand Down
1 change: 1 addition & 0 deletions src/backend/access/nbtree/nbtree.c
Expand Up @@ -122,6 +122,7 @@ bthandler(PG_FUNCTION_ARGS)
amroutine->ambuild = btbuild;
amroutine->ambuildempty = btbuildempty;
amroutine->aminsert = btinsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = btbulkdelete;
amroutine->amvacuumcleanup = btvacuumcleanup;
amroutine->amcanreturn = btcanreturn;
Expand Down
1 change: 1 addition & 0 deletions src/backend/access/spgist/spgutils.c
Expand Up @@ -70,6 +70,7 @@ spghandler(PG_FUNCTION_ARGS)
amroutine->ambuild = spgbuild;
amroutine->ambuildempty = spgbuildempty;
amroutine->aminsert = spginsert;
amroutine->aminsertcleanup = NULL;
amroutine->ambulkdelete = spgbulkdelete;
amroutine->amvacuumcleanup = spgvacuumcleanup;
amroutine->amcanreturn = spgcanreturn;
Expand Down
5 changes: 5 additions & 0 deletions src/backend/executor/execIndexing.c
Expand Up @@ -233,15 +233,20 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
int i;
int numIndices;
RelationPtr indexDescs;
IndexInfo **indexInfos;

numIndices = resultRelInfo->ri_NumIndices;
indexDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfos = resultRelInfo->ri_IndexRelationInfo;

for (i = 0; i < numIndices; i++)
{
if (indexDescs[i] == NULL)
continue; /* shouldn't happen? */

/* Give the index a chance to do some post-insert cleanup */
index_insert_cleanup(indexDescs[i], indexInfos[i]);

/* Drop lock acquired by ExecOpenIndices */
index_close(indexDescs[i], RowExclusiveLock);
}
Expand Down
4 changes: 4 additions & 0 deletions src/include/access/amapi.h
Expand Up @@ -113,6 +113,9 @@ typedef bool (*aminsert_function) (Relation indexRelation,
bool indexUnchanged,
struct IndexInfo *indexInfo);

/* cleanup after insert */
typedef void (*aminsertcleanup_function) (struct IndexInfo *indexInfo);

/* bulk delete */
typedef IndexBulkDeleteResult *(*ambulkdelete_function) (IndexVacuumInfo *info,
IndexBulkDeleteResult *stats,
Expand Down Expand Up @@ -261,6 +264,7 @@ typedef struct IndexAmRoutine
ambuild_function ambuild;
ambuildempty_function ambuildempty;
aminsert_function aminsert;
aminsertcleanup_function aminsertcleanup;
ambulkdelete_function ambulkdelete;
amvacuumcleanup_function amvacuumcleanup;
amcanreturn_function amcanreturn; /* can be NULL */
Expand Down
1 change: 1 addition & 0 deletions src/include/access/brin_internal.h
Expand Up @@ -96,6 +96,7 @@ extern bool brininsert(Relation idxRel, Datum *values, bool *nulls,
IndexUniqueCheck checkUnique,
bool indexUnchanged,
struct IndexInfo *indexInfo);
extern void brininsertcleanup(struct IndexInfo *indexInfo);
extern IndexScanDesc brinbeginscan(Relation r, int nkeys, int norderbys);
extern int64 bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
extern void brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
Expand Down
2 changes: 2 additions & 0 deletions src/include/access/genam.h
Expand Up @@ -148,6 +148,8 @@ extern bool index_insert(Relation indexRelation,
IndexUniqueCheck checkUnique,
bool indexUnchanged,
struct IndexInfo *indexInfo);
extern void index_insert_cleanup(Relation indexRelation,
struct IndexInfo *indexInfo);

extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
Expand Down

0 comments on commit c1ec02b

Please sign in to comment.