From 11fda58f829c03d2a7c6476affc61862a078f741 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Mon, 17 Apr 2023 22:27:04 +0900
Subject: [PATCH v32 17/18] tidstore, vacuum: Specify the init and max DSA
 segment size based on m_w_m

---
 src/backend/access/common/tidstore.c  | 32 +++++----------------------
 src/backend/commands/vacuumparallel.c | 21 ++++++++++++++----
 2 files changed, 23 insertions(+), 30 deletions(-)

diff --git a/src/backend/access/common/tidstore.c b/src/backend/access/common/tidstore.c
index 9360520482..571d15c5c3 100644
--- a/src/backend/access/common/tidstore.c
+++ b/src/backend/access/common/tidstore.c
@@ -180,39 +180,15 @@ TidStoreCreate(size_t max_bytes, int max_off, dsa_area *area)
 
 	ts = palloc0(sizeof(TidStore));
 
-	/*
-	 * Create the radix tree for the main storage.
-	 *
-	 * Memory consumption depends on the number of stored tids, but also on the
-	 * distribution of them, how the radix tree stores, and the memory management
-	 * that backed the radix tree. The maximum bytes that a TidStore can
-	 * use is specified by the max_bytes in TidStoreCreate(). We want the total
-	 * amount of memory consumption by a TidStore not to exceed the max_bytes.
-	 *
-	 * In local TidStore cases, the radix tree uses slab allocators for each kind
-	 * of node class. The most memory consuming case while adding Tids associated
-	 * with one page (i.e. during TidStoreSetBlockOffsets()) is that we allocate a new
-	 * slab block for a new radix tree node, which is approximately 70kB. Therefore,
-	 * we deduct 70kB from the max_bytes.
-	 *
-	 * In shared cases, DSA allocates the memory segments big enough to follow
-	 * a geometric series that approximately doubles the total DSA size (see
-	 * make_new_segment() in dsa.c). We simulated the how DSA increases segment
-	 * size and the simulation revealed, the 75% threshold for the maximum bytes
-	 * perfectly works in case where the max_bytes is a power-of-2, and the 60%
-	 * threshold works for other cases.
-	 */
 	if (area != NULL)
 	{
 		dsa_pointer dp;
-		float ratio = ((max_bytes & (max_bytes - 1)) == 0) ? 0.75 : 0.6;
 
 		ts->tree.shared = shared_rt_create(CurrentMemoryContext, area,
 										   LWTRANCHE_SHARED_TIDSTORE);
 
 		dp = dsa_allocate0(area, sizeof(TidStoreControl));
 		ts->control = (TidStoreControl *) dsa_get_address(area, dp);
-		ts->control->max_bytes = (size_t) (max_bytes * ratio);
 		ts->area = area;
 
 		ts->control->magic = TIDSTORE_MAGIC;
@@ -223,11 +199,15 @@ TidStoreCreate(size_t max_bytes, int max_off, dsa_area *area)
 	else
 	{
 		ts->tree.local = local_rt_create(CurrentMemoryContext);
-
 		ts->control = (TidStoreControl *) palloc0(sizeof(TidStoreControl));
-		ts->control->max_bytes = max_bytes - (70 * 1024);
 	}
 
+	/*
+	 * max_bytes is forced to be at least 64kB, the current minimum valid value
+	 * for the work_mem GUC.
+	 */
+	ts->control->max_bytes = Max(64 * 1024L, max_bytes);
+
 	ts->control->max_off = max_off;
 	ts->control->max_off_nbits = pg_ceil_log2_32(max_off);
 
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 8385d375db..17699aa007 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -252,6 +252,8 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
 	Size		est_indstats_len;
 	Size		est_shared_len;
 	Size		dsa_minsize = dsa_minimum_size();
+	Size		init_segsize;
+	Size		max_segsize;
 	int			nindexes_mwm = 0;
 	int			parallel_workers = 0;
 	int			querylen;
@@ -367,12 +369,23 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
 	shm_toc_insert(pcxt->toc, PARALLEL_VACUUM_KEY_INDEX_STATS, indstats);
 	pvs->indstats = indstats;
 
-	/* Prepare DSA space for dead items */
+	/*
+	 * Prepare DSA space for dead items.
+	 *
+	 * Since total DSA size grows while following a geometric series by default,
+	 * we specify both the initial DSA segment and maximum DSA segment sizes
+	 * based on the memory available for parallel vacuum. Typically, the initial
+	 * segment size is 1MB and the maximum segment size is vac_work_mem / 8, and
+	 * heap scan stops after allocating 1.125 times more memory than vac_work_mem.
+	 */
+	init_segsize = Min(vac_work_mem / 4, (1024 * 1024));
+	max_segsize = Max(vac_work_mem / 8, (8 * 1024 * 1024));
 	area_space = shm_toc_allocate(pcxt->toc, dsa_minsize);
 	shm_toc_insert(pcxt->toc, PARALLEL_VACUUM_KEY_DEAD_ITEMS, area_space);
-	dead_items_dsa = dsa_create_in_place(area_space, dsa_minsize,
-										 LWTRANCHE_PARALLEL_VACUUM_DSA,
-										 pcxt->seg);
+	dead_items_dsa = dsa_create_in_place_extended(area_space, dsa_minsize,
+												  LWTRANCHE_PARALLEL_VACUUM_DSA,
+												  pcxt->seg,
+												  init_segsize, max_segsize);
 	dead_items = TidStoreCreate(vac_work_mem, max_offset, dead_items_dsa);
 	pvs->dead_items = dead_items;
 	pvs->dead_items_area = dead_items_dsa;
-- 
2.31.1

