From 70dac855e18ce19aad4d0c9b98028da1a6d9e470 Mon Sep 17 00:00:00 2001
From: Evdokimov Ilia <ilya.evdokimov@tantorlabs.com>
Date: Fri, 9 Oct 2026 00:02:22 +0500
Subject: [PATCH v1] Release memory allocated by ndistinct_for_combination

Building ndistinct extended statistics calls ndistinct_for_combination()
once for every combination of columns.  Each call allocates arrays for
the whole sample (SortItems, Datums and null flags), plus the
MultiSortSupport, and never frees them, so the memory accumulates until
the whole statistics object is built.  The sample has 300 * target rows,
so with large statistics targets this can add up to gigabytes;

Fixed by calling ndistinct_for_combination() in a dedicated memory
context and resetting it after each combination, as is already done
for dependency_degree() in statext_dependencies_build().  We only need
the estimated number of distinct values, so we don't need to copy
anything out of the context.
---
 src/backend/statistics/mvdistinct.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index 601f108a2b4..0add6455f6a 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
 #include "statistics/extended_stats_internal.h"
+#include "utils/memutils.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 #include "varatt.h"
@@ -89,6 +90,7 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
 	uint32		itemcnt;
 	int			numattrs = data->nattnums;
 	int			numcombs = num_combinations(numattrs);
+	MemoryContext cxt;
 
 	result = palloc(offsetof(MVNDistinct, items) +
 					numcombs * sizeof(MVNDistinctItem));
@@ -96,6 +98,10 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
 	result->type = STATS_NDISTINCT_TYPE_BASIC;
 	result->nitems = numcombs;
 
+	cxt = AllocSetContextCreate(CurrentMemoryContext,
+								"ndistinct_for_combination cxt",
+								ALLOCSET_DEFAULT_SIZES);
+
 	itemcnt = 0;
 	for (k = 2; k <= numattrs; k++)
 	{
@@ -109,6 +115,7 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
 		{
 			MVNDistinctItem *item = &result->items[itemcnt];
 			int			j;
+			MemoryContext oldcxt;
 
 			item->attributes = palloc_array(AttrNumber, k);
 			item->nattributes = k;
@@ -121,8 +128,12 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
 				Assert(AttributeNumberIsValid(item->attributes[j]));
 			}
 
+			/* release memory used by ndistinct calculation */
+			oldcxt = MemoryContextSwitchTo(cxt);
 			item->ndistinct =
 				ndistinct_for_combination(totalrows, data, k, combination);
+			MemoryContextSwitchTo(oldcxt);
+			MemoryContextReset(cxt);
 
 			itemcnt++;
 			Assert(itemcnt <= result->nitems);
@@ -131,6 +142,8 @@ statext_ndistinct_build(double totalrows, StatsBuildData *data)
 		generator_free(generator);
 	}
 
+	MemoryContextDelete(cxt);
+
 	/* must consume exactly the whole output array */
 	Assert(itemcnt == result->nitems);
 
-- 
2.53.0

