Calcite升级至1.32/1.34后,有状态规则与Volcano Planner兼容异常
Apache Calcite升级后有状态规则matches返回false触发AssertionError问题
问题概述
将Apache Calcite从1.21.0升级到1.32.0或1.34.0版本后,自定义有状态规则StateFullRule(继承RelOptRule)与Volcano Planner配合时出现异常:当matches方法返回false时,会抛出AssertionError,该规则在1.21.0版本中可正常运行。
规则核心代码如下:
public class StateFullRule extends RelOptRule { // 规则内部维护的状态 //private boolean isProccessed = false; //private Map<RelNodeKey, Object> ancestorsMap; public StateFullRule() { super(operand(LogicalUnion.class, operand(LogicalProject.class, any()), new RelOptRuleOperand[]{operand(LogicalProject.class, any())}), "StateFullRule"); } @Override public boolean matches(RelOptRuleCall call) { // 基于输入构建中间状态 Boolean matches = determineIfRuleMatches(); // 可能返回true或false if(matches) { // 更新规则内部状态 //ancestorsMap = subsetAncestorsMap; } return matches; } private Boolean determineIfRuleMatches() { // 基于中间状态和规则自身状态判断是否匹配 return false; } public void onMatch(RelOptRuleCall call) { // 使用状态执行转换逻辑 } }
异常堆栈:
java.lang.AssertionError: null at org.apache.calcite.plan.volcano.IterativeRuleDriver.drive(IterativeRuleDriver.java:57) ~[calcite-core-1.34.0.jar:1.34.0] at org.apache.calcite.plan.volcano.VolcanoPlanner.findBestExp(VolcanoPlanner.java:523) ~[calcite-core-1.34.0.jar:1.34.0]
原因分析
- 规则设计规范冲突:Calcite的
RelOptRule设计初衷是无状态的,规则实例会在规划过程中被复用、甚至多线程调用。自定义规则中维护内部状态的做法本身就不符合Calcite的设计原则,旧版本未做严格校验,但新版本增加了断言检查。 - 匹配流程的断言强化:1.32.0+版本的
IterativeRuleDriver和TopDownRuleDriver中,当规则的operand模式匹配成功(即RelNode结构符合规则定义的操作数模板)后,如果matches方法返回false,会触发断言。Calcite认为operand应足够精确过滤不需要的场景,matches仅用于operand无法覆盖的细粒度判断,频繁出现operand匹配但matches返回false属于规则设计缺陷。
解决方案
1. 移除规则内部状态(推荐)
将规则依赖的状态转移到外部上下文,比如:
- 通过
RelOptRuleCall.getPlanner().getContext()存储全局状态; - 利用
RelMetadataQuery获取RelNode元数据,避免规则内部维护状态; - 将状态附加到RelNode的TraitSet或自定义属性中传递。
修改后的无状态规则示例:
public class StatelessRule extends RelOptRule { public StatelessRule() { super(operand(LogicalUnion.class, operand(LogicalProject.class, any()), operand(LogicalProject.class, any())), "StatelessRule"); } @Override public boolean matches(RelOptRuleCall call) { // 仅基于RelNode本身和元数据判断匹配,不维护规则内部状态 LogicalUnion union = call.rel(0); return determineIfRuleMatches(union); } private boolean determineIfRuleMatches(LogicalUnion union) { // 基于RelNode的属性、元数据做判断 return union.getInputs().size() == 2; } public void onMatch(RelOptRuleCall call) { // 转换逻辑仅依赖输入RelNode和上下文,不依赖规则内部状态 LogicalUnion union = call.rel(0); // ... 执行转换逻辑 } }
2. 优化Operand定义,减少matches判断
尽可能通过Operand模式精确匹配目标RelNode结构,减少matches方法的判断逻辑,避免出现operand匹配但matches返回false的情况。比如针对特定字段的LogicalProject,可在Operand中增加条件过滤。
3. 临时禁用断言(不推荐)
若需快速验证,可通过JVM启动参数-ea:org.apache.calcite.plan.volcano.-关闭Volcano模块的断言,但这仅为临时方案,无法解决有状态规则带来的线程安全、规划结果不可预测等潜在问题。
关键疑问解答
Calcite并非完全不允许matches返回false,而是当Operand已匹配成功的前提下,matches返回false会触发断言。这是新版本对规则设计合理性的校验,引导开发者写出更精确的规则。实现TransformationRule接口无法解决问题,因为该接口本质是RelOptRule的扩展,核心约束一致。
内容的提问来源于stack exchange,提问作者Parag Chimanpure
相关产品推荐
相关产品推荐

