This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new c56d387d41a [opt](memo) reduce enforcer number in memo (#59600)
c56d387d41a is described below

commit c56d387d41a93c989b7bc552308af49abb25efcb
Author: morrySnow <[email protected]>
AuthorDate: Fri Jan 16 10:31:42 2026 +0800

    [opt](memo) reduce enforcer number in memo (#59600)
    
    This pull request refactors and simplifies the handling of job context
    and enforcer management in the Cascades optimizer framework. The main
    changes remove the cost upper bound from `JobContext`, improve enforcer
    deduplication for distribution specs, and streamline how broadcast joins
    are detected and handled. Additionally, the `PlanContext` class is
    refactored for clarity and efficiency, and equality checks are improved
    for distribution specs.
    
    **Refactoring and Simplification of Job Context:**
    
    - Removed the `costUpperBound` parameter and related logic from
    `JobContext` and its usage throughout the optimizer, simplifying job
    scheduling and context management.
    
    **Enforcer Deduplication and Management:**
    
    - Added a new `enforcerSpecs` map in `Group` to deduplicate enforcers
    based on `DistributionSpec`, ensuring only one enforcer per spec and
    improving efficiency during group merges and enforcer addition.
    - Updated enforcer lookup and creation logic in both property
    enforcement and regulation helpers to use the new deduplication
    mechanism.
    
    **PlanContext Refactoring:**
    
    - Refactored `PlanContext` to remove redundant fields, directly use
    `GroupExpression`, and determine broadcast join status based on child
    properties, making the class more focused and reducing unnecessary
    state.
    
    **DistributionSpec Equality Improvements:**
    
    - Implemented `equals` and `hashCode` methods for
    `DistributionSpecHiveTableSinkHashPartitioned` to ensure correct
    behavior in collections and when used as map keys, supporting the new
    enforcer deduplication logic.
    
    **Miscellaneous Improvements:**
    
    - Ensured that ownership is properly set when merging groups and
    updating cost plans, preventing inconsistencies in group expression
    ownership.
    - Minor code cleanups and import adjustments to support the above
    changes.
    
    **Test Result:**
    
    - In TPC-DS Q72, this PR could reduce enforcer number from 89837 to
    16344, reduce Memory usage more than 200MB.
---
 .../org/apache/doris/nereids/CascadesContext.java  |  4 +--
 .../java/org/apache/doris/nereids/PlanContext.java | 41 +++++++++-------------
 .../apache/doris/nereids/cost/CostCalculator.java  |  8 +----
 .../org/apache/doris/nereids/jobs/JobContext.java  | 12 +------
 .../nereids/jobs/cascades/CostAndEnforcerJob.java  | 12 ++-----
 .../hypergraph/receiver/PlanReceiver.java          |  2 +-
 .../java/org/apache/doris/nereids/memo/Group.java  | 23 ++++++++++++
 .../apache/doris/nereids/memo/GroupExpression.java |  2 +-
 .../properties/ChildOutputPropertyDeriver.java     |  4 ++-
 .../properties/ChildrenPropertiesRegulator.java    |  3 ++
 ...stributionSpecHiveTableSinkHashPartitioned.java | 18 ++++++++++
 .../properties/EnforceMissingPropertiesHelper.java | 17 ++++++---
 .../nereids/properties/RequestPropertyDeriver.java |  4 ++-
 .../exploration/join/OuterJoinLAsscomProject.java  |  8 +----
 .../nereids/trees/expressions/Expression.java      | 17 +++++----
 .../trees/plans/physical/PhysicalHashJoin.java     |  6 ++--
 .../nereids/jobs/cascades/DeriveStatsJobTest.java  |  2 +-
 .../rules/rewrite/ConstantPropagationTest.java     |  2 +-
 .../org/apache/doris/nereids/util/PlanChecker.java |  3 +-
 19 files changed, 103 insertions(+), 85 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
index 1517d6f2a7a..845faa58db2 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/CascadesContext.java
@@ -155,7 +155,7 @@ public class CascadesContext implements ScheduleContext {
         this.ruleSet = new RuleSet();
         this.jobPool = new JobStack();
         this.jobScheduler = new SimpleJobScheduler();
-        this.currentJobContext = new JobContext(this, requireProperties, 
Double.MAX_VALUE);
+        this.currentJobContext = new JobContext(this, requireProperties);
         this.subqueryExprIsAnalyzed = new HashMap<>();
         IdGenerator<RuntimeFilterId> runtimeFilterIdGen = 
RuntimeFilterId.createGenerator();
         this.runtimeFilterContext = new 
RuntimeFilterContext(getConnectContext().getSessionVariable(),
@@ -362,7 +362,7 @@ public class CascadesContext implements ScheduleContext {
     }
 
     public CascadesContext setJobContext(PhysicalProperties 
physicalProperties) {
-        this.currentJobContext = new JobContext(this, physicalProperties, 
Double.MAX_VALUE);
+        this.currentJobContext = new JobContext(this, physicalProperties);
         return this;
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/PlanContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/PlanContext.java
index 3ab95423e24..09285a638de 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/PlanContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/PlanContext.java
@@ -18,12 +18,13 @@
 package org.apache.doris.nereids;
 
 import org.apache.doris.nereids.memo.GroupExpression;
+import org.apache.doris.nereids.properties.DistributionSpecReplicated;
+import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.SessionVariable;
 import org.apache.doris.statistics.Statistics;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -34,23 +35,21 @@ import java.util.List;
  */
 public class PlanContext {
     private final ConnectContext connectContext;
-    private final List<Statistics> childrenStats;
-    private final Statistics planStats;
-    private final int arity;
-    private boolean isBroadcastJoin = false;
-    private final boolean isStatsReliable;
+    private final GroupExpression groupExpression;
+    private final boolean isBroadcastJoin;
 
     /**
      * Constructor for PlanContext.
      */
-    public PlanContext(ConnectContext connectContext, GroupExpression 
groupExpression) {
+    public PlanContext(ConnectContext connectContext, GroupExpression 
groupExpression,
+            List<PhysicalProperties> childrenProperties) {
         this.connectContext = connectContext;
-        this.arity = groupExpression.arity();
-        this.planStats = groupExpression.getOwnerGroup().getStatistics();
-        this.isStatsReliable = 
groupExpression.getOwnerGroup().isStatsReliable();
-        this.childrenStats = new ArrayList<>(groupExpression.arity());
-        for (int i = 0; i < groupExpression.arity(); i++) {
-            childrenStats.add(groupExpression.childStatistics(i));
+        this.groupExpression = groupExpression;
+        if (childrenProperties.size() >= 2
+                && childrenProperties.get(1).getDistributionSpec() instanceof 
DistributionSpecReplicated) {
+            isBroadcastJoin = true;
+        } else {
+            isBroadcastJoin = false;
         }
     }
 
@@ -58,35 +57,27 @@ public class PlanContext {
         return connectContext.getSessionVariable();
     }
 
-    public void setBroadcastJoin() {
-        isBroadcastJoin = true;
-    }
-
     public boolean isBroadcastJoin() {
         return isBroadcastJoin;
     }
 
     public int arity() {
-        return arity;
+        return groupExpression.arity();
     }
 
     public Statistics getStatisticsWithCheck() {
-        return planStats;
+        return groupExpression.getOwnerGroup().getStatistics();
     }
 
     public boolean isStatsReliable() {
-        return isStatsReliable;
+        return groupExpression.getOwnerGroup().isStatsReliable();
     }
 
     /**
      * Get child statistics.
      */
     public Statistics getChildStatistics(int index) {
-        return childrenStats.get(index);
-    }
-
-    public List<Statistics> getChildrenStatistics() {
-        return childrenStats;
+        return groupExpression.childStatistics(index);
     }
 
     public StatementContext getStatementContext() {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/cost/CostCalculator.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/cost/CostCalculator.java
index 7661e1aec51..8ec69391691 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/cost/CostCalculator.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/cost/CostCalculator.java
@@ -19,7 +19,6 @@ package org.apache.doris.nereids.cost;
 
 import org.apache.doris.nereids.PlanContext;
 import org.apache.doris.nereids.memo.GroupExpression;
-import org.apache.doris.nereids.properties.DistributionSpecReplicated;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.qe.ConnectContext;
@@ -38,12 +37,7 @@ public class CostCalculator {
      */
     public static Cost calculateCost(ConnectContext connectContext, 
GroupExpression groupExpression,
             List<PhysicalProperties> childrenProperties) {
-        PlanContext planContext = new PlanContext(connectContext, 
groupExpression);
-        if (childrenProperties.size() >= 2
-                && childrenProperties.get(1).getDistributionSpec() instanceof 
DistributionSpecReplicated) {
-            planContext.setBroadcastJoin();
-        }
-
+        PlanContext planContext = new PlanContext(connectContext, 
groupExpression, childrenProperties);
         CostModel costModelV1 = new CostModel(connectContext);
         return groupExpression.getPlan().accept(costModelV1, planContext);
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/JobContext.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/JobContext.java
index 88ca252e537..24c3beadf6b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/JobContext.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/JobContext.java
@@ -37,7 +37,6 @@ public class JobContext {
     // use for optimizer
     protected final ScheduleContext scheduleContext;
     protected final PhysicalProperties requiredProperties;
-    protected double costUpperBound;
 
     // use for rewriter
     protected boolean rewritten = false;
@@ -46,10 +45,9 @@ public class JobContext {
     // user for trace
     protected Map<RuleType, Integer> ruleInvokeTimes = Maps.newLinkedHashMap();
 
-    public JobContext(ScheduleContext scheduleContext, PhysicalProperties 
requiredProperties, double costUpperBound) {
+    public JobContext(ScheduleContext scheduleContext, PhysicalProperties 
requiredProperties) {
         this.scheduleContext = scheduleContext;
         this.requiredProperties = requiredProperties;
-        this.costUpperBound = costUpperBound;
     }
 
     public ScheduleContext getScheduleContext() {
@@ -64,14 +62,6 @@ public class JobContext {
         return requiredProperties;
     }
 
-    public double getCostUpperBound() {
-        return costUpperBound;
-    }
-
-    public void setCostUpperBound(double costUpperBound) {
-        this.costUpperBound = costUpperBound;
-    }
-
     public boolean isRewritten() {
         return rewritten;
     }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/cascades/CostAndEnforcerJob.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/cascades/CostAndEnforcerJob.java
index 76a659870d0..31206e85e38 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/cascades/CostAndEnforcerJob.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/cascades/CostAndEnforcerJob.java
@@ -174,9 +174,7 @@ public class CostAndEnforcerJob extends Job implements 
Cloneable {
                     // Meaning that optimize recursively by derive job.
                     prevChildIndex = curChildIndex;
                     pushJob(clone());
-                    double newCostUpperBound = context.getCostUpperBound() - 
curTotalCost.getValue();
-                    JobContext jobContext = new 
JobContext(context.getCascadesContext(),
-                            requestChildProperty, newCostUpperBound);
+                    JobContext jobContext = new 
JobContext(context.getCascadesContext(), requestChildProperty);
                     pushJob(new OptimizeGroupJob(childGroup, jobContext));
                     return;
                 }
@@ -215,13 +213,7 @@ public class CostAndEnforcerJob extends Job implements 
Cloneable {
             // This mean that we successfully optimize all child groups.
             // if break when running the loop above, the condition must be 
false.
             if (curChildIndex == groupExpression.arity()) {
-                if (!calculateEnforce(requestChildrenProperties, 
outputChildrenProperties)) {
-                    clear();
-                    continue; // if error exists, return
-                }
-                if (curTotalCost.getValue() < context.getCostUpperBound()) {
-                    context.setCostUpperBound(curTotalCost.getValue());
-                }
+                calculateEnforce(requestChildrenProperties, 
outputChildrenProperties);
             }
             clear();
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
index b4629ef9de6..9047a176322 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/jobs/joinorder/hypergraph/receiver/PlanReceiver.java
@@ -188,7 +188,7 @@ public class PlanReceiver implements AbstractReceiver {
 
     private void proposeAllDistributedPlans(GroupExpression groupExpression) {
         jobContext.getCascadesContext().pushJob(new 
OptimizeGroupExpressionJob(groupExpression,
-                new JobContext(jobContext.getCascadesContext(), 
PhysicalProperties.ANY, Double.MAX_VALUE)));
+                new JobContext(jobContext.getCascadesContext(), 
PhysicalProperties.ANY)));
         if (!groupExpression.isStatDerived()) {
             jobContext.getCascadesContext().pushJob(new 
DeriveStatsJob(groupExpression,
                     jobContext.getCascadesContext().getCurrentJobContext()));
diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/Group.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/Group.java
index b3d45e6c80a..0b9c78bdec6 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/Group.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/Group.java
@@ -19,6 +19,7 @@ package org.apache.doris.nereids.memo;
 
 import org.apache.doris.common.Pair;
 import org.apache.doris.nereids.cost.Cost;
+import org.apache.doris.nereids.properties.DistributionSpec;
 import org.apache.doris.nereids.properties.LogicalProperties;
 import org.apache.doris.nereids.properties.PhysicalProperties;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
@@ -27,6 +28,7 @@ import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalJoin;
 import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.physical.PhysicalDistribute;
 import org.apache.doris.nereids.trees.plans.physical.PhysicalPlan;
 import org.apache.doris.nereids.util.TreeStringUtils;
 import org.apache.doris.nereids.util.Utils;
@@ -60,6 +62,7 @@ public class Group {
     private final List<GroupExpression> logicalExpressions = 
Lists.newArrayList();
     private final List<GroupExpression> physicalExpressions = 
Lists.newArrayList();
     private final Map<GroupExpression, GroupExpression> enforcers = 
Maps.newHashMap();
+    private final Map<DistributionSpec, GroupExpression> enforcerSpecs = 
Maps.newHashMap();
     private boolean isStatsReliable = true;
     private LogicalProperties logicalProperties;
 
@@ -243,8 +246,17 @@ public class Group {
         return null;
     }
 
+    /**
+     * add a new enforcer to this group.
+     */
     public void addEnforcer(GroupExpression enforcer) {
         enforcer.setOwnerGroup(this);
+        if (enforcer.getPlan() instanceof PhysicalDistribute) {
+            DistributionSpec distributionSpec = ((PhysicalDistribute) 
enforcer.getPlan()).getDistributionSpec();
+            if (null != enforcerSpecs.put(distributionSpec, enforcer)) {
+                return;
+            }
+        }
         enforcers.put(enforcer, enforcer);
     }
 
@@ -252,6 +264,10 @@ public class Group {
         return enforcers;
     }
 
+    public Map<DistributionSpec, GroupExpression> getEnforcerSpecs() {
+        return enforcerSpecs;
+    }
+
     /**
      * Set or update lowestCostPlans: properties --> Pair.of(cost, expression)
      */
@@ -356,6 +372,7 @@ public class Group {
         // TODO: dedup?
         enforcers.forEach((k, v) -> target.addEnforcer(k));
         enforcers.clear();
+        enforcerSpecs.clear();
 
         // move LogicalExpression PhysicalExpression Ownership
         Map<GroupExpression, GroupExpression> logicalSet = 
target.getLogicalExpressions().stream()
@@ -390,10 +407,16 @@ public class Group {
         lowestCostPlans.forEach((physicalProperties, costAndGroupExpr) -> {
             // move lowestCostPlans Ownership
             if (!target.lowestCostPlans.containsKey(physicalProperties)) {
+                // we must set owner group here, because the instance in 
logical expression, physical expression
+                // and enforcer maybe not same with the instance in the 
lowestCostPlans map
+                costAndGroupExpr.second.setOwnerGroup(target);
                 target.lowestCostPlans.put(physicalProperties, 
costAndGroupExpr);
             } else {
                 if (costAndGroupExpr.first.getValue()
                         < 
target.lowestCostPlans.get(physicalProperties).first.getValue()) {
+                    // we must set owner group here, because the instance in 
logical expression, physical expression
+                    // and enforcer maybe not same with the instance in the 
lowestCostPlans map
+                    costAndGroupExpr.second.setOwnerGroup(target);
                     target.lowestCostPlans.put(physicalProperties, 
costAndGroupExpr);
                 }
             }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/GroupExpression.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/GroupExpression.java
index d6a9e29dd9e..96b4180db15 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/GroupExpression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/memo/GroupExpression.java
@@ -54,7 +54,7 @@ public class GroupExpression {
     private static final EventProducer COST_STATE_TRACER = new 
EventProducer(CostStateUpdateEvent.class,
             EventChannel.getDefaultChannel().addConsumers(new 
LogConsumer(CostStateUpdateEvent.class,
                     EventChannel.LOG)));
-    private Cost cost;
+    private Cost cost = null;
     private Group ownerGroup;
     private final List<Group> children;
     private final Plan plan;
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildOutputPropertyDeriver.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildOutputPropertyDeriver.java
index a7dfc11046f..9416a4c3b91 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildOutputPropertyDeriver.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildOutputPropertyDeriver.java
@@ -76,6 +76,7 @@ import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -101,7 +102,8 @@ public class ChildOutputPropertyDeriver extends 
PlanVisitor<PhysicalProperties,
     }
 
     public PhysicalProperties getOutputProperties(ConnectContext 
connectContext, GroupExpression groupExpression) {
-        return groupExpression.getPlan().accept(this, new 
PlanContext(connectContext, groupExpression));
+        return groupExpression.getPlan().accept(this,
+                new PlanContext(connectContext, groupExpression, 
Collections.emptyList()));
     }
 
     @Override
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildrenPropertiesRegulator.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildrenPropertiesRegulator.java
index d6e0a4c8787..b415b1f8007 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildrenPropertiesRegulator.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/ChildrenPropertiesRegulator.java
@@ -878,6 +878,9 @@ public class ChildrenPropertiesRegulator extends 
PlanVisitor<List<List<PhysicalP
             currentCost = newChildAndCost.first;
         }
 
+        if (child.getOwnerGroup().getEnforcerSpecs().containsKey(target)) {
+            return;
+        }
         PhysicalProperties newOutputProperty = new PhysicalProperties(target);
         GroupExpression enforcer = target.addEnforcer(child.getOwnerGroup());
         child.getOwnerGroup().addEnforcer(enforcer);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/DistributionSpecHiveTableSinkHashPartitioned.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/DistributionSpecHiveTableSinkHashPartitioned.java
index 4275bb53dd8..87a5eafc46f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/DistributionSpecHiveTableSinkHashPartitioned.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/DistributionSpecHiveTableSinkHashPartitioned.java
@@ -20,6 +20,7 @@ package org.apache.doris.nereids.properties;
 import org.apache.doris.nereids.trees.expressions.ExprId;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * use for shuffle data by partition keys before sink.
@@ -44,4 +45,21 @@ public class DistributionSpecHiveTableSinkHashPartitioned 
extends DistributionSp
     public boolean satisfy(DistributionSpec other) {
         return other instanceof DistributionSpecHiveTableSinkHashPartitioned;
     }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        DistributionSpecHiveTableSinkHashPartitioned that = 
(DistributionSpecHiveTableSinkHashPartitioned) o;
+        return Objects.equals(outputColExprIds, that.outputColExprIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(), outputColExprIds);
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/EnforceMissingPropertiesHelper.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/EnforceMissingPropertiesHelper.java
index 6dc6fe565a2..81ce94e5f2f 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/EnforceMissingPropertiesHelper.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/EnforceMissingPropertiesHelper.java
@@ -123,7 +123,11 @@ public class EnforceMissingPropertiesHelper {
         }
 
         PhysicalProperties newOutputProperty = new 
PhysicalProperties(outputDistributionSpec);
-        GroupExpression enforcer = 
outputDistributionSpec.addEnforcer(groupExpression.getOwnerGroup());
+        GroupExpression enforcer = 
groupExpression.getOwnerGroup().getEnforcerSpecs().get(outputDistributionSpec);
+
+        if (enforcer == null) {
+            enforcer = 
outputDistributionSpec.addEnforcer(groupExpression.getOwnerGroup());
+        }
         addEnforcerUpdateCost(enforcer, oldOutputProperty, newOutputProperty);
         return newOutputProperty;
     }
@@ -160,10 +164,13 @@ public class EnforceMissingPropertiesHelper {
                 oldOutputProperty, newOutputProperty);
         ENFORCER_TRACER.log(EnforcerEvent.of(groupExpression, ((PhysicalPlan) 
enforcer.getPlan()),
                 oldOutputProperty, newOutputProperty));
-        
enforcer.setEstOutputRowCount(enforcer.getOwnerGroup().getStatistics().getRowCount());
-        Cost enforcerCost = CostCalculator.calculateCost(connectContext, 
enforcer,
-                Lists.newArrayList(oldOutputProperty));
-        enforcer.setCost(enforcerCost);
+        Cost enforcerCost = enforcer.getCost();
+        if (enforcerCost == null) {
+            
enforcer.setEstOutputRowCount(enforcer.getOwnerGroup().getStatistics().getRowCount());
+            enforcerCost = CostCalculator.calculateCost(connectContext, 
enforcer,
+                    Lists.newArrayList(oldOutputProperty));
+            enforcer.setCost(enforcerCost);
+        }
         curTotalCost = CostCalculator.addChildCost(
                 connectContext,
                 enforcer.getPlan(),
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/RequestPropertyDeriver.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/RequestPropertyDeriver.java
index a5bf8e2fe92..4a39289c5f9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/RequestPropertyDeriver.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/properties/RequestPropertyDeriver.java
@@ -74,6 +74,7 @@ import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -114,7 +115,8 @@ public class RequestPropertyDeriver extends 
PlanVisitor<Void, PlanContext> {
      */
     public List<List<PhysicalProperties>> 
getRequestChildrenPropertyList(GroupExpression groupExpression) {
         requestPropertyToChildren = Lists.newArrayList();
-        groupExpression.getPlan().accept(this, new PlanContext(connectContext, 
groupExpression));
+        groupExpression.getPlan().accept(this,
+                new PlanContext(connectContext, groupExpression, 
Collections.emptyList()));
         return requestPropertyToChildren;
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
index ac9787bed48..24fa7722970 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/join/OuterJoinLAsscomProject.java
@@ -23,7 +23,6 @@ import org.apache.doris.nereids.rules.RuleType;
 import org.apache.doris.nereids.rules.exploration.CBOUtils;
 import org.apache.doris.nereids.rules.exploration.OneExplorationRuleFactory;
 import org.apache.doris.nereids.trees.expressions.ExprId;
-import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.plans.GroupPlan;
 import org.apache.doris.nereids.trees.plans.JoinType;
 import org.apache.doris.nereids.trees.plans.Plan;
@@ -36,7 +35,6 @@ import com.google.common.collect.ImmutableSet;
 
 import java.util.HashSet;
 import java.util.Set;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -116,11 +114,7 @@ public class OuterJoinLAsscomProject extends 
OneExplorationRuleFactory {
                         topJoin.getHashJoinConjuncts().stream(),
                         topJoin.getOtherJoinConjuncts().stream())
                 .allMatch(expr -> {
-                    Set<ExprId> usedExprIdSet = 
expr.<SlotReference>collect(SlotReference.class::isInstance)
-                            .stream()
-                            .map(SlotReference::getExprId)
-                            .collect(Collectors.toSet());
-                    return !Utils.isIntersecting(usedExprIdSet, 
bOutputExprIdSet);
+                    return !Utils.isIntersecting(expr.getInputSlotExprIds(), 
bOutputExprIdSet);
                 });
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
index d6b091b8718..3f5dbca2468 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/Expression.java
@@ -71,6 +71,16 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
     private final boolean hasUnbound;
     private final Supplier<Set<Slot>> inputSlots = LazyCompute.of(
             () -> collect(e -> e instanceof Slot && !(e instanceof 
ArrayItemSlot)));
+    private final Supplier<Set<ExprId>> inputExprIds = LazyCompute.of(
+            () -> {
+                Set<Slot> inputSlots = getInputSlots();
+                Builder<ExprId> exprIds = 
ImmutableSet.builderWithExpectedSize(inputSlots.size());
+                for (Slot inputSlot : inputSlots) {
+                    exprIds.add(inputSlot.getExprId());
+                }
+                return exprIds.build();
+            }
+    );
     private final int fastChildrenHashCode;
     private final Supplier<String> toSqlCache = 
LazyCompute.of(this::computeToSql);
     private final Supplier<Integer> hashCodeCache = 
LazyCompute.of(this::computeHashCode);
@@ -394,12 +404,7 @@ public abstract class Expression extends 
AbstractTreeNode<Expression> implements
      * Note that the input slots of subquery's inner plan is not included.
      */
     public final Set<ExprId> getInputSlotExprIds() {
-        Set<Slot> inputSlots = getInputSlots();
-        Builder<ExprId> exprIds = 
ImmutableSet.builderWithExpectedSize(inputSlots.size());
-        for (Slot inputSlot : inputSlots) {
-            exprIds.add(inputSlot.getExprId());
-        }
-        return exprIds.build();
+        return inputExprIds.get();
     }
 
     public boolean isLiteral() {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalHashJoin.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalHashJoin.java
index ca84e1afc2f..d90a74f4199 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalHashJoin.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/physical/PhysicalHashJoin.java
@@ -131,13 +131,11 @@ public class PhysicalHashJoin<
         //  mark join with non-empty hash join conjuncts allow shuffle join by 
hash join conjuncts
         Preconditions.checkState(!(isMarkJoin() && 
hashJoinConjuncts.isEmpty()),
                 "shouldn't call mark join's getHashConjunctsExprIds method for 
standalone mark join");
-        int size = hashJoinConjuncts.size();
-
-        List<ExprId> exprIds1 = new ArrayList<>(size);
-        List<ExprId> exprIds2 = new ArrayList<>(size);
 
         Set<ExprId> leftExprIds = left().getOutputExprIdSet();
         Set<ExprId> rightExprIds = right().getOutputExprIdSet();
+        List<ExprId> exprIds1 = new ArrayList<>(leftExprIds.size());
+        List<ExprId> exprIds2 = new ArrayList<>(rightExprIds.size());
 
         for (Expression expr : hashJoinConjuncts) {
             for (ExprId exprId : expr.getInputSlotExprIds()) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
index 181ab5ba08c..aa5a21adcdc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/jobs/cascades/DeriveStatsJobTest.java
@@ -68,7 +68,7 @@ public class DeriveStatsJobTest {
         LogicalAggregate agg = constructAgg(olapScan);
         CascadesContext cascadesContext = 
MemoTestUtils.createCascadesContext(agg);
         new 
DeriveStatsJob(cascadesContext.getMemo().getRoot().getLogicalExpression(),
-                new JobContext(cascadesContext, null, 
Double.MAX_VALUE)).execute();
+                new JobContext(cascadesContext, null)).execute();
         while (!cascadesContext.getJobPool().isEmpty()) {
             cascadesContext.getJobPool().pop().execute();
         }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagationTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagationTest.java
index a1ef8f8d7b4..285e8313e6a 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagationTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ConstantPropagationTest.java
@@ -88,7 +88,7 @@ class ConstantPropagationTest {
     ConstantPropagationTest() {
         cascadesContext = MemoTestUtils.createCascadesContext(
                 new UnboundRelation(new RelationId(1), 
ImmutableList.of("tbl")));
-        jobContext = new JobContext(cascadesContext, null, Double.MAX_VALUE);
+        jobContext = new JobContext(cascadesContext, null);
 
         student = new LogicalOlapScan(PlanConstructor.getNextRelationId(), 
PlanConstructor.student, ImmutableList.of(""));
         studentId = (SlotReference) student.getOutput().get(0);
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanChecker.java 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanChecker.java
index c23ff24c868..5084ef09823 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanChecker.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/util/PlanChecker.java
@@ -700,8 +700,7 @@ public class PlanChecker {
     public PlanChecker setMaxInvokeTimesPerRule(int maxInvokeTime) {
         JobContext originJobContext = cascadesContext.getCurrentJobContext();
         cascadesContext.setCurrentJobContext(
-                new JobContext(cascadesContext,
-                        originJobContext.getRequiredProperties(), 
originJobContext.getCostUpperBound()) {
+                new JobContext(cascadesContext, 
originJobContext.getRequiredProperties()) {
                     @Override
                     public void onInvokeRule(RuleType ruleType) {
                         // add invoke times


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to