Skip to content

Commit 1a179f3

Browse files
committed
Fix GEQO to not assume its join order heuristic always works.
Back in commit 400e2c9 I rewrote GEQO's gimme_tree function to improve its heuristic for modifying the given tour into a legal join order. In what can only be called a fit of hubris, I supposed that this new heuristic would *always* find a legal join order, and ripped out the old logic that allowed gimme_tree to sometimes fail. The folly of this is exposed by bug #12760, in which the "greedy" clumping behavior of merge_clump() can lead it into a dead end which could only be recovered from by un-clumping. We have no code for that and wouldn't know exactly what to do with it if we did. Rather than try to improve the heuristic rules still further, let's just recognize that it *is* a heuristic and probably must always have failure cases. So, put back the code removed in the previous commit to allow for failure (but comment it a bit better this time). It's possible that this code was actually fully correct at the time and has only been broken by the introduction of LATERAL. But having seen this example I no longer have much faith in that proposition, so back-patch to all supported branches.
1 parent 1f393fc commit 1a179f3

File tree

3 files changed

+49
-11
lines changed

3 files changed

+49
-11
lines changed

src/backend/optimizer/geqo/geqo_eval.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ static bool desirable_join(PlannerInfo *root,
4949
* geqo_eval
5050
*
5151
* Returns cost of a query tree as an individual of the population.
52+
*
53+
* If no legal join order can be extracted from the proposed tour,
54+
* returns DBL_MAX.
5255
*/
5356
Cost
5457
geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
5558
{
5659
MemoryContext mycontext;
5760
MemoryContext oldcxt;
5861
RelOptInfo *joinrel;
59-
Path *best_path;
6062
Cost fitness;
6163
int savelength;
6264
struct HTAB *savehash;
@@ -100,16 +102,22 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
100102

101103
/* construct the best path for the given combination of relations */
102104
joinrel = gimme_tree(root, tour, num_gene);
103-
best_path = joinrel->cheapest_total_path;
104105

105106
/*
106-
* compute fitness
107+
* compute fitness, if we found a valid join
107108
*
108109
* XXX geqo does not currently support optimization for partial result
109110
* retrieval, nor do we take any cognizance of possible use of
110111
* parameterized paths --- how to fix?
111112
*/
112-
fitness = best_path->total_cost;
113+
if (joinrel)
114+
{
115+
Path *best_path = joinrel->cheapest_total_path;
116+
117+
fitness = best_path->total_cost;
118+
}
119+
else
120+
fitness = DBL_MAX;
113121

114122
/*
115123
* Restore join_rel_list to its former state, and put back original
@@ -134,7 +142,8 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
134142
* 'tour' is the proposed join order, of length 'num_gene'
135143
*
136144
* Returns a new join relation whose cheapest path is the best plan for
137-
* this join order.
145+
* this join order. NB: will return NULL if join order is invalid and
146+
* we can't modify it into a valid order.
138147
*
139148
* The original implementation of this routine always joined in the specified
140149
* order, and so could only build left-sided plans (and right-sided and
@@ -147,7 +156,10 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
147156
* postpones joins that are illegal or seem unsuitable according to some
148157
* heuristic rules. This allows correct bushy plans to be generated at need,
149158
* and as a nice side-effect it seems to materially improve the quality of the
150-
* generated plans.
159+
* generated plans. Note however that since it's just a heuristic, it can
160+
* still fail in some cases. (In particular, we might clump together
161+
* relations that actually mustn't be joined yet due to LATERAL restrictions;
162+
* since there's no provision for un-clumping, this must lead to failure.)
151163
*/
152164
RelOptInfo *
153165
gimme_tree(PlannerInfo *root, Gene *tour, int num_gene)
@@ -164,9 +176,8 @@ gimme_tree(PlannerInfo *root, Gene *tour, int num_gene)
164176
* to; if there is none then it becomes a new clump of its own. When we
165177
* enlarge an existing clump we check to see if it can now be merged with
166178
* any other clumps. After the tour is all scanned, we forget about the
167-
* heuristics and try to forcibly join any remaining clumps. Some forced
168-
* joins might still fail due to semantics, but we should always be able
169-
* to find some join order that works.
179+
* heuristics and try to forcibly join any remaining clumps. If we are
180+
* unable to merge all the clumps into one, fail.
170181
*/
171182
clumps = NIL;
172183

@@ -208,7 +219,7 @@ gimme_tree(PlannerInfo *root, Gene *tour, int num_gene)
208219

209220
/* Did we succeed in forming a single join relation? */
210221
if (list_length(clumps) != 1)
211-
elog(ERROR, "failed to join all relations together");
222+
return NULL;
212223

213224
return ((Clump *) linitial(clumps))->joinrel;
214225
}

src/backend/optimizer/geqo/geqo_main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ geqo(PlannerInfo *root, int number_of_rels, List *initial_rels)
261261

262262
best_rel = gimme_tree(root, best_tour, pool->string_length);
263263

264+
if (best_rel == NULL)
265+
elog(ERROR, "geqo failed to make a valid plan");
266+
264267
/* DBG: show the query plan */
265268
#ifdef NOT_USED
266269
print_plan(best_plan, root);

src/backend/optimizer/geqo/geqo_pool.c

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,37 @@ random_init_pool(PlannerInfo *root, Pool *pool)
9292
{
9393
Chromosome *chromo = (Chromosome *) pool->data;
9494
int i;
95+
int bad = 0;
9596

96-
for (i = 0; i < pool->size; i++)
97+
/*
98+
* We immediately discard any invalid individuals (those that geqo_eval
99+
* returns DBL_MAX for), thereby not wasting pool space on them.
100+
*
101+
* If we fail to make any valid individuals after 10000 tries, give up;
102+
* this probably means something is broken, and we shouldn't just let
103+
* ourselves get stuck in an infinite loop.
104+
*/
105+
i = 0;
106+
while (i < pool->size)
97107
{
98108
init_tour(root, chromo[i].string, pool->string_length);
99109
pool->data[i].worth = geqo_eval(root, chromo[i].string,
100110
pool->string_length);
111+
if (pool->data[i].worth < DBL_MAX)
112+
i++;
113+
else
114+
{
115+
bad++;
116+
if (i == 0 && bad >= 10000)
117+
elog(ERROR, "geqo failed to make a valid plan");
118+
}
101119
}
120+
121+
#ifdef GEQO_DEBUG
122+
if (bad > 0)
123+
elog(DEBUG1, "%d invalid tours found while selecting %d pool entries",
124+
bad, pool->size);
125+
#endif
102126
}
103127

104128
/*

0 commit comments

Comments
 (0)