-
Notifications
You must be signed in to change notification settings - Fork 28.9k
[SPARK-30556][SQL] Copy sparkContext.localproperties to child thread inSubqueryExec.executionContext #27267
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
[SPARK-30556][SQL] Copy sparkContext.localproperties to child thread inSubqueryExec.executionContext #27267
Changes from all commits
5042156
577904f
b6b5bae
c2b157a
1ca7981
fdc71e3
aeea500
ade75c6
3f44356
89b95a3
f1cac4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,9 +145,17 @@ object StaticSQLConf { | |
| "cause longer waiting for other broadcasting. Also, increasing parallelism may " + | ||
| "cause memory problem.") | ||
| .intConf | ||
| .checkValue(thres => thres > 0 && thres <= 128, "The threshold must be in [0,128].") | ||
| .checkValue(thres => thres > 0 && thres <= 128, "The threshold must be in (0,128].") | ||
| .createWithDefault(128) | ||
|
|
||
| val SUBQUERY_MAX_THREAD_THRESHOLD = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should submit a separate PR for this change.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could keep this in StaticSQLConf.
cc @hvanhovell |
||
| buildStaticConf("spark.sql.subquery.maxThreadThreshold") | ||
| .internal() | ||
| .doc("The maximum degree of parallelism to execute the subquery.") | ||
| .intConf | ||
| .checkValue(thres => thres > 0 && thres <= 128, "The threshold must be in (0,128].") | ||
| .createWithDefault(16) | ||
|
|
||
| val SQL_EVENT_TRUNCATE_LENGTH = buildStaticConf("spark.sql.event.truncate.length") | ||
| .doc("Threshold of SQL length beyond which it will be truncated before adding to " + | ||
| "event. Defaults to no truncation. If set to 0, callsite will be logged instead.") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ package org.apache.spark.sql.execution | |
| import java.util.concurrent.ConcurrentHashMap | ||
| import java.util.concurrent.atomic.AtomicLong | ||
|
|
||
| import scala.concurrent.{ExecutionContext, Future} | ||
|
|
||
| import org.apache.spark.SparkContext | ||
| import org.apache.spark.internal.config.Tests.IS_TESTING | ||
| import org.apache.spark.sql.SparkSession | ||
|
|
@@ -164,4 +166,20 @@ object SQLExecution { | |
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Wrap passed function to ensure necessary thread-local variables like | ||
| * SparkContext local properties are forwarded to execution thread | ||
| */ | ||
| def withThreadLocalCaptured[T]( | ||
| sparkSession: SparkSession, exec: ExecutionContext)(body: => T): Future[T] = { | ||
| val activeSession = sparkSession | ||
ajithme marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| val sc = sparkSession.sparkContext | ||
| val localProps = Utils.cloneProperties(sc.getLocalProperties) | ||
| Future { | ||
| SparkSession.setActiveSession(activeSession) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please unset/restore both of them to their old state?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean i should clear the thread locals.? as these are worker threads, they do not have any old state as such. For example, when the thread is created first time, it will inherit from parent thread, but when resued, restoring would cause the previous properties to be set which is stale. IMO, setting this on every execution should suffice. Please correct me if wrong
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should restore them to their previous state. In this particular case you can hold on to a SparkSession that is no longer in use. |
||
| sc.setLocalProperties(localProps) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @hvanhovell two questions:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the And I think the order doesn't matter. |
||
| body | ||
| }(exec) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,9 +19,9 @@ package org.apache.spark.sql.internal | |
|
|
||
| import org.scalatest.Assertions._ | ||
|
|
||
| import org.apache.spark.{SparkException, SparkFunSuite} | ||
| import org.apache.spark.{SparkException, SparkFunSuite, TaskContext} | ||
| import org.apache.spark.rdd.RDD | ||
| import org.apache.spark.sql.SparkSession | ||
| import org.apache.spark.sql.{Dataset, SparkSession} | ||
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.expressions.Attribute | ||
| import org.apache.spark.sql.catalyst.plans.logical.LocalRelation | ||
|
|
@@ -125,6 +125,38 @@ class ExecutorSideSQLConfSuite extends SparkFunSuite with SQLTestUtils { | |
| val e = intercept[SparkException](dummyQueryExecution1.toRdd.collect()) | ||
| assert(e.getCause.isInstanceOf[NoSuchElementException]) | ||
| } | ||
|
|
||
| test("SPARK-30556 propagate local properties to subquery execution thread") { | ||
| withSQLConf(StaticSQLConf.SUBQUERY_MAX_THREAD_THRESHOLD.key -> "1") { | ||
| withTempView("l", "m", "n") { | ||
| Seq(true).toDF().createOrReplaceTempView("l") | ||
| val confKey = "spark.sql.y" | ||
|
|
||
| def createDataframe(confKey: String, confValue: String): Dataset[Boolean] = { | ||
| Seq(true) | ||
| .toDF() | ||
| .mapPartitions { _ => | ||
| TaskContext.get.getLocalProperty(confKey) == confValue match { | ||
| case true => Iterator(true) | ||
| case false => Iterator.empty | ||
| } | ||
| } | ||
| } | ||
cloud-fan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // set local configuration and assert | ||
| val confValue1 = "e" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it is better to use something unique here to avoid a fluke. How about using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am neutral about both approaches. Just wanted to have a fixed input value to get predictable output value from testcase. I can update this and raise followup if you insist |
||
| createDataframe(confKey, confValue1).createOrReplaceTempView("m") | ||
| spark.sparkContext.setLocalProperty(confKey, confValue1) | ||
| assert(sql("SELECT * FROM l WHERE EXISTS (SELECT * FROM m)").collect.size == 1) | ||
|
|
||
| // change the conf value and assert again | ||
| val confValue2 = "f" | ||
| createDataframe(confKey, confValue2).createOrReplaceTempView("n") | ||
| spark.sparkContext.setLocalProperty(confKey, confValue2) | ||
| assert(sql("SELECT * FROM l WHERE EXISTS (SELECT * FROM n)").collect().size == 1) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| case class SQLConfAssertPlan(confToCheck: Seq[(String, String)]) extends LeafExecNode { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a different change right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should also be a static configuration since we can only change it at startup.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just opening as a configuration to make the change testable. DO you want me to raise separate PR just to make this configuration change seperate.?
This is part of
StaticSQLConfwhich is defined at startup, is there any other mechanism to define static conf.?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If its a static conf, then why isn't your unite test failing? Moreover, if its static then setting it in your test probably does not have any effect because we use the same JVM/SparkContext to run most tests, the chances are pretty high that it has been set before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see. as per documentation in
spark/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala
Line 26 in 705fc6a
BroadcastExchangeExeccreates executionContext. My initial guess is executionContext is not created till first subquery hence it work for UT. i will further investigate and get back with analysis.