Repository navigation
feat: set CometShuffleManager from CometPlugin on Spark 4.0 and later #7336
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -28,12 +28,12 @@ import scala.util.Try | |
|
|
||
| import org.apache.spark.api.plugin.{DriverPlugin, ExecutorPlugin, PluginContext, SparkPlugin} | ||
| import org.apache.spark.internal.Logging | ||
| import org.apache.spark.internal.config.{EVENT_LOG_ENABLED, EXECUTOR_MEMORY_OVERHEAD, EXECUTOR_MEMORY_OVERHEAD_FACTOR} | ||
| import org.apache.spark.internal.config.{EVENT_LOG_ENABLED, EXECUTOR_MEMORY_OVERHEAD, EXECUTOR_MEMORY_OVERHEAD_FACTOR, SHUFFLE_MANAGER} | ||
| import org.apache.spark.scheduler.{SparkListener, SparkListenerApplicationEnd, SparkListenerExecutorMetricsUpdate, SparkListenerExecutorRemoved} | ||
| import org.apache.spark.serializer.KryoSerializer | ||
| import org.apache.spark.sql.comet.execution.arrow.ArrowCachedBatchSerializer | ||
| import org.apache.spark.sql.comet.execution.shuffle.{CometCelebornShuffleManager, CometShuffleManager} | ||
| import org.apache.spark.sql.internal.StaticSQLConf | ||
| import org.apache.spark.sql.internal.{SQLConf, StaticSQLConf} | ||
| import org.apache.spark.util.{Clock, SystemClock} | ||
|
|
||
| import org.apache.comet.{COMET_VERSION, CometExecIterator, CometExecutorMemoryUsage, CometSparkSessionExtensions, NativeBase} | ||
|
|
@@ -112,6 +112,8 @@ class CometDriverPlugin private[spark] (clock: Clock) extends DriverPlugin with | |
|
|
||
| val extraConfs = new ju.HashMap[String, String]() | ||
|
|
||
| // Before the cache serializer, which is installed only with one of Comet's shuffle managers. | ||
| CometDriverPlugin.maybeSetShuffleManager(sc.conf) | ||
| CometDriverPlugin.maybeSetCacheSerializer(sc.conf, extraConfs) | ||
| CometDriverPlugin.warnIfKryoRegistrationsMissing(sc.conf) | ||
|
|
||
|
|
@@ -174,6 +176,36 @@ object CometDriverPlugin extends Logging { | |
| /** Spark config key under which the loaded Comet version is exposed at runtime. */ | ||
| val COMET_VERSION_CONFIG = "spark.comet.version" | ||
|
|
||
| // Choose Comet's shuffle manager for an application that did not, so that Comet does not | ||
| // disable itself. Only from Spark 4.0 (SPARK-45762) does SparkContext create the manager after | ||
| // it initializes the plugins, from the same conf that executors then fetch. An application's own | ||
| // choice, even Spark's, is kept: it may depend on that manager, as Celeborn does. | ||
| // Nor is it set where Comet disables itself whatever the manager, since Comet would not run and | ||
| // Spark would copy every row of its exchanges for want of a SortShuffleManager. | ||
| private[apache] def maybeSetShuffleManager(conf: SparkConf): Unit = { | ||
|
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. Thanks for working this out from the Spark 4.0 source. I checked the same ordering in v4.0.0 and it holds on the driver and the executors. One case concerns me. maybeSetShuffleManager checks spark.comet.enabled and Comet shuffle, but isCometLoaded can still disable Comet afterwards. That happens on a big-endian host, when spark.sql.parquet.int96TimestampConversion=true, or when the native library fails to load and spark.comet.nativeLoadRequired is left at false. In those applications this PR swaps SortShuffleManager for CometShuffleManager, and Comet never runs. Spark's ShuffleExchangeExec.needToCopyObjectsBeforeShuffle returns true for any manager that is not a SortShuffleManager, so every Spark exchange then copies each row before the shuffle. On 1.2 the same application ran on Spark's manager with no copy. Could the plugin skip setting the manager when it can see one of those conditions at startup? A small helper shared with isCometLoaded would keep the two from drifting. A test with one of those settings would lock it in.
Member
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. Done in bfc14c7. |
||
| if (CometSparkSessionExtensions.isSpark40Plus && | ||
| getBooleanConf(conf, CometConf.COMET_ENABLED) && | ||
| getBooleanConf(conf, CometConf.COMET_SHUFFLE_ENABLED) && | ||
| !conf.contains(SHUFFLE_MANAGER) && | ||
| !cometUnsupportedAtStartup(conf)) { | ||
| val value = classOf[CometShuffleManager].getName | ||
| conf.set(SHUFFLE_MANAGER.key, value) | ||
| logInfo(s"Auto-set ${SHUFFLE_MANAGER.key}=$value") | ||
| } | ||
| } | ||
|
|
||
| // Applies the checks that isCometLoaded makes on the platform and settings. A library that | ||
| // fails to load when nativeLoadRequired is set counts as supported: isCometLoaded throws then, | ||
| // when Comet first plans a query, and the application should not fail here instead. | ||
| private def cometUnsupportedAtStartup(conf: SparkConf): Boolean = { | ||
| val int96Conversion = conf.getBoolean(SQLConf.PARQUET_INT96_TIMESTAMP_CONVERSION.key, false) | ||
| val nativeLoadRequired = getBooleanConf(conf, CometConf.COMET_NATIVE_LOAD_REQUIRED) | ||
| Try( | ||
| CometSparkSessionExtensions | ||
| .unsupportedEnvironmentReason(int96Conversion, nativeLoadRequired) | ||
| .isDefined).getOrElse(false) | ||
| } | ||
|
|
||
| // Use Comet's cache serializer only when the native in-memory cache scan can run, which needs | ||
| // Comet and its native execution as well as the cache config. spark.sql.cache.serializer is | ||
| // static, so an application that starts with Comet or native execution off would otherwise | ||
|
|
@@ -264,7 +296,7 @@ object CometDriverPlugin extends Logging { | |
| // class name. | ||
| private def isCometShuffleManager(conf: SparkConf): Boolean = | ||
| Set(classOf[CometShuffleManager].getName, classOf[CometCelebornShuffleManager].getName) | ||
| .contains(conf.get("spark.shuffle.manager", "")) | ||
| .contains(conf.get(SHUFFLE_MANAGER.key, "")) | ||
|
|
||
| // Comet's native allocations are made by the Rust global allocator and live in the native heap. | ||
| // In off-heap mode the share that operators reserve is charged against a memory pool, but | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,14 +26,15 @@ import java.util.concurrent.TimeUnit | |
|
|
||
| import org.apache.logging.log4j.Level | ||
| import org.apache.spark.api.plugin.{DriverPlugin, ExecutorPlugin, PluginContext, SparkPlugin} | ||
| import org.apache.spark.internal.config.SHUFFLE_MANAGER | ||
| import org.apache.spark.scheduler.{SparkListenerApplicationEnd, SparkListenerEvent, SparkListenerExecutorMetricsUpdate, SparkListenerExecutorRemoved} | ||
| import org.apache.spark.sql.{CometTestBase, SaveMode, SparkSession} | ||
| import org.apache.spark.sql.comet.CometPlan | ||
| import org.apache.spark.sql.comet.execution.shuffle.{CometShuffleExchangeExec, CometShuffleManager} | ||
| import org.apache.spark.sql.internal.StaticSQLConf | ||
| import org.apache.spark.sql.comet.execution.shuffle.{CometCelebornShuffleManager, CometShuffleExchangeExec, CometShuffleManager} | ||
| import org.apache.spark.sql.internal.{SQLConf, StaticSQLConf} | ||
| import org.apache.spark.util.{JsonProtocol, ManualClock, Utils} | ||
|
|
||
| import org.apache.comet.{COMET_VERSION, CometConf, CometExecIterator, CometExecutorMemoryUsage} | ||
| import org.apache.comet.{COMET_VERSION, CometConf, CometExecIterator, CometExecutorMemoryUsage, CometSparkSessionExtensions} | ||
|
|
||
| class CometPluginsSuite extends CometTestBase { | ||
| override protected def sparkConf: SparkConf = { | ||
|
|
@@ -326,6 +327,51 @@ class CometPluginsUnifiedModeSuite extends CometTestBase { | |
| assert(execMemOverhead3 == "1G") | ||
| assert(execMemOverhead4 == "1G") | ||
| } | ||
|
|
||
| // This suite names no shuffle manager. From Spark 4.0 the plugin chooses Comet's; before that | ||
| // Spark has built its own by the time the plugin runs, and Comet disables itself. | ||
| test("the plugin sets Comet's shuffle manager from Spark 4.0 and Comet runs") { | ||
| val expected = CometSparkSessionExtensions.isSpark40Plus | ||
| assert(SparkEnv.get.shuffleManager.isInstanceOf[CometShuffleManager] == expected) | ||
| withParquetTable((0 until 100).map(i => (i, (i % 7).toString)), "tbl") { | ||
| val (_, plan) = checkSparkAnswer("SELECT _2, count(*) FROM tbl GROUP BY _2") | ||
| assert(collect(plan) { case op: CometShuffleExchangeExec => op }.nonEmpty == expected, plan) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class CometDriverPluginShuffleManagerSuite extends SparkFunSuite { | ||
|
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. CometDriverPluginShuffleManagerSuite is a new top-level suite. CI runs the plugin suites from an explicit list passed as -DwildcardSuites in .github/workflows/pr_build_linux.yml (around line 582) and pr_build_macos.yml (around line 248). Nothing in either list matches this name, so the version-gate tests and the keep-the-user's-value tests don't run in CI. Could you add it to both lists? Once it's registered, the run-all-spark-profiles label would cover 3.4 and 4.2, which the local runs in the description didn't include.
Member
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. Added to both |
||
| private val cometManager = classOf[CometShuffleManager].getName | ||
|
|
||
| private def chosen(conf: SparkConf): Option[String] = { | ||
| CometDriverPlugin.maybeSetShuffleManager(conf) | ||
| conf.getOption(SHUFFLE_MANAGER.key) | ||
| } | ||
|
|
||
| test("sets Comet's shuffle manager only on Spark 4.0 and later") { | ||
| val expected = if (CometSparkSessionExtensions.isSpark40Plus) Some(cometManager) else None | ||
| assert(chosen(new SparkConf(false)) == expected) | ||
| } | ||
|
|
||
| test("keeps the shuffle manager the application set") { | ||
| Seq("sort", "tungsten-sort", classOf[CometCelebornShuffleManager].getName, "com.example.Mgr") | ||
| .foreach { manager => | ||
| assert(chosen(new SparkConf(false).set(SHUFFLE_MANAGER.key, manager)).contains(manager)) | ||
| } | ||
| } | ||
|
|
||
| test("leaves the shuffle manager unset when Comet or Comet shuffle is disabled") { | ||
| val disablingKeys = Seq(CometConf.COMET_ENABLED.key, CometConf.COMET_SHUFFLE_ENABLED.key) ++ | ||
| CometConf.COMET_SHUFFLE_ENABLED.alternatives | ||
| disablingKeys.foreach { key => | ||
| assert(chosen(new SparkConf(false).set(key, "false")).isEmpty, key) | ||
| } | ||
| } | ||
|
|
||
| test("leaves the shuffle manager unset where Comet disables itself whatever the manager") { | ||
| val conf = new SparkConf(false).set(SQLConf.PARQUET_INT96_TIMESTAMP_CONVERSION.key, "true") | ||
| assert(chosen(conf).isEmpty) | ||
| } | ||
| } | ||
|
|
||
| class CometPluginsExtensionOnlySuite extends CometTestBase { | ||
|
|
||
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.
The guide's introduction says every behavior change below names a
spark.comet.legacy.*key, and the 1.2.0 section says up front when none is needed. This entry names no key. Running an application in Comet instead of Spark is the "which operators run natively" kind of change that the introduction excludes, so could the 1.3.0 section say it needs no legacy key, as 1.2.0 does?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.
The 1.3.0 section now says up front that it needs no
spark.comet.legacy.*key, as 1.2.0 does (bfc14c7).