limit-indexpath-plan-time.patch

text/x-patch

Filename: limit-indexpath-plan-time.patch
Type: text/x-patch
Part: 0
Message: Limiting the number of parameterized indexpaths created

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: unified
File+
src/backend/optimizer/path/indxpath.c 0 0
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 444ec2a40e4ac2b0ed8435017149c7664869e34c..78aaca4f279d6ba580643c75d82d82bfc88fba38 100644
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** static void consider_index_join_outer_re
*** 92,97 ****
--- 92,98 ----
  							   IndexClauseSet *eclauseset,
  							   List **bitindexpaths,
  							   List *indexjoinclauses,
+ 							   int considered_clauses,
  							   List **considered_relids);
  static void get_join_index_paths(PlannerInfo *root, RelOptInfo *rel,
  					 IndexOptInfo *index,
*************** static void get_join_index_paths(Planner
*** 101,106 ****
--- 102,109 ----
  					 List **bitindexpaths,
  					 Relids relids,
  					 List **considered_relids);
+ static bool eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
+ 					List *indexjoinclauses);
  static bool bms_equal_any(Relids relids, List *relids_list);
  static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
  				IndexOptInfo *index, IndexClauseSet *clauses,
*************** consider_index_join_clauses(PlannerInfo 
*** 447,452 ****
--- 450,456 ----
  							IndexClauseSet *eclauseset,
  							List **bitindexpaths)
  {
+ 	int			considered_clauses = 0;
  	List	   *considered_relids = NIL;
  	int			indexcol;
  
*************** consider_index_join_clauses(PlannerInfo 
*** 460,467 ****
  	 * filter (qpqual); which is where an available clause would end up being
  	 * applied if we omit it from the indexquals.
  	 *
! 	 * This looks expensive, but in practical cases there won't be very many
! 	 * distinct sets of outer rels to consider.
  	 *
  	 * For simplicity in selecting relevant clauses, we represent each set of
  	 * outer rels as a maximum set of clause_relids --- that is, the indexed
--- 464,474 ----
  	 * filter (qpqual); which is where an available clause would end up being
  	 * applied if we omit it from the indexquals.
  	 *
! 	 * This looks expensive, but in most practical cases there won't be very
! 	 * many distinct sets of outer rels to consider.  As a safety valve when
! 	 * that's not true, we use a heuristic: limit the number of outer rel sets
! 	 * considered to a multiple of the number of clauses considered.  (We'll
! 	 * always consider using each individual join clause, though.)
  	 *
  	 * For simplicity in selecting relevant clauses, we represent each set of
  	 * outer rels as a maximum set of clause_relids --- that is, the indexed
*************** consider_index_join_clauses(PlannerInfo 
*** 471,486 ****
--- 478,497 ----
  	for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
  	{
  		/* Consider each applicable simple join clause */
+ 		considered_clauses += list_length(jclauseset->indexclauses[indexcol]);
  		consider_index_join_outer_rels(root, rel, index,
  									   rclauseset, jclauseset, eclauseset,
  									   bitindexpaths,
  									   jclauseset->indexclauses[indexcol],
+ 									   considered_clauses,
  									   &considered_relids);
  		/* Consider each applicable eclass join clause */
+ 		considered_clauses += list_length(eclauseset->indexclauses[indexcol]);
  		consider_index_join_outer_rels(root, rel, index,
  									   rclauseset, jclauseset, eclauseset,
  									   bitindexpaths,
  									   eclauseset->indexclauses[indexcol],
+ 									   considered_clauses,
  									   &considered_relids);
  	}
  }
*************** consider_index_join_clauses(PlannerInfo 
*** 494,499 ****
--- 505,511 ----
   * 'rel', 'index', 'rclauseset', 'jclauseset', 'eclauseset', and
   *		'bitindexpaths' as above
   * 'indexjoinclauses' is a list of RestrictInfos for join clauses
+  * considered_clauses is the total number of clauses considered so far
   * '*considered_relids' is a list of all relids sets already considered
   */
  static void
*************** consider_index_join_outer_rels(PlannerIn
*** 504,509 ****
--- 516,522 ----
  							   IndexClauseSet *eclauseset,
  							   List **bitindexpaths,
  							   List *indexjoinclauses,
+ 							   int considered_clauses,
  							   List **considered_relids)
  {
  	ListCell   *lc;
*************** consider_index_join_outer_rels(PlannerIn
*** 522,528 ****
  		/*
  		 * Generate the union of this clause's relids set with each
  		 * previously-tried set.  This ensures we try this clause along with
! 		 * every interesting subset of previous clauses.
  		 *
  		 * Note: get_join_index_paths adds entries to *considered_relids, but
  		 * it prepends them to the list, so that we won't visit new entries
--- 535,543 ----
  		/*
  		 * Generate the union of this clause's relids set with each
  		 * previously-tried set.  This ensures we try this clause along with
! 		 * every interesting subset of previous clauses.  However, to avoid
! 		 * exponential growth of planning time when there are many clauses,
! 		 * limit the number of relid sets accepted to 10 * considered_clauses.
  		 *
  		 * Note: get_join_index_paths adds entries to *considered_relids, but
  		 * it prepends them to the list, so that we won't visit new entries
*************** consider_index_join_outer_rels(PlannerIn
*** 543,548 ****
--- 558,584 ----
  			if (bms_subset_compare(clause_relids, oldrelids) != BMS_DIFFERENT)
  				continue;
  
+ 			/*
+ 			 * If this clause was derived from an equivalence class, the
+ 			 * clause list may contain other clauses derived from the same
+ 			 * eclass.  We should not consider that combining this clause with
+ 			 * one of those clauses generates a usefully different
+ 			 * parameterization; so skip if any clause derived from the same
+ 			 * eclass would already have been included by oldrelids.
+ 			 */
+ 			if (rinfo->parent_ec &&
+ 				eclass_already_used(rinfo->parent_ec, oldrelids,
+ 									indexjoinclauses))
+ 				continue;
+ 
+ 			/*
+ 			 * If the number of relid sets considered exceeds our heuristic
+ 			 * limit, stop considering combinations of clauses.  We'll still
+ 			 * consider the current clause alone, though (below this loop).
+ 			 */
+ 			if (list_length(*considered_relids) >= 10 * considered_clauses)
+ 				break;
+ 
  			/* OK, try the union set */
  			get_join_index_paths(root, rel, index,
  								 rclauseset, jclauseset, eclauseset,
*************** get_join_index_paths(PlannerInfo *root, 
*** 648,653 ****
--- 684,711 ----
  }
  
  /*
+  * eclass_already_used
+  *		True if any join clause usable with oldrelids was generated from
+  *		the specified equivalence class.
+  */
+ static bool
+ eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
+ 					List *indexjoinclauses)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, indexjoinclauses)
+ 	{
+ 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ 
+ 		if (rinfo->parent_ec == parent_ec &&
+ 			bms_is_subset(rinfo->clause_relids, oldrelids))
+ 			return true;
+ 	}
+ 	return false;
+ }
+ 
+ /*
   * bms_equal_any
   *		True if relids is bms_equal to any member of relids_list
   *