Hi hackers,

We came across a slowdown in planning, where queries use tables with many
indexes. In setups with wide tables it is not uncommon to have easily
10-100 indexes on a single table. The slowdown is already visible in serial
workloads with just a handful of indexes, but gets drastically amplified
when running queries with more indexes in parallel at high throughput.

We measured the TPS and planning time of running parallel streams of simple
point look-up queries on a single empty table with 60 columns and 60
indexes. The query used is 'SELECT * FROM synth_table WHERE col5 = 42'. No
rows are returned because the table is empty. We used a machine with 64
physical CPU cores. The schema and sysbench script to reproduce these
numbers are attached. We used the TPS as reported by sysbench and obtained
planning time by running 'EXPLAIN ANALYZE' on the same query in a
separately opened connection. We averaged the planning time of 3 successive
'EXPLAIN ANALYZE' runs. sysbench ran on the same machine with varying
numbers of threads using the following command line:

sysbench repro.lua --db-driver=pgsql --pgsql-host=localhost
--pgsql-db=postgres --pgsql-port=? --pgsql-user=? --pgsql-password=?
--report-interval=1 --threads=64 run

The following table shows the results. It is clearly visible that the TPS
flatten out already at 8 parallel streams, while the planning time is
increasing drastically.

Parallel streams | TPS (before) | Planning time (before)
-----------------|--------------|-----------------------
1                |  5,486       | 0.13 ms
2                |  8,098       | 0.22 ms
4                | 15,013       | 0.19 ms
8                | 27,107       | 0.29 ms
16               | 30,938       | 0.43 ms
32               | 26,330       | 1.68 ms
64               | 24,314       | 2.48 ms

We tracked down the root cause of this slowdown to lock contention in
'get_relation_info()'. The index lock of every single index of every single
table used in that query is acquired. We attempted a fix by pre-filtering
out all indexes that anyways cannot be used with a certain query, without
taking the index locks (credits to Luc Vlaming for idea and
implementation). The patch does so by caching the columns present in every
index, inside 'struct Relation', similarly to 'rd_indexlist'. Then, before
opening (= locking) the indexes in 'get_relation_info()', we check if the
index can actually contribute to the query and if not it is discarded right
away. Caching the index info saves considerable work for every query run
subsequently, because less indexes must be inspected and thereby locked.
This way we also save cycles in any code that later on goes over all
relation indexes.

The work-in-progress version of the patch is attached. It is still fairly
rough (e.g. uses a global variable, selects the best index in scans without
restrictions by column count instead of physical column size, is missing
some renaming, etc.), but shows the principle.

The following table shows the TPS, planning time and speed-ups after
applying the patch and rerunning above described benchmark. Now, the
planning time remains roughly constant and TPS roughly doubles each time
the number of parallel streams is doubled. The higher the stream count the
more severe the lock contention is and the more pronounced the gained
speed-up gets. Interestingly, even for a single query stream the speed-up
in planning time is already very significant. This applies also for lower
index counts. For example just with 10 indexes the TPS for a single query
stream goes from 9,159 to 12,558. We can do more measurements if there is
interest in details for a lower number of indexes.

Parallel streams | TPS (after) | Planning time (after) | Speed-up TPS |
Speed-up planning
-----------------|-------------|-----------------------|--------------|------------------
1                |  10,344     | 0.046                 |  1.9x        |
 2.8x
2                |  20,140     | 0.045 ms              |  2.5x        |
 4.9x
4                |  40,349     | 0.047 ms              |  2.7x        |
 4.0x
8                |  80,121     | 0.046 ms              |  3.0x        |
 6.3x
16               | 152,632     | 0.051 ms              |  4.9x        |
 8.4x
32               | 301,359     | 0.052 ms              | 11.4x        |
32.3x
64               | 525,115     | 0.062 ms              | 21.6x        |
40.0x

We are happy to receive your feedback and polish up the patch.

--
David Geier
(ServiceNow)
From 60e300b5cac8f527e61483296df81ab783670ac9 Mon Sep 17 00:00:00 2001
From: David Geier <david.ge...@servicenow.com>
Date: Fri, 15 Jul 2022 11:53:27 +0200
Subject: [PATCH] Index filtering

---
 src/backend/optimizer/path/Makefile           |   3 +-
 src/backend/optimizer/path/index_filtering.c  | 220 ++++++++++++++++++
 src/backend/optimizer/plan/planmain.c         |   7 +
 src/backend/optimizer/util/plancat.c          | 124 ++++++----
 src/backend/utils/cache/relcache.c            |  17 +-
 src/include/optimizer/planner_index_locking.h |  40 ++++
 6 files changed, 361 insertions(+), 50 deletions(-)
 create mode 100644 src/backend/optimizer/path/index_filtering.c
 create mode 100644 src/include/optimizer/planner_index_locking.h

diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f..933c4df8f5 100644
--- a/src/backend/optimizer/path/Makefile
+++ b/src/backend/optimizer/path/Makefile
@@ -21,6 +21,7 @@ OBJS = \
 	joinpath.o \
 	joinrels.o \
 	pathkeys.o \
-	tidpath.o
+	tidpath.o \
+	index_filtering.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/index_filtering.c b/src/backend/optimizer/path/index_filtering.c
new file mode 100644
index 0000000000..b9d05f85f2
--- /dev/null
+++ b/src/backend/optimizer/path/index_filtering.c
@@ -0,0 +1,220 @@
+#include <postgres.h> // NOTE: this include MUST come first
+
+#include <access/htup_details.h>
+#include <access/sysattr.h>
+#include <catalog/pg_am.h>
+#include <catalog/pg_class.h>
+#include <nodes/pathnodes.h>
+#include <optimizer/optimizer.h>
+#include <optimizer/planner_index_locking.h>
+#include <utils/builtins.h>
+#include <utils/catcache.h>
+#include <utils/rel.h>
+#include <utils/syscache.h>
+
+bool FilterIndexes = false;
+
+// similar to e.g. RelationGetPrimaryKeyIndex
+List *s64_RelationGetIndexBitmapList(Relation relation)
+{
+    if (!relation->rd_indexvalid)
+        list_free(RelationGetIndexList(relation));
+
+    return list_copy(relation->rd_indexprs);
+}
+
+IndexBitmapset *s64_RelationBuildIndexBitmapset(HeapTuple htup)
+{
+    Form_pg_index   index         = (Form_pg_index)GETSTRUCT(htup);
+    Bitmapset      *keys          = NULL;
+    IndexBitmapset *bitmap        = NULL;
+    MemoryContext   oldcxt;
+    int             i;
+
+    for (i = 0; i < index->indnatts; ++i)
+        if (index->indkey.values[i] != 0)
+            keys = bms_add_member(keys,
+                                  index->indkey.values[i] - FirstLowInvalidHeapAttributeNumber);
+
+    // Find also all columns referenced by any expression
+    // From RelationGetIndexExpressions and inlined so that we don't need extra locks
+    if (!heap_attisnull(htup, Anum_pg_index_indexprs, NULL))
+    {
+        bool  isNull;
+        Datum exprsDatum =
+                heap_getattr(htup, Anum_pg_index_indexprs, GetPgIndexDescriptor(), &isNull);
+        char *exprsString = TextDatumGetCString(exprsDatum);
+        Node *exprs       = (Node *)stringToNode(exprsString);
+        pull_varattnos(exprs, 1, &keys);
+        pfree(exprsString);
+        pfree(exprs);
+    }
+
+    // Also find any column referenced by any predicate
+    if (!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
+    {
+        bool  isNull;
+        Datum predDatum =
+                heap_getattr(htup, Anum_pg_index_indpred, GetPgIndexDescriptor(), &isNull);
+        char *predString = TextDatumGetCString(predDatum);
+        Node *pred       = (Node *)stringToNode(predString);
+        pull_varattnos(pred, 1, &keys);
+        pfree(predString);
+        pfree(pred);
+    }
+
+    oldcxt                = MemoryContextSwitchTo(CacheMemoryContext);
+    bitmap                = palloc0(sizeof(IndexBitmapset));
+    bitmap->Index         = index->indexrelid;
+    bitmap->Keys          = bms_copy(keys);
+    MemoryContextSwitchTo(oldcxt);
+    bms_free(keys);
+    return bitmap;
+}
+
+// The best index for an index-only scan is the index that:
+// - is the smallest
+// - has all the fields so that it can actually be an index-only scan.
+Oid s64_RelationGetBestIndexForIndexOnly(Relation relation, Bitmapset *required)
+{
+    int       smallest  = INDEX_MAX_KEYS + 1;
+    Oid       bestIndex = InvalidOid;
+    ListCell *lc;
+
+    if (!relation->rd_indexvalid)
+        list_free(RelationGetIndexList(relation));
+
+    foreach (lc, relation->rd_indexprs)
+    {
+        IndexBitmapset *bitmap = (IndexBitmapset *)lfirst(lc);
+
+        if (required == NULL || bms_is_subset(required, bitmap->Keys))
+        {
+            if (bms_num_members(bitmap->Keys) < smallest)
+            {
+                bestIndex = bitmap->Index;
+                smallest  = bms_num_members(bitmap->Keys);
+            }
+        }
+    }
+
+    return bestIndex;
+}
+
+int list_bitmapset_qcmp(const void *p1, const void *p2)
+{
+    return list_bitmapset_cmp(*(ListCell **)p1, *(ListCell **)p2);
+}
+
+int list_bitmapset_cmp(const ListCell *p1, const ListCell *p2)
+{
+    const IndexBitmapset *v1 = (IndexBitmapset *)(lfirst(p1));
+    const IndexBitmapset *v2 = (IndexBitmapset *)(lfirst(p2));
+    if (v1->Index < v2->Index)
+        return -1;
+    if (v1->Index > v2->Index)
+        return 1;
+    return 0;
+}
+
+Bitmapset *s64_RelationUsedClauses(PlannerInfo *root, RelOptInfo *rel)
+{
+    ListCell  *lc1  = NULL;
+    ListCell  *lc2  = NULL;
+    Bitmapset *used = NULL;
+
+    // All columns we filter on
+    foreach (lc1, rel->baserestrictinfo)
+    {
+        RestrictInfo *rinfo = (RestrictInfo *)lfirst(lc1);
+        pull_varattnos((Node *)(rinfo->clause), rel->relid, &used);
+    }
+
+    // All columns explicitly used in a join
+    foreach (lc1, rel->joininfo)
+    {
+        RestrictInfo *rinfo = (RestrictInfo *)lfirst(lc1);
+        pull_varattnos((Node *)(rinfo->clause), rel->relid, &used);
+    }
+
+    // All columns used in any ordering.
+    // Query pathkeys contains any useful ordering for query_planner, so we don't have to figure
+    // out which ordering would be most useful (grouping, window, distinct, sort, etc).
+    foreach (lc1, root->query_pathkeys)
+    {
+        PathKey *pathKey = (PathKey *)lfirst(lc1);
+        foreach (lc2, pathKey->pk_eclass->ec_members)
+        {
+            EquivalenceMember *ecMember = (EquivalenceMember *)lfirst(lc2);
+            if (bms_is_member(rel->relid, ecMember->em_relids))
+                pull_varattnos((Node *)(ecMember->em_expr), rel->relid, &used);
+        }
+    }
+
+    // All useful equivalences there might be, can be used e.g. when you have an index
+    // which can return ordered data for a join indirectly (because the data is somehow
+    // ordered the same way, e.g. because of a PK)
+    foreach (lc1, root->eq_classes)
+    {
+        EquivalenceClass *ec = (EquivalenceClass *)lfirst(lc1);
+        foreach (lc2, ec->ec_members)
+        {
+            EquivalenceMember *ecMember = (EquivalenceMember *)lfirst(lc2);
+            if (bms_is_member(rel->relid, ecMember->em_relids))
+                pull_varattnos((Node *)(ecMember->em_expr), rel->relid, &used);
+        }
+    }
+
+    return used;
+}
+
+Bitmapset *s64_RelationUsedTList(PlannerInfo *root, RelOptInfo *rel)
+{
+    Bitmapset *used = NULL;
+
+    pull_varattnos((Node *)(rel->reltarget->exprs), rel->relid, &used);
+    return used;
+}
+
+// Any index that cannot provide a reduction of the data read is unnecessary.
+// An index can filter out (enough) data when:
+// 1. the columns used in the clauses that the index covers can filter
+//    out enough rows to not hit all pages of the table
+// 2. the index has all fields required to allow an index-only scan,
+//    the data is all-visible, and we want actual fields.
+// 3. we don't want any fields and this is the smallest index.
+//    this is used in a `SELECT COUNT(*) FROM`
+// 4. the index can provide the data ordered on a column that is useful
+bool s64_IsUnnecessaryIndex(PlannerInfo    *root,
+                            IndexBitmapset *bitmap,
+                            Bitmapset      *clauses,
+                            Bitmapset      *all,
+                            Oid             smallestIndex)
+{
+    if (!FilterIndexes)
+        return false;
+
+    // for safety only do this on SELECT statements
+    if (root->parse->commandType != CMD_SELECT)
+        return false;
+
+    // approximation of case 1 and 4. doesn't (yet) take into account visibility fraction
+    if (bms_overlap(clauses, bitmap->Keys))
+        return false;
+
+    if (!bms_is_empty(all))
+    {
+        // case 2.
+        if (bms_is_subset(all, bitmap->Keys))
+            return false;
+    }
+    else
+    {
+        // case 3
+        if (bitmap->Index == smallestIndex)
+            return false;
+    }
+
+    // TestCountIndexesFiltered++;
+    return true;
+}
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 273ac0acf7..370711ca50 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -29,6 +29,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/placeholder.h"
 #include "optimizer/planmain.h"
+#include "optimizer/planner_index_locking.h"
 
 
 /*
@@ -163,6 +164,7 @@ query_planner(PlannerInfo *root,
 	 * for rels not actively part of the query, for example views.  We don't
 	 * want to make RelOptInfos for them.
 	 */
+	FilterIndexes = true;
 	add_base_rels_to_query(root, (Node *) parse->jointree);
 
 	/*
@@ -213,6 +215,11 @@ query_planner(PlannerInfo *root,
 	 */
 	fix_placeholder_input_needed_levels(root);
 
+	/* add all required indexes for the baserels, so that the following functions have the required indexes:
+	 * reduce_unique_semijoins, remove_useless_joins. This is therefore the latest we can do this. */
+	s64_add_indexes(root);
+	FilterIndexes = false;
+
 	/*
 	 * Remove any useless outer joins.  Ideally this would be done during
 	 * jointree preprocessing, but the necessary information isn't available
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..225b9f9a8e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -53,6 +53,8 @@
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
+#include "optimizer/planner_index_locking.h"
+
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
 
@@ -113,10 +115,7 @@ void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 				  RelOptInfo *rel)
 {
-	Index		varno = rel->relid;
 	Relation	relation;
-	bool		hasindex;
-	List	   *indexinfos = NIL;
 
 	/*
 	 * We need not lock the relation since it was already locked, either by
@@ -150,9 +149,60 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 		estimate_rel_size(relation, rel->attr_widths - rel->min_attr,
 						  &rel->pages, &rel->tuples, &rel->allvisfrac);
 
+	if (!FilterIndexes)
+		s64_add_indexes_for_rel(root, relation->rd_id, inhparent, rel);
+
 	/* Retrieve the parallel_workers reloption, or -1 if not set. */
 	rel->rel_parallel_workers = RelationGetParallelWorkers(relation, -1);
 
+	rel->statlist = get_relation_statistics(rel, relation);
+
+	/* Grab foreign-table info using the relcache, while we have it */
+	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
+		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
+	}
+	else
+	{
+		rel->serverid = InvalidOid;
+		rel->fdwroutine = NULL;
+	}
+
+	/* Collect info about relation's foreign keys, if relevant */
+	get_relation_foreign_keys(root, rel, relation, inhparent);
+
+	/* Collect info about functions implemented by the rel's table AM. */
+	if (relation->rd_tableam &&
+		relation->rd_tableam->scan_set_tidrange != NULL &&
+		relation->rd_tableam->scan_getnextslot_tidrange != NULL)
+		rel->amflags |= AMFLAG_HAS_TID_RANGE;
+
+	/*
+	 * Collect info about relation's partitioning scheme, if any. Only
+	 * inheritance parents may be partitioned.
+	 */
+	if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+		set_relation_partition_info(root, rel, relation);
+
+	table_close(relation, NoLock);
+
+	/*
+	 * Allow a plugin to editorialize on the info we obtained from the
+	 * catalogs.  Actions might include altering the assumed relation size,
+	 * removing an index, or adding a hypothetical index to the indexlist.
+	 */
+	if (get_relation_info_hook)
+		(*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
+}
+
+void s64_add_indexes_for_rel(PlannerInfo* root, Oid relationObjectId, bool inhparent, RelOptInfo *rel)
+{
+	Relation relation = table_open(relationObjectId, NoLock);
+	List	   *indexinfos = NIL;
+	Index		varno = rel->relid;
+	bool		hasindex;
+
 	/*
 	 * Make list of indexes.  Ignore indexes on system catalogs if told to.
 	 * Don't bother with indexes for an inheritance parent, either.
@@ -165,11 +215,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 	if (hasindex)
 	{
-		List	   *indexoidlist;
+		List	   *index_bitmaps;
 		LOCKMODE	lmode;
 		ListCell   *l;
+		Bitmapset* requiredTList = s64_RelationUsedTList(root, rel);
+		Bitmapset* requiredClauses = s64_RelationUsedClauses(root, rel);
+		Bitmapset* required = bms_union(requiredTList, requiredClauses);
+		Oid smallestIndex = s64_RelationGetBestIndexForIndexOnly(relation, required);
 
-		indexoidlist = RelationGetIndexList(relation);
+		index_bitmaps = s64_RelationGetIndexBitmapList(relation);
 
 		/*
 		 * For each index, we get the same type of lock that the executor will
@@ -181,9 +235,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 		 */
 		lmode = root->simple_rte_array[varno]->rellockmode;
 
-		foreach(l, indexoidlist)
+		foreach(l, index_bitmaps)
 		{
-			Oid			indexoid = lfirst_oid(l);
+			IndexBitmapset* bitmap = (IndexBitmapset*) lfirst(l);
+			Oid		indexoid = bitmap->Index;
 			Relation	indexRelation;
 			Form_pg_index index;
 			IndexAmRoutine *amroutine;
@@ -192,6 +247,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 						nkeycolumns;
 			int			i;
 
+			if (s64_IsUnnecessaryIndex(root, bitmap, requiredClauses, required, smallestIndex))
+				continue;
+
 			/*
 			 * Extract info from the relation descriptor for the index.
 			 */
@@ -436,51 +494,27 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 			indexinfos = lcons(info, indexinfos);
 		}
 
-		list_free(indexoidlist);
+		list_free(index_bitmaps);
+		bms_free(requiredTList);
+		bms_free(requiredClauses);
+		bms_free(required);
 	}
 
 	rel->indexlist = indexinfos;
 
-	rel->statlist = get_relation_statistics(rel, relation);
+	table_close(relation, NoLock);
+}
 
-	/* Grab foreign-table info using the relcache, while we have it */
-	if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
-	{
-		rel->serverid = GetForeignServerIdByRelId(RelationGetRelid(relation));
-		rel->fdwroutine = GetFdwRoutineForRelation(relation, true);
-	}
-	else
+void s64_add_indexes(PlannerInfo *root)
+{
+	for (int i = 0; i < root->simple_rel_array_size; ++i)
 	{
-		rel->serverid = InvalidOid;
-		rel->fdwroutine = NULL;
+		RangeTblEntry* rte = root->simple_rte_array[i];
+		RelOptInfo* rel = root->simple_rel_array[i];
+		if (rel != NULL && rte->rtekind == RTE_RELATION && rel->indexlist == NULL)
+			s64_add_indexes_for_rel(root, rte->relid, rte->inh, rel);
 	}
-
-	/* Collect info about relation's foreign keys, if relevant */
-	get_relation_foreign_keys(root, rel, relation, inhparent);
-
-	/* Collect info about functions implemented by the rel's table AM. */
-	if (relation->rd_tableam &&
-		relation->rd_tableam->scan_set_tidrange != NULL &&
-		relation->rd_tableam->scan_getnextslot_tidrange != NULL)
-		rel->amflags |= AMFLAG_HAS_TID_RANGE;
-
-	/*
-	 * Collect info about relation's partitioning scheme, if any. Only
-	 * inheritance parents may be partitioned.
-	 */
-	if (inhparent && relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		set_relation_partition_info(root, rel, relation);
-
-	table_close(relation, NoLock);
-
-	/*
-	 * Allow a plugin to editorialize on the info we obtained from the
-	 * catalogs.  Actions might include altering the assumed relation size,
-	 * removing an index, or adding a hypothetical index to the indexlist.
-	 */
-	if (get_relation_info_hook)
-		(*get_relation_info_hook) (root, relationObjectId, inhparent, rel);
-}
+ }
 
 /*
  * get_relation_foreign_keys -
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index dcf56d4790..cc8b1c3cab 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -86,6 +86,7 @@
 #include "utils/resowner_private.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "optimizer/planner_index_locking.h"
 
 #define RELCACHE_INIT_FILEMAGIC		0x573266	/* version ID value */
 
@@ -290,7 +291,6 @@ static void write_item(const void *data, Size len, FILE *fp);
 static void formrdesc(const char *relationName, Oid relationReltype,
 					  bool isshared, int natts, const FormData_pg_attribute *attrs);
 
-static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic);
 static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
@@ -298,7 +298,6 @@ static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
 static TupleDesc GetPgClassDescriptor(void);
-static TupleDesc GetPgIndexDescriptor(void);
 static void AttrDefaultFetch(Relation relation, int ndef);
 static int	AttrDefaultCmp(const void *a, const void *b);
 static void CheckConstraintFetch(Relation relation);
@@ -330,7 +329,7 @@ static void unlink_initfile(const char *initfilename, int elevel);
  *		NB: the returned tuple has been copied into palloc'd storage
  *		and must eventually be freed with heap_freetuple.
  */
-static HeapTuple
+HeapTuple
 ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic)
 {
 	HeapTuple	pg_class_tuple;
@@ -4334,7 +4333,7 @@ GetPgClassDescriptor(void)
 	return pgclassdesc;
 }
 
-static TupleDesc
+TupleDesc
 GetPgIndexDescriptor(void)
 {
 	static TupleDesc pgindexdesc = NULL;
@@ -4680,6 +4679,7 @@ RelationGetIndexList(Relation relation)
 	ScanKeyData skey;
 	HeapTuple	htup;
 	List	   *result;
+	List	   *bitmaps;
 	List	   *oldlist;
 	char		replident = relation->rd_rel->relreplident;
 	Oid			pkeyIndex = InvalidOid;
@@ -4697,6 +4697,7 @@ RelationGetIndexList(Relation relation)
 	 * if we get some sort of error partway through.
 	 */
 	result = NIL;
+	bitmaps = NIL;
 
 	/* Prepare to scan pg_index for entries having indrelid = this rel. */
 	ScanKeyInit(&skey,
@@ -4724,6 +4725,10 @@ RelationGetIndexList(Relation relation)
 		/* add index's OID to result list */
 		result = lappend_oid(result, index->indexrelid);
 
+		/* build bitmap and add to bitmaps list, but only for valid indexes */
+		if (index->indisvalid)
+			bitmaps = lappend(bitmaps, s64_RelationBuildIndexBitmapset(htup));
+
 		/*
 		 * Invalid, non-unique, non-immediate or predicate indexes aren't
 		 * interesting for either oid indexes or replication identity indexes,
@@ -4750,10 +4755,14 @@ RelationGetIndexList(Relation relation)
 	/* Sort the result list into OID order, per API spec. */
 	list_sort(result, list_oid_cmp);
 
+	/* sort the bitmaps into OID order, per API spec. */
+	list_sort(bitmaps, list_bitmapset_cmp);
+
 	/* Now save a copy of the completed list in the relcache entry. */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	oldlist = relation->rd_indexlist;
 	relation->rd_indexlist = list_copy(result);
+	relation->rd_indexprs = list_copy(bitmaps);
 	relation->rd_pkindex = pkeyIndex;
 	if (replident == REPLICA_IDENTITY_DEFAULT && OidIsValid(pkeyIndex))
 		relation->rd_replidindex = pkeyIndex;
diff --git a/src/include/optimizer/planner_index_locking.h b/src/include/optimizer/planner_index_locking.h
new file mode 100644
index 0000000000..27adec56d7
--- /dev/null
+++ b/src/include/optimizer/planner_index_locking.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <postgres.h>
+
+#include <nodes/plannodes.h>
+#include <optimizer/paths.h>
+#include <utils/rel.h>
+
+extern bool FilterIndexes;
+
+// introduces a bitmap that caches which index covers which columns, so that we can filter the
+// possible indexes we should add to any RelOptInfo* to only be the ones that have any overlap with
+// the requested columns for a relation.
+
+extern TupleDesc GetPgIndexDescriptor(void);
+extern HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_historic);
+extern List     *s64_RelationGetIndexBitmapList(Relation relation);
+extern Oid       s64_RelationGetBestIndexForIndexOnly(Relation relation, Bitmapset *required);
+extern void      s64_add_indexes(PlannerInfo *root);
+extern void      s64_add_indexes_for_rel(PlannerInfo *root,
+                                         Oid          relationObjectId,
+                                         bool         inhparent,
+                                         RelOptInfo  *rel);
+
+typedef struct IndexBitmapset
+{
+    Oid        Index;
+    Bitmapset *Keys;
+} IndexBitmapset;
+extern IndexBitmapset *s64_RelationBuildIndexBitmapset(HeapTuple htup);
+extern bool            s64_IsUnnecessaryIndex(PlannerInfo    *root,
+                                              IndexBitmapset *bitmap,
+                                              Bitmapset      *clauses,
+                                              Bitmapset      *all,
+                                              Oid             smallestIndex);
+
+extern int        list_bitmapset_qcmp(const void *p1, const void *p2);
+extern int        list_bitmapset_cmp(const ListCell *p1, const ListCell *p2);
+extern Bitmapset *s64_RelationUsedTList(PlannerInfo *root, RelOptInfo *rel);
+extern Bitmapset *s64_RelationUsedClauses(PlannerInfo *root, RelOptInfo *rel);
-- 
2.34.1

Attachment: schema.sql
Description: application/sql

function thread_init()
  drv = sysbench.sql.driver()
  con = drv:connect()
end

function thread_done()
  con:disconnect()
end

function event()
  con:query('SELECT * FROM synth_table WHERE col05 = 52')
end

Reply via email to