Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d7ae3bb
perf: project cached batches by buffer selection, prune on collated s…
andygrove Aug 28, 2026
f59c9dc
refactor: use Spark's interpreted ordering for bounds, hoist projecti…
andygrove Aug 29, 2026
ccd469e
fix: relocate the arrow-compression service file when shading
andygrove Aug 29, 2026
e71d802
test: drop the cache leak test that depends on zstd corruption detection
andygrove Aug 29, 2026
d8d4196
feat: enable Comet's in-memory cache by default
andygrove Sep 2, 2026
4010f78
test: cover nested columns in the cached-batch projection tests and b…
andygrove Sep 5, 2026
8c19267
fix: drop a redundant string interpolator flagged by scalafix Redunda…
andygrove Sep 5, 2026
1699665
Merge remote-tracking branch 'apache/main' into feat/cache-buffer-sel…
andygrove Sep 7, 2026
792a465
Merge branch 'main' into feat/cache-enabled-by-default
andygrove Sep 8, 2026
bac454e
Merge remote-tracking branch 'apache/main' into feat/cache-buffer-sel…
cincrement Sep 10, 2026
f05c204
Merge branch 'main' into feat/cache-buffer-selection-projection
andygrove Sep 14, 2026
3e74e9d
review: check the cached layout, and address the rest of the review
andygrove Sep 15, 2026
b978e54
review: own the write-side compression buffers, fix the activation ex…
andygrove Sep 15, 2026
dbf487b
Merge remote-tracking branch 'origin/feat/cache-buffer-selection-proj…
andygrove Sep 21, 2026
a71e8cb
Merge remote-tracking branch 'apache/main' into feat/cache-enabled-by…
andygrove Sep 21, 2026
e483d08
fix: write cached batches to the schema width, not the batch width
andygrove Sep 21, 2026
3cf15ac
Merge branch 'fix/cache-wide-columnar-batch' into feat/cache-enabled-…
andygrove Sep 21, 2026
c7f1ce3
Merge branch 'main' into feat/cache-enabled-by-default
andygrove Sep 22, 2026
89cd107
Merge remote-tracking branch 'apache/main' into HEAD
andygrove Sep 24, 2026
d5983c2
Merge remote-tracking branch 'apache/main' into feat/cache-enabled-by…
andygrove Sep 24, 2026
299d381
Merge remote-tracking branch 'apache/main' into feat/cache-enabled-by…
andygrove Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/source/user-guide/latest/in-memory-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ format that Comet operators read directly. Without it, a cached table is stored
format and every scan of it has to convert each batch before Comet can continue, which shows up in
the plan as a `CometSparkColumnarToColumnar` above the cache scan.

This feature is **experimental and disabled by default**. Turn it on at startup, alongside the rest
of Comet's configuration:
This feature is **experimental and enabled by default**. To turn it off, set the config at startup,
alongside the rest of Comet's configuration:
Comment on lines +27 to +28

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key did not exist in 1.0.0, so a user upgrading from 1.0.0 goes from Spark's cache format to Comet's without setting anything. With spark.kryo.registrationRequired=true and no CometKryoRegistrator, a df.cache() that spills to disk now fails with "Class is not registered" where it did not before. The plugin only logs a warning for that.

The versioning policy counts a new error under the same explicit configuration as a behavior change. Could you add an entry to the upgrade guide under the next release that covers the format change and the Kryo requirement? The policy asks for a spark.comet.legacy.* key, but spark.comet.exec.inMemoryCache.enabled=false already restores the old behavior, so naming that key in the entry seems enough. If you read the policy differently, it would be good to settle that here, since this is one of the first behavior changes since 1.0.0.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the upgrade guide entry, covering both the format change and the Kryo requirement. Moving the flip past 1.1.0 changes one premise, though. 1.1.0 ships this key with a default of false, so turning it on in the next release is a change to an existing key's default, which is the first case the policy lists. Let's settle the legacy-key question when this comes out of draft.


```shell
$SPARK_HOME/bin/spark-shell \
... \
--conf spark.comet.exec.inMemoryCache.enabled=true
--conf spark.comet.exec.inMemoryCache.enabled=false
```

It has to be set before the `SparkContext` starts. Comet's driver plugin chooses
Expand Down Expand Up @@ -99,7 +99,7 @@ nowhere to record either that a column is dictionary encoded or the dictionary i

| Config | Default | Description |
| ------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `spark.comet.exec.inMemoryCache.enabled` | `false` | Whether to store and scan Spark's in-memory cache in Comet's format. Read at startup. |
| `spark.comet.exec.inMemoryCache.enabled` | `true` | Whether to store and scan Spark's in-memory cache in Comet's format. Read at startup. |
| `spark.comet.exec.inMemoryCache.compression.codec` | `zstd` | Arrow IPC compression codec for cached data: `zstd` or `none`. Affects newly cached data only — a batch records the codec it was written with. |
| `spark.comet.exec.inMemoryCache.compression.zstd.level` | `1` | Compression level when the codec is `zstd`. Ignored otherwise. |

Expand Down Expand Up @@ -186,8 +186,8 @@ format, and the narrower the read, the wider the gap. Measured by the same bench
| 3 of 6 columns | 98 ms | 331 ms | 3.4x |
| 6 of 6 columns | 410 ms | 623 ms | 1.5x |

This is why the feature is off by default. The cause is not yet established;
[#5485](https://github.com/apache/datafusion-comet/issues/5485) tracks it.
This is the main reason the feature is still described as experimental. The cause is not yet
established; [#5485](https://github.com/apache/datafusion-comet/issues/5485) tracks it.

Comet's serializer exists because Spark's own Arrow cache format
([SPARK-57268](https://issues.apache.org/jira/browse/SPARK-57268)) is only available from Spark
Expand Down
2 changes: 1 addition & 1 deletion spark/src/main/scala/org/apache/comet/CometConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ object CometConf extends ShimCometConf {
"SparkContext, otherwise caching fails as soon as a block is serialized, including " +
"the disk half of the default MEMORY_AND_DISK storage level.")
.booleanConf
.createWithDefault(false)
.createWithDefault(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#5485 treats the slower Spark-operator reads as acceptable because "the cache path is off by default ... rather than a regression in a shipped path." This PR makes it a shipped path, and the format is fixed when the relation materializes, so a user can't avoid it for one query.

With the plugin gated as suggested above, the remaining exposure is a query where the cached scan runs natively and a Spark operator above it reads through a columnar-to-row transition, and a session that turns Comet off at runtime after caching. Is there a benchmark number for the first case against Spark's own cache format? The published numbers compare against Comet off entirely. For the second case, option 1 in #5485 (a fallback reason when Spark operators read a relation stored in Comet's format) is what would tell a user to turn the feature off. Could that land before or with this PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The audit turned up part of the answer. Under AQE the first case is the normal outcome, not an edge case. Once the table-cache stage materializes, the re-plan leaves the operators above it on Spark, so a plain aggregate or join over a cached table reads Comet's format through a CometColumnarToRow (#6202). CometInMemoryCacheBenchmark runs with AQE off, so the published numbers don't show it. I'll fix #6202 first and then benchmark with AQE on against Spark's own format, so the number measures the path users will actually get.

Yes to option 1 from #5485 landing with the default flip. It will need to cover the #6202 path too, since nothing records a fallback reason there today.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6202 is fixed by #6208, which is now merged into this branch in 299d381. Under AQE, an aggregate or join over a cached table now stays native once the table-cache stage materializes, so the first case is no longer the normal outcome: it takes an operator Comet does not support above the cached scan. Option 1 no longer has a #6202 path to cover either, since the operators above the stage now convert, or record their own fallback reason, like any other operator. The benchmark with AQE on against Spark's own format is next.


val COMET_EXEC_IN_MEMORY_CACHE_COMPRESSION_CODEC: ConfigEntry[String] =
conf("spark.comet.exec.inMemoryCache.compression.codec")
Expand Down
4 changes: 3 additions & 1 deletion spark/src/main/scala/org/apache/spark/Plugins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ object CometDriverPlugin extends Logging {
private[apache] def maybeSetCacheSerializer(
conf: SparkConf,
extraConfs: ju.HashMap[String, String]): Unit = {
if (conf.getBoolean(CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.key, false)) {
if (conf.getBoolean(
CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.key,
CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.defaultValue.get)) {
Comment on lines +109 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the default flipped, this installs Comet's serializer for every application that loads CometPlugin, including ones that start with spark.comet.enabled=false or spark.comet.exec.enabled=false. Those applications can never plan CometInMemoryTableScan, so every cached read goes through Spark operators on top of Comet's format. That is the 1.5x to 5.2x slower case in the Limitations table. Keeping the plugin in spark.plugins cluster-wide and switching Comet off with spark.comet.enabled=false is a common setup, and before this PR it left the cache format alone.

#5485 already lists this check as sound, and only calls it narrow because anyone who opted in would have execution enabled. That premise no longer holds once the feature is on by default. Could the plugin also require both configs at startup? This object already has a getBooleanConf helper that falls back to the entry's default, so the new read can use it too:

Suggested change
if (conf.getBoolean(
CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.key,
CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED.defaultValue.get)) {
if (getBooleanConf(conf, CometConf.COMET_ENABLED) &&
getBooleanConf(conf, CometConf.COMET_EXEC_ENABLED) &&
getBooleanConf(conf, CometConf.COMET_EXEC_IN_MEMORY_CACHE_ENABLED)) {

A session that starts with execution off and turns it on later would then keep Spark's format, and CometExecRule already records a fallback reason for that case. That seems like the right trade, since the serializer is fixed for the application.

The driver-plugin test sets the key to true explicitly, so nothing exercises the fallback to the default, which is the code change in this file. Could you add a case where the key is unset (serializer installed), plus cases for spark.comet.enabled=false and spark.comet.exec.enabled=false (serializer not installed)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. With the default on, an application that can never plan the native scan shouldn't get Comet's format. I'll gate the install on spark.comet.enabled and spark.comet.exec.enabled through getBooleanConf, and add the three plugin cases, before this comes out of draft. It won't avoid the slow path on its own, though: with both on, AQE still puts Spark operators above the cache scan, which is #6202.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#6202 is fixed by #6208, which is now merged into this branch in 299d381. With both configs on, AQE keeps the operators above the cache scan native after the re-plan, so the caveat at the end of my reply no longer applies. The gate and the three plugin cases are still to come.

val serializerKey = StaticSQLConf.SPARK_CACHE_SERIALIZER.key
val serializerValue =
"org.apache.spark.sql.comet.execution.arrow.ArrowCachedBatchSerializer"
Expand Down
Loading