Skip to content

Commit b2011a2

Browse files
Eric5553cloud-fan
authored andcommitted
[SPARK-30326][SQL] Raise exception if analyzer exceed max iterations
### What changes were proposed in this pull request? Enhance RuleExecutor strategy to take different actions when exceeding max iterations. And raise exception if analyzer exceed max iterations. ### Why are the changes needed? Currently, both analyzer and optimizer just log warning message if rule execution exceed max iterations. They should have different behavior. Analyzer should raise exception to indicates the plan is not fixed after max iterations, while optimizer just log warning to keep the current plan. This is more feasible after SPARK-30138 was introduced. ### Does this PR introduce any user-facing change? No ### How was this patch tested? Add test in AnalysisSuite Closes #26977 from Eric5553/EnhanceMaxIterations. Authored-by: Eric Wu <492960551@qq.com> Signed-off-by: Wenchen Fan <wenchen@databricks.com>
1 parent 5a24060 commit b2011a2

4 files changed

Lines changed: 60 additions & 7 deletions

File tree

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,15 @@ class Analyzer(
176176

177177
def resolver: Resolver = conf.resolver
178178

179-
protected val fixedPoint = FixedPoint(maxIterations)
179+
/**
180+
* If the plan cannot be resolved within maxIterations, analyzer will throw exception to inform
181+
* user to increase the value of SQLConf.ANALYZER_MAX_ITERATIONS.
182+
*/
183+
protected val fixedPoint =
184+
FixedPoint(
185+
maxIterations,
186+
errorOnExceed = true,
187+
maxIterationsSetting = SQLConf.ANALYZER_MAX_ITERATIONS.key)
180188

181189
/**
182190
* Override to provide additional rules for the "Resolution" batch.

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ abstract class Optimizer(catalogManager: CatalogManager)
5353
"PartitionPruning",
5454
"Extract Python UDFs")
5555

56-
protected def fixedPoint = FixedPoint(SQLConf.get.optimizerMaxIterations)
56+
protected def fixedPoint =
57+
FixedPoint(
58+
SQLConf.get.optimizerMaxIterations,
59+
maxIterationsSetting = SQLConf.OPTIMIZER_MAX_ITERATIONS.key)
5760

5861
/**
5962
* Defines the default rule batches in the Optimizer.

sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/rules/RuleExecutor.scala

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,17 @@ abstract class RuleExecutor[TreeType <: TreeNode[_]] extends Logging {
4545
* An execution strategy for rules that indicates the maximum number of executions. If the
4646
* execution reaches fix point (i.e. converge) before maxIterations, it will stop.
4747
*/
48-
abstract class Strategy { def maxIterations: Int }
48+
abstract class Strategy {
49+
50+
/** The maximum number of executions. */
51+
def maxIterations: Int
52+
53+
/** Whether to throw exception when exceeding the maximum number. */
54+
def errorOnExceed: Boolean = false
55+
56+
/** The key of SQLConf setting to tune maxIterations */
57+
def maxIterationsSetting: String = null
58+
}
4959

5060
/** A strategy that is run once and idempotent. */
5161
case object Once extends Strategy { val maxIterations = 1 }
@@ -54,7 +64,10 @@ abstract class RuleExecutor[TreeType <: TreeNode[_]] extends Logging {
5464
* A strategy that runs until fix point or maxIterations times, whichever comes first.
5565
* Especially, a FixedPoint(1) batch is supposed to run only once.
5666
*/
57-
case class FixedPoint(maxIterations: Int) extends Strategy
67+
case class FixedPoint(
68+
override val maxIterations: Int,
69+
override val errorOnExceed: Boolean = false,
70+
override val maxIterationsSetting: String = null) extends Strategy
5871

5972
/** A batch of rules. */
6073
protected case class Batch(name: String, strategy: Strategy, rules: Rule[TreeType]*)
@@ -155,8 +168,14 @@ abstract class RuleExecutor[TreeType <: TreeNode[_]] extends Logging {
155168
if (iteration > batch.strategy.maxIterations) {
156169
// Only log if this is a rule that is supposed to run more than once.
157170
if (iteration != 2) {
158-
val message = s"Max iterations (${iteration - 1}) reached for batch ${batch.name}"
159-
if (Utils.isTesting) {
171+
val endingMsg = if (batch.strategy.maxIterationsSetting == null) {
172+
"."
173+
} else {
174+
s", please set '${batch.strategy.maxIterationsSetting}' to a larger value."
175+
}
176+
val message = s"Max iterations (${iteration - 1}) reached for batch ${batch.name}" +
177+
s"$endingMsg"
178+
if (Utils.isTesting || batch.strategy.errorOnExceed) {
160179
throw new TreeNodeException(curPlan, message, null)
161180
} else {
162181
logWarning(message)

sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/AnalysisSuite.scala

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import org.scalatest.Matchers
2525

2626
import org.apache.spark.api.python.PythonEvalType
2727
import org.apache.spark.sql.catalyst.TableIdentifier
28-
import org.apache.spark.sql.catalyst.catalog.{CatalogStorageFormat, CatalogTable, CatalogTableType}
28+
import org.apache.spark.sql.catalyst.catalog.{CatalogStorageFormat, CatalogTable, CatalogTableType, InMemoryCatalog, SessionCatalog}
2929
import org.apache.spark.sql.catalyst.dsl.expressions._
3030
import org.apache.spark.sql.catalyst.dsl.plans._
31+
import org.apache.spark.sql.catalyst.errors.TreeNodeException
3132
import org.apache.spark.sql.catalyst.expressions._
3233
import org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression, Count, Sum}
3334
import org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parsePlan
@@ -745,4 +746,26 @@ class AnalysisSuite extends AnalysisTest with Matchers {
745746
CollectMetrics("evt1", sumWithFilter :: Nil, testRelation),
746747
"aggregates with filter predicate are not allowed" :: Nil)
747748
}
749+
750+
test("Analysis exceed max iterations") {
751+
// RuleExecutor only throw exception or log warning when the rule is supposed to run
752+
// more than once.
753+
val maxIterations = 2
754+
val conf = new SQLConf().copy(SQLConf.ANALYZER_MAX_ITERATIONS -> maxIterations)
755+
val testAnalyzer = new Analyzer(
756+
new SessionCatalog(new InMemoryCatalog, FunctionRegistry.builtin, conf), conf)
757+
758+
val plan = testRelation2.select(
759+
$"a" / Literal(2) as "div1",
760+
$"a" / $"b" as "div2",
761+
$"a" / $"c" as "div3",
762+
$"a" / $"d" as "div4",
763+
$"e" / $"e" as "div5")
764+
765+
val message = intercept[TreeNodeException[LogicalPlan]] {
766+
testAnalyzer.execute(plan)
767+
}.getMessage
768+
assert(message.startsWith(s"Max iterations ($maxIterations) reached for batch Resolution, " +
769+
s"please set '${SQLConf.ANALYZER_MAX_ITERATIONS.key}' to a larger value."))
770+
}
748771
}

0 commit comments

Comments
 (0)