@@ -183,6 +183,17 @@ restore_selectivities(List *clauselist,
183
183
return lst ;
184
184
}
185
185
186
+ static bool
187
+ HasNeverVisitedNodes (PlanState * ps , void * context )
188
+ {
189
+ Assert (context == NULL );
190
+
191
+ InstrEndLoop (ps -> instrument );
192
+ if (ps -> instrument == NULL || ps -> instrument -> nloops == 0 )
193
+ return true;
194
+
195
+ return planstate_tree_walker (ps , HasNeverVisitedNodes , NULL );
196
+ }
186
197
/*
187
198
* Walks over obtained PlanState tree, collects relation objects with their
188
199
* clauses, selectivities and relids and passes each object to learn_sample.
@@ -233,7 +244,6 @@ learnOnPlanState(PlanState *p, void *context)
233
244
double learn_rows = 0. ;
234
245
double predicted = 0. ;
235
246
236
- InstrEndLoop (p -> instrument );
237
247
if (p -> instrument -> nloops > 0. )
238
248
{
239
249
/* If we can strongly calculate produced rows, do it. */
@@ -269,41 +279,42 @@ learnOnPlanState(PlanState *p, void *context)
269
279
270
280
if (p -> plan -> predicted_cardinality > 0. )
271
281
predicted = p -> plan -> predicted_cardinality ;
272
- else if (p -> plan -> parallel_aware ||
273
- (p -> plan -> path_parallel_workers > 0 &&
274
- (nodeTag (p -> plan ) == T_HashJoin ||
275
- nodeTag (p -> plan ) == T_MergeJoin ||
276
- nodeTag (p -> plan ) == T_NestLoop )))
282
+ else if (IsParallelTuplesProcessing (p -> plan ))
277
283
predicted = p -> plan -> plan_rows *
278
284
get_parallel_divisor (p -> plan -> path_parallel_workers );
279
285
else
280
286
predicted = p -> plan -> plan_rows ;
281
287
282
288
/* It is needed for correct exp(result) calculation. */
289
+ predicted = clamp_row_est (predicted );
283
290
learn_rows = clamp_row_est (learn_rows );
284
291
}
285
292
else
286
293
{
287
294
/*
288
- * LAV: I found only one case for this code: if query returns
289
- * with error. May be we will process this case and not learn
290
- * AQO on the query?
295
+ * LAV: I found two cases for this code:
296
+ * 1. if query returns with error.
297
+ * 2. plan node has never visited.
298
+ * Both cases can't be used to learning AQO because give an
299
+ * incorrect number of rows.
291
300
*/
292
- learn_rows = 1. ;
301
+ elog ( PANIC , "AQO: impossible situation" ) ;
293
302
}
294
303
295
- cardinality_sum_errors += fabs ( log ( predicted ) -
296
- log (learn_rows ));
304
+ Assert ( predicted >= 1 && learn_rows >= 1 );
305
+ cardinality_sum_errors += fabs ( log ( predicted ) - log (learn_rows ));
297
306
cardinality_num_objects += 1 ;
298
307
299
308
/*
300
309
* A subtree was not visited. In this case we can not teach AQO
301
310
* because ntuples value is equal to 0 and we will got
302
311
* learn rows == 1.
303
- * It is false teaching, because at another place of a plan
304
- * scanning of the node may produce many tuples.
312
+ * It is false knowledge: at another place of a plan, scanning of
313
+ * the node may produce many tuples.
305
314
*/
306
- if (ctx -> learn && p -> instrument -> nloops >= 1 )
315
+ Assert (p -> instrument -> nloops >= 1 );
316
+
317
+ if (ctx -> learn )
307
318
learn_sample (SubplanCtx .clauselist , SubplanCtx .selectivities ,
308
319
p -> plan -> path_relids , learn_rows , predicted );
309
320
}
@@ -327,16 +338,18 @@ update_query_stat_row(double *et, int *et_size,
327
338
double cardinality_error ,
328
339
int64 * n_exec )
329
340
{
330
- int i ;
341
+ int i ;
331
342
332
- if (cardinality_error >= 0 )
333
- {
334
- if (* ce_size >= aqo_stat_size )
343
+ /*
344
+ * If plan contains one or more "never visited" nodes, cardinality_error
345
+ * have -1 value and will be written to the knowledge base. User can use it
346
+ * as a sign that AQO ignores this query.
347
+ */
348
+ if (* ce_size >= aqo_stat_size )
335
349
for (i = 1 ; i < aqo_stat_size ; ++ i )
336
350
ce [i - 1 ] = ce [i ];
337
351
* ce_size = (* ce_size >= aqo_stat_size ) ? aqo_stat_size : (* ce_size + 1 );
338
352
ce [* ce_size - 1 ] = cardinality_error ;
339
- }
340
353
341
354
if (* et_size >= aqo_stat_size )
342
355
for (i = 1 ; i < aqo_stat_size ; ++ i )
@@ -367,9 +380,12 @@ void
367
380
aqo_ExecutorStart (QueryDesc * queryDesc , int eflags )
368
381
{
369
382
instr_time current_time ;
383
+ bool use_aqo ;
384
+
385
+ use_aqo = !IsParallelWorker () && (query_context .use_aqo ||
386
+ query_context .learn_aqo || force_collect_stat );
370
387
371
- if (!IsParallelWorker () &&
372
- (query_context .use_aqo || query_context .learn_aqo || force_collect_stat ))
388
+ if (use_aqo )
373
389
{
374
390
INSTR_TIME_SET_CURRENT (current_time );
375
391
INSTR_TIME_SUBTRACT (current_time , query_context .query_starttime );
@@ -391,8 +407,8 @@ aqo_ExecutorStart(QueryDesc *queryDesc, int eflags)
391
407
standard_ExecutorStart (queryDesc , eflags );
392
408
393
409
/* Plan state has initialized */
394
-
395
- StorePlanInternals (queryDesc );
410
+ if ( use_aqo )
411
+ StorePlanInternals (queryDesc );
396
412
}
397
413
398
414
/*
@@ -403,12 +419,15 @@ aqo_ExecutorStart(QueryDesc *queryDesc, int eflags)
403
419
void
404
420
aqo_ExecutorEnd (QueryDesc * queryDesc )
405
421
{
406
- double totaltime ;
407
- double cardinality_error ;
408
- QueryStat * stat = NULL ;
409
- instr_time endtime ;
422
+ double totaltime ;
423
+ double cardinality_error ;
424
+ QueryStat * stat = NULL ;
425
+ instr_time endtime ;
410
426
EphemeralNamedRelation enr = get_ENR (queryDesc -> queryEnv , PlanStateInfo );
411
427
428
+ cardinality_sum_errors = 0. ;
429
+ cardinality_num_objects = 0 ;
430
+
412
431
if (!ExtractFromQueryContext (queryDesc ))
413
432
/* AQO keep all query-related preferences at the query context.
414
433
* It is needed to prevent from possible recursive changes, at
@@ -418,8 +437,7 @@ aqo_ExecutorEnd(QueryDesc *queryDesc)
418
437
*/
419
438
goto end ;
420
439
421
- if (enr )
422
- njoins = * (int * ) enr -> reldata ;
440
+ njoins = (enr != NULL ) ? * (int * ) enr -> reldata : -1 ;
423
441
424
442
Assert (!IsParallelWorker ());
425
443
@@ -429,13 +447,11 @@ aqo_ExecutorEnd(QueryDesc *queryDesc)
429
447
query_context .collect_stat = false;
430
448
}
431
449
432
- if (query_context .learn_aqo || query_context .collect_stat )
450
+ if ((query_context .learn_aqo || query_context .collect_stat ) &&
451
+ !HasNeverVisitedNodes (queryDesc -> planstate , NULL ))
433
452
{
434
453
aqo_obj_stat ctx = {NIL , NIL , NIL , query_context .learn_aqo };
435
454
436
- cardinality_sum_errors = 0. ;
437
- cardinality_num_objects = 0 ;
438
-
439
455
learnOnPlanState (queryDesc -> planstate , (void * ) & ctx );
440
456
list_free (ctx .clauselist );
441
457
list_free (ctx .relidslist );
@@ -448,8 +464,7 @@ aqo_ExecutorEnd(QueryDesc *queryDesc)
448
464
INSTR_TIME_SUBTRACT (endtime , query_context .query_starttime );
449
465
totaltime = INSTR_TIME_GET_DOUBLE (endtime );
450
466
if (cardinality_num_objects > 0 )
451
- cardinality_error = cardinality_sum_errors /
452
- cardinality_num_objects ;
467
+ cardinality_error = cardinality_sum_errors / cardinality_num_objects ;
453
468
else
454
469
cardinality_error = -1 ;
455
470
@@ -668,6 +683,12 @@ RemoveFromQueryContext(QueryDesc *queryDesc)
668
683
unregister_ENR (queryDesc -> queryEnv , AQOPrivateData );
669
684
pfree (enr -> reldata );
670
685
pfree (enr );
686
+
687
+ /* Remove the plan state internals */
688
+ enr = get_ENR (queryDesc -> queryEnv , PlanStateInfo );
689
+ unregister_ENR (queryDesc -> queryEnv , PlanStateInfo );
690
+ pfree (enr -> reldata );
691
+ pfree (enr );
671
692
}
672
693
673
694
/*
@@ -705,13 +726,18 @@ void print_into_explain(PlannedStmt *plannedstmt, IntoClause *into,
705
726
ExplainPropertyText ("AQO mode" , "LEARN" , es );
706
727
break ;
707
728
case AQO_MODE_FROZEN :
708
- ExplainPropertyText ("AQO mode" , "FIXED " , es );
729
+ ExplainPropertyText ("AQO mode" , "FROZEN " , es );
709
730
break ;
710
731
default :
711
732
elog (ERROR , "Bad AQO state" );
712
733
break ;
713
734
}
714
735
736
+ /*
737
+ * Query hash provides an user the conveniently use of the AQO
738
+ * auxiliary functions.
739
+ */
740
+ ExplainPropertyInteger ("Query hash" , NULL , query_context .query_hash , es );
715
741
ExplainPropertyInteger ("JOINS" , NULL , njoins , es );
716
742
}
717
743
query_context .explain_aqo = false;
0 commit comments