v29-0011-Debug-TIDStore.patch.txt

text/plain

Filename: v29-0011-Debug-TIDStore.patch.txt
Type: text/plain
Part: 0
Message: Re: [PoC] Improve dead tuple storage for lazy vacuum
From 107aa2af2966c10ce750e6b410ae570462423aab Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Wed, 22 Feb 2023 14:43:15 +0900
Subject: [PATCH v29 11/11] Debug TIDStore.

---
 src/backend/access/common/tidstore.c | 242 ++++++++++++++++++++++++++-
 1 file changed, 238 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/common/tidstore.c b/src/backend/access/common/tidstore.c
index 9360520482..438bf0c800 100644
--- a/src/backend/access/common/tidstore.c
+++ b/src/backend/access/common/tidstore.c
@@ -28,12 +28,20 @@
 #include "postgres.h"
 
 #include "access/tidstore.h"
+#include "catalog/index.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "storage/lwlock.h"
 #include "utils/dsa.h"
 #include "utils/memutils.h"
 
+#define TIDSTORE_DEBUG
+
+/* Enable TidStore debugging only when USE_ASSERT_CHECKING */
+#if defined(TIDSTORE_DEBUG) && !defined(USE_ASSERT_CHECKING)
+#undef TIDSTORE_DEBUG
+#endif
+
 /*
  * For encoding purposes, a tid is represented as a pair of 64-bit key and
  * 64-bit value.
@@ -115,6 +123,12 @@ typedef struct TidStoreControl
 	/* handles for TidStore and radix tree */
 	TidStoreHandle		handle;
 	shared_rt_handle	tree_handle;
+
+#ifdef TIDSTORE_DEBUG
+	dsm_handle		tids_handle;
+	int64			max_tids;
+	bool			tids_unordered;
+#endif
 } TidStoreControl;
 
 /* Per-backend state for a TidStore */
@@ -135,6 +149,11 @@ struct TidStore
 
 	/* DSA area for TidStore if used */
 	dsa_area	*area;
+
+#ifdef TIDSTORE_DEBUG
+	dsm_segment		*tids_seg;
+	ItemPointerData	*tids;
+#endif
 };
 #define TidStoreIsShared(ts) ((ts)->area != NULL)
 
@@ -157,6 +176,11 @@ typedef struct TidStoreIter
 	tidkey		next_tidkey;
 	offsetbm	next_off_bitmap;
 
+#ifdef TIDSTORE_DEBUG
+	/* iterator index for the ts->tids array */
+	int64		tids_idx;
+#endif
+
 	/*
 	 * output for the caller. Must be last because variable-size.
 	 */
@@ -169,6 +193,15 @@ static inline tidkey encode_blk_off(TidStore *ts, BlockNumber block,
 									OffsetNumber offset, offsetbm *off_bit);
 static inline tidkey encode_tid(TidStore *ts, ItemPointer tid, offsetbm *off_bit);
 
+/* debug functions available only when TIDSTORE_DEBUG */
+#ifdef TIDSTORE_DEBUG
+static void ts_debug_set_block_offsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
+									   int num_offsets);
+static void ts_debug_iter_check_tids(TidStoreIter *iter);
+static bool ts_debug_is_member(TidStore *ts, ItemPointer tid);
+static int itemptr_cmp(const void *left, const void *right);
+#endif
+
 /*
  * Create a TidStore. The returned object is allocated in backend-local memory.
  * The radix tree for storage is allocated in DSA area is 'area' is non-NULL.
@@ -237,6 +270,26 @@ TidStoreCreate(size_t max_bytes, int max_off, dsa_area *area)
 	ts->control->upper_off_nbits =
 		ts->control->max_off_nbits - LOWER_OFFSET_NBITS;
 
+#ifdef TIDSTORE_DEBUG
+	{
+		int64		max_tids = max_bytes / sizeof(ItemPointerData);
+
+		/* Allocate the array of tids too */
+		if (TidStoreIsShared(ts))
+		{
+			ts->tids_seg = dsm_create(sizeof(ItemPointerData) * max_tids, 0);
+			ts->tids = dsm_segment_address(ts->tids_seg);
+			ts->control->tids_handle = dsm_segment_handle(ts->tids_seg);
+			ts->control->max_tids = max_tids;
+		}
+		else
+		{
+			ts->tids = palloc(sizeof(ItemPointerData) * max_tids);
+			ts->control->max_tids = max_tids;
+		}
+	}
+#endif
+
 	return ts;
 }
 
@@ -266,6 +319,11 @@ TidStoreAttach(dsa_area *area, TidStoreHandle handle)
 	ts->tree.shared = shared_rt_attach(area, ts->control->tree_handle);
 	ts->area = area;
 
+#ifdef TIDSTORE_DEBUG
+	ts->tids_seg = dsm_attach(ts->control->tids_handle);
+	ts->tids = (ItemPointer) dsm_segment_address(ts->tids_seg);
+#endif
+
 	return ts;
 }
 
@@ -280,6 +338,11 @@ TidStoreDetach(TidStore *ts)
 {
 	Assert(TidStoreIsShared(ts) && ts->control->magic == TIDSTORE_MAGIC);
 
+#ifdef TIDSTORE_DEBUG
+	if (TidStoreIsShared(ts))
+		dsm_detach(ts->tids_seg);
+#endif
+
 	shared_rt_detach(ts->tree.shared);
 	pfree(ts);
 }
@@ -315,6 +378,13 @@ TidStoreDestroy(TidStore *ts)
 		local_rt_free(ts->tree.local);
 	}
 
+#ifdef TIDSTORE_DEBUG
+	if (TidStoreIsShared(ts))
+		dsm_detach(ts->tids_seg);
+	else
+		 pfree(ts->tids);
+#endif
+
 	pfree(ts);
 }
 
@@ -434,6 +504,11 @@ TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
 		}
 	}
 
+#ifdef TIDSTORE_DEBUG
+	/* Insert tids into the tid array too */
+	ts_debug_set_block_offsets(ts, blkno, offsets, num_offsets);
+#endif
+
 	/* update statistics */
 	ts->control->num_tids += num_offsets;
 
@@ -451,6 +526,11 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid)
 	offsetbm off_bitmap = 0;
 	offsetbm off_bit;
 	bool found;
+	bool ret;
+
+#ifdef TIDSTORE_DEBUG
+	bool ret_debug = ts_debug_is_member(ts, tid);
+#endif
 
 	key = encode_tid(ts, tid, &off_bit);
 
@@ -460,9 +540,20 @@ TidStoreIsMember(TidStore *ts, ItemPointer tid)
 		found = local_rt_search(ts->tree.local, key, &off_bitmap);
 
 	if (!found)
+	{
+#ifdef TIDSTORE_DEBUG
+		Assert(!ret_debug);
+#endif
 		return false;
+	}
+
+	ret = (off_bitmap & off_bit) != 0;
 
-	return (off_bitmap & off_bit) != 0;
+#ifdef TIDSTORE_DEBUG
+	Assert(ret == ret_debug);
+#endif
+
+	return ret;
 }
 
 /*
@@ -494,6 +585,10 @@ TidStoreBeginIterate(TidStore *ts)
 	if (TidStoreNumTids(ts) == 0)
 		iter->finished = true;
 
+#ifdef TIDSTORE_DEBUG
+	iter->tids_idx = 0;
+#endif
+
 	return iter;
 }
 
@@ -515,6 +610,7 @@ TidStoreIterResult *
 TidStoreIterateNext(TidStoreIter *iter)
 {
 	tidkey key;
+	bool	iter_found;
 	offsetbm off_bitmap = 0;
 	TidStoreIterResult *output = &(iter->output);
 
@@ -532,7 +628,7 @@ TidStoreIterateNext(TidStoreIter *iter)
 	if (iter->next_off_bitmap > 0)
 		iter_decode_key_off(iter, iter->next_tidkey, iter->next_off_bitmap);
 
-	while (tidstore_iter(iter, &key, &off_bitmap))
+	while ((iter_found = tidstore_iter(iter, &key, &off_bitmap)))
 	{
 		BlockNumber blkno = key_get_blkno(iter->ts, key);
 
@@ -545,14 +641,20 @@ TidStoreIterateNext(TidStoreIter *iter)
 			 */
 			iter->next_tidkey = key;
 			iter->next_off_bitmap = off_bitmap;
-			return output;
+			break;
 		}
 
 		/* Collect tids decoded from the key and offset bitmap */
 		iter_decode_key_off(iter, key, off_bitmap);
 	}
 
-	iter->finished = true;
+	if (!iter_found)
+		iter->finished = true;
+
+#ifdef TIDSTORE_DEBUG
+	ts_debug_iter_check_tids(iter);
+#endif
+
 	return output;
 }
 
@@ -699,3 +801,135 @@ encode_blk_off(TidStore *ts, BlockNumber block, OffsetNumber offset,
 
 	return key;
 }
+
+#ifdef TIDSTORE_DEBUG
+/* Comparator routines for ItemPointer */
+static int
+itemptr_cmp(const void *left, const void *right)
+{
+	BlockNumber lblk,
+		rblk;
+	OffsetNumber loff,
+		roff;
+
+	lblk = ItemPointerGetBlockNumber((ItemPointer) left);
+	rblk = ItemPointerGetBlockNumber((ItemPointer) right);
+
+	if (lblk < rblk)
+		return -1;
+	if (lblk > rblk)
+		return 1;
+
+	loff = ItemPointerGetOffsetNumber((ItemPointer) left);
+	roff = ItemPointerGetOffsetNumber((ItemPointer) right);
+
+	if (loff < roff)
+		return -1;
+	if (loff > roff)
+		return 1;
+
+	return 0;
+}
+
+/* Insert tids to the tid array for debugging */
+static void
+ts_debug_set_block_offsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets,
+						   int num_offsets)
+{
+	if (ts->control->num_tids > 0 &&
+		blkno < ItemPointerGetBlockNumber(&(ts->tids[ts->control->num_tids - 1])))
+	{
+		/* The array will be sorted at ts_debug_is_member() */
+		ts->control->tids_unordered = true;
+	}
+
+	for (int i = 0; i < num_offsets; i++)
+	{
+		ItemPointer tid;
+		int idx = ts->control->num_tids + i;
+
+		/* Enlarge the tid array if necessary */
+		if (idx >= ts->control->max_tids)
+		{
+			ts->control->max_tids *= 2;
+
+			if (TidStoreIsShared(ts))
+			{
+				dsm_segment *new_seg =
+					dsm_create(sizeof(ItemPointerData) * ts->control->max_tids, 0);
+				ItemPointer new_tids = dsm_segment_address(new_seg);
+
+				/* copy tids from old to new array */
+				memcpy(new_tids, ts->tids,
+					   sizeof(ItemPointerData) * (ts->control->max_tids / 2));
+
+				dsm_detach(ts->tids_seg);
+				ts->tids = new_tids;
+			}
+			else
+				ts->tids = repalloc(ts->tids,
+									sizeof(ItemPointerData) * ts->control->max_tids);
+		}
+
+		tid = &(ts->tids[idx]);
+		ItemPointerSetBlockNumber(tid, blkno);
+		ItemPointerSetOffsetNumber(tid, offsets[i]);
+	}
+}
+
+/* Return true if the given tid is present in the tid array */
+static bool
+ts_debug_is_member(TidStore *ts, ItemPointer tid)
+{
+	int64	litem,
+		ritem,
+		item;
+	ItemPointer res;
+
+	if (ts->control->num_tids == 0)
+		return false;
+
+	/* Make sure the tid array is sorted */
+	if (ts->control->tids_unordered)
+	{
+		qsort(ts->tids, ts->control->num_tids, sizeof(ItemPointerData), itemptr_cmp);
+		ts->control->tids_unordered = false;
+	}
+
+	litem = itemptr_encode(&ts->tids[0]);
+	ritem = itemptr_encode(&ts->tids[ts->control->num_tids - 1]);
+	item = itemptr_encode(tid);
+
+	/*
+	 * Doing a simple bound check before bsearch() is useful to avoid the
+	 * extra cost of bsearch(), especially if dead items on the heap are
+	 * concentrated in a certain range.	Since this function is called for
+	 * every index tuple, it pays to be really fast.
+	 */
+	if (item < litem || item > ritem)
+		return false;
+
+	res = bsearch(tid, ts->tids, ts->control->num_tids, sizeof(ItemPointerData),
+				  itemptr_cmp);
+
+	return (res != NULL);
+}
+
+/* Verify if the iterator output matches the tids in the array for debugging */
+static void
+ts_debug_iter_check_tids(TidStoreIter *iter)
+{
+	BlockNumber blkno = iter->output.blkno;
+
+	for (int i = 0; i < iter->output.num_offsets; i++)
+	{
+		ItemPointer tid = &(iter->ts->tids[iter->tids_idx + i]);
+
+		Assert((iter->tids_idx + i) < iter->ts->control->max_tids);
+		Assert(ItemPointerGetBlockNumber(tid) == blkno);
+		Assert(ItemPointerGetOffsetNumber(tid) == iter->output.offsets[i]);
+	}
+
+	iter->tids_idx += iter->output.num_offsets;
+}
+#endif
-- 
2.31.1