Skip to content
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

Support persistent CTE's #20887

Merged
merged 3 commits into from Dec 13, 2023
Merged

Support persistent CTE's #20887

merged 3 commits into from Dec 13, 2023

Conversation

jaystarshot
Copy link
Member

@jaystarshot jaystarshot commented Sep 15, 2023

Description

Support persistent query-level CTEs

The goal is to allow users to define persistent ctes and support them
Session property cte_materialization_strategy is added which when set to ALL will materialize all ctes to HDFS

This will be the first step for 19744.

Motivation and Context

This change is based on the design. The eventual goal is to materialize or stream the CTEs (either in local memory or remote) using CBO rules and the architecture reflects that.
The temporary tables created for each cte will be partitioned on the first input column
In the current implementation, all CTEs will be evaluated before the query is run.

This change has 5 main commits

Add session property and ast changes

This change adds the session property which allows CTE materialization. This information is also added to the CTEInformation collector.
The relational planner has the main change where a CTEReference Node is added if a CTE is persistent. This reference node will be used by the LogicalCteOptimizer to process.

Planner changes

Main changes
LogicalCteOptimizer - Processes the CTE references into CteConsumers and CteProducers. It also creates a sequence node which is added as a child below the topmost node. The sequence node has a list of leftSources and a right source. The leftSources list has the ctes ordered in a topological order of execution. {a,b,c,d} --> order of execution {d->c->b->a}.

PhysicalCteOptimizer - Processes the CteConsumers and CteProducers into table writes and temporary table scan. The processing code is based on basePlanFragmenter. From logical to physical optimizers, the CteProducer, CteConsumers, and Sequence Node are present to enable further logical optimizations. Physical optimization is applied just before adding exchanges. Post this optimization stage, CteConsumers and CteProducers are no longer present.

Code change in other optimizers and visitors

AddExchanges.java - Handling Sequence Node
HashGenerationOptimizer.java - Handling Sequence Node
StreamPropertyDerivations.java - Handling Sequence Node
PropertyDerivations.java - Handling Sequence Node
PruneUnreferencedOutputs.java - Handling CteConsumer, CteProducer and Sequnce
PushdownSubfields.java - handling CteProducer Node
StreamPropertyDerivations.java - handling sequence
UnaliasSymbolReferences.java - Handling CteConsumer, CteProducer and Sequnce

Scheduling changes

BasePlanFragmenter - Handle and transform sequence nodes. {a,b,c,d} --> order of execution {d->c->b->a}
We will create a chain of different sections of plans {d->c->b->a} and add it as a child of the main query section.

StreamingPlanSection will make sure that the children are scheduled and executed before the parent

Tests

Integration test to test end to end and result correctness
Testing logical cte optimizer

Current wips

  • Handle UnaliasSymbolReferences.java.
  • Handle ValidateDependenciesChecker.java
  • Check if result correctness can be done at a record level
  • Add parser and planner unit tests

Impact

Test Plan

Production tested with ALL CTEs materialized

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.

Todo in future PRs

  1. Integrate with HBO and cost model
  2. Local auto replacement of cte in the optimizer
  3. Select the top five candidate strategies from the optimizer, execute the planning process for each from the logical to the physical stage within the optimizer, and determine the most optimal one.
  4. Support non partitioned temporary tables

Final Quality ToDos

  • Fix cases where the bucketing is done on unsupported buckets
  • Production test again with changes

Release Notes

Please follow release notes guidelines and fill in the release notes below.

== RELEASE NOTES ==

General Changes
* New Feature: Materialization of Common Table Expressions (CTEs) in Queries
* The relevant user connectors must support creating temporary tables 
* By materializing CTEs, users can take advantage of reusing common table expressions within their queries, which can lead to reductions in both storage operations and computational costs

## Recommended Configurations

1. CTE Materialization Strategy:
   - Enable by setting the session property ``cte_materialization_strategy`` or the configuration property ``cte-materialization-strategy`` to ``ALL``.
2. Temporary Table Storage Format:
   - For faster processing, set the configuration ``hive.temporary-table-storage-format`` or the session property ``temporary_table_storage_format`` to ``PAGEFILE``.
3. Temporary Table Schema:
   - It is recommended to set the configuration ``hive.temporary-table-schema`` or the session property ``temporary_table_schema`` to use the user's schema.
 4. Partitioning Provider Catalog
 - The config property ``query.partitioning-provider-catalog`` ``partitioning_provider_catalog`` session property must be set to enable cte materialization. The catalog must support providing a custom partitioning and ability to store temporary tables.

@github-actions
Copy link

github-actions bot commented Oct 24, 2023

Codenotify: Notifying subscribers in CODENOTIFY files for diff 3b8e8cc...8993b02.

No notifications.

@mlyublena
Copy link
Contributor

General comment: I wonder if it would have been possible to achieve the same without introducing new syntax (for example through a session parameter). I know if't not as flexible as allowing specification per CTE but also not as disruptive as new syntax

@jaystarshot
Copy link
Member Author

It won't because the canonical hashes won't be generated and properly linked with the execution.
I have 3 follow up prs'

  1. Add these new nodes in cost frameworks - link
  2. Support HBO with these new nodes - link
  3. Locally auto materialize the ctes - link

@mlyublena
Copy link
Contributor

It won't because the canonical hashes won't be generated and properly linked with the execution. I have 3 follow up prs'

  1. Add these new nodes in cost frameworks - link
  2. Support HBO with these new nodes - link
  3. Locally auto materialize the ctes - link

makes sense.
what about the opposite: if the CTE is not materialized, would HBO work as before?

@jaystarshot
Copy link
Member Author

Yes, the optimizer and entire flow remains unchanged unless the relational planner adds a cteReferenceNode, which only occurs if the specific flag is enabled.

@jaystarshot
Copy link
Member Author

jaystarshot commented Nov 28, 2023

Does this PR look good to merge? Will need a committer approval

@jaystarshot
Copy link
Member Author

As a follow up PR, I will also add a materialization strategy to only materialize ctes if references > threshold times and have a join or aggregate and maybe reduce data size

@tdcmeehan
Copy link
Contributor

Does this PR look good to merge? Will need a committer approval

I'll have a look this week, other committers are free to get to it sooner!

catch (PrestoException e) {
if (e.getErrorCode().equals(NOT_SUPPORTED.toErrorCode())) {
throw new PrestoException(
NOT_SUPPORTED,
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add this check to is isCteMaterializable?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think here its just a catch for the metadata.createTemporaryTable() which cannot be invoked in the parser since we do not have partition metadata and other info there

Copy link
Contributor

@mlyublena mlyublena left a comment

Choose a reason for hiding this comment

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

Looks good!

@jaystarshot
Copy link
Member Author

Adding a nit (using partitioningProviderCatalog) instead of hardcoded "hive" string in a use case.

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.

Please make sure to consistently validate inputs. Please squash the Tests commit.

@Override
public PlanNode replaceChildren(List<PlanNode> newChildren)
{
checkArgument(newChildren.size() == 1, "expected newChildren to contain 1 node");
Copy link
Contributor

Choose a reason for hiding this comment

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

Please also validate that the input is not null with Objects.requireNonNull.

{
}

public static TableScanNode createTemporaryTableScan(
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and elsewhere: Please validate inputs are not null

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks added in other cases, not adding here because not really needed at this static function.


public List<PlanNode> getTopologicalOrdering()
{
List<PlanNode> topSortedCteProducerList = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Use immutable list


public Map<String, CteProducerNode> getCteProducerMap()
{
return cteProducerMap;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this return an immutable copy

}

@Override
public PlanOptimizerResult optimize(PlanNode plan, Session session, TypeProvider types, VariableAllocator variableAllocator, PlanNodeIdAllocator idAllocator, WarningCollector warningCollector)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here and elsewhere--please always validate the inputs are not null


public CteProducerRewiter(Session session, PlanNodeIdAllocator idAllocator, VariableAllocator variableAllocator)
{
this.idAllocator = idAllocator;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto


@Override
public PlanNode visitCteProducer(CteProducerNode node, RewriteContext<PhysicalCteTransformerContext> context)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto

tempScan.getCurrentConstraint(),
tempScan.getEnforcedConstraint());

/* The temporary table scan might have columns removed by the UnaliasSymbolReferences and other optimizers (its a plan tree after all),
Copy link
Contributor

Choose a reason for hiding this comment

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

In general we prefer // comments

public TemporaryTableInfo(TableScanNode tableScanNode, List<VariableReferenceExpression> originalOutputVariables)
{
this.tableScanNode = tableScanNode;
this.originalOutputVariables = originalOutputVariables;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please check inputs are not null, and create an immutable copy of the original output variables (no cost if the input is immutable as well)

{
// Since this is topologically sorted by the LogicalCtePlanner, need to make sure that execution order follows
// Can be optimized further to avoid non dependents from getting blocked
int n = node.getCteProducers().size();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please do not use single letter variables. cteProducerCount or something similar

@tdcmeehan
Copy link
Contributor

Please look into the merge conflicts.

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.

Just one last nit, and I think good to merge.

@jaystarshot jaystarshot merged commit 77dfcc8 into prestodb:master Dec 13, 2023
56 checks passed
@wanglinsong wanglinsong mentioned this pull request Feb 12, 2024
64 tasks
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.

Left a few comments for Presto style and best practices that are likely to come up elsewhere in the future.

String cteName)
{
super(sourceLocation, id, statsEquivalentPlanNode);
this.cteName = cteName;
Copy link
Contributor

Choose a reason for hiding this comment

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

Always check requireNonNull() when setting Object fields in a constructor. (sometimes other argument checks are appropriate as well, but we almost never allow nulls. Generally we enforce this in constructors, and then elsewhere assume that nothing is null.)

public class NestedCteStack
{
@VisibleForTesting
public static final String delimiter = "_*%$_";
Copy link
Contributor

Choose a reason for hiding this comment

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

naming convention note: static final fields should be in all caps (so DELIMETER)


public NestedCteStack()
{
this.cteStack = new Stack<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

initialize these where they are declared. They don't need anything from the constructor.


public void push(String cteName, Query query)
{
this.cteStack.push(cteName);
Copy link
Contributor

Choose a reason for hiding this comment

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

style note: we generally only use the "this" prefix in the constructor or setters. Otherwise, we refer to fields without the "this" prefix as long as there is no conflict requiring it. (not sure why, i think just to be concise).

PlanNode primarySource)
{
super(sourceLocation, planNodeId, statsEquivalentPlanNode);
this.cteProducers = leftList;
Copy link
Contributor

Choose a reason for hiding this comment

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

when setting a field to a collection passed in as input, make a defensive copy using ImmutableList.copyOf(). If it's already an immutable list this will be a no-op. (these should have requireNonNullChecks too)

{
List<PlanNode> children = new ArrayList<>(cteProducers);
children.add(primarySource);
return children;
Copy link
Contributor

Choose a reason for hiding this comment

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

use immutable collections whenever possible. It prevents bugs caused by things being modified unexpectedly. mutable collections should only be used when you plan something to be modified, and should generally stay local to wherever they are.

Here you could do

return ImmutableList.builder()
.addAll(cteProducers);
.add(primarySource)
.build();

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.

None yet

6 participants