Fix optimizer performance regression for large IN#22664
Fix optimizer performance regression for large IN#22664ZacBlanco merged 3 commits intoprestodb:masterfrom
Conversation
37237da to
298bb28
Compare
|
"The main problem is that when the DIsjointRangeDomainHistogram was introduced it was designed as immutable. When folding all of the instances together via the reduce() call at FilterStatsCalculator#L755 it re-creates the internal TreeRangeSet. The set of disjoint ranges is the size of the IN list, so we were re-creating the set 10k times. The first with range a set size of 1, then 2, ... etc up to 10k." Tangential note: this is exactly the pattern that led to the handshoe DoS attack on protobuf. Immutable data structures are not as risk-free as they seem. |
|
I missed some context, but why is this a problem when the property (use_histogram) is turned off? |
|
This happened because the histograms were still computed in Before the histogram PR, this line was not there. For a large |
|
Thanks for identifying the root cause so fast!
Second @jaystarshot on this. I think we need to have this histogram computation disabled when this property is turned off. Ideally, we should have the histogram related changes to be completely turned off when the property is set to false. I see that this was discussed in the original PR #21236 (comment), and understand that this may need additional code change and having additional boolean flag passed around. But I think we need to find a sweet balance point, and try to gate as much change as possible. |
115f618 to
d74daa7
Compare
d74daa7 to
ffdf74b
Compare
presto-main/src/main/java/com/facebook/presto/cost/VariableStatsEstimate.java
Outdated
Show resolved
Hide resolved
presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueries.java
Show resolved
Hide resolved
| OptionalDouble literalValue, | ||
| ComparisonExpression.Operator operator) | ||
| ComparisonExpression.Operator operator, | ||
| Optional<Session> session) |
There was a problem hiding this comment.
If only histogram field in the session is used, can we pass a boolean instead of the whole session around?
There was a problem hiding this comment.
Do you see a specific downside to passing the session? If we intend to make changes in the future which reference more feature flags, I feel it would be better to pass the session rather than adding a new argument for each potentially new flag
There was a problem hiding this comment.
I was thinking about keeping it consistent as I see in some other cases it's passing a boolean flag. But also fine if you think passing session is better.
ffdf74b to
9390dcf
Compare
...java/com/facebook/presto/operator/scalar/queryplan/TestJsonPrestoQueryPlanFunctionUtils.java
Outdated
Show resolved
Hide resolved
10d4345 to
60c5f06
Compare
60c5f06 to
43ef3e7
Compare
| // We ignore the histogram during serialization because histograms can be | ||
| // quite large. Histograms are not used outside the coordinator, so there | ||
| // isn't a need to serialize them | ||
| @JsonIgnore |
There was a problem hiding this comment.
@rschlussel This is the change to prevent serializing histograms. An associated test is added in TestVariableStatsEstimate
rschlussel
left a comment
There was a problem hiding this comment.
just fix the json property and should be good.
presto-main/src/main/java/com/facebook/presto/cost/VariableStatsEstimate.java
Outdated
Show resolved
Hide resolved
43ef3e7 to
bf57810
Compare
| @JsonProperty("highValue") double highValue, | ||
| @JsonProperty("nullsFraction") double nullsFraction, | ||
| @JsonProperty("averageRowSize") double averageRowSize, | ||
| @JsonProperty("distinctValuesCount") double distinctValuesCount) |
There was a problem hiding this comment.
ah sorry I thought you should be able to remove the json property from the original constructor, but I can't find similar examples, so maybe not. Anyway, before was fine and this is also fine.
hantangwangd
left a comment
There was a problem hiding this comment.
The description in the first commit has a little inaccuracy,ConnectorHistogram have 2 methods, not 4 :-).
bf57810 to
9d6b7fb
Compare
hantangwangd
left a comment
There was a problem hiding this comment.
Thanks for the great work, overall LGTM except some little things.
presto-main/src/main/java/com/facebook/presto/cost/DisjointRangeDomainHistogram.java
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/HistogramCalculator.java
Outdated
Show resolved
Hide resolved
9d6b7fb to
6b04326
Compare
This commits provides two critical changes: 1. Adds a new enum value to ColumnStatisticType: Histogram. 2. Utilizes the new histograms in optimizer's cost calculations Implementation details below: With this change, a new column statistic type for histograms is introduced. In addition, a new SPI class `ConnectorHistogram` is also introduced. This interface is designed to be able to be implemented by either the connectors or in the main presto codebase. This should allow connectors to return utilize histogram statistics in any format regardless of the source. The API is straightforward and includes 2 methods. - cumulativeProbability(double value, boolean inclusive): -> CDF function - inverseCumulativeProbability(double probability) -> inverse CDF function A reference implementation is provided inside of UniformDistributionHistogram. This implementation results in the same logic and same plans as the previous cost-based calculations. The math ends up being the same, but just utilizing the histogram API. Additionally, to propagate histogram information further up into a plan, another implementation of histograms is provided inside of DisjointRangeDomainHistogram. This class is used to bound a source histogram with a given domain as additional filters may be applied further up in the plan. Previously all cost calculations were performed inside of the ComparisonStatsCalculator using logic from the StatisticRange class to calculate the filter factors of overlapping and intersecting ranges. This change introduces cost calculation using the new histogram model and API. The core of the filter proportion calculation logic exists in the new HistogramCalculator utility class. If the underlying Histogram implementation is swapped from the UniformDistributionHistogram, then the stats calculator will calculate the costs using the histogram information.
6b04326 to
e7061d8
Compare
hantangwangd
left a comment
There was a problem hiding this comment.
LGTM. Thanks for the fix and the great work!
presto-main/src/main/java/com/facebook/presto/cost/PlanNodeStatsEstimateMath.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/StatisticRange.java
Outdated
Show resolved
Hide resolved
presto-main/src/main/java/com/facebook/presto/cost/ConnectorFilterStatsCalculatorService.java
Show resolved
Hide resolved
This bug occurs due to large IN lists. When computing statistics for queries with this clause, we generate an instance of VariableStatisticEstimate for each expression in the list. For example, a query with SELECT ... IN (1, 2, ... 10_000); generates 10k VariableStatisticEstimate instances. Then, for each instance, we sum the statistics and distinct values to get a final result to determine the probability of a value occurring. Creating these VariableStatisticEstimate instances were not the root cause of the issue. The main problem is that when the DIsjointRangeDomainHistogram was introduced it was designed as immutable. When folding all the instances together through the `reduce()` call at FilterStatsCalculator#L755, it re-creates the internal TreeRangeSet. The set of disjoint ranges is the size of the IN list, so we were re-creating the set 10k times. The first with range a set size of 1, then 2, ... etc up to 10k. So the running time of this ended up being polynomial. The following change updates the DisjointRangeDomain histogram to use a lazily initialized RangeSet. This prevents incurring the high cost of re-creating the range set for every new addition to the IN list. Additionally, the StatisticRange class now serializes to a byte encoded format to decrease the amount of bytes required to serialize plans with many filters. This is mainly useful for when a query has thousands of filters in a complex plan and the filters are applied to the histogram.
e7061d8 to
75c1c07
Compare
|
I saw a test failure today from testLargeInWithHistograms test #22944 |
Description
Fixes the bug found in #22661
This bug occurs due to large IN lists. When computing
statistics for queries with this clause, we generate an instance
of VariableStatisticEstimate for each expression in the list.
For example, a query with
generates 10k VariableStatisticEstimate instances. Then, for
each instance, we sum the statistics and distinct values
to get a final result to determine the probability of a value
occurring. Creating these VariableStatisticEstimate instances
were not the root cause of the issue.
The main problem is that when the
DIsjointRangeDomainHistogram was introduced it was designed as
immutable. When folding all the instances together through
the
reduce()call at FilterStatsCalculator#L755, it re-createsthe internal TreeRangeSet. The set of disjoint ranges is the size
of the IN list, so we were re-creating the set 10k times. The first
with range a set size of 1, then 2, ... etc up to 10k. So the
running time of this ended up being polynomial.
The following change updates the DisjointRangeDomain histogram
to use a lazily initialized RangeSet. This prevents incurring the
high cost of re-creating the range set for every new addition to
the IN list.
Additionally, the StatisticRange class now serializes to a byte
encoded format to decrease the amount of bytes required to
serialize plans with many filters. This is mainly useful for
when a query has thousands of filters in a complex plan and the
filters are applied to the histogram.
Impact
experimental.internal-communication.max-task-update-sizedue to filter ranges also being stored on top of the histogram. In local testing I've been able to hit about 17k filter values before reaching the plan serialization limit. To combat this, I've disabled histograms from being serialized in the JSON structure of the statistics because it shouldn't be used outside of the coordinator.optimizer_use_histogramsis set to false.Test Plan
Contributor checklist
Release Notes