v14-0002-Notes-small-fixes-for-nbtree-SAOP-patch.patch

application/octet-stream

Filename: v14-0002-Notes-small-fixes-for-nbtree-SAOP-patch.patch
Type: application/octet-stream
Part: 1
Message: Re: Optimizing nbtree ScalarArrayOp execution, allowing multi-column ordered scans, skip scan

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v14-0002
Subject: Notes & small fixes for nbtree SAOP patch
File+
src/backend/access/nbtree/nbtsearch.c 1 1
src/backend/access/nbtree/nbtutils.c 92 4
From 9f9ce415a5f66d54fce627103c4fc73289d430cf Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Fri, 8 Mar 2024 14:02:19 +0100
Subject: [PATCH v14 2/3] Notes & small fixes for nbtree SAOP patch

- Comments fixed
- Assertions updated
- improve _bt_merge_arrays to only alloc when assertions are enabled.
---
 src/backend/access/nbtree/nbtsearch.c |  2 +-
 src/backend/access/nbtree/nbtutils.c  | 96 +++++++++++++++++++++++++--
 2 files changed, 93 insertions(+), 5 deletions(-)

diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 68bc32c6e1..2b61bda523 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -1763,7 +1763,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
 			int			truncatt;
 
 			truncatt = BTreeTupleGetNAtts(itup, scan->indexRelation);
-			pstate.prechecked = false;	/* prechecked earlier tuple */
+			pstate.prechecked = false;	/* prechecked didn't cover HIKEY */
 			_bt_checkkeys(scan, &pstate, arrayKeys, itup, truncatt);
 		}
 
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 08219001a6..6e97455c87 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -678,8 +678,10 @@ _bt_merge_arrays(ScanKey skey, FmgrInfo *sortproc, bool reverse,
 				 Datum *elems_next, int nelems_next)
 {
 	BTSortArrayContext cxt;
-	Datum	   *merged = palloc(sizeof(Datum) * Min(nelems_orig, nelems_next));
 	int			merged_nelems = 0;
+#ifdef USE_ASSERT_CHECKING
+	Datum	   *merged = palloc(sizeof(Datum) * Min(nelems_orig, nelems_next));
+#endif
 
 	/*
 	 * Incrementally copy the original array into a temp buffer, skipping over
@@ -694,15 +696,23 @@ _bt_merge_arrays(ScanKey skey, FmgrInfo *sortproc, bool reverse,
 
 		if (bsearch_arg(elem, elems_next, nelems_next, sizeof(Datum),
 						_bt_compare_array_elements, &cxt))
-			merged[merged_nelems++] = *elem;
+		{
+			elems_orig[merged_nelems] = *elem;
+#ifdef USE_ASSERT_CHECKING
+			merged[merged_nelems] = *elem;
+#endif
+			merged_nelems++;
+		}
 	}
 
+#ifdef USE_ASSERT_CHECKING
 	/*
 	 * Overwrite the original array with temp buffer so that we're only left
 	 * with intersecting array elements
 	 */
-	memcpy(elems_orig, merged, merged_nelems * sizeof(Datum));
+	Assert(memcmp(elems_orig, merged, merged_nelems * sizeof(Datum)) == 0);
 	pfree(merged);
+#endif
 
 	return merged_nelems;
 }
@@ -1396,6 +1406,48 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 
 	so->scanBehind = false;		/* reset */
 
+	/*
+	 * Phase 1: Move array keys to more-appropriate positions
+	 * 
+	 * We move the trigger scankey (i.e. the first non-matching scankey that
+	 * the caller found) to the first appropriate array key value. This
+	 * has 6 possible options:
+	 * 
+	 * The scankey is a required equality array key, and
+	 *	 1. The tuple's attribute value is present in the array key
+	 *	 2. The tuple's attribute value is not present in the array key, but
+	 *		the array has a larger value.
+	 *	 3. The tuple's attribute value is not present in the array key, and
+	 *		no larger value exists in the array
+	 * The scankey is not a required equality array key (so: non-required, or
+	 * inequality key, or non-array)
+	 *	 4. The scankey is required and does match the tuple.
+	 *	 5. The scankey is required and does not match the tuple.
+	 *	 6. The scankey is not required.
+	 *
+	 * In case 1, we just update that key, and continue with the next key as
+	 * if that were the next trigger scankey.
+	 * 
+	 * In case 2, we update this array key to the larger value, and set all
+	 * subsequent array keys to their first value.
+	 * 
+	 * In case 3 and 5, we set beyond_end_advance to true, and we set all
+	 * array scankeys starting from this one to their last value.  Phase 2
+	 * will handle the array wraparounds triggered by beyond_end_advance.
+	 * 
+	 * In case 4, we just continue on to the next key.
+	 * 
+	 * In case 6, we ignore the key for the purposes of this system.
+	 * 
+	 * We also gather several flags used later:
+	 *	 - arrays_advanced: Did we update any array keys yet?
+	 *		This is used mostly to detect whether we've found the end of the
+	 *		index scan.
+	 *	 - sktrig_required: Was the trigger scankey a required scankey?
+	 *	 - all_required_satisfied: Could we apply case 1 for all keys?
+	 *	 - so->scanBehind: XXX
+	 *	 - XXX: Document more flags?
+	 */
 	for (int ikey = 0; ikey < so->numberOfKeys; ikey++)
 	{
 		ScanKey		cur = so->keyData + ikey;
@@ -1419,6 +1471,12 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 		}
 		else
 		{
+			/*
+			 * This is an inequality key, so this should never be required in
+			 * both directions.  However, let's try to be very sure about that.
+			 */
+			Assert((cur->sk_flags & (SK_BT_REQBKWD|SK_BT_REQFWD)) !=
+				   (SK_BT_REQBKWD|SK_BT_REQFWD));
 			/*
 			 * Are any inequalities required in the opposite direction only
 			 * present here?
@@ -1687,11 +1745,20 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 		}
 	}
 
+	/*
+	 * Phase 2: post-processing
+	 */
+	
 	/*
 	 * Consider if we need to advance the array keys incrementally to finish
 	 * off "beyond end of array element" array advancement.  This is the only
 	 * way that the array keys can be exhausted, which is how top-level index
 	 * scans usually determine that they've run out of tuples to return.
+	 *
+	 * This is similar to adding 1 to 999 and carrying the digits up to 1000:
+	 * If we try to advance an array key that's at it's last element, we wrap
+	 * it around and increment the preceding array key by one. If that too is
+	 * at its last element, wrap that, and increment the one before, etc.
 	 */
 	arrays_exhausted = false;
 	if (beyond_end_advance)
@@ -1765,6 +1832,27 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 		 * here will advance the array keys to the maximum possible extent
 		 * that we can know to be safe based on caller's tuple alone.  If we
 		 * didn't perform this step, then that guarantee wouldn't quite hold.
+		 *
+		 * Example:
+		 * 
+		 * If we were called with the following arguments:
+		 *		ScanKey (a=1 [1,2,3], b < 3)
+		 *		Tuple    a=2, b=4
+		 * Then by now, the ScanKey would look like this:
+		 *		ScanKey (a=2 [1,2,3], b<3)
+		 * This doesn't match the tuple (4 is not less than 3), and also won't
+		 * match more tuples in the scan direction.  The required unsatisfied
+		 * inequality condition "b<3" was previously hidden by the unsatisfied
+		 * array key, but now indicates we should update the array keys again.
+		 * After another round of _bt_advance_array_keys, the key will look
+		 * like this, and thus will be ready for further use:
+		 *		ScanKey (a=3 [1,2,3], b<3)
+		 *
+		 * Note that we will only ever need one additional advancement:  The
+		 * key is already advanced and at least as large as the current tuple
+		 * (after accounting for the scan direction). Advancement will further
+		 * increase this; this is only preparation so that the next scan may
+		 * hit matching tuples (rather than being guaranteed to not match).
 		 */
 		if (unlikely(!pstate->continuescan))
 		{
@@ -1821,7 +1909,7 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
 	 * By here we have established that the scan's required arrays were
 	 * advanced, and that they haven't become exhausted.
 	 */
-	Assert(arrays_advanced || !arrays_exhausted);
+	Assert(arrays_advanced && !arrays_exhausted);
 
 	/*
 	 * We generally permit primitive index scans to continue onto the next
-- 
2.40.1