Conversation
|
@findepi Gentle reminder on this PR. |
|
How does this relate to @clemensvonschwerin's #8659? |
|
@przemekak is going to help benchmark this |
|
cc @alexjo2144 |
|
Can you give a short explanation of how this is different from the existing |
|
@alexjo2144 Sure. Yeah it's indeed a bit confusing. Essentially the difference is that |
core/trino-main/src/main/java/io/trino/cost/TableStatsProvider.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/cost/TableStatsProvider.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Why do we need a new entity here, the tableStatsProvider?
Could StatsProvider sourceStats do the job?
There was a problem hiding this comment.
StatsProvider sourceStats is called to get children plan nodes' stats.
TableStatsProvider tableStatsProvider is used when calculating the "ultimate" source stats: table scan stats. In fact, all this PR does is to wire in a CachingTableStatsProvider for TableScanStatsRule to use.
There was a problem hiding this comment.
You can easily imagine CachingStatsProvider that caches stats on per-PlanNode basis. This would be a generally useful implementation, potentially allowing us to cut down some stats calculation cost.
The currently existing CachingStatsProvider caches stats in Memo's group. This is useful as well -- the group contains alternative plans (currently always exactly one at a time) that produce same relation. Same relation implies that once the stats are calculated, they are applicable to all group members.
Now, these two concepts are different, but not exclusionary:
- we could have "L2" cache that's on per-PlanNode basis
- then "L1" cache that's based on Memo group
Now the question is, whether per-PlanNode basis and per-TableHandle basis are significantly different.
I think there are not. I don't think we ever have a case where we have two TableScanNode that have same TableHandle.
There was a problem hiding this comment.
Now the question is, whether per-PlanNode basis and per-TableHandle basis are significantly different.
Actually they would be different.
Plan nodes have no equality, they compare by identity. Upon each exit from IterativeOptimizer (exit from Memo), a fully new plan structure is created. This can be improved a bit (eg produce new plan only if anything changed), but still would break identity-based caching, except for root nodes (table scans). Thus, the solution would look more generic, but would not actually be.
core/trino-main/src/main/java/io/trino/cost/CachingTableStatsProvider.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
A connector creates new TableHandles during various ConnectorMetadata.apply* calls.
A table handle may become "seen" in the plan and then discarded, make obsolete.
I think we should use weak keys here. Otherwise we need to size this as a regular cache.
(WeakHashMap provides weak keys with equality-based lookup, so i'd recommend that)
There was a problem hiding this comment.
I don't fully understand this comment.. My understanding is that CachingTableStatsProvider is created per query and its lifecycle is within query planning. So the cache here can be GCed after the query planning. What issue did you see with it?
There was a problem hiding this comment.
It would greatly help reviewing, if you had two commits
- all the logic, which adds new
PlanOptimizer.optimizeoverload, delegating to the old one- this would change only those PlanOptimizers which use this new info, or which delegate to some other PlanOptimizer
- remove the old method overload, which would add
, TableStatsProvider tableStatsProviderto many files (mechanical change)
There was a problem hiding this comment.
Makes sense. Separated them. Please feel free to squash them when merging if you feel appropriate.
...in/src/test/java/io/trino/sql/planner/optimizations/TestRemoveUnsupportedDynamicFilters.java
Outdated
Show resolved
Hide resolved
|
please don't rebase when applying comments. |
i see a conflict here, so you'll need a rebase. |
|
@lxynov let me know if you need help with the rebase |
bfb1298 to
be37e0d
Compare
be37e0d to
5d549d3
Compare
| this.metadata = requireNonNull(metadata, "metadata cannot be null"); | ||
| this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator cannot be null"); | ||
| this.session = requireNonNull(session, "session cannot be null"); | ||
| this.session = requireNonNull(session, "sesssion cannot be null"); |
| Optional.empty(), | ||
| Optional.empty()); | ||
|
|
||
| return singleAggregation( |
| public CachingTableStatsProvider(Metadata metadata) | ||
| { | ||
| this.metadata = metadata; | ||
| this.metadata = requireNonNull(metadata, "metadata is null"); |
There was a problem hiding this comment.
Squash "Address comments" with respective commits.
(You did rebase anyway, and split the commit into two in this PR, so keeping some changes as a fixup commit doesn't make review easier)
| } | ||
|
|
||
| protected abstract Optional<PlanNodeStatsEstimate> doCalculate(T node, StatsProvider sourceStats, Lookup lookup, Session session, TypeProvider types); | ||
|
|
There was a problem hiding this comment.
Can you give "Add table stats cache 2: remove method overloading" commit a better tittle?
e.g. how would you name the commit, it it was going in a separate PR?
| { | ||
| TableStatsProvider EMPTY = (session, tableHandle) -> TableStatistics.empty(); | ||
|
|
||
| public TableStatistics getTableStatistics(Session session, TableHandle tableHandle); |
There was a problem hiding this comment.
it's instantiated on per-query basis, so can take session as constructor argument
There was a problem hiding this comment.
You can easily imagine CachingStatsProvider that caches stats on per-PlanNode basis. This would be a generally useful implementation, potentially allowing us to cut down some stats calculation cost.
The currently existing CachingStatsProvider caches stats in Memo's group. This is useful as well -- the group contains alternative plans (currently always exactly one at a time) that produce same relation. Same relation implies that once the stats are calculated, they are applicable to all group members.
Now, these two concepts are different, but not exclusionary:
- we could have "L2" cache that's on per-PlanNode basis
- then "L1" cache that's based on Memo group
Now the question is, whether per-PlanNode basis and per-TableHandle basis are significantly different.
I think there are not. I don't think we ever have a case where we have two TableScanNode that have same TableHandle.
| @Override | ||
| protected Optional<PlanNodeStatsEstimate> doCalculate(TableScanNode node, StatsProvider sourceStats, Lookup lookup, Session session, TypeProvider types) | ||
| { | ||
| throw new IllegalStateException("This is not expected to be called because the other overload is implemented."); |
|
I applied comments and posted a copy of this PR: #13047 |
Cache table statistics during query planning. So they can be shared by CBOs like
ReorderJoinsandDetermineJoinDistributionType. It helps reduce Iceberg metadata processing when planning a query joining Iceberg tables.To demo the helpfulness of this PR and #11858, I did some experiment with a locally-running Trino and a production join query,
#11858and this PR: the planning takes 2 min 24s#11858but not this PR: the planning takes 1 min 12sDescription
Improvement.
Engine.
Improve query planning performance.
Related issues, pull requests, and links
#11708 #11858
Documentation
(x) No documentation is needed.
( ) Sufficient documentation is included in this PR.
( ) Documentation PR is available with #prnumber.
( ) Documentation issue #issuenumber is filed, and can be handled later.
Release notes
( ) No release notes entries required.
(x) Release notes entries required with the following suggested text: