Skip to content

Fix optimizer performance regression for large IN#22664

Merged
ZacBlanco merged 3 commits intoprestodb:masterfrom
ZacBlanco:upstream-fix-slow-large-in
Jun 4, 2024
Merged

Fix optimizer performance regression for large IN#22664
ZacBlanco merged 3 commits intoprestodb:masterfrom
ZacBlanco:upstream-fix-slow-large-in

Conversation

@ZacBlanco
Copy link
Contributor

@ZacBlanco ZacBlanco commented May 3, 2024

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

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.

Impact

  • Re-adds the original changes from the histogram PR
  • Plans with a significant number of filters can more quickly hit the limit imposed by the configuration: experimental.internal-communication.max-task-update-size due 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.
  • Prevents performing any calculations on histograms when optimizer_use_histograms is set to false.

Test Plan

  • Added a test to ensure that large filter lists don't degrade the optimizer performance

Contributor checklist

  • Please make sure your submission complies with our development, formatting, commit message, and attribution guidelines.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.

Release Notes

== NO RELEASE NOTE ==

@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from 37237da to 298bb28 Compare May 3, 2024 19:42
@elharo
Copy link
Contributor

elharo commented May 4, 2024

"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.

elharo
elharo previously approved these changes May 4, 2024
@jaystarshot
Copy link
Member

I missed some context, but why is this a problem when the property (use_histogram) is turned off?

@ZacBlanco
Copy link
Contributor Author

ZacBlanco commented May 6, 2024

This happened because the histograms were still computed in PlanNodeStatsEstimateMath here which was not gated by the property:

https://github.com/ZacBlanco/prestodb/blob/b377b41532c4607dd79ff83fa56d9da374995a76/presto-main/src/main/java/com/facebook/presto/cost/PlanNodeStatsEstimateMath.java#L289

Before the histogram PR, this line was not there. For a large IN list we end up calling addColumnStats 1 time for each constant in the IN list. The reason the performance regressed is due to the cost of creating a new instance of the TreeRangeSet with many constant values

@feilong-liu
Copy link
Contributor

feilong-liu commented May 6, 2024

Thanks for identifying the root cause so fast!

I missed some context, but why is this a problem when the property (use_histogram) is turned off?

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.

@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch 9 times, most recently from 115f618 to d74daa7 Compare May 10, 2024 21:39
@ZacBlanco ZacBlanco marked this pull request as ready for review May 13, 2024 17:48
@ZacBlanco ZacBlanco requested a review from presto-oss May 13, 2024 17:48
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from d74daa7 to ffdf74b Compare May 14, 2024 21:52
OptionalDouble literalValue,
ComparisonExpression.Operator operator)
ComparisonExpression.Operator operator,
Optional<Session> session)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only histogram field in the session is used, can we pass a boolean instead of the whole session around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from ffdf74b to 9390dcf Compare May 16, 2024 23:05
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from 10d4345 to 60c5f06 Compare May 21, 2024 16:51
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from 60c5f06 to 43ef3e7 Compare May 23, 2024 22:37
Comment on lines +118 to +122
// 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rschlussel This is the change to prevent serializing histograms. An associated test is added in TestVariableStatsEstimate

rschlussel
rschlussel previously approved these changes May 24, 2024
Copy link
Contributor

@rschlussel rschlussel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just fix the json property and should be good.

rschlussel
rschlussel previously approved these changes May 24, 2024
@JsonProperty("highValue") double highValue,
@JsonProperty("nullsFraction") double nullsFraction,
@JsonProperty("averageRowSize") double averageRowSize,
@JsonProperty("distinctValuesCount") double distinctValuesCount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

aaneja
aaneja previously approved these changes May 28, 2024
Copy link
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description in the first commit has a little inaccuracy,ConnectorHistogram have 2 methods, not 4 :-).

@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from bf57810 to 9d6b7fb Compare May 29, 2024 18:19
Copy link
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the great work, overall LGTM except some little things.

@ZacBlanco ZacBlanco dismissed stale reviews from rschlussel and aaneja via 6b04326 May 30, 2024 23:02
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from 9d6b7fb to 6b04326 Compare May 30, 2024 23:02
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.
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from 6b04326 to e7061d8 Compare May 31, 2024 15:32
hantangwangd
hantangwangd previously approved these changes Jun 3, 2024
Copy link
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the fix and the great work!

feilong-liu
feilong-liu previously approved these changes Jun 3, 2024
ZacBlanco added 2 commits June 3, 2024 17:57
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.
@ZacBlanco ZacBlanco dismissed stale reviews from feilong-liu and hantangwangd via 75c1c07 June 4, 2024 01:04
@ZacBlanco ZacBlanco force-pushed the upstream-fix-slow-large-in branch from e7061d8 to 75c1c07 Compare June 4, 2024 01:04
Copy link
Contributor

@tdcmeehan tdcmeehan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ZacBlanco

@ZacBlanco ZacBlanco merged commit 39e11a5 into prestodb:master Jun 4, 2024
@rschlussel
Copy link
Contributor

I saw a test failure today from testLargeInWithHistograms test #22944

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants