-
Notifications
You must be signed in to change notification settings - Fork 248
[Bug] Fix ORCA selectivity damping for outer references #2006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -81,7 +81,6 @@ CFilterStatsProcessor::SelectivityOfPredicate(CMemoryPool *mp, | |
| CColRefSet *used_col_refs = pred->DeriveUsedColumns(); | ||
| CColRefSet *used_local_col_refs = | ||
| GPOS_NEW(mp) CColRefSet(mp, *used_col_refs); | ||
| ULONG num_outer_ref_preds = 0; | ||
|
|
||
| if (nullptr != outer_refs) | ||
| { | ||
|
|
@@ -101,7 +100,7 @@ CFilterStatsProcessor::SelectivityOfPredicate(CMemoryPool *mp, | |
|
|
||
| const COptCtxt *poctxt = COptCtxt::PoctxtFromTLS(); | ||
| CMDAccessor *md_accessor = poctxt->Pmda(); | ||
| // grab default stats config | ||
| // use the current optimizer statistics configuration | ||
| CStatisticsConfig *stats_config = | ||
| poctxt->GetOptimizerConfig()->GetStatsConf(); | ||
| // we don't care about the width of the columns, just the row count | ||
|
|
@@ -115,14 +114,15 @@ CFilterStatsProcessor::SelectivityOfPredicate(CMemoryPool *mp, | |
| IStatistics *result_stats = CFilterStatsProcessor::MakeStatsFilter( | ||
| mp, dynamic_cast<CStatistics *>(base_table_stats), pred_stats, false); | ||
|
|
||
| CDouble result = result_stats->Rows() / base_table_stats->Rows(); | ||
| BOOL have_local_preds = (result < 1.0); | ||
| const CDouble local_selectivity = | ||
| result_stats->Rows() / base_table_stats->Rows(); | ||
| pred_stats->Release(); | ||
| used_local_col_refs->Release(); | ||
| base_table_stats->Release(); | ||
| dummy_width_set->Release(); | ||
|
|
||
| // handle outer_refs | ||
| // estimate outer predicates using statistics after local filtering | ||
| CDoubleArray *outer_scale_factors = GPOS_NEW(mp) CDoubleArray(mp); | ||
| if (nullptr != expr_with_outer_refs) | ||
| { | ||
| CExpressionArray *outer_ref_exprs = | ||
|
|
@@ -132,7 +132,13 @@ CFilterStatsProcessor::SelectivityOfPredicate(CMemoryPool *mp, | |
| for (ULONG ul = 0; ul < size; ul++) | ||
| { | ||
| CExpression *pexpr = (*outer_ref_exprs)[ul]; | ||
| if (CUtils::FScalarConstTrue(pexpr)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| CColRef *local_col_ref = nullptr; | ||
| CDouble scale_factor = 1 / CHistogram::DefaultSelectivity; | ||
|
|
||
| if (CPredicateUtils::FIdentCompareOuterRefExprIgnoreCast( | ||
| pexpr, outer_refs, &local_col_ref)) | ||
|
|
@@ -144,64 +150,30 @@ CFilterStatsProcessor::SelectivityOfPredicate(CMemoryPool *mp, | |
| GPOS_ASSERT(nullptr != local_col_ref); | ||
| CDouble ndv = result_stats->GetNDVs(local_col_ref); | ||
|
|
||
| if (ndv < 1.0) | ||
| { | ||
| // An NDV of less than 1 means that we have no stats on this column | ||
| result = result * CHistogram::DefaultSelectivity; | ||
| } | ||
| else | ||
| // an NDV below 1 means that we have no stats on this column | ||
| if (ndv >= 1.0) | ||
| { | ||
| result = result * (1 / ndv); | ||
| scale_factor = ndv; | ||
| } | ||
| } | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So you suggest to add here a block: else
{
scale_factor =
CScaleFactorUtils::DefaultInequalityJoinPredScaleFactor;
}? |
||
| else | ||
| { | ||
| // a comparison col op <outer ref> other than an equals | ||
| result = result * CHistogram::DefaultSelectivity; | ||
| } | ||
| num_outer_ref_preds++; | ||
| } | ||
| else | ||
| { | ||
| // if it is a true filter, then we had no expressions with outer refs | ||
| if (!CUtils::FScalarConstTrue(pexpr)) | ||
| { | ||
| // some other expression, not of the form col op <outer ref>, | ||
| // e.g. an OR expression | ||
| result = result * CHistogram::DefaultSelectivity; | ||
| num_outer_ref_preds++; | ||
| } | ||
| } | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
And here you suggest to add a block: else
{
// if it is a true filter, then we had no expressions with outer refs
if (!CUtils::FScalarConstTrue(pexpr))
{
// some other expression, not of the form col op <outer ref>,
// e.g. an OR expression
scale_factor = CScaleFactorUtils::DefaultJoinPredScaleFactor;
}
}? Could it be too severe reduction in selectivity? |
||
| outer_scale_factors->Append(GPOS_NEW(mp) CDouble(scale_factor)); | ||
| } | ||
|
|
||
| expr_with_outer_refs->Release(); | ||
| outer_ref_exprs->Release(); | ||
| } | ||
|
|
||
| // apply damping factor to the outer ref predicates whose selectivities we multiplied above | ||
| if (have_local_preds) | ||
| { | ||
| // add one for the combined non-outer refs which were dampened internally, | ||
| // but not in combination with the preds on outer refs | ||
| num_outer_ref_preds++; | ||
| } | ||
| if (1 < num_outer_ref_preds) | ||
| { | ||
| CStatisticsConfig *stats_config = | ||
| CStatisticsConfig::PstatsconfDefault(mp); | ||
|
|
||
| result = | ||
| std::min(result.Get() / CScaleFactorUtils::DampedFilterScaleFactor( | ||
| stats_config, num_outer_ref_preds) | ||
| .Get(), | ||
| 1.0); | ||
|
|
||
| stats_config->Release(); | ||
| } | ||
| const CDouble outer_scale_factor = | ||
| CScaleFactorUtils::CalcScaleFactorCumulativeConj(stats_config, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reusing
n=2: both x1.78. n=3: old x2.37, new x5.62. n=4: old x3.16, new x17.8. Repro on the patched build (AO table, CREATE TABLE ao3 (id int, x int, y int, z int, w int) WITH (appendonly=true) DISTRIBUTED RANDOMLY;
INSERT INTO ao3 SELECT n, n % 3, (n/3) % 3, (n/9) % 3, n % 7 FROM generate_series(0, 9999) g(n);
CREATE INDEX ao3_xyz ON ao3 USING btree (x, y, z);
CREATE TABLE out3 (x int, y int, z int) DISTRIBUTED REPLICATED;
INSERT INTO out3 VALUES (0,0,0);
ANALYZE ao3; ANALYZE out3;
SET optimizer_enable_hashjoin = off;
EXPLAIN SELECT i.* FROM out3 o CROSS JOIN ao3 i WHERE i.x = o.x AND i.y = o.y AND i.z = o.z;True selectivity is 371/10000 = 0.037. gdb at the return: old
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree for this case
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess if the problem happens only for a few number of predicate we can keep the previous formula but cap its result at local selectivity. |
||
| outer_scale_factors); | ||
| outer_scale_factors->Release(); | ||
| result_stats->Release(); | ||
| local_expr->Release(); | ||
|
|
||
| return result; | ||
| // Outer selectivities are conditional on the local filter. Damping only | ||
| // their conjunction preserves the local estimate as an upper bound. | ||
| return local_selectivity / outer_scale_factor; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This treats the local group and the outer group as independent (no damping between them), whereas the old code counted the local group as one more damped predicate. Two consequences I measured:
The stated invariant (
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I guess there is no problem here ( I'll add a patch that is coherent with your suggestion.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I looked closer at ORCA's local path (
For example, let's say we have the same SELECT *
FROM damping_partial
WHERE a = 10 AND b = 0;Total rows = 20000 The overall formula to count such dependencies: /*
* Now factor in the selectivity for all the "implied" clauses into
* the final one, using this formula:
*
* P(a,b) = P(a) * (f + (1-f) * P(b))
*
* where 'f' is the degree of validity of the dependency.
*/Existing implementation uses the total frequency of the normalized Correct implementation (when we first get a histogram after applying a filter and then count scale factor) uses the selectivity of b = 0, giving The real value number of rows with It seems important because after the function |
||
| } | ||
|
|
||
| // create new structure from a list of statistics filters | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, and pre-existing, but since this block is being rewritten with
ParseCmpType()already available: these outer-ref conjuncts are join predicates from a statistics standpoint (see the comment aboveDeriveStatsWithOuterRefsinCJoinStatsProcessor.cpp), and the real join pipeline scores them differently:<, <=, >, >=->CScaleFactorUtils::DefaultInequalityJoinPredScaleFactor(3.0) inCHistogram.cppCScaleFactorUtils::DefaultJoinPredScaleFactor(100) inCJoinStatsProcessor.cppHere both fall into
1 / CHistogram::DefaultSelectivity(2.5), so the same predicate is scored 2.5 for index ranking and 3.0 (or 100) for cardinality. Using the named join constants would align the two without changing the structure;1 / CHistogram::DefaultSelectivityis also already spelled out verbatim in three other places in stats code, so a named constant would help either way.