From 07f14a22f84df988c6d7a0b5a810b320993f5138 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Sat, 18 Apr 2026 10:52:59 -0600 Subject: [PATCH 1/8] first draft of Comet 0.15.0 blog post (generated by Claude, not reviewed yet) --- .../2026-04-18-datafusion-comet-0.15.0.md | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 content/blog/2026-04-18-datafusion-comet-0.15.0.md diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md new file mode 100644 index 00000000..01182da4 --- /dev/null +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -0,0 +1,148 @@ +--- +layout: post +title: Apache DataFusion Comet 0.15.0 Release +date: 2026-04-18 +author: pmc +categories: [subprojects] +--- + + + +[TOC] + +The Apache DataFusion PMC is pleased to announce version 0.15.0 of the [Comet](https://datafusion.apache.org/comet/) subproject. + +Comet is an accelerator for Apache Spark that translates Spark physical plans to DataFusion physical plans for +improved performance and efficiency without requiring any code changes. + +This release covers approximately four weeks of development work and is the result of merging 142 PRs from 19 +contributors. See the [change log] for more information. + +[change log]: https://github.com/apache/datafusion-comet/blob/main/dev/changelog/0.15.0.md + +## Key Features + +### Native Iceberg Reader Enabled by Default + +This release marks a major milestone for Iceberg users: **Comet's fully-native Iceberg reader is now enabled by +default**. Workloads that read Iceberg tables will automatically benefit from native Rust-based scans built on +iceberg-rust, with no additional configuration required. + +To support this change, the release bundles a broad set of Iceberg-focused improvements: + +- **Correct classloader handling**: Iceberg classes are now loaded via the thread context classloader, resolving + class-loading issues in environments where the executor classloader differs from the application classloader. +- **Continuous Iceberg CI**: Iceberg Spark integration tests now run on every PR and push to `main`, providing + continuous validation of the native Iceberg code path. Test diffs for Spark 3.4 were updated to keep the matrix + green across supported Spark versions. +- **iceberg-rust upgrade**: Comet picks up the latest iceberg-rust, pulling in fixes for Parquet reader edge cases + discovered in earlier testing. +- **Refreshed documentation**: The Iceberg user guide has been rewritten to reflect current capabilities, and the + contributor guide now documents how to run the Iceberg Spark test suites locally. + +Users who need to fall back to the previous behavior can still opt out, but we encourage the community to exercise +the native reader and report any issues. + +### Auto Mode Enables `native_datafusion` Scan + +The `auto` scan mode now enables the `native_datafusion` scan where supported, giving users the benefits of the +native Parquet reader without having to explicitly opt in. This is part of the ongoing effort to make +`native_datafusion` the default Parquet path once the deprecation of `native_iceberg_compat` completes. + +### New Expressions and Function Support + +This release adds support for the following: + +- **Date/time functions**: `days`, `hours`, `date_from_unix_date` +- **String/JSON functions**: native `get_json_object` with improved performance over the fallback path +- **Hash/math functions**: `crc32c`, `bin` +- **Array functions**: `sort_array` +- **Window functions**: `LEAD` and `LAG` with `IGNORE NULLS` +- **Aggregates**: SQL `FILTER (WHERE ...)` clauses now execute natively; `Corr` aggregate enabled + +### Expanded Metrics and Observability + +Comet metrics can now be exposed through Spark's external monitoring system, making it easier to integrate Comet +execution statistics with existing observability dashboards. Native DataFusion scans also now report accurate +`filesScanned` and `bytesScanned` input metrics, matching Spark's native Parquet scan reporting. + +## Performance Improvements + +Performance was a major theme of this release, with a series of targeted optimizations across the shuffle, scan, +and execution layers: + +- **Shuffle read path**: The native shuffle reader no longer uses FFI on the read side, removing a per-batch cost + that was particularly visible in shuffle-heavy queries. +- **Broadcast exchanges**: Batches are now coalesced before broadcasting, reducing the number of small batches + crossing the JVM/native boundary. +- **Columnar-to-row (C2R)**: Native C2R conversion is now exercised for a broader set of query shapes. +- **FFI-safe operators**: More operators are marked as FFI-safe, avoiding unnecessary deep copies when crossing + the JVM/native boundary. +- **Shared memory pools**: Unified memory pools are now shared across native execution contexts within a Spark + task, improving memory accounting and reducing fragmentation. +- **Object store caching**: Object stores and bucket region lookups are cached, dramatically reducing DNS query + volume on workloads that open many files. +- **`get_ranges` performance**: Picked up an upstream `opendal` fix that restores fast range reads from object + storage. +- **Spill I/O**: Removed a redundant `BufReader` wrapper when copying spill files to shuffle output. + +Together, these changes reduce CPU and memory overhead for shuffle-heavy, broadcast-heavy, and +object-storage-bound workloads. + +## Stability and Correctness + +A significant portion of this release is dedicated to stability and Spark compatibility. Highlights include: + +- **Cast string to timestamp**: Multiple fixes for UTC timestamps, timezone handling, special formats + (`epoch`, `now`, etc.), and compatibility with Spark's semantics. +- **Cast decimal to string**: Added legacy mode handling to match Spark's output formatting. +- **String to decimal**: Support for full-width characters, null characters, and negative scale. +- **Decimal arithmetic**: Fixes for decimal division and additional test coverage for ANSI overflow handling, + including scalar decimal overflow. +- **Array expressions**: Corrected `GetArrayItem` null handling for dynamic indices; `array_append` return type + fixed and marked `Compatible`; audited `array_insert` for correctness; `array_compact` marked `Compatible`; + array-to-array cast enabled. +- **DateTrunc/TimestampTrunc**: Fixed native crashes when the input is a literal. +- **Ambiguous local times**: Correct handling of ambiguous and non-existent local times across DST transitions. +- **Case-insensitive Parquet fields**: `native_datafusion` now correctly detects duplicate/ambiguous fields in + case-insensitive mode and falls back where appropriate. +- **Shuffle planning**: Shuffle fallback decisions are now "sticky" across planning passes, and Comet columnar + shuffle is skipped for stages containing DPP scans to avoid mismatched partitioning. +- **Error propagation**: Native error messages are now propagated through `SparkException` even when the + `errorClass` is empty, and file-not-found errors flow through the standard Spark error JSON path. +- **Trigonometric compatibility**: `tan` and `atan2` are now Spark-compatible. + +## Dependency Upgrades + +This release upgrades to **DataFusion 53.1** and **Arrow 58.1**, and picks up the latest `iceberg-rust` release +with additional reader fixes. The `jni` crate was upgraded to 0.22.4. + +## Deprecations and Removals + +The `SupportsComet` interface has been removed, along with the Java-based Iceberg integration path (which is +fully superseded by the native Iceberg reader). The `native_iceberg_compat` scan remains deprecated and is +expected to be removed in a future release in favor of `native_datafusion`. + +## Compatibility + +Supported platforms include Spark 3.4.3, 3.5.4–3.5.8, and Spark 4.0.x with various JDK and Scala combinations. + +The community encourages users to test Comet with existing Spark and Iceberg workloads and welcomes contributions +to ongoing development. From 031317d537592591be5552f2f26aca8abd613cd8 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Mon, 20 Apr 2026 10:13:12 -0600 Subject: [PATCH 2/8] performance charts --- .../2026-04-18-datafusion-comet-0.15.0.md | 28 ++++++++++++++++++ .../images/comet-0.15.0/tpch_allqueries.png | Bin 0 -> 23347 bytes .../comet-0.15.0/tpch_queries_compare.png | Bin 0 -> 26102 bytes 3 files changed, 28 insertions(+) create mode 100644 content/images/comet-0.15.0/tpch_allqueries.png create mode 100644 content/images/comet-0.15.0/tpch_queries_compare.png diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index 01182da4..6f5b8125 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -37,6 +37,34 @@ contributors. See the [change log] for more information. [change log]: https://github.com/apache/datafusion-comet/blob/main/dev/changelog/0.15.0.md +## Performance + +**Comet provides a 2x speedup for TPC-H @ 1TB, resulting in 50% cost savings.** + +That 2x speedup gives you a choice: finish the same Spark workload in half the time on the cluster you already +have, or match your current Spark performance on roughly half the resources. Either way, the gain translates +directly into lower cloud bills, reduced on-prem capacity, and lower energy usage, with no changes to your +existing Spark SQL, DataFrame, or PySpark code. Comet runs on commodity hardware: no GPUs, FPGAs, or other +specialized accelerators are required, so the savings come from better utilization of the infrastructure you +already run on. + + + + + +See the [Comet Benchmarking Guide](https://datafusion.apache.org/comet/contributor-guide/benchmarking.html) for +more details. + ## Key Features ### Native Iceberg Reader Enabled by Default diff --git a/content/images/comet-0.15.0/tpch_allqueries.png b/content/images/comet-0.15.0/tpch_allqueries.png new file mode 100644 index 0000000000000000000000000000000000000000..e2cacf7701bcf5bc637c7b9ec4035a1bb1b57066 GIT binary patch literal 23347 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 jNB)n;@)uVLt$gC$DVs|Rkge Date: Mon, 20 Apr 2026 10:18:32 -0600 Subject: [PATCH 3/8] minor update --- content/blog/2026-04-18-datafusion-comet-0.15.0.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index 6f5b8125..91ef2a84 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -124,12 +124,11 @@ and execution layers: - **FFI-safe operators**: More operators are marked as FFI-safe, avoiding unnecessary deep copies when crossing the JVM/native boundary. - **Shared memory pools**: Unified memory pools are now shared across native execution contexts within a Spark - task, improving memory accounting and reducing fragmentation. + task, improving memory accounting and reducing OOMs. - **Object store caching**: Object stores and bucket region lookups are cached, dramatically reducing DNS query volume on workloads that open many files. - **`get_ranges` performance**: Picked up an upstream `opendal` fix that restores fast range reads from object storage. -- **Spill I/O**: Removed a redundant `BufReader` wrapper when copying spill files to shuffle output. Together, these changes reduce CPU and memory overhead for shuffle-heavy, broadcast-heavy, and object-storage-bound workloads. From b2278cf3d768ba1c94f63b9b4e53b99caee16185 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Mon, 20 Apr 2026 10:22:24 -0600 Subject: [PATCH 4/8] edit --- .../2026-04-18-datafusion-comet-0.15.0.md | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index 91ef2a84..41375916 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -39,7 +39,7 @@ contributors. See the [change log] for more information. ## Performance -**Comet provides a 2x speedup for TPC-H @ 1TB, resulting in 50% cost savings.** +**Comet 0.15.0 provides a 2x speedup for TPC-H @ 1TB, resulting in 50% cost savings.** That 2x speedup gives you a choice: finish the same Spark workload in half the time on the cluster you already have, or match your current Spark performance on roughly half the resources. Either way, the gain translates @@ -65,9 +65,45 @@ alt="TPC-H Query-by-Query Comparison" See the [Comet Benchmarking Guide](https://datafusion.apache.org/comet/contributor-guide/benchmarking.html) for more details. -## Key Features +Performance was a major theme of this release, with a series of targeted optimizations across the shuffle, scan, +and execution layers. + +### Reducing JVM/Native Boundary Overhead + +Several changes in this release target the cost of crossing between the JVM and native sides, which can dominate +execution time in shuffle- and broadcast-heavy workloads: + +- **Shuffle read path**: The native shuffle reader no longer uses FFI on the read side, removing a per-batch cost + that was particularly visible in shuffle-heavy queries. +- **Broadcast exchanges**: Batches are now coalesced before broadcasting, reducing the number of small batches + crossing the JVM/native boundary. +- **FFI-safe operators**: More operators are marked as FFI-safe, avoiding unnecessary deep copies when crossing + the JVM/native boundary. + +### Expanded Native Execution Coverage + +- **Columnar-to-row (C2R)**: Native C2R conversion is now exercised for a broader set of query shapes. +- **`auto` scan mode**: The `auto` scan mode now enables the `native_datafusion` scan where supported, giving + users the benefits of the native Parquet reader without having to explicitly opt in. This is part of the + ongoing effort to make `native_datafusion` the default Parquet path once the deprecation of + `native_iceberg_compat` completes. + +### Memory Management + +- **Shared memory pools**: Unified memory pools are now shared across native execution contexts within a Spark + task, improving memory accounting and reducing OOMs. + +### Object Storage I/O -### Native Iceberg Reader Enabled by Default +- **Object store caching**: Object stores and bucket region lookups are cached, dramatically reducing DNS query + volume on workloads that open many files. +- **`get_ranges` performance**: Picked up an upstream `opendal` fix that restores fast range reads from object + storage. + +Together, these changes reduce CPU and memory overhead for shuffle-heavy, broadcast-heavy, and +object-storage-bound workloads. + +## Native Iceberg Reader Enabled by Default This release marks a major milestone for Iceberg users: **Comet's fully-native Iceberg reader is now enabled by default**. Workloads that read Iceberg tables will automatically benefit from native Rust-based scans built on @@ -88,11 +124,7 @@ To support this change, the release bundles a broad set of Iceberg-focused impro Users who need to fall back to the previous behavior can still opt out, but we encourage the community to exercise the native reader and report any issues. -### Auto Mode Enables `native_datafusion` Scan - -The `auto` scan mode now enables the `native_datafusion` scan where supported, giving users the benefits of the -native Parquet reader without having to explicitly opt in. This is part of the ongoing effort to make -`native_datafusion` the default Parquet path once the deprecation of `native_iceberg_compat` completes. +## Other Key Features ### New Expressions and Function Support @@ -111,28 +143,6 @@ Comet metrics can now be exposed through Spark's external monitoring system, mak execution statistics with existing observability dashboards. Native DataFusion scans also now report accurate `filesScanned` and `bytesScanned` input metrics, matching Spark's native Parquet scan reporting. -## Performance Improvements - -Performance was a major theme of this release, with a series of targeted optimizations across the shuffle, scan, -and execution layers: - -- **Shuffle read path**: The native shuffle reader no longer uses FFI on the read side, removing a per-batch cost - that was particularly visible in shuffle-heavy queries. -- **Broadcast exchanges**: Batches are now coalesced before broadcasting, reducing the number of small batches - crossing the JVM/native boundary. -- **Columnar-to-row (C2R)**: Native C2R conversion is now exercised for a broader set of query shapes. -- **FFI-safe operators**: More operators are marked as FFI-safe, avoiding unnecessary deep copies when crossing - the JVM/native boundary. -- **Shared memory pools**: Unified memory pools are now shared across native execution contexts within a Spark - task, improving memory accounting and reducing OOMs. -- **Object store caching**: Object stores and bucket region lookups are cached, dramatically reducing DNS query - volume on workloads that open many files. -- **`get_ranges` performance**: Picked up an upstream `opendal` fix that restores fast range reads from object - storage. - -Together, these changes reduce CPU and memory overhead for shuffle-heavy, broadcast-heavy, and -object-storage-bound workloads. - ## Stability and Correctness A significant portion of this release is dedicated to stability and Spark compatibility. Highlights include: From fc3ec45b24cd79083f3b43c4c337a65976f48e67 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Mon, 20 Apr 2026 10:24:30 -0600 Subject: [PATCH 5/8] edit --- content/blog/2026-04-18-datafusion-comet-0.15.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index 41375916..c7b3ad46 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -39,7 +39,7 @@ contributors. See the [change log] for more information. ## Performance -**Comet 0.15.0 provides a 2x speedup for TPC-H @ 1TB, resulting in 50% cost savings.** +**Comet 0.15.0 provides a 2x speedup for TPC-H @ SF1000 (1TB), resulting in 50% cost savings.** That 2x speedup gives you a choice: finish the same Spark workload in half the time on the cluster you already have, or match your current Spark performance on roughly half the resources. Either way, the gain translates From a62a9da5021b7ed961a7c6a5b2ead0199d042aa2 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Mon, 20 Apr 2026 13:35:56 -0600 Subject: [PATCH 6/8] Apply suggestion from @mbutrovich Co-authored-by: Matt Butrovich --- .../2026-04-18-datafusion-comet-0.15.0.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index c7b3ad46..fd5b7b34 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -123,7 +123,27 @@ To support this change, the release bundles a broad set of Iceberg-focused impro Users who need to fall back to the previous behavior can still opt out, but we encourage the community to exercise the native reader and report any issues. +### Sort-Merge Join Performance +Comet relies heavily on sort-merge join (SMJ) because DataFusion's hash joins do not yet support spilling to +disk. For larger-than-memory joins, SMJ is the only viable path, making its performance critical for real-world +workloads at scale. + +DataFusion 53 includes several SMJ improvements that Comet 0.15.0 benefits from directly: + +- **Zero-copy slicing** instead of the take kernel ([datafusion#20463](https://github.com/apache/datafusion/pull/20463)) +- **Streaming output** instead of waiting for all input before emitting ([datafusion#20482](https://github.com/apache/datafusion/pull/20482)) +- **Cached row counts** to avoid O(n) recounting ([datafusion#20478](https://github.com/apache/datafusion/pull/20478)) + +Additional SMJ work is landing in upstream DataFusion and will arrive in a future Comet release: + +- Specialized semi/anti join stream ([datafusion#20806](https://github.com/apache/datafusion/pull/20806)) +- Batch deferred filtering with 20–50x improvements for near-unique LEFT and FULL joins ([datafusion#21184](https://github.com/apache/datafusion/pull/21184)) +- DynComparator for ~5% TPC-H improvement ([datafusion#21484](https://github.com/apache/datafusion/pull/21484)) +- Vec-based filter state replacing HashMap ([datafusion#21517](https://github.com/apache/datafusion/pull/21517)) +- Full outer join correctness fix for NULL filter results ([datafusion#21660](https://github.com/apache/datafusion/pull/21660)) + +With these performance improvements, the next release of Comet will enable SMJ with filters by default. ## Other Key Features ### New Expressions and Function Support From c9dfbf9596b53d768de6bd906ef021ae82b832c3 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Tue, 21 Apr 2026 08:49:09 -0600 Subject: [PATCH 7/8] docs: address review feedback on Comet 0.15.0 post - Remove crc32c from new expressions (it's a shuffle checksum, not an expression) - Add Dynamic Partition Pruning callout for native Iceberg reader - Link to comet#2921 for Iceberg direction context - Fix missing blank lines around SMJ section --- content/blog/2026-04-18-datafusion-comet-0.15.0.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index fd5b7b34..c9b1ea2b 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -111,6 +111,8 @@ iceberg-rust, with no additional configuration required. To support this change, the release bundles a broad set of Iceberg-focused improvements: +- **Dynamic Partition Pruning (DPP)**: The native Iceberg reader supports DPP, allowing partition filters + derived at runtime to prune Iceberg file scans and substantially reduce I/O for star-schema-style queries. - **Correct classloader handling**: Iceberg classes are now loaded via the thread context classloader, resolving class-loading issues in environments where the executor classloader differs from the application classloader. - **Continuous Iceberg CI**: Iceberg Spark integration tests now run on every PR and push to `main`, providing @@ -123,6 +125,7 @@ To support this change, the release bundles a broad set of Iceberg-focused impro Users who need to fall back to the previous behavior can still opt out, but we encourage the community to exercise the native reader and report any issues. + ### Sort-Merge Join Performance Comet relies heavily on sort-merge join (SMJ) because DataFusion's hash joins do not yet support spilling to @@ -144,6 +147,7 @@ Additional SMJ work is landing in upstream DataFusion and will arrive in a futur - Full outer join correctness fix for NULL filter results ([datafusion#21660](https://github.com/apache/datafusion/pull/21660)) With these performance improvements, the next release of Comet will enable SMJ with filters by default. + ## Other Key Features ### New Expressions and Function Support @@ -152,7 +156,7 @@ This release adds support for the following: - **Date/time functions**: `days`, `hours`, `date_from_unix_date` - **String/JSON functions**: native `get_json_object` with improved performance over the fallback path -- **Hash/math functions**: `crc32c`, `bin` +- **Hash/math functions**: `bin` - **Array functions**: `sort_array` - **Window functions**: `LEAD` and `LAG` with `IGNORE NULLS` - **Aggregates**: SQL `FILTER (WHERE ...)` clauses now execute natively; `Corr` aggregate enabled @@ -194,8 +198,9 @@ with additional reader fixes. The `jni` crate was upgraded to 0.22.4. ## Deprecations and Removals The `SupportsComet` interface has been removed, along with the Java-based Iceberg integration path (which is -fully superseded by the native Iceberg reader). The `native_iceberg_compat` scan remains deprecated and is -expected to be removed in a future release in favor of `native_datafusion`. +fully superseded by the native Iceberg reader). See [comet#2921](https://github.com/apache/datafusion-comet/issues/2921) +for background on the decision to standardize on the native iceberg-rust integration. The `native_iceberg_compat` +scan remains deprecated and is expected to be removed in a future release in favor of `native_datafusion`. ## Compatibility From 8a68672bbf25952ce22ddd1f5da6e6f77031be1a Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Wed, 22 Apr 2026 07:25:23 -0600 Subject: [PATCH 8/8] docs: add installation guide CTA to Comet 0.15.0 post --- content/blog/2026-04-18-datafusion-comet-0.15.0.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/content/blog/2026-04-18-datafusion-comet-0.15.0.md b/content/blog/2026-04-18-datafusion-comet-0.15.0.md index c9b1ea2b..52f8a91b 100644 --- a/content/blog/2026-04-18-datafusion-comet-0.15.0.md +++ b/content/blog/2026-04-18-datafusion-comet-0.15.0.md @@ -208,3 +208,8 @@ Supported platforms include Spark 3.4.3, 3.5.4–3.5.8, and Spark 4.0.x with var The community encourages users to test Comet with existing Spark and Iceberg workloads and welcomes contributions to ongoing development. + +## Get Started with Comet 0.15.0 + +Ready to try it out? Follow the [Comet 0.15.0 Installation Guide](https://datafusion.apache.org/comet/user-guide/0.15/installation.html) +to get up and running, then point Comet at your existing Spark workloads and see the speedup for yourself.