From 80f84c5933967518bb1de5fac19480596f0b6a39 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sun, 16 Aug 2026 07:29:53 -0400 Subject: [PATCH 01/31] Add draft blog post for DataFusion 55.0.0 release Co-Authored-By: Claude Fable 5 --- content/blog/2026-08-16-datafusion-55.0.0.md | 517 +++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100644 content/blog/2026-08-16-datafusion-55.0.0.md diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md new file mode 100644 index 00000000..dd045de4 --- /dev/null +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -0,0 +1,517 @@ +--- +layout: post +title: Apache DataFusion 55.0.0 Released +date: 2026-08-16 +author: pmc +categories: [release] +--- + + + +[TOC] + +We are proud to announce the release of [DataFusion 55.0.0]. This post highlights +some of the major improvements since [DataFusion 54.0.0]. Notable additions +include range partitioning, `MERGE INTO` support, per-partition TopK for window +functions, and runtime row-group pruning for TopK queries, alongside significant +aggregation, function, and planning performance improvements. The complete list +of changes is available in the [changelog]. This release represents roughly 9 +weeks of development and 877 commits. Thanks to the [175 contributors] +(a new record!) for making it possible. + +[DataFusion 55.0.0]: https://crates.io/crates/datafusion/55.0.0 +[DataFusion 54.0.0]: https://datafusion.apache.org/blog/2026/06/12/datafusion-54.0.0/ +[changelog]: https://github.com/apache/datafusion/blob/branch-55/dev/changelog/55.0.0.md +[175 contributors]: https://github.com/apache/datafusion/blob/branch-55/dev/changelog/55.0.0.md#credits + +## Performance Improvements 🚀 + + + + +**Figure 1**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. +Query times are normalized using the ClickBench definition. See the +[DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) +for more details. + +We continue to make significant performance improvements in DataFusion, as +explained below. This release skips more work at runtime using statistics and +dynamic filters, and makes window functions, aggregation, and many built-in +functions faster. + +### Sort Pushdown: Runtime Row-Group Pruning for TopK Queries + +The multi-release [Sort Pushdown effort] makes `ORDER BY` and +`ORDER BY ... LIMIT` (TopK) queries on Parquet skip work end-to-end: skip the +sort, skip row groups via min/max statistics, and skip rows via dynamic filters. +DataFusion 55 lands the next phase: as a TopK query runs and its dynamic filter +threshold tightens, the Parquet reader now re-evaluates the threshold against +the remaining row groups at every row-group boundary and drops those that can +no longer contribute — zero IO and zero decode for the skipped row groups +([#22450]). This release also adds multi-column lexicographic statistics +reordering ([#23888]), so compound `ORDER BY` queries benefit too. In the +`topk_tpch` benchmark suite, 5 of 11 queries got 3-4x faster with no +regressions, reducing total suite runtime by 44%. +Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. + +[Sort Pushdown effort]: https://github.com/apache/datafusion/issues/23036 + +### Per-Partition TopK for Window Functions + +A common analytics pattern selects the top N rows per group using a window +function: + +```sql +SELECT * FROM ( + SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn, * + FROM sales +) WHERE rn <= 5; +``` + +DataFusion previously sorted the *entire* input to evaluate the window +function, even though only a handful of rows per partition survive the filter. +DataFusion 55 recognizes this pattern and uses a new `PartitionedTopKExec` +operator that keeps only the top N rows per partition, dramatically reducing +sorting and memory for high-cardinality inputs. The optimization applies to +`ROW_NUMBER` and `RANK`, resolving a feature request first filed in 2023 +([#6899]). +Thanks to [@SubhamSinghal] for implementing this feature, with reviews from +[@2010YOUY01] and [@kosiew]. Related PRs: [#21479], [#22885], [#23096] + +### Aggregation Improvements + +**Complete Multi-Column `GROUP BY` Type Coverage**: +DataFusion's column-wise `GROUP BY` storage (`GroupValuesColumn`) has +type-specific fast paths, but previously any unsupported column type dragged +the entire grouping onto a slower row-encoded fallback. DataFusion 55 completes +the type coverage ([#22715]): new specializations were added for `Decimal256`, +`Float16`, and `Interval` ([#23849], [#23785], [#23786]), and a new generic +`Rows`-backed `GroupColumn` keeps mixed schemas on the column-wise path +([#23523]). +Thanks to [@zhuqi-lucas] and [@tohuya6] for this work. + +**Split Aggregation Streams**: +The hash aggregation state machine previously handled partial aggregation, +final aggregation, and streaming cases in one shared implementation. DataFusion +55 splits these semantically distinct paths into dedicated streams ([#22729]), +making the code easier to optimize and extend (part of epic [#22710]). +Thanks to [@2010YOUY01] for this work, with reviews from [@Rachelint] and +[@alamb]. + +### Faster Functions + +DataFusion ships hundreds of built-in functions, so speeding them up pays off +across many workloads. This release optimizes many, including [find_in_set] +(up to 24x faster), [trunc] (10x), [replace] (2x), [regexp_instr] (40%), +[regexp_match], [round], [date_trunc], [date_part], [get_field], [upper], and +[string_trim], plus dictionary-encoding preservation for many string functions +([#23743], [#23930], [#24100]) and a 100x improvement to [approx_distinct] for +low-cardinality inputs with many groups ([#22768]). The `approx_distinct` +aggregate also gained support for many more types, including `Decimal`, +`Interval`, `Duration`, `Struct`, `Map`, and `Union`, thanks to [@mkleen]. +Thanks to the many contributors who drove this work, especially [@andygrove], +[@neilconway], [@lyne7-sc], [@theirix], and [@haohuaijin]. + +**Faster `IN` List Evaluation**: +`IN` list membership checks can run millions of times during a scan, especially +with dynamic filter pushdown. DataFusion 55 adds exact lookup strategies +selected by type and list size, including bitmap filters for small integer +types and branchless filters for small primitive lists ([#19241]). +Thanks to [@geoffreyclaude] for driving this work, with contributions from +[@alamb]. Related PRs: [#23012], [#23014], [#23299] + +### Planner Improvements + +**Unified Distribution and Sorting Enforcement**: +The `EnforceDistribution` and `EnforceSorting` physical optimizer passes are +now merged into a single `EnsureRequirements` pass with idempotent sort +pushdown ([#21976]), fixing longstanding ordering issues between the two passes +and enabling the sort pushdown work described above. +Thanks to [@zhuqi-lucas] for this work, with reviews from [@2010YOUY01] and +[@alamb]. + +**Smarter Join Planning**: +DataFusion 55 converts inner joins to semi joins when equivalent ([#22652]), +eliminates `LEFT`/`RIGHT` joins with redundant sides ([#23566]), handles +intermediate projections in outer join elimination ([#22534]), and reorders +predicates in conjunctions using a cost heuristic ([#22343]). +Thanks to [@neilconway] and [@simonvandel] for this work. + +**Scalar UDF Strictness Metadata**: +Scalar UDFs can now declare that they are *strict* (they return `NULL` when any +input is `NULL`) ([#23148]). The optimizer uses this metadata to prove that +filters reject `NULL`s, unlocking outer join elimination for queries that +filter on the result of a function call. +Thanks to [@lyne7-sc] for implementing this feature, with reviews from [@alamb] +and [@kosiew]. + +**Faster Optimizer Passes**: +The logical optimizer now skips subquery traversal when there are no subqueries +and rewrites plans in place ([#22298]), collapses chained projections in a +single pass ([#22389]), and avoids re-inlining expensive common subexpressions +during projection pushdown ([#23459]). +Thanks to [@adriangb], [@Dandandan], and [@fordN] for this work. + +### Scan Improvements + +**Pruning Unread Parquet Leaves for Nested Columns**: +When a table declares a nested column narrower than the Parquet file's physical +type, DataFusion previously read every leaf of the column and dropped the extra +subfields in memory. DataFusion 55 derives the projection mask through casts, +so only the leaves that are actually accessed are read — one production query +reported by the [DataFusion Comet] project went from reading 1.35 TB to reading +only the required data ([#24090]). +Thanks to [@mbutrovich] for this work, with reviews from [@adriangb]. + +[DataFusion Comet]: https://datafusion.apache.org/comet/ + +**Other Scan Improvements**: +DataFusion 55 also skips loading the page index (and an expensive +`ParquetMetaData` clone) when a file has no page index ([#24150]), supports +file-level Parquet row selections ([#22940]), and lowers the default +`repartition_file_min_size` from 10 MiB to 1 MiB for better parallelism on +small files ([#22439]). +Thanks to [@alamb], [@haohuaijin], and [@adriangb]. + +## New Features ✨ + +### Range Partitioning + +DataFusion previously supported only hash and round-robin repartitioning. +DataFusion 55 adds *range partitioning*, where rows are distributed to +partitions based on ordered split points ([#22395], design discussion +[#21992]). Range partitioning preserves ordering across partitions, which is a +natural fit for pre-sorted data and for distributed engines that shuffle by +range. + +This release adds the new `Partitioning::Range` physical variant ([#22207]), +a logical representation ([#22777]), physical execution and planning +([#23231], [#23617]), and planner support so range-partitioned plans avoid +unnecessary repartitioning: range-partitioned inputs can now satisfy hash +joins ([#23484], [#23583]), window functions ([#23416]), and per-partition +TopK requirements ([#23290]). Dynamic filter pushdown also now works for +range-partitioned joins, routing probe-side filters to the correct build +partition using the range split points ([#23854]). + +Thanks to [@gene-bordegaray], [@jayshrivastava], [@saadtajwar], [@peterxcli], +[@gmhelmold], [@mattp5657], and [@mithuncy] for driving this substantial +community effort. + +### `MERGE INTO` + +`MERGE INTO` (SQL:2003) is a widely used DML statement for upsert and +conditional update workloads, and a key building block for table formats such +as Apache Iceberg and Delta Lake. DataFusion 55 adds the logical plan types +([#20763]) along with SQL planner, physical planner, and a new +`TableProvider::merge_into` hook ([#22988]) so table implementations can +execute merge operations: + +```sql +MERGE INTO target t +USING source s +ON t.id = s.id +WHEN MATCHED AND s.deleted THEN DELETE +WHEN MATCHED THEN UPDATE SET name = s.name +WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name); +``` + +Built-in table providers do not yet implement the hook, but custom +`TableProvider` implementations (such as lakehouse table formats) can now plug +in their own merge execution. +Thanks to [@wirybeaver] for implementing this feature, with reviews from +[@alamb] and [@kosiew]. + +### Parquet Virtual Columns: `file_row_index` and `input_file_name` + +DataFusion 55 plumbs Parquet *virtual columns* through the scan ([#22026]), +building on arrow-rs support for computing row indexes during the read. Two new +functions expose this to queries: [file_row_index] returns the 0-based row +index of each row within its Parquet file ([#22604]), and [input_file_name] +returns the file each row came from ([#22978]). These are useful for building +change data capture, debugging, and Spark-compatible workloads (Spark exposes +similar metadata columns). +Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from +[@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. + +### `unnest_outer` + +DataFusion's `unnest` drops rows whose input list is empty or `NULL`. The new +[unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, +matching Spark's `explode_outer` semantics: + +```sql +SELECT id, unnest_outer(tags) +FROM VALUES (1, ['a', 'b']), (2, []), (3, NULL) AS t(id, tags); +-- 1, 'a' +-- 1, 'b' +-- 2, NULL +-- 3, NULL +``` + +Thanks to [@athlcode] for implementing this feature, with reviews from +[@comphead]. + +### Pluggable Spill Backends + +DataFusion spills to disk when a query exceeds its memory budget, but the spill +infrastructure was previously hardwired to OS-level temporary files. DataFusion +55 introduces a pluggable `SpillFile` trait and `TempFileFactory` ([#21882], +[#22230]) so hosts can route spill data through their own storage layers — for +example, Postgres extensions like ParadeDB can now direct spills through +Postgres `BufFile` APIs to respect `temp_tablespaces` and `temp_file_limit`. +Thanks to [@pantShrey] for this work, with reviews from [@alamb]. + +### Extensibility for Distributed Engines + +Several new APIs make it easier to build distributed systems such as +[datafusion-distributed], [DataFusion Ballista], and [DataFusion Python] on +top of DataFusion: + +- **Dynamic filter propagation across network boundaries**: new + `ExecutionPlan::apply_expressions` and + `ExecutionPlan::dynamic_expressions_produced` methods let engines discover + which plan nodes produce dynamic filters and re-wire them across stage + boundaries ([#24018], [#24068]). Thanks to [@jayshrivastava]. +- **`FFI_QueryPlanner`**: foreign libraries can now provide a custom query + planner over the FFI boundary — for example, connecting a distributed + planner to a `SessionContext` in Python ([#24028]). Thanks to [@timsaucer]. +- **Self-serializing execution plans**: built-in `ExecutionPlan`s were ported + to per-type `try_to_proto` / `try_from_proto` hooks ([#23494]), eliminating + the central downcast chain in `datafusion-proto` and putting built-in and + third-party plans on the same code path. Thanks to [@adriangb]. +- **Window accumulator state access**: `BoundedWindowAggExec` can now expose + finalized accumulator state to an observer callback, enabling incremental / + prefix-scan use cases ([#24035]). Thanks to [@avantgardnerio], with reviews + from [@alamb] and [@timsaucer]. + +[datafusion-distributed]: https://github.com/datafusion-contrib/datafusion-distributed +[DataFusion Ballista]: https://datafusion.apache.org/ballista/ +[DataFusion Python]: https://datafusion.apache.org/python/ + +### `EXPLAIN` Improvements + +DataFusion 55 adds a Postgres-style `EXPLAIN (...)` option list ([#21768]) and +a `pgjson` output format for `EXPLAIN ANALYZE` ([#21767]), making plan output +easier to consume with existing Postgres tooling such as plan visualizers. +Thanks to [@adriangb] for this work. + +### New Functions + +**SQL and Scalar Functions**: +DataFusion 55 adds new array math functions [array_scale], [array_add], +[array_subtract], [array_sum], and [array_avg], plus the higher-order +[array_first] function. +Thanks to [@crm26], [@SubhamSinghal], and [@EdsonPetry] for these +contributions. + +**Spark-Compatible Functions**: +The [datafusion-spark crate] gains new or improved Spark-compatible functions, +including [hypot], [atan2], [weekday], [monthname], and [concat_ws] with array +support, plus a new Spark SQL parser dialect config ([#22529]). +Thanks to the contributors who drove this work, especially +[@KarpagamKarthikeyan], [@sjhddh], [@JeelRajodiya], [@davidlghellin], and +[@kumarUjjawal]. + +## Upgrade Guide and Changelog 📖 + +Upgrading to 55.0.0 should be straightforward for most users, though there are +some breaking changes. See the [Upgrade Guide] for details and +migration snippets, and the [changelog] for the full list of changes. + +## About DataFusion + +[Apache DataFusion] is an extensible query engine, written in [Rust], that uses +[Apache Arrow] as its in-memory format. DataFusion is used by developers to +create new, fast, data-centric systems such as databases, dataframe libraries, +and machine learning and streaming applications. While [DataFusion's primary +design goal] is to accelerate the creation of other data-centric systems, it +provides a reasonable experience directly out of the box as a [dataframe +library], [Python library], and [command-line SQL tool]. + +DataFusion's core thesis is that, as a community, together we can build much +more advanced technology than any of us as individuals or companies could build +alone. Without DataFusion, highly performant vectorized query engines would +remain the domain of a few large companies and world-class research +institutions. With DataFusion, we can all build on top of a shared foundation +and focus on what makes our projects unique. + +## How to Get Involved + +DataFusion is not a project built or driven by a single person, company, or +foundation. Rather, our community of users and contributors works together to +build a shared technology that none of us could have built alone. + +If you are interested in joining us, we would love to have you. You can try out +DataFusion on some of your own data and projects and let us know how it goes, +contribute suggestions, documentation, bug reports, or a PR with documentation, +tests, or code. A list of open issues suitable for beginners is [here], and you +can find out how to reach us on the [communication doc]. + +[Apache DataFusion]: https://datafusion.apache.org/ +[Rust]: https://www.rust-lang.org/ +[Apache Arrow]: https://arrow.apache.org +[DataFusion's primary design goal]: https://datafusion.apache.org/user-guide/introduction.html#project-goals +[dataframe library]: https://datafusion.apache.org/user-guide/dataframe.html +[Python library]: https://datafusion.apache.org/python/ +[command-line SQL tool]: https://datafusion.apache.org/user-guide/cli/ +[Upgrade Guide]: https://datafusion.apache.org/library-user-guide/upgrading.html +[here]: https://github.com/apache/arrow-datafusion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 +[communication doc]: https://datafusion.apache.org/contributor-guide/communication.html + +[@2010YOUY01]: https://github.com/2010YOUY01 +[@AdamGS]: https://github.com/AdamGS +[@Dandandan]: https://github.com/Dandandan +[@EdsonPetry]: https://github.com/EdsonPetry +[@JeelRajodiya]: https://github.com/JeelRajodiya +[@KarpagamKarthikeyan]: https://github.com/KarpagamKarthikeyan +[@Rachelint]: https://github.com/Rachelint +[@SubhamSinghal]: https://github.com/SubhamSinghal +[@adriangb]: https://github.com/adriangb +[@alamb]: https://github.com/alamb +[@andygrove]: https://github.com/andygrove +[@athlcode]: https://github.com/athlcode +[@avantgardnerio]: https://github.com/avantgardnerio +[@comphead]: https://github.com/comphead +[@crm26]: https://github.com/crm26 +[@davidlghellin]: https://github.com/davidlghellin +[@fordN]: https://github.com/fordN +[@gene-bordegaray]: https://github.com/gene-bordegaray +[@geoffreyclaude]: https://github.com/geoffreyclaude +[@gmhelmold]: https://github.com/gmhelmold +[@haohuaijin]: https://github.com/haohuaijin +[@jayshrivastava]: https://github.com/jayshrivastava +[@jkylling]: https://github.com/jkylling +[@kosiew]: https://github.com/kosiew +[@kumarUjjawal]: https://github.com/kumarUjjawal +[@lyne7-sc]: https://github.com/lyne7-sc +[@mattp5657]: https://github.com/mattp5657 +[@mbutrovich]: https://github.com/mbutrovich +[@mithuncy]: https://github.com/mithuncy +[@mkleen]: https://github.com/mkleen +[@neilconway]: https://github.com/neilconway +[@niebayes]: https://github.com/niebayes +[@pantShrey]: https://github.com/pantShrey +[@peterxcli]: https://github.com/peterxcli +[@saadtajwar]: https://github.com/saadtajwar +[@simonvandel]: https://github.com/simonvandel +[@sjhddh]: https://github.com/sjhddh +[@theirix]: https://github.com/theirix +[@timsaucer]: https://github.com/timsaucer +[@tohuya6]: https://github.com/tohuya6 +[@wirybeaver]: https://github.com/wirybeaver +[@zhuqi-lucas]: https://github.com/zhuqi-lucas + +[find_in_set]: https://github.com/apache/datafusion/pull/23460 +[trunc]: https://github.com/apache/datafusion/pull/23593 +[replace]: https://github.com/apache/datafusion/pull/23589 +[regexp_instr]: https://github.com/apache/datafusion/pull/23540 +[regexp_match]: https://github.com/apache/datafusion/pull/23547 +[round]: https://github.com/apache/datafusion/pull/23471 +[date_trunc]: https://github.com/apache/datafusion/pull/23542 +[date_part]: https://github.com/apache/datafusion/pull/23491 +[get_field]: https://github.com/apache/datafusion/pull/23537 +[upper]: https://github.com/apache/datafusion/pull/23588 +[string_trim]: https://github.com/apache/datafusion/pull/23541 +[approx_distinct]: https://github.com/apache/datafusion/pull/22768 +[file_row_index]: https://github.com/apache/datafusion/pull/22604 +[input_file_name]: https://github.com/apache/datafusion/pull/22978 +[unnest_outer]: https://github.com/apache/datafusion/pull/22100 +[array_scale]: https://github.com/apache/datafusion/pull/22466 +[array_add]: https://github.com/apache/datafusion/pull/22459 +[array_subtract]: https://github.com/apache/datafusion/pull/22556 +[array_sum]: https://github.com/apache/datafusion/pull/22542 +[array_avg]: https://github.com/apache/datafusion/pull/23168 +[array_first]: https://github.com/apache/datafusion/pull/23267 +[hypot]: https://github.com/apache/datafusion/pull/23774 +[atan2]: https://github.com/apache/datafusion/pull/23962 +[weekday]: https://github.com/apache/datafusion/pull/22740 +[monthname]: https://github.com/apache/datafusion/pull/21639 +[concat_ws]: https://github.com/apache/datafusion/pull/20928 + +[#6899]: https://github.com/apache/datafusion/issues/6899 +[#19241]: https://github.com/apache/datafusion/issues/19241 +[#20763]: https://github.com/apache/datafusion/pull/20763 +[#21479]: https://github.com/apache/datafusion/pull/21479 +[#21768]: https://github.com/apache/datafusion/pull/21768 +[#21767]: https://github.com/apache/datafusion/pull/21767 +[#21882]: https://github.com/apache/datafusion/pull/21882 +[#21976]: https://github.com/apache/datafusion/pull/21976 +[#21992]: https://github.com/apache/datafusion/issues/21992 +[#22026]: https://github.com/apache/datafusion/pull/22026 +[#22100]: https://github.com/apache/datafusion/pull/22100 +[#22207]: https://github.com/apache/datafusion/pull/22207 +[#22230]: https://github.com/apache/datafusion/pull/22230 +[#22298]: https://github.com/apache/datafusion/pull/22298 +[#22343]: https://github.com/apache/datafusion/pull/22343 +[#22389]: https://github.com/apache/datafusion/pull/22389 +[#22439]: https://github.com/apache/datafusion/pull/22439 +[#22450]: https://github.com/apache/datafusion/pull/22450 +[#22529]: https://github.com/apache/datafusion/pull/22529 +[#22534]: https://github.com/apache/datafusion/pull/22534 +[#22604]: https://github.com/apache/datafusion/pull/22604 +[#22652]: https://github.com/apache/datafusion/pull/22652 +[#22715]: https://github.com/apache/datafusion/issues/22715 +[#22710]: https://github.com/apache/datafusion/issues/22710 +[#22729]: https://github.com/apache/datafusion/pull/22729 +[#22768]: https://github.com/apache/datafusion/pull/22768 +[#22777]: https://github.com/apache/datafusion/pull/22777 +[#22885]: https://github.com/apache/datafusion/pull/22885 +[#22940]: https://github.com/apache/datafusion/pull/22940 +[#22978]: https://github.com/apache/datafusion/pull/22978 +[#22988]: https://github.com/apache/datafusion/pull/22988 +[#23012]: https://github.com/apache/datafusion/pull/23012 +[#23014]: https://github.com/apache/datafusion/pull/23014 +[#23096]: https://github.com/apache/datafusion/pull/23096 +[#23148]: https://github.com/apache/datafusion/pull/23148 +[#23231]: https://github.com/apache/datafusion/pull/23231 +[#23290]: https://github.com/apache/datafusion/issues/23290 +[#23299]: https://github.com/apache/datafusion/pull/23299 +[#23416]: https://github.com/apache/datafusion/pull/23416 +[#23459]: https://github.com/apache/datafusion/pull/23459 +[#23484]: https://github.com/apache/datafusion/pull/23484 +[#23494]: https://github.com/apache/datafusion/issues/23494 +[#23523]: https://github.com/apache/datafusion/pull/23523 +[#23566]: https://github.com/apache/datafusion/pull/23566 +[#23583]: https://github.com/apache/datafusion/pull/23583 +[#23617]: https://github.com/apache/datafusion/pull/23617 +[#23743]: https://github.com/apache/datafusion/pull/23743 +[#23785]: https://github.com/apache/datafusion/pull/23785 +[#23786]: https://github.com/apache/datafusion/pull/23786 +[#23849]: https://github.com/apache/datafusion/pull/23849 +[#23854]: https://github.com/apache/datafusion/pull/23854 +[#23888]: https://github.com/apache/datafusion/pull/23888 +[#23930]: https://github.com/apache/datafusion/pull/23930 +[#23954]: https://github.com/apache/datafusion/pull/23954 +[#24018]: https://github.com/apache/datafusion/pull/24018 +[#24028]: https://github.com/apache/datafusion/pull/24028 +[#24035]: https://github.com/apache/datafusion/pull/24035 +[#24068]: https://github.com/apache/datafusion/pull/24068 +[#24090]: https://github.com/apache/datafusion/pull/24090 +[#24100]: https://github.com/apache/datafusion/pull/24100 +[#24150]: https://github.com/apache/datafusion/pull/24150 +[#22395]: https://github.com/apache/datafusion/issues/22395 + +[datafusion-spark crate]: https://docs.rs/datafusion-spark/latest/datafusion_spark/index.html From 54d4c5f8f3e5c420d37e82147a769dbcfec19a24 Mon Sep 17 00:00:00 2001 From: Gene Bordegaray Date: Sun, 23 Aug 2026 10:54:32 -0400 Subject: [PATCH 02/31] tidy up range --- content/blog/2026-08-16-datafusion-55.0.0.md | 111 +++++++++++++++---- 1 file changed, 91 insertions(+), 20 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index dd045de4..113bed39 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -200,25 +200,85 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ### Range Partitioning -DataFusion previously supported only hash and round-robin repartitioning. -DataFusion 55 adds *range partitioning*, where rows are distributed to -partitions based on ordered split points ([#22395], design discussion -[#21992]). Range partitioning preserves ordering across partitions, which is a -natural fit for pre-sorted data and for distributed engines that shuffle by -range. - -This release adds the new `Partitioning::Range` physical variant ([#22207]), -a logical representation ([#22777]), physical execution and planning -([#23231], [#23617]), and planner support so range-partitioned plans avoid -unnecessary repartitioning: range-partitioned inputs can now satisfy hash -joins ([#23484], [#23583]), window functions ([#23416]), and per-partition -TopK requirements ([#23290]). Dynamic filter pushdown also now works for -range-partitioned joins, routing probe-side filters to the correct build -partition using the range split points ([#23854]). - -Thanks to [@gene-bordegaray], [@jayshrivastava], [@saadtajwar], [@peterxcli], -[@gmhelmold], [@mattp5657], and [@mithuncy] for driving this substantial -community effort. +Range partitioning assigns rows to partitions by ordered key ranges, so +partition 0 holds the lowest keys, partition 1 the next range, and so on. It is +the layout of time-series tables written one file per day or hour, and of tables +partitioned by ID range. + +DataFusion 55 adds *range partitioning* ([#22395], design discussion +[#21992]). A range partitioning declares an ordering and a list of split +points. Partition `i` holds the keys that fall between split point `i-1` and +split point `i`: + +```text +ordering = [date ASC NULLS LAST] +split_points = [(2022-01-01), (2023-01-01)] + +partition 0: date < 2022-01-01 +partition 1: 2022-01-01 <= date < 2023-01-01 +partition 2: date >= 2023-01-01 +``` + +Compound keys work the same way, with split points compared lexicographically. +DataFusion does not validate the layout: a source that declares range +partitioning is responsible for placing every row in the partition its split +points describe. Wrong split points produce skew or missing join matches rather +than an error. The physical `Partitioning::Range` variant landed in [#22207], +the logical representation in [#22777], and execution plus planning in +[#23231] and [#23617]. + +**Declaring a layout.** There is no SQL syntax for this yet. A table declares +its partitioning through `ListingOptions::with_output_partitioning` or +`FileScanConfig::with_output_partitioning` ([#22657]): + +```rust +let output_partitioning = Partitioning::Range(RangePartitioning::try_new( + vec![col("range_key").sort(true, true)], + vec![ + SplitPoint::new(vec![ScalarValue::Int32(Some(10))]), + SplitPoint::new(vec![ScalarValue::Int32(Some(20))]), + SplitPoint::new(vec![ScalarValue::Int32(Some(30))]), + ], +)?); + +let options = ListingOptions::new(Arc::new(ParquetFormat::default())) + .with_output_partitioning(Some(output_partitioning)); +``` + +**What it buys you.** The payoff is the `RepartitionExec` the planner no longer +inserts. A declared range layout now satisfies `Distribution::KeyPartitioned` +([#23680]), so aggregates ([#23239]), partitioned hash joins for inner +([#23184]), left ([#23487]), right ([#23484]), and full ([#23583]) types, sort +merge and symmetric hash joins ([#23480]), window functions ([#23416]), +per-partition TopK ([#23355]), and `InterleaveExec` ([#23623]) can all run +directly on the declared partitions: + +```text +> EXPLAIN SELECT range_key, SUM(value) FROM range_partitioned GROUP BY range_key; + +AggregateExec: mode=SinglePartitioned, gby=[range_key@0 as range_key], aggr=[sum(range_partitioned.value)] + DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet +``` + +Joins need both sides on the same layout, not merely on some range layout: + +```text +> EXPLAIN SELECT l.range_key, l.value, r.value + FROM range_partitioned l JOIN range_partitioned r ON l.range_key = r.range_key; + +HashJoinExec: mode=Partitioned, join_type=Inner, on=[(range_key@0, range_key@0)] + DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet + DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet +``` + +Dynamic filter pushdown also now works for range-partitioned joins, routing +build-side filters to the correct probe partition using the range split points +([#23854]). + +Thanks to [@gene-bordegaray], [@saadtajwar], [@peterxcli], [@stuhood], +[@gmhelmold], [@mattp5657], [@mithuncy], [@JSOD11], [@EdsonPetry], +[@Rich-T-kid], and [@blinding-pixels] for driving this substantial community +effort. ### `MERGE INTO` @@ -385,15 +445,18 @@ can find out how to reach us on the [communication doc]. [@AdamGS]: https://github.com/AdamGS [@Dandandan]: https://github.com/Dandandan [@EdsonPetry]: https://github.com/EdsonPetry +[@JSOD11]: https://github.com/JSOD11 [@JeelRajodiya]: https://github.com/JeelRajodiya [@KarpagamKarthikeyan]: https://github.com/KarpagamKarthikeyan [@Rachelint]: https://github.com/Rachelint +[@Rich-T-kid]: https://github.com/Rich-T-kid [@SubhamSinghal]: https://github.com/SubhamSinghal [@adriangb]: https://github.com/adriangb [@alamb]: https://github.com/alamb [@andygrove]: https://github.com/andygrove [@athlcode]: https://github.com/athlcode [@avantgardnerio]: https://github.com/avantgardnerio +[@blinding-pixels]: https://github.com/blinding-pixels [@comphead]: https://github.com/comphead [@crm26]: https://github.com/crm26 [@davidlghellin]: https://github.com/davidlghellin @@ -418,6 +481,7 @@ can find out how to reach us on the [communication doc]. [@saadtajwar]: https://github.com/saadtajwar [@simonvandel]: https://github.com/simonvandel [@sjhddh]: https://github.com/sjhddh +[@stuhood]: https://github.com/stuhood [@theirix]: https://github.com/theirix [@timsaucer]: https://github.com/timsaucer [@tohuya6]: https://github.com/tohuya6 @@ -473,6 +537,7 @@ can find out how to reach us on the [communication doc]. [#22534]: https://github.com/apache/datafusion/pull/22534 [#22604]: https://github.com/apache/datafusion/pull/22604 [#22652]: https://github.com/apache/datafusion/pull/22652 +[#22657]: https://github.com/apache/datafusion/pull/22657 [#22715]: https://github.com/apache/datafusion/issues/22715 [#22710]: https://github.com/apache/datafusion/issues/22710 [#22729]: https://github.com/apache/datafusion/pull/22729 @@ -486,17 +551,23 @@ can find out how to reach us on the [communication doc]. [#23014]: https://github.com/apache/datafusion/pull/23014 [#23096]: https://github.com/apache/datafusion/pull/23096 [#23148]: https://github.com/apache/datafusion/pull/23148 +[#23184]: https://github.com/apache/datafusion/pull/23184 [#23231]: https://github.com/apache/datafusion/pull/23231 -[#23290]: https://github.com/apache/datafusion/issues/23290 +[#23239]: https://github.com/apache/datafusion/pull/23239 [#23299]: https://github.com/apache/datafusion/pull/23299 +[#23355]: https://github.com/apache/datafusion/pull/23355 [#23416]: https://github.com/apache/datafusion/pull/23416 [#23459]: https://github.com/apache/datafusion/pull/23459 +[#23480]: https://github.com/apache/datafusion/pull/23480 [#23484]: https://github.com/apache/datafusion/pull/23484 +[#23487]: https://github.com/apache/datafusion/pull/23487 [#23494]: https://github.com/apache/datafusion/issues/23494 [#23523]: https://github.com/apache/datafusion/pull/23523 [#23566]: https://github.com/apache/datafusion/pull/23566 [#23583]: https://github.com/apache/datafusion/pull/23583 [#23617]: https://github.com/apache/datafusion/pull/23617 +[#23623]: https://github.com/apache/datafusion/pull/23623 +[#23680]: https://github.com/apache/datafusion/pull/23680 [#23743]: https://github.com/apache/datafusion/pull/23743 [#23785]: https://github.com/apache/datafusion/pull/23785 [#23786]: https://github.com/apache/datafusion/pull/23786 From 9e1436329b97fedd79941a0364e9d3c142ab865c Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 09:02:04 -0400 Subject: [PATCH 03/31] rework perf --- content/blog/2026-08-16-datafusion-55.0.0.md | 61 ++++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 113bed39..821fe092 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -27,14 +27,14 @@ limitations under the License. [TOC] -We are proud to announce the release of [DataFusion 55.0.0]. This post highlights -some of the major improvements since [DataFusion 54.0.0]. Notable additions -include range partitioning, `MERGE INTO` support, per-partition TopK for window -functions, and runtime row-group pruning for TopK queries, alongside significant -aggregation, function, and planning performance improvements. The complete list -of changes is available in the [changelog]. This release represents roughly 9 -weeks of development and 877 commits. Thanks to the [175 contributors] -(a new record!) for making it possible. +We are proud to announce the release of [DataFusion 55.0.0]. This post +highlights some of the major improvements since [DataFusion 54.0.0]. Notable +additions include as always significant performance improvements, as well as +range partitioning, `MERGE INTO` support, per-partition TopK for window +functions, and runtime row-group pruning for TopK queries. The complete list of +changes is available in the [changelog]. This release represents roughly 9 weeks +of development and 877 commits. Thanks to the [175 contributors] (a new record!) +for making it possible. [DataFusion 55.0.0]: https://crates.io/crates/datafusion/55.0.0 [DataFusion 54.0.0]: https://datafusion.apache.org/blog/2026/06/12/datafusion-54.0.0/ @@ -43,24 +43,16 @@ weeks of development and 877 commits. Thanks to the [175 contributors] ## Performance Improvements 🚀 - - +This release, we focused our optimizations on making DataFusion faster across +the board rather than further optimizing our already great ClickBench numbers +(DataFusion is already the fastest on ClickBench for bare metal see the +[appendix]), as ClickBench represents only a tiny fraction of what our actual users +do (e.g. its files have no PageIndex, and has only integers and string columns). -**Figure 1**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. -Query times are normalized using the ClickBench definition. See the -[DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) -for more details. +Here is a representative sample of the performance improvements in this release: + + -We continue to make significant performance improvements in DataFusion, as -explained below. This release skips more work at runtime using statistics and -dynamic filters, and makes window functions, aggregation, and many built-in -functions faster. ### Sort Pushdown: Runtime Row-Group Pruning for TopK Queries @@ -430,6 +422,27 @@ contribute suggestions, documentation, bug reports, or a PR with documentation, tests, or code. A list of open issues suitable for beginners is [here], and you can find out how to reach us on the [communication doc]. +# Appendix: ClickBench Results + +(DataFusion is already the fastest on ClickBench for bare metal XXX TODO +GET CITATION) + + + + + +**Figure 1**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. +Query times are normalized using the ClickBench definition. See the +[DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) +for more details. + + [Apache DataFusion]: https://datafusion.apache.org/ [Rust]: https://www.rust-lang.org/ [Apache Arrow]: https://arrow.apache.org From f99e2b7109fae9c2560fd99dbd849fecddb30c6d Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 09:35:23 -0400 Subject: [PATCH 04/31] Add development activity chart (commits, commits/day, contributors) Co-Authored-By: Claude Fable 5 --- content/blog/2026-08-16-datafusion-55.0.0.md | 14 +++++- .../commits_contributors.svg | 49 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 content/images/datafusion-55.0.0/commits_contributors.svg diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 821fe092..739d2354 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -41,6 +41,18 @@ for making it possible. [changelog]: https://github.com/apache/datafusion/blob/branch-55/dev/changelog/55.0.0.md [175 contributors]: https://github.com/apache/datafusion/blob/branch-55/dev/changelog/55.0.0.md#credits + + +**Figure 1**: Development activity over the last three DataFusion releases: +total commits, commits per day, and unique contributors, computed from each +release's [changelog] and release dates. All three measures reached new records +in DataFusion 55. + ## Performance Improvements 🚀 This release, we focused our optimizations on making DataFusion faster across @@ -437,7 +449,7 @@ class="img-fluid" alt="Performance over time" /> -**Figure 1**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. +**Figure 2**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. Query times are normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. diff --git a/content/images/datafusion-55.0.0/commits_contributors.svg b/content/images/datafusion-55.0.0/commits_contributors.svg new file mode 100644 index 00000000..48bd3269 --- /dev/null +++ b/content/images/datafusion-55.0.0/commits_contributors.svg @@ -0,0 +1,49 @@ + + DataFusion development activity: releases 53.0.0 to 55.0.0 + + + + + Commits + + + + + 475 + 740 + 877 + 53.0.0 + 54.0.0 + 55.0.0 + + + Commits / day + + + + + 6.8 + 9.6 + 12.4 + 53.0.0 + 54.0.0 + 55.0.0 + + + Contributors + + + + + 114 + 139 + 175 + 53.0.0 + 54.0.0 + 55.0.0 + From b0a0edcaf9f09ae07a9d199c3dc80d5a5cb59f80 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 10:04:06 -0400 Subject: [PATCH 05/31] more performance numbers, remove topk mention --- content/blog/2026-08-16-datafusion-55.0.0.md | 152 ++++++++++++++----- 1 file changed, 113 insertions(+), 39 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 739d2354..7e5d8bb7 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -30,8 +30,8 @@ limitations under the License. We are proud to announce the release of [DataFusion 55.0.0]. This post highlights some of the major improvements since [DataFusion 54.0.0]. Notable additions include as always significant performance improvements, as well as -range partitioning, `MERGE INTO` support, per-partition TopK for window -functions, and runtime row-group pruning for TopK queries. The complete list of +range partitioning, `MERGE INTO` support, and runtime row-group pruning for +TopK queries. The complete list of changes is available in the [changelog]. This release represents roughly 9 weeks of development and 877 commits. Thanks to the [175 contributors] (a new record!) for making it possible. @@ -61,49 +61,33 @@ the board rather than further optimizing our already great ClickBench numbers [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no PageIndex, and has only integers and string columns). -Here is a representative sample of the performance improvements in this release: +Here is a representative sample of the performance improvements in this release. - +| Improvement | Issue / PR | Representative Result | Area | +| --- | --- | --- | --- | +| Runtime row-group pruning for TopK | [#23036], [#22450] | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | +| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | +| Prune unread Parquet leaves for nested columns | [#24090] | [1.35 TB read → 30.9 GB for a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | +| Fewer object store requests for CSV | [#22962] | [1.70x faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | +| Faster `SortPreservingMerge` tie-breaker | [#23107] | [1.08x faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), and [no longer runs out of memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | ### Sort Pushdown: Runtime Row-Group Pruning for TopK Queries -The multi-release [Sort Pushdown effort] makes `ORDER BY` and -`ORDER BY ... LIMIT` (TopK) queries on Parquet skip work end-to-end: skip the -sort, skip row groups via min/max statistics, and skip rows via dynamic filters. -DataFusion 55 lands the next phase: as a TopK query runs and its dynamic filter -threshold tightens, the Parquet reader now re-evaluates the threshold against -the remaining row groups at every row-group boundary and drops those that can -no longer contribute — zero IO and zero decode for the skipped row groups -([#22450]). This release also adds multi-column lexicographic statistics -reordering ([#23888]), so compound `ORDER BY` queries benefit too. In the -`topk_tpch` benchmark suite, 5 of 11 queries got 3-4x faster with no -regressions, reducing total suite runtime by 44%. -Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. +The multi-release [Sort Pushdown effort] continues to optimize `ORDER BY` and +`ORDER BY ... LIMIT` (TopK) queries. In DataFusion 55, as a dynamic filter +threshold tightens, the Parquet reader re-evaluates the threshold against the +remaining row groups and drops those that can no longer contribute ([#22450]). +This is a powerful optimization but required careful engineering to avoid +regressions. See our [Optimizing for Almost Sorted Data] blog post for more +details. DataFusion 55 also supports compound `ORDER BY` queries. 5 of 11 +queries in the `topk_tpch` got 3-4x faster with no regressions, reducing total +suite runtime by 44%. Thanks to [@zhuqi-lucas] for driving this work, with +reviews from [@adriangb]. [Sort Pushdown effort]: https://github.com/apache/datafusion/issues/23036 - -### Per-Partition TopK for Window Functions - -A common analytics pattern selects the top N rows per group using a window -function: - -```sql -SELECT * FROM ( - SELECT ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn, * - FROM sales -) WHERE rn <= 5; -``` - -DataFusion previously sorted the *entire* input to evaluate the window -function, even though only a handful of rows per partition survive the filter. -DataFusion 55 recognizes this pattern and uses a new `PartitionedTopKExec` -operator that keeps only the top N rows per partition, dramatically reducing -sorting and memory for high-cardinality inputs. The optimization applies to -`ROW_NUMBER` and `RANK`, resolving a feature request first filed in 2023 -([#6899]). -Thanks to [@SubhamSinghal] for implementing this feature, with reviews from -[@2010YOUY01] and [@kosiew]. Related PRs: [#21479], [#22885], [#23096] +[Optimizing for Almost Sorted Data]: https://datafusion.apache.org/blog/2026/07/20/sort-pushdown/ ### Aggregation Improvements @@ -254,7 +238,7 @@ inserts. A declared range layout now satisfies `Distribution::KeyPartitioned` ([#23680]), so aggregates ([#23239]), partitioned hash joins for inner ([#23184]), left ([#23487]), right ([#23484]), and full ([#23583]) types, sort merge and symmetric hash joins ([#23480]), window functions ([#23416]), -per-partition TopK ([#23355]), and `InterleaveExec` ([#23623]) can all run +and `InterleaveExec` ([#23623]) can all run directly on the declared partitions: ```text @@ -454,6 +438,88 @@ Query times are normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. +# Appendix: Full List of Performance Improvements + +The tables below list the performance improvements in this release along with a +representative measurement for each. Results marked *(micro)* come from +Criterion microbenchmarks and are not expected to translate directly into +end-to-end query speedups. + +### Sort / TopK + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| Runtime row-group pruning for TopK | [#23036](https://github.com/apache/datafusion/issues/23036), [#22450](https://github.com/apache/datafusion/pull/22450) | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | +| " (suite-level) | " | [total `topk_tpch` runtime −44%, no regressions](https://github.com/apache/datafusion/issues/23036) | +| Faster `SortPreservingMerge` tie-breaker | [#23107](https://github.com/apache/datafusion/pull/23107) | [1.08x faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | + +### Window Functions + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| `LEAD` / `LAG` with `IGNORE NULLS` | [#23711](https://github.com/apache/datafusion/pull/23711) | [21.7x faster for `List`, 10.5x for `Utf8View`](https://github.com/apache/datafusion/pull/23711) *(micro)* | +| Sliding-window `MIN` / `MAX` monotonic deques | [#23827](https://github.com/apache/datafusion/pull/23827) | [3.5x faster](https://github.com/apache/datafusion/pull/23827#issuecomment-5067789139) *(micro)* | +| Skip fully calculated window partitions | [#24127](https://github.com/apache/datafusion/pull/24127) | [33% faster with 32,768 sparse partitions](https://github.com/apache/datafusion/pull/24127) | +| Skip re-slicing quiet window partitions | [#24047](https://github.com/apache/datafusion/pull/24047) | [21% faster with 32,768 sparse partitions](https://github.com/apache/datafusion/pull/24047) | + +### Aggregation + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| `approx_distinct` with many groups | [#22768](https://github.com/apache/datafusion/pull/22768) | [101x faster: 1723ms → 17ms (Int64, 50K groups)](https://github.com/apache/datafusion/pull/22768#issuecomment-4627539820) | +| " (end-to-end) | " | [32.6s → 0.12s, on par with DuckDB](https://github.com/apache/datafusion/pull/22768) | +| `array_agg(DISTINCT ...)` | [#23716](https://github.com/apache/datafusion/pull/23716) | [4.0x faster at high cardinality](https://github.com/apache/datafusion/pull/23716) *(micro)* | +| `percentile_cont` / `median` | [#23954](https://github.com/apache/datafusion/pull/23954) | [63% faster: 247µs → 92µs (`median`, window=256)](https://github.com/apache/datafusion/pull/23954) *(micro)* | +| Multi-column `GROUP BY` type coverage | [#22715](https://github.com/apache/datafusion/issues/22715), [#23523](https://github.com/apache/datafusion/pull/23523) | [46% less memory for mixed-schema keys: 1096KB → 594KB](https://github.com/apache/datafusion/pull/23523) | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645](https://github.com/apache/datafusion/issues/23645), [#23646](https://github.com/apache/datafusion/pull/23646) | [1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), and [no longer runs out of memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | +| Semi / anti join index alignment | [#22794](https://github.com/apache/datafusion/pull/22794) | [TPC-DS Q15 1.16x faster](https://github.com/apache/datafusion/pull/22794#issuecomment-4640131159) | + +### Expressions and Functions + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | +| " (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | +| " (`Int8` / `Int16`) | [#23299](https://github.com/apache/datafusion/pull/23299) | [4.4x faster for `i16`, 4-element list](https://github.com/apache/datafusion/pull/23299#issuecomment-4875954295) *(micro)* | +| Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | +| " (trim family) | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | +| " (`ascii`, `bit_length`, `octet_length`) | [#23743](https://github.com/apache/datafusion/pull/23743) | [28.7x faster `ascii`](https://github.com/apache/datafusion/pull/23743) *(micro)* | +| `find_in_set` | [#23460](https://github.com/apache/datafusion/pull/23460) | [24x faster: 1.25ms → 52µs](https://github.com/apache/datafusion/pull/23460) *(micro)* | +| `array_has` with array needle | [#23337](https://github.com/apache/datafusion/pull/23337) | [15.1x faster; join query 0.95s → 0.059s](https://github.com/apache/datafusion/pull/23337) | +| `trunc` with scalar precision | [#23593](https://github.com/apache/datafusion/pull/23593) | [12.9x faster: 9.5µs → 735ns](https://github.com/apache/datafusion/pull/23593) *(micro)* | +| `encode` / hex | [#23456](https://github.com/apache/datafusion/pull/23456) | [5.2x faster: 200µs → 39.5µs](https://github.com/apache/datafusion/pull/23456) *(micro)* | +| `arrays_zip` perfect-list fast path | [#22285](https://github.com/apache/datafusion/pull/22285) | [4.2x faster](https://github.com/apache/datafusion/pull/22285#issuecomment-4537748133) *(micro)* | +| `overlay` | [#22182](https://github.com/apache/datafusion/pull/22182) | [81% faster on high-null input](https://github.com/apache/datafusion/pull/22182) *(micro)* | +| `translate` | [#22171](https://github.com/apache/datafusion/pull/22171) | [77% faster: 265µs → 60µs](https://github.com/apache/datafusion/pull/22171) *(micro)* | +| `date_trunc` | [#23542](https://github.com/apache/datafusion/pull/23542) | [76% faster for `week`](https://github.com/apache/datafusion/pull/23542) *(micro)* | +| `replace` | [#23589](https://github.com/apache/datafusion/pull/23589) | [60% faster: 228µs → 91µs](https://github.com/apache/datafusion/pull/23589) *(micro)* | +| `regexp_instr` | [#23540](https://github.com/apache/datafusion/pull/23540) | [47% faster](https://github.com/apache/datafusion/pull/23540) *(micro)* | +| `left` / `right` | [#23762](https://github.com/apache/datafusion/pull/23762) | [45% faster on `string_view` long results](https://github.com/apache/datafusion/pull/23762) *(micro)* | +| `round` | [#23471](https://github.com/apache/datafusion/pull/23471) | [44% faster](https://github.com/apache/datafusion/pull/23471) *(micro)* | +| `regexp_match` with literal pattern | [#23547](https://github.com/apache/datafusion/pull/23547) | [37% faster with literal pattern and flags](https://github.com/apache/datafusion/pull/23547) *(micro)* | +| `date_part` `isodow` | [#23491](https://github.com/apache/datafusion/pull/23491) | [38% faster](https://github.com/apache/datafusion/pull/23491#issuecomment-4962484527) *(micro)* | + +### Planning + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| Collapse chained projections | [#22389](https://github.com/apache/datafusion/pull/22389) | [75% faster planning: 623ms → 155ms](https://github.com/apache/datafusion/pull/22389) | +| Skip subquery traversal, rewrite in place | [#22298](https://github.com/apache/datafusion/pull/22298) | [23% faster TPC-DS planning: 220ms → 170ms](https://github.com/apache/datafusion/pull/22298) | +| Don't re-inline CSE'd expensive expressions | [#23459](https://github.com/apache/datafusion/pull/23459) | [40% faster on repeated `power(a, 2)`](https://github.com/apache/datafusion/pull/23459) | +| Skip `ensure_distribution` rebuild for unchanged children | [#22521](https://github.com/apache/datafusion/pull/22521) | [2.87x speedup, 65% less CPU per call](https://github.com/apache/datafusion/pull/22521) | +| Unified `EnsureRequirements` pass | [#21976](https://github.com/apache/datafusion/pull/21976) | [TPC-H: 8 queries faster, 0 slower](https://github.com/apache/datafusion/pull/21976#issuecomment-4521276189) | +| Predicate reordering heuristic | [#22343](https://github.com/apache/datafusion/pull/22343) | [ClickBench Q21 10-13% faster](https://github.com/apache/datafusion/pull/22343#issuecomment-4483681627) | + +### Scan / IO + +| Improvement | Issue / PR | Representative Result | +| --- | --- | --- | +| Prune unread Parquet leaves for nested columns | [#24090](https://github.com/apache/datafusion/pull/24090) | [1.35 TB read → 30.9 GB for a production Comet query](https://github.com/apache/datafusion/pull/24090) | +| " (benchmark) | " | [45% less wall time on wide structs: 166ms → 91ms](https://github.com/apache/datafusion/pull/24090#issuecomment-5194655874) | +| Fewer object store requests for CSV | [#22962](https://github.com/apache/datafusion/pull/22962) | [1.70x faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | +| Lower `repartition_file_min_size` to 1 MiB | [#22439](https://github.com/apache/datafusion/pull/22439) | [TPC-H Q22 1.68x faster](https://github.com/apache/datafusion/pull/22439#issuecomment-4511995613) | +| Skip page index load when the file has none | [#24149](https://github.com/apache/datafusion/issues/24149), [#24150](https://github.com/apache/datafusion/pull/24150) | [ClickBench (single file) Q1 1.38x faster](https://github.com/apache/datafusion/pull/24150#issuecomment-5226213508) | + [Apache DataFusion]: https://datafusion.apache.org/ [Rust]: https://www.rust-lang.org/ @@ -540,6 +606,8 @@ for more details. [monthname]: https://github.com/apache/datafusion/pull/21639 [concat_ws]: https://github.com/apache/datafusion/pull/20928 +[appendix]: #appendix-clickbench-results + [#6899]: https://github.com/apache/datafusion/issues/6899 [#19241]: https://github.com/apache/datafusion/issues/19241 [#20763]: https://github.com/apache/datafusion/pull/20763 @@ -570,11 +638,15 @@ for more details. [#22777]: https://github.com/apache/datafusion/pull/22777 [#22885]: https://github.com/apache/datafusion/pull/22885 [#22940]: https://github.com/apache/datafusion/pull/22940 +[#22962]: https://github.com/apache/datafusion/pull/22962 [#22978]: https://github.com/apache/datafusion/pull/22978 [#22988]: https://github.com/apache/datafusion/pull/22988 +[#23011]: https://github.com/apache/datafusion/pull/23011 [#23012]: https://github.com/apache/datafusion/pull/23012 [#23014]: https://github.com/apache/datafusion/pull/23014 +[#23036]: https://github.com/apache/datafusion/issues/23036 [#23096]: https://github.com/apache/datafusion/pull/23096 +[#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 [#23184]: https://github.com/apache/datafusion/pull/23184 [#23231]: https://github.com/apache/datafusion/pull/23231 @@ -592,6 +664,8 @@ for more details. [#23583]: https://github.com/apache/datafusion/pull/23583 [#23617]: https://github.com/apache/datafusion/pull/23617 [#23623]: https://github.com/apache/datafusion/pull/23623 +[#23645]: https://github.com/apache/datafusion/issues/23645 +[#23646]: https://github.com/apache/datafusion/pull/23646 [#23680]: https://github.com/apache/datafusion/pull/23680 [#23743]: https://github.com/apache/datafusion/pull/23743 [#23785]: https://github.com/apache/datafusion/pull/23785 From 297a4109adcb58ff144b1437b5c6d234ac84bc32 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 10:32:29 -0400 Subject: [PATCH 06/31] remove mention of topk per partition --- content/blog/2026-08-16-datafusion-55.0.0.md | 187 ++++++++++++------- 1 file changed, 118 insertions(+), 69 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 7e5d8bb7..2816fc3a 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -94,18 +94,28 @@ reviews from [@adriangb]. **Complete Multi-Column `GROUP BY` Type Coverage**: DataFusion's column-wise `GROUP BY` storage (`GroupValuesColumn`) has type-specific fast paths, but previously any unsupported column type dragged -the entire grouping onto a slower row-encoded fallback. DataFusion 55 completes -the type coverage ([#22715]): new specializations were added for `Decimal256`, -`Float16`, and `Interval` ([#23849], [#23785], [#23786]), and a new generic -`Rows`-backed `GroupColumn` keeps mixed schemas on the column-wise path -([#23523]). -Thanks to [@zhuqi-lucas] and [@tohuya6] for this work. - -**Split Aggregation Streams**: -The hash aggregation state machine previously handled partial aggregation, -final aggregation, and streaming cases in one shared implementation. DataFusion -55 splits these semantically distinct paths into dedicated streams ([#22729]), -making the code easier to optimize and extend (part of epic [#22710]). +the entire grouping onto a slower row-encoded fallback. + +For example, a query like this to deduplicate a table of UUIDs used to +fall back to the slow path. + +```sql + SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); +``` + + +DataFusion 55 completes the type coverage ([#22715]) with new specializations for +`FixedSizeBinary`, `Decimal256`, `Float16`, `Duration`, and `Interval` ([#23646], +[#23849], [#23785], [#23783], [#23786]), and a new generic +`Rows`-backed `GroupColumn` ([#23523]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. This results in significant +performance improvements for queries that previously fell back to the row-encoded path, +such as a 1.06x speedup and much less memory use for queries of 200M UUIDs, which previously could +also run out of memory entirely ([#23645], [#23646]). Specializing `Dictionary` +and run-end encoded group keys remains as future work ([#23993]). + +**Split Aggregation Streams**: We are in the process of refactoring the +DataFusion aggregation path into dedicated streams ([#22729]) as the first step +towards better memory management for large aggregates (part of epic [#22710]). Thanks to [@2010YOUY01] for this work, with reviews from [@Rachelint] and [@alamb]. @@ -124,29 +134,40 @@ Thanks to the many contributors who drove this work, especially [@andygrove], [@neilconway], [@lyne7-sc], [@theirix], and [@haohuaijin]. **Faster `IN` List Evaluation**: -`IN` list membership checks can run millions of times during a scan, especially -with dynamic filter pushdown. DataFusion 55 adds exact lookup strategies -selected by type and list size, including bitmap filters for small integer -types and branchless filters for small primitive lists ([#19241]). -Thanks to [@geoffreyclaude] for driving this work, with contributions from -[@alamb]. Related PRs: [#23012], [#23014], [#23299] + +Many queries use `IN` list membership checks, often with large lists of values. +For example to find data for a particular set of hosts, a query might look like: + +```sql +SELECT ... WHERE host_id IN (12321321, ... 18568924) +``` + +DataFusion's general `IN` list implementation is already very fast, but 55 adds +additional specializations selected by type and list size, including bitmap +filters for small integer types and branchless filters for small primitive lists +([#19241]). Thanks to [@geoffreyclaude] for driving this work, with +contributions from [@alamb]. Related PRs: [#23012], [#23014], [#23299] ### Planner Improvements **Unified Distribution and Sorting Enforcement**: -The `EnforceDistribution` and `EnforceSorting` physical optimizer passes are -now merged into a single `EnsureRequirements` pass with idempotent sort +The [`EnforceDistribution`] and [`EnforceSorting`] physical optimizer passes are +now merged into a single [`EnsureRequirements`] pass with idempotent sort pushdown ([#21976]), fixing longstanding ordering issues between the two passes and enabling the sort pushdown work described above. Thanks to [@zhuqi-lucas] for this work, with reviews from [@2010YOUY01] and [@alamb]. +[`EnforceDistribution`]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_distribution/struct.EnforceDistribution.html +[`EnforceSorting`]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_sorting/struct.EnforceSorting.html +[`EnsureRequirements`]: https://docs.rs/datafusion/55.0.0/datafusion/physical_optimizer/ensure_requirements/struct.EnsureRequirements.html + **Smarter Join Planning**: -DataFusion 55 converts inner joins to semi joins when equivalent ([#22652]), +DataFusion 55 now converts inner joins to more efficient semi joins when equivalent ([#22652]), eliminates `LEFT`/`RIGHT` joins with redundant sides ([#23566]), handles intermediate projections in outer join elimination ([#22534]), and reorders predicates in conjunctions using a cost heuristic ([#22343]). -Thanks to [@neilconway] and [@simonvandel] for this work. +Thanks to [@neilconway] and [@simonvandel] for driving this work. **Scalar UDF Strictness Metadata**: Scalar UDFs can now declare that they are *strict* (they return `NULL` when any @@ -156,7 +177,7 @@ filter on the result of a function call. Thanks to [@lyne7-sc] for implementing this feature, with reviews from [@alamb] and [@kosiew]. -**Faster Optimizer Passes**: +**Faster Optimizer**: The logical optimizer now skips subquery traversal when there are no subqueries and rewrites plans in place ([#22298]), collapses chained projections in a single pass ([#22389]), and avoids re-inlining expensive common subexpressions @@ -166,19 +187,36 @@ Thanks to [@adriangb], [@Dandandan], and [@fordN] for this work. ### Scan Improvements **Pruning Unread Parquet Leaves for Nested Columns**: -When a table declares a nested column narrower than the Parquet file's physical -type, DataFusion previously read every leaf of the column and dropped the extra -subfields in memory. DataFusion 55 derives the projection mask through casts, -so only the leaves that are actually accessed are read — one production query -reported by the [DataFusion Comet] project went from reading 1.35 TB to reading -only the required data ([#24090]). -Thanks to [@mbutrovich] for this work, with reviews from [@adriangb]. + +Systems that embed DataFusion — such as [DataFusion Comet], [delta-rs], and +Iceberg integrations — often hand DataFusion a table schema that includes only the nested +subfields the query needs. For example, given a file whose `events` column +physically holds four subfields, a table might declare only two of them: + +```sql +-- events.parquet physically stores +-- events: ARRAY> +-- Table definition only refers to the first two subfields id, and name +CREATE EXTERNAL TABLE events ( + events ARRAY> +) +STORED AS PARQUET LOCATION 'events.parquet'; +``` + +DataFusion correctly reconciles these schemas, but prior to DataFusion 55 all +four leaves were read from the file, including the large `payload` and `trace` +subfields, decoded them, and then threw them away in memory. DataFusion 55 does +not read `id` and `name` leaves from the file at all, resulting in a report from +the Comet project that a production query went from reading 1.35 TB to reading +30.9 GB ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from +[@adriangb]. [DataFusion Comet]: https://datafusion.apache.org/comet/ +[delta-rs]: https://github.com/delta-io/delta-rs **Other Scan Improvements**: DataFusion 55 also skips loading the page index (and an expensive -`ParquetMetaData` clone) when a file has no page index ([#24150]), supports +[ParquetMetaData](https://docs.rs/parquet/latest/parquet/file/metadata/struct.ParquetMetaData.html) clone) when a file has no page index ([#24150]), supports file-level Parquet row selections ([#22940]), and lowers the default `repartition_file_min_size` from 10 MiB to 1 MiB for better parallelism on small files ([#22439]). @@ -186,7 +224,49 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ## New Features ✨ -### Range Partitioning +### `file_row_index()` and `input_file_name()` + +DataFusion 55 plumbs Parquet *virtual columns* through the scan ([#22026]) and +adds two new scalar functions that expose them to queries: [file_row_index] +returns the zero-based row offset of each row within its source file ([#22604]), +and [input_file_name] returns the path of the file each row came from +([#22978]): + +```sql +SELECT input_file_name(), file_row_index(), * FROM t; +``` + +Both functions are rewritten at file scan time, so they return an error if they +can not be pushed down into a scan (for example when evaluated outside a file +scan). They are similar to Spark's `input_file_name()` and +`monotonically_increasing_id()` functions, and are useful for building change +data capture, debugging, and Spark-compatible workloads. +Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from +[@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. + +### `unnest_outer` + +DataFusion's `unnest` drops rows whose input list is empty or `NULL`. The new +[unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, +matching Spark's `explode_outer` semantics: + +```sql +SELECT id, unnest_outer(tags) +FROM VALUES (1, ['a', 'b']), (2, []), (3, NULL) AS t(id, tags); ++----+----------------------+ +| id | UNNEST_OUTER(t.tags) | ++----+----------------------+ +| 1 | a | +| 1 | b | +| 2 | NULL | +| 3 | NULL | ++----+----------------------+ +``` + +Thanks to [@athlcode] for implementing this feature, with reviews from +[@comphead]. + +### Support for Range Partitioning Range partitioning assigns rows to partitions by ordered key ranges, so partition 0 holds the lowest keys, partition 1 the next range, and so on. It is @@ -268,7 +348,7 @@ Thanks to [@gene-bordegaray], [@saadtajwar], [@peterxcli], [@stuhood], [@Rich-T-kid], and [@blinding-pixels] for driving this substantial community effort. -### `MERGE INTO` +### `MERGE INTO` Planner Support `MERGE INTO` (SQL:2003) is a widely used DML statement for upsert and conditional update workloads, and a key building block for table formats such @@ -292,35 +372,6 @@ in their own merge execution. Thanks to [@wirybeaver] for implementing this feature, with reviews from [@alamb] and [@kosiew]. -### Parquet Virtual Columns: `file_row_index` and `input_file_name` - -DataFusion 55 plumbs Parquet *virtual columns* through the scan ([#22026]), -building on arrow-rs support for computing row indexes during the read. Two new -functions expose this to queries: [file_row_index] returns the 0-based row -index of each row within its Parquet file ([#22604]), and [input_file_name] -returns the file each row came from ([#22978]). These are useful for building -change data capture, debugging, and Spark-compatible workloads (Spark exposes -similar metadata columns). -Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from -[@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. - -### `unnest_outer` - -DataFusion's `unnest` drops rows whose input list is empty or `NULL`. The new -[unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, -matching Spark's `explode_outer` semantics: - -```sql -SELECT id, unnest_outer(tags) -FROM VALUES (1, ['a', 'b']), (2, []), (3, NULL) AS t(id, tags); --- 1, 'a' --- 1, 'b' --- 2, NULL --- 3, NULL -``` - -Thanks to [@athlcode] for implementing this feature, with reviews from -[@comphead]. ### Pluggable Spill Backends @@ -562,6 +613,7 @@ end-to-end query speedups. [@kumarUjjawal]: https://github.com/kumarUjjawal [@lyne7-sc]: https://github.com/lyne7-sc [@mattp5657]: https://github.com/mattp5657 +[@maxburke]: https://github.com/maxburke [@mbutrovich]: https://github.com/mbutrovich [@mithuncy]: https://github.com/mithuncy [@mkleen]: https://github.com/mkleen @@ -591,8 +643,8 @@ end-to-end query speedups. [upper]: https://github.com/apache/datafusion/pull/23588 [string_trim]: https://github.com/apache/datafusion/pull/23541 [approx_distinct]: https://github.com/apache/datafusion/pull/22768 -[file_row_index]: https://github.com/apache/datafusion/pull/22604 -[input_file_name]: https://github.com/apache/datafusion/pull/22978 +[file_row_index]: https://datafusion.apache.org/user-guide/sql/scalar_functions.html#file-row-index +[input_file_name]: https://datafusion.apache.org/user-guide/sql/scalar_functions.html#input-file-name [unnest_outer]: https://github.com/apache/datafusion/pull/22100 [array_scale]: https://github.com/apache/datafusion/pull/22466 [array_add]: https://github.com/apache/datafusion/pull/22459 @@ -608,10 +660,8 @@ end-to-end query speedups. [appendix]: #appendix-clickbench-results -[#6899]: https://github.com/apache/datafusion/issues/6899 [#19241]: https://github.com/apache/datafusion/issues/19241 [#20763]: https://github.com/apache/datafusion/pull/20763 -[#21479]: https://github.com/apache/datafusion/pull/21479 [#21768]: https://github.com/apache/datafusion/pull/21768 [#21767]: https://github.com/apache/datafusion/pull/21767 [#21882]: https://github.com/apache/datafusion/pull/21882 @@ -636,7 +686,6 @@ end-to-end query speedups. [#22729]: https://github.com/apache/datafusion/pull/22729 [#22768]: https://github.com/apache/datafusion/pull/22768 [#22777]: https://github.com/apache/datafusion/pull/22777 -[#22885]: https://github.com/apache/datafusion/pull/22885 [#22940]: https://github.com/apache/datafusion/pull/22940 [#22962]: https://github.com/apache/datafusion/pull/22962 [#22978]: https://github.com/apache/datafusion/pull/22978 @@ -645,14 +694,12 @@ end-to-end query speedups. [#23012]: https://github.com/apache/datafusion/pull/23012 [#23014]: https://github.com/apache/datafusion/pull/23014 [#23036]: https://github.com/apache/datafusion/issues/23036 -[#23096]: https://github.com/apache/datafusion/pull/23096 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 [#23184]: https://github.com/apache/datafusion/pull/23184 [#23231]: https://github.com/apache/datafusion/pull/23231 [#23239]: https://github.com/apache/datafusion/pull/23239 [#23299]: https://github.com/apache/datafusion/pull/23299 -[#23355]: https://github.com/apache/datafusion/pull/23355 [#23416]: https://github.com/apache/datafusion/pull/23416 [#23459]: https://github.com/apache/datafusion/pull/23459 [#23480]: https://github.com/apache/datafusion/pull/23480 @@ -668,6 +715,7 @@ end-to-end query speedups. [#23646]: https://github.com/apache/datafusion/pull/23646 [#23680]: https://github.com/apache/datafusion/pull/23680 [#23743]: https://github.com/apache/datafusion/pull/23743 +[#23783]: https://github.com/apache/datafusion/pull/23783 [#23785]: https://github.com/apache/datafusion/pull/23785 [#23786]: https://github.com/apache/datafusion/pull/23786 [#23849]: https://github.com/apache/datafusion/pull/23849 @@ -675,6 +723,7 @@ end-to-end query speedups. [#23888]: https://github.com/apache/datafusion/pull/23888 [#23930]: https://github.com/apache/datafusion/pull/23930 [#23954]: https://github.com/apache/datafusion/pull/23954 +[#23993]: https://github.com/apache/datafusion/issues/23993 [#24018]: https://github.com/apache/datafusion/pull/24018 [#24028]: https://github.com/apache/datafusion/pull/24028 [#24035]: https://github.com/apache/datafusion/pull/24035 From 864102201a100d18fb3e4cc4bfa722da59c46343 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 10:40:06 -0400 Subject: [PATCH 07/31] more examples --- content/blog/2026-08-16-datafusion-55.0.0.md | 31 ++++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 2816fc3a..365cce84 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -226,23 +226,28 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ### `file_row_index()` and `input_file_name()` -DataFusion 55 plumbs Parquet *virtual columns* through the scan ([#22026]) and -adds two new scalar functions that expose them to queries: [file_row_index] -returns the zero-based row offset of each row within its source file ([#22604]), -and [input_file_name] returns the path of the file each row came from -([#22978]): +DataFusion 55 adds [file_row_index] ([#22604]), and [input_file_name] ([#22978]) functions +to expose Parquet virtual columns: ```sql -SELECT input_file_name(), file_row_index(), * FROM t; +COPY (SELECT * from values (100), (200), (300)) to '/tmp/foo.parquet'; + +> select *, input_file_name(), file_row_index() from '/tmp/foo.parquet'; ++---------+-------------------+------------------+ +| column1 | input_file_name() | file_row_index() | ++---------+-------------------+------------------+ +| 100 | tmp/foo.parquet | 0 | +| 200 | tmp/foo.parquet | 1 | +| 300 | tmp/foo.parquet | 2 | ++---------+-------------------+------------------+ +3 row(s) fetched. +Elapsed 0.003 seconds. ``` -Both functions are rewritten at file scan time, so they return an error if they -can not be pushed down into a scan (for example when evaluated outside a file -scan). They are similar to Spark's `input_file_name()` and -`monotonically_increasing_id()` functions, and are useful for building change -data capture, debugging, and Spark-compatible workloads. -Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from -[@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. +Such functions are useful for building change data capture, debugging, and +Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work +(reviving earlier work from [@jkylling]), with reviews from [@adriangb], +[@comphead], and [@niebayes]. ### `unnest_outer` From ce3b0d1be4ddebabe3f217eb649b80179f98ec81 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 11:05:49 -0400 Subject: [PATCH 08/31] images --- content/blog/2026-08-16-datafusion-55.0.0.md | 188 +++++++++++++----- .../clickbench.c6a.4xlarge.2026_08_24.png | Bin 0 -> 94827 bytes ...ickbench.c7a.metal-48xlarge.2026_08_24.png | Bin 0 -> 96436 bytes .../performance_over_time_clickbench.png | Bin 0 -> 55855 bytes 4 files changed, 138 insertions(+), 50 deletions(-) create mode 100644 content/images/datafusion-55.0.0/clickbench.c6a.4xlarge.2026_08_24.png create mode 100644 content/images/datafusion-55.0.0/clickbench.c7a.metal-48xlarge.2026_08_24.png create mode 100644 content/images/datafusion-55.0.0/performance_over_time_clickbench.png diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 365cce84..b7d30129 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -122,11 +122,11 @@ Thanks to [@2010YOUY01] for this work, with reviews from [@Rachelint] and ### Faster Functions DataFusion ships hundreds of built-in functions, so speeding them up pays off -across many workloads. This release optimizes many, including [find_in_set] -(up to 24x faster), [trunc] (10x), [replace] (2x), [regexp_instr] (40%), -[regexp_match], [round], [date_trunc], [date_part], [get_field], [upper], and -[string_trim], plus dictionary-encoding preservation for many string functions -([#23743], [#23930], [#24100]) and a 100x improvement to [approx_distinct] for +across many workloads. This release optimizes many, including [`find_in_set`][find_in_set] +(up to 24x faster), [`trunc`][trunc] (10x), [`replace`][replace] (2x), [`regexp_instr`][regexp_instr] (40%), +[`regexp_match`][regexp_match], [`round`][round], [`date_trunc`][date_trunc], [`date_part`][date_part], [`get_field`][get_field], [`upper`][upper], and +[`string_trim`][string_trim], plus dictionary-encoding preservation for many string functions +([#23743], [#23930], [#24100]) and a 100x improvement to [`approx_distinct`][approx_distinct] for low-cardinality inputs with many groups ([#22768]). The `approx_distinct` aggregate also gained support for many more types, including `Decimal`, `Interval`, `Duration`, `Struct`, `Map`, and `Union`, thanks to [@mkleen]. @@ -151,16 +151,17 @@ contributions from [@alamb]. Related PRs: [#23012], [#23014], [#23299] ### Planner Improvements **Unified Distribution and Sorting Enforcement**: -The [`EnforceDistribution`] and [`EnforceSorting`] physical optimizer passes are -now merged into a single [`EnsureRequirements`] pass with idempotent sort +The [`EnforceDistribution`][EnforceDistribution] and +[`EnforceSorting`][EnforceSorting] physical optimizer passes are +now merged into a single [`EnsureRequirements`][EnsureRequirements] pass with idempotent sort pushdown ([#21976]), fixing longstanding ordering issues between the two passes and enabling the sort pushdown work described above. Thanks to [@zhuqi-lucas] for this work, with reviews from [@2010YOUY01] and [@alamb]. -[`EnforceDistribution`]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_distribution/struct.EnforceDistribution.html -[`EnforceSorting`]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_sorting/struct.EnforceSorting.html -[`EnsureRequirements`]: https://docs.rs/datafusion/55.0.0/datafusion/physical_optimizer/ensure_requirements/struct.EnsureRequirements.html +[EnforceDistribution]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_distribution/struct.EnforceDistribution.html +[EnforceSorting]: https://docs.rs/datafusion/54.0.0/datafusion/physical_optimizer/enforce_sorting/struct.EnforceSorting.html +[EnsureRequirements]: https://docs.rs/datafusion/55.0.0/datafusion/physical_optimizer/ensure_requirements/struct.EnsureRequirements.html **Smarter Join Planning**: DataFusion 55 now converts inner joins to more efficient semi joins when equivalent ([#22652]), @@ -216,7 +217,7 @@ the Comet project that a production query went from reading 1.35 TB to reading **Other Scan Improvements**: DataFusion 55 also skips loading the page index (and an expensive -[ParquetMetaData](https://docs.rs/parquet/latest/parquet/file/metadata/struct.ParquetMetaData.html) clone) when a file has no page index ([#24150]), supports +[`ParquetMetaData`](https://docs.rs/parquet/latest/parquet/file/metadata/struct.ParquetMetaData.html) clone) when a file has no page index ([#24150]), supports file-level Parquet row selections ([#22940]), and lowers the default `repartition_file_min_size` from 10 MiB to 1 MiB for better parallelism on small files ([#22439]). @@ -226,7 +227,7 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ### `file_row_index()` and `input_file_name()` -DataFusion 55 adds [file_row_index] ([#22604]), and [input_file_name] ([#22978]) functions +DataFusion 55 adds [`file_row_index`][file_row_index] ([#22604]), and [`input_file_name`][input_file_name] ([#22978]) functions to expose Parquet virtual columns: ```sql @@ -252,7 +253,7 @@ Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work ### `unnest_outer` DataFusion's `unnest` drops rows whose input list is empty or `NULL`. The new -[unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, +[`unnest_outer`][unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, matching Spark's `explode_outer` semantics: ```sql @@ -359,7 +360,7 @@ effort. conditional update workloads, and a key building block for table formats such as Apache Iceberg and Delta Lake. DataFusion 55 adds the logical plan types ([#20763]) along with SQL planner, physical planner, and a new -`TableProvider::merge_into` hook ([#22988]) so table implementations can +[`TableProvider::merge_into`][TableProvider::merge_into] hook ([#22988]) so table implementations can execute merge operations: ```sql @@ -372,7 +373,7 @@ WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name); ``` Built-in table providers do not yet implement the hook, but custom -`TableProvider` implementations (such as lakehouse table formats) can now plug +[`TableProvider`][TableProvider] implementations (such as lakehouse table formats) can now plug in their own merge execution. Thanks to [@wirybeaver] for implementing this feature, with reviews from [@alamb] and [@kosiew]. @@ -382,7 +383,8 @@ Thanks to [@wirybeaver] for implementing this feature, with reviews from DataFusion spills to disk when a query exceeds its memory budget, but the spill infrastructure was previously hardwired to OS-level temporary files. DataFusion -55 introduces a pluggable `SpillFile` trait and `TempFileFactory` ([#21882], +55 introduces a pluggable [`SpillFile`][SpillFile] trait and +[`TempFileFactory`][TempFileFactory] ([#21882], [#22230]) so hosts can route spill data through their own storage layers — for example, Postgres extensions like ParadeDB can now direct spills through Postgres `BufFile` APIs to respect `temp_tablespaces` and `temp_file_limit`. @@ -420,20 +422,83 @@ top of DataFusion: DataFusion 55 adds a Postgres-style `EXPLAIN (...)` option list ([#21768]) and a `pgjson` output format for `EXPLAIN ANALYZE` ([#21767]), making plan output easier to consume with existing Postgres tooling such as plan visualizers. -Thanks to [@adriangb] for this work. + +`EXPLAIN ANALYZE` can produce a lot of metrics, and the knobs for narrowing them +down previously lived only in session config, so you had to `SET` them +out-of-band before running the query. They are now available inline, per +statement: + +```sql +> EXPLAIN (ANALYZE, METRICS 'rows', LEVEL summary) + SELECT region_id, sum(amount) FROM orders GROUP BY region_id; + +AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amount)], metrics=[output_rows=5] + DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet, metrics=[output_rows=100.0 K, files_ranges_pruned_statistics=1 total → 1 matched, row_groups_pruned_statistics=1 total → 1 matched, row_groups_pruned_bloom_filter=1 total → 1 matched, page_index_pages_pruned=0 total → 0 matched, limit_pruned_row_groups=0 total → 0 matched, scan_efficiency_ratio=7.83% (28.30 K/361.5 K)] +``` + +Alongside `ANALYZE` and `VERBOSE`, the option list accepts `FORMAT`, `METRICS`, +`LEVEL`, `TIMING`, `SUMMARY`, and `COSTS`. Postgres-only options such as +`BUFFERS` and `WAL` report a clear "not supported" error rather than a parse +failure. The existing keyword form (`EXPLAIN ANALYZE VERBOSE FORMAT tree ...`) +continues to work unchanged. + +Adding `FORMAT pgjson` renders the same physical plan and its live metrics as +Postgres-compatible JSON, which can be pasted straight into plan visualizers +such as [Dalibo] or PEV2. Row counts and timings map onto the PG-canonical +`Actual Rows` / `Actual Total Time` keys, and DataFusion-specific metrics are +preserved under `Extras`: + +```sql +> EXPLAIN (ANALYZE, FORMAT pgjson, METRICS 'rows', LEVEL summary) + SELECT region_id, sum(amount) FROM orders GROUP BY region_id; +``` + +```json +[ + { + "Plan": { + "Node Type": "AggregateExec", + "Details": "AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amount)]", + "Actual Rows": 5, + "Plans": [ + { + "Node Type": "DataSourceExec", + "Details": "DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet", + "Actual Rows": 100000, + "Extras": { + "files_ranges_pruned_statistics": "1 total → 1 matched", + "row_groups_pruned_statistics": "1 total → 1 matched", + "row_groups_pruned_bloom_filter": "1 total → 1 matched", + "page_index_pages_pruned": "0 total → 0 matched", + "limit_pruned_row_groups": "0 total → 0 matched", + "scan_efficiency_ratio": "7.83% (28.30 K/361.5 K)" + }, + "Plans": [] + } + ] + } + } +] +``` + +See the [EXPLAIN usage guide] for the full option list. Thanks to [@adriangb] +for this work. + +[Dalibo]: https://explain.dalibo.com/ +[EXPLAIN usage guide]: https://datafusion.apache.org/user-guide/explain-usage.html ### New Functions **SQL and Scalar Functions**: -DataFusion 55 adds new array math functions [array_scale], [array_add], -[array_subtract], [array_sum], and [array_avg], plus the higher-order -[array_first] function. +DataFusion 55 adds new array math functions [`array_scale`][array_scale], [`array_add`][array_add], +[`array_subtract`][array_subtract], [`array_sum`][array_sum], and [`array_avg`][array_avg], plus the higher-order +[`array_first`][array_first] function. Thanks to [@crm26], [@SubhamSinghal], and [@EdsonPetry] for these contributions. **Spark-Compatible Functions**: The [datafusion-spark crate] gains new or improved Spark-compatible functions, -including [hypot], [atan2], [weekday], [monthname], and [concat_ws] with array +including [`hypot`][hypot], [`atan2`][atan2], [`weekday`][weekday], [`monthname`][monthname], and [`concat_ws`][concat_ws] with array support, plus a new Spark SQL parser dialect config ([#22529]). Thanks to the contributors who drove this work, especially [@KarpagamKarthikeyan], [@sjhddh], [@JeelRajodiya], [@davidlghellin], and @@ -476,17 +541,37 @@ can find out how to reach us on the [communication doc]. # Appendix: ClickBench Results -(DataFusion is already the fastest on ClickBench for bare metal XXX TODO -GET CITATION) +DataFusion is at the - + +**Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24], showing DataFusion 55.0.0 as the fastest engine for +processing parquet files. See the [ClickBench results page] for latest results + +[ClickBench results for c7a.metal-48xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ae-&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- +[ClickBench results page]: https://benchmark.clickhouse.com/ + + +**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24], showing DataFusion 55.0.0 as the fastest engine for +processing parquet files. See the[ClickBench results page] for latest results + +[ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- + **Figure 2**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. @@ -532,28 +617,28 @@ end-to-end query speedups. ### Expressions and Functions -| Improvement | Issue / PR | Representative Result | -| --- | --- | --- | -| Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | -| " (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | -| " (`Int8` / `Int16`) | [#23299](https://github.com/apache/datafusion/pull/23299) | [4.4x faster for `i16`, 4-element list](https://github.com/apache/datafusion/pull/23299#issuecomment-4875954295) *(micro)* | -| Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | -| " (trim family) | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | -| " (`ascii`, `bit_length`, `octet_length`) | [#23743](https://github.com/apache/datafusion/pull/23743) | [28.7x faster `ascii`](https://github.com/apache/datafusion/pull/23743) *(micro)* | -| `find_in_set` | [#23460](https://github.com/apache/datafusion/pull/23460) | [24x faster: 1.25ms → 52µs](https://github.com/apache/datafusion/pull/23460) *(micro)* | -| `array_has` with array needle | [#23337](https://github.com/apache/datafusion/pull/23337) | [15.1x faster; join query 0.95s → 0.059s](https://github.com/apache/datafusion/pull/23337) | -| `trunc` with scalar precision | [#23593](https://github.com/apache/datafusion/pull/23593) | [12.9x faster: 9.5µs → 735ns](https://github.com/apache/datafusion/pull/23593) *(micro)* | -| `encode` / hex | [#23456](https://github.com/apache/datafusion/pull/23456) | [5.2x faster: 200µs → 39.5µs](https://github.com/apache/datafusion/pull/23456) *(micro)* | -| `arrays_zip` perfect-list fast path | [#22285](https://github.com/apache/datafusion/pull/22285) | [4.2x faster](https://github.com/apache/datafusion/pull/22285#issuecomment-4537748133) *(micro)* | -| `overlay` | [#22182](https://github.com/apache/datafusion/pull/22182) | [81% faster on high-null input](https://github.com/apache/datafusion/pull/22182) *(micro)* | -| `translate` | [#22171](https://github.com/apache/datafusion/pull/22171) | [77% faster: 265µs → 60µs](https://github.com/apache/datafusion/pull/22171) *(micro)* | -| `date_trunc` | [#23542](https://github.com/apache/datafusion/pull/23542) | [76% faster for `week`](https://github.com/apache/datafusion/pull/23542) *(micro)* | -| `replace` | [#23589](https://github.com/apache/datafusion/pull/23589) | [60% faster: 228µs → 91µs](https://github.com/apache/datafusion/pull/23589) *(micro)* | -| `regexp_instr` | [#23540](https://github.com/apache/datafusion/pull/23540) | [47% faster](https://github.com/apache/datafusion/pull/23540) *(micro)* | -| `left` / `right` | [#23762](https://github.com/apache/datafusion/pull/23762) | [45% faster on `string_view` long results](https://github.com/apache/datafusion/pull/23762) *(micro)* | -| `round` | [#23471](https://github.com/apache/datafusion/pull/23471) | [44% faster](https://github.com/apache/datafusion/pull/23471) *(micro)* | -| `regexp_match` with literal pattern | [#23547](https://github.com/apache/datafusion/pull/23547) | [37% faster with literal pattern and flags](https://github.com/apache/datafusion/pull/23547) *(micro)* | -| `date_part` `isodow` | [#23491](https://github.com/apache/datafusion/pull/23491) | [38% faster](https://github.com/apache/datafusion/pull/23491#issuecomment-4962484527) *(micro)* | +| Improvement | Issue / PR | Representative Result | +|--------------------------------------------------------------------------| --- | --- | +| Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | +| "Faster `IN` list (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | +| Faster `IN` list (`Int8` / `Int16`) | [#23299](https://github.com/apache/datafusion/pull/23299) | [4.4x faster for `i16`, 4-element list](https://github.com/apache/datafusion/pull/23299#issuecomment-4875954295) *(micro)* | +| Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | +| Preserve dictionary encoding for trim family | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | +| Preserve dictionary encoding for (`ascii`, `bit_length`, `octet_length`) | [#23743](https://github.com/apache/datafusion/pull/23743) | [28.7x faster `ascii`](https://github.com/apache/datafusion/pull/23743) *(micro)* | +| `find_in_set` | [#23460](https://github.com/apache/datafusion/pull/23460) | [24x faster: 1.25ms → 52µs](https://github.com/apache/datafusion/pull/23460) *(micro)* | +| `array_has` with array needle | [#23337](https://github.com/apache/datafusion/pull/23337) | [15.1x faster; join query 0.95s → 0.059s](https://github.com/apache/datafusion/pull/23337) | +| `trunc` with scalar precision | [#23593](https://github.com/apache/datafusion/pull/23593) | [12.9x faster: 9.5µs → 735ns](https://github.com/apache/datafusion/pull/23593) *(micro)* | +| `encode` / hex | [#23456](https://github.com/apache/datafusion/pull/23456) | [5.2x faster: 200µs → 39.5µs](https://github.com/apache/datafusion/pull/23456) *(micro)* | +| `arrays_zip` perfect-list fast path | [#22285](https://github.com/apache/datafusion/pull/22285) | [4.2x faster](https://github.com/apache/datafusion/pull/22285#issuecomment-4537748133) *(micro)* | +| `overlay` | [#22182](https://github.com/apache/datafusion/pull/22182) | [81% faster on high-null input](https://github.com/apache/datafusion/pull/22182) *(micro)* | +| `translate` | [#22171](https://github.com/apache/datafusion/pull/22171) | [77% faster: 265µs → 60µs](https://github.com/apache/datafusion/pull/22171) *(micro)* | +| `date_trunc` | [#23542](https://github.com/apache/datafusion/pull/23542) | [76% faster for `week`](https://github.com/apache/datafusion/pull/23542) *(micro)* | +| `replace` | [#23589](https://github.com/apache/datafusion/pull/23589) | [60% faster: 228µs → 91µs](https://github.com/apache/datafusion/pull/23589) *(micro)* | +| `regexp_instr` | [#23540](https://github.com/apache/datafusion/pull/23540) | [47% faster](https://github.com/apache/datafusion/pull/23540) *(micro)* | +| `left` / `right` | [#23762](https://github.com/apache/datafusion/pull/23762) | [45% faster on `string_view` long results](https://github.com/apache/datafusion/pull/23762) *(micro)* | +| `round` | [#23471](https://github.com/apache/datafusion/pull/23471) | [44% faster](https://github.com/apache/datafusion/pull/23471) *(micro)* | +| `regexp_match` with literal pattern | [#23547](https://github.com/apache/datafusion/pull/23547) | [37% faster with literal pattern and flags](https://github.com/apache/datafusion/pull/23547) *(micro)* | +| `date_part` `isodow` | [#23491](https://github.com/apache/datafusion/pull/23491) | [38% faster](https://github.com/apache/datafusion/pull/23491#issuecomment-4962484527) *(micro)* | ### Planning @@ -571,7 +656,6 @@ end-to-end query speedups. | Improvement | Issue / PR | Representative Result | | --- | --- | --- | | Prune unread Parquet leaves for nested columns | [#24090](https://github.com/apache/datafusion/pull/24090) | [1.35 TB read → 30.9 GB for a production Comet query](https://github.com/apache/datafusion/pull/24090) | -| " (benchmark) | " | [45% less wall time on wide structs: 166ms → 91ms](https://github.com/apache/datafusion/pull/24090#issuecomment-5194655874) | | Fewer object store requests for CSV | [#22962](https://github.com/apache/datafusion/pull/22962) | [1.70x faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | | Lower `repartition_file_min_size` to 1 MiB | [#22439](https://github.com/apache/datafusion/pull/22439) | [TPC-H Q22 1.68x faster](https://github.com/apache/datafusion/pull/22439#issuecomment-4511995613) | | Skip page index load when the file has none | [#24149](https://github.com/apache/datafusion/issues/24149), [#24150](https://github.com/apache/datafusion/pull/24150) | [ClickBench (single file) Q1 1.38x faster](https://github.com/apache/datafusion/pull/24150#issuecomment-5226213508) | @@ -584,7 +668,7 @@ end-to-end query speedups. [dataframe library]: https://datafusion.apache.org/user-guide/dataframe.html [Python library]: https://datafusion.apache.org/python/ [command-line SQL tool]: https://datafusion.apache.org/user-guide/cli/ -[Upgrade Guide]: https://datafusion.apache.org/library-user-guide/upgrading.html +[Upgrade Guide]: https://datafusion.apache.org/library-user-guide/upgrading/55.0.0.html [here]: https://github.com/apache/arrow-datafusion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 [communication doc]: https://datafusion.apache.org/contributor-guide/communication.html @@ -664,6 +748,10 @@ end-to-end query speedups. [concat_ws]: https://github.com/apache/datafusion/pull/20928 [appendix]: #appendix-clickbench-results +[TableProvider]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html +[TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#tymethod.merge_into +[SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html +[TempFileFactory]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.TempFileFactory.html [#19241]: https://github.com/apache/datafusion/issues/19241 [#20763]: https://github.com/apache/datafusion/pull/20763 diff --git a/content/images/datafusion-55.0.0/clickbench.c6a.4xlarge.2026_08_24.png b/content/images/datafusion-55.0.0/clickbench.c6a.4xlarge.2026_08_24.png new file mode 100644 index 0000000000000000000000000000000000000000..e22e397247a8d4fd33e52b16d833276a0e84d475 GIT binary patch literal 94827 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

2x{i(S*dS5$ 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 L`KVUWD(t@iS>$Qi literal 0 HcmV?d00001 diff --git a/content/images/datafusion-55.0.0/clickbench.c7a.metal-48xlarge.2026_08_24.png b/content/images/datafusion-55.0.0/clickbench.c7a.metal-48xlarge.2026_08_24.png new file mode 100644 index 0000000000000000000000000000000000000000..8ac2994234b00124e3c7c0afe95b93e6f5e27130 GIT binary patch literal 96436 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≧;YA3AI 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 Date: Mon, 24 Aug 2026 11:16:30 -0400 Subject: [PATCH 09/31] update performance --- content/blog/2026-08-16-datafusion-55.0.0.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index b7d30129..a258f42e 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -541,29 +541,35 @@ can find out how to reach us on the [communication doc]. # Appendix: ClickBench Results -DataFusion is at the +DataFusion has reached the top of the ClickBench leaderboard for processing +parquet files. While performance varies slightly by engine and instance type, it +is clear DataFusion has the same level of performance on local file systems as +other state of the art engines such as ClickHouse, and often superior +performance when processing on object storage. It is likely, of course that +DataFusion will continue to improve the performance over time but we are proud +of the results so far. DataFusion clickbench performance on c7a.metal-48xlarge -**Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24], showing DataFusion 55.0.0 as the fastest engine for +**Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]; DataFusion 55.0.0 is the fastest engine for processing parquet files. See the [ClickBench results page] for latest results [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ae-&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- [ClickBench results page]: https://benchmark.clickhouse.com/ DataFusion clickbench performance on c6a.4xlarge -**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24], showing DataFusion 55.0.0 as the fastest engine for -processing parquet files. See the[ClickBench results page] for latest results +**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion 55.0.0 is the second +fastest engine for processing parquet files on this VM type. See the[ClickBench results page] for latest results [ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- From b88f8e34573b27b46052d3c84dfb7c9facabe3cc Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 11:22:52 -0400 Subject: [PATCH 10/31] update performance --- content/blog/2026-08-16-datafusion-55.0.0.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index a258f42e..f1164931 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -541,13 +541,17 @@ can find out how to reach us on the [communication doc]. # Appendix: ClickBench Results -DataFusion has reached the top of the ClickBench leaderboard for processing -parquet files. While performance varies slightly by engine and instance type, it -is clear DataFusion has the same level of performance on local file systems as -other state of the art engines such as ClickHouse, and often superior -performance when processing on object storage. It is likely, of course that -DataFusion will continue to improve the performance over time but we are proud -of the results so far. +We try not to get too excited by benchmarks, though it is hard to avoid getting +excited by them. As noted above, ClickBench covers only a tiny fraction of what +our users actually do as well and it focuses on reading from local files rather +than object storage. However, we are happy that our optimization efforts over +the last 6 months have paid off with DataFusion sitting at the top of the +ClickBench leaderboard for processing parquet files. Performance varies slightly +by engine and instance type, but DataFusion clearly matches any other state of +the art engine reading from file systems and often exceeds them when +significantly when reading from object storage. This largely reflects the +community interest in this benchmark, and there is still plenty left to +optimize, of course. Date: Mon, 24 Aug 2026 11:29:43 -0400 Subject: [PATCH 11/31] More --- content/blog/2026-08-16-datafusion-55.0.0.md | 39 +++++++------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index f1164931..2efd7327 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -423,10 +423,9 @@ DataFusion 55 adds a Postgres-style `EXPLAIN (...)` option list ([#21768]) and a `pgjson` output format for `EXPLAIN ANALYZE` ([#21767]), making plan output easier to consume with existing Postgres tooling such as plan visualizers. -`EXPLAIN ANALYZE` can produce a lot of metrics, and the knobs for narrowing them -down previously lived only in session config, so you had to `SET` them -out-of-band before running the query. They are now available inline, per -statement: +`EXPLAIN ANALYZE` can produce many metrics, and you can narrow them down +explicitly with the `METRICS` option. For example, to see only row counts for +each plan node, use this: ```sql > EXPLAIN (ANALYZE, METRICS 'rows', LEVEL summary) @@ -436,17 +435,9 @@ AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amo DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet, metrics=[output_rows=100.0 K, files_ranges_pruned_statistics=1 total → 1 matched, row_groups_pruned_statistics=1 total → 1 matched, row_groups_pruned_bloom_filter=1 total → 1 matched, page_index_pages_pruned=0 total → 0 matched, limit_pruned_row_groups=0 total → 0 matched, scan_efficiency_ratio=7.83% (28.30 K/361.5 K)] ``` -Alongside `ANALYZE` and `VERBOSE`, the option list accepts `FORMAT`, `METRICS`, -`LEVEL`, `TIMING`, `SUMMARY`, and `COSTS`. Postgres-only options such as -`BUFFERS` and `WAL` report a clear "not supported" error rather than a parse -failure. The existing keyword form (`EXPLAIN ANALYZE VERBOSE FORMAT tree ...`) -continues to work unchanged. - Adding `FORMAT pgjson` renders the same physical plan and its live metrics as Postgres-compatible JSON, which can be pasted straight into plan visualizers -such as [Dalibo] or PEV2. Row counts and timings map onto the PG-canonical -`Actual Rows` / `Actual Total Time` keys, and DataFusion-specific metrics are -preserved under `Extras`: +such as [Dalibo] or PEV2: ```sql > EXPLAIN (ANALYZE, FORMAT pgjson, METRICS 'rows', LEVEL summary) @@ -541,17 +532,13 @@ can find out how to reach us on the [communication doc]. # Appendix: ClickBench Results -We try not to get too excited by benchmarks, though it is hard to avoid getting -excited by them. As noted above, ClickBench covers only a tiny fraction of what -our users actually do as well and it focuses on reading from local files rather -than object storage. However, we are happy that our optimization efforts over -the last 6 months have paid off with DataFusion sitting at the top of the -ClickBench leaderboard for processing parquet files. Performance varies slightly -by engine and instance type, but DataFusion clearly matches any other state of -the art engine reading from file systems and often exceeds them when -significantly when reading from object storage. This largely reflects the -community interest in this benchmark, and there is still plenty left to -optimize, of course. +We try not to get too excited by benchmarks, though it is hard not to. As noted +above, ClickBench covers only a tiny fraction of what our users actually do, and +reads local files rather than object storage. Even so, DataFusion now sits at +the top of the ClickBench leaderboard for processing parquet files. Results vary +slightly by engine and instance type, but DataFusion matches any other +state-of-the-art engine on local files and often significantly exceeds them on +object storage. Plenty left to optimize, of course. DataFusion clickbench performance on c7a.metal-48xlarge -**Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]; DataFusion 55.0.0 is the fastest engine for +**Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]; DataFusion is the fastest engine for processing parquet files. See the [ClickBench results page] for latest results [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ae-&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- @@ -572,7 +559,7 @@ width="100%" class="img-fluid" alt="DataFusion clickbench performance on c6a.4xlarge" /> -**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion 55.0.0 is the second +**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion is the second fastest engine for processing parquet files on this VM type. See the[ClickBench results page] for latest results [ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- From 1982f6027d535af5427497ab1fcbecc0a7663c11 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 12:03:04 -0400 Subject: [PATCH 12/31] example --- content/blog/2026-08-16-datafusion-55.0.0.md | 37 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 2efd7327..4141b847 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -385,9 +385,37 @@ DataFusion spills to disk when a query exceeds its memory budget, but the spill infrastructure was previously hardwired to OS-level temporary files. DataFusion 55 introduces a pluggable [`SpillFile`][SpillFile] trait and [`TempFileFactory`][TempFileFactory] ([#21882], -[#22230]) so hosts can route spill data through their own storage layers — for -example, Postgres extensions like ParadeDB can now direct spills through -Postgres `BufFile` APIs to respect `temp_tablespaces` and `temp_file_limit`. +[#22230]) so hosts can route spill data through their own storage layers, for +example, extensions like ParadeDB can now integrate spilling into the +Postgres buffer pool. To use a custom backend, implement `TempFileFactory` to hand DataFusion your own +`SpillFile` implementation, and install the factory on the `RuntimeEnv`: + +```rust +// Creates spill files backed by some custom storage layer +struct MyTempFileFactory { /* ... */ } + +// Implement TempFileFactor API +impl TempFileFactory for MyTempFileFactory { + fn create_temp_file(&self, description: &str) -> Result> { + Ok(Arc::new(MySpillFile::new(description))) + } +} + +// Install the factory in the RuntimeEnv used to run queries +let runtime = RuntimeEnvBuilder::new() + .with_disk_manager_builder( + DiskManagerBuilder::default() + .with_temp_file_factory(Arc::new(MyTempFileFactory::new())), + ) + .build_arc()?; + +let ctx = SessionContext::new_with_config_rt(SessionConfig::new(), runtime); +``` + +The new [`object_store_spill.rs`] example shows a complete, +runnable implementation that spills through an [`ObjectStore`], the same +pattern you would use to spill to S3, GCS, or Azure Blob Storage. + Thanks to [@pantShrey] for this work, with reviews from [@alamb]. ### Extensibility for Distributed Engines @@ -749,6 +777,8 @@ end-to-end query speedups. [TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#tymethod.merge_into [SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html [TempFileFactory]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.TempFileFactory.html +[`object_store_spill.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs +[`ObjectStore`]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html [#19241]: https://github.com/apache/datafusion/issues/19241 [#20763]: https://github.com/apache/datafusion/pull/20763 @@ -786,6 +816,7 @@ end-to-end query speedups. [#23036]: https://github.com/apache/datafusion/issues/23036 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 +[#23170]: https://github.com/apache/datafusion/pull/23170 [#23184]: https://github.com/apache/datafusion/pull/23184 [#23231]: https://github.com/apache/datafusion/pull/23231 [#23239]: https://github.com/apache/datafusion/pull/23239 From 55d342d5a41007f12d5296f2fb89458f5f03b530 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Mon, 24 Aug 2026 12:21:06 -0400 Subject: [PATCH 13/31] example / liks --- content/blog/2026-08-16-datafusion-55.0.0.md | 22 +++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 4141b847..c907af47 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -73,7 +73,7 @@ Here is a representative sample of the performance improvements in this release. | Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), and [no longer runs out of memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | -### Sort Pushdown: Runtime Row-Group Pruning for TopK Queries +### Sort Pushdown + TopK Pruning The multi-release [Sort Pushdown effort] continues to optimize `ORDER BY` and `ORDER BY ... LIMIT` (TopK) queries. In DataFusion 55, as a dynamic filter @@ -387,36 +387,28 @@ infrastructure was previously hardwired to OS-level temporary files. DataFusion [`TempFileFactory`][TempFileFactory] ([#21882], [#22230]) so hosts can route spill data through their own storage layers, for example, extensions like ParadeDB can now integrate spilling into the -Postgres buffer pool. To use a custom backend, implement `TempFileFactory` to hand DataFusion your own -`SpillFile` implementation, and install the factory on the `RuntimeEnv`: +Postgres buffer pool. Implement [`TempFileFactory`][TempFileFactory] and install +it on the [`RuntimeEnv`][RuntimeEnv]: ```rust -// Creates spill files backed by some custom storage layer -struct MyTempFileFactory { /* ... */ } - -// Implement TempFileFactor API impl TempFileFactory for MyTempFileFactory { fn create_temp_file(&self, description: &str) -> Result> { Ok(Arc::new(MySpillFile::new(description))) } } -// Install the factory in the RuntimeEnv used to run queries let runtime = RuntimeEnvBuilder::new() .with_disk_manager_builder( DiskManagerBuilder::default() .with_temp_file_factory(Arc::new(MyTempFileFactory::new())), ) .build_arc()?; - let ctx = SessionContext::new_with_config_rt(SessionConfig::new(), runtime); ``` -The new [`object_store_spill.rs`] example shows a complete, -runnable implementation that spills through an [`ObjectStore`], the same -pattern you would use to spill to S3, GCS, or Azure Blob Storage. - -Thanks to [@pantShrey] for this work, with reviews from [@alamb]. +See the [`object_store_spill.rs`] example for a complete implementation that +spills to an [`ObjectStore`] such as S3. Thanks to [@pantShrey] for this work, +with reviews from [@alamb]. ### Extensibility for Distributed Engines @@ -777,6 +769,7 @@ end-to-end query speedups. [TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#tymethod.merge_into [SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html [TempFileFactory]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.TempFileFactory.html +[RuntimeEnv]: https://docs.rs/datafusion/55.0.0/datafusion/execution/runtime_env/struct.RuntimeEnv.html [`object_store_spill.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs [`ObjectStore`]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html @@ -816,7 +809,6 @@ end-to-end query speedups. [#23036]: https://github.com/apache/datafusion/issues/23036 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 -[#23170]: https://github.com/apache/datafusion/pull/23170 [#23184]: https://github.com/apache/datafusion/pull/23184 [#23231]: https://github.com/apache/datafusion/pull/23231 [#23239]: https://github.com/apache/datafusion/pull/23239 From e0bb604686824e71832965252017e4c1acd8c07d Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 09:45:42 -0400 Subject: [PATCH 14/31] updates --- content/blog/2026-08-16-datafusion-55.0.0.md | 62 +++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index c907af47..38f1ed4f 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -179,11 +179,12 @@ Thanks to [@lyne7-sc] for implementing this feature, with reviews from [@alamb] and [@kosiew]. **Faster Optimizer**: -The logical optimizer now skips subquery traversal when there are no subqueries -and rewrites plans in place ([#22298]), collapses chained projections in a -single pass ([#22389]), and avoids re-inlining expensive common subexpressions -during projection pushdown ([#23459]). -Thanks to [@adriangb], [@Dandandan], and [@fordN] for this work. +The optimizer continues to get faster, with improvements such as selective subquery +traversal and rewrites ([#22298]), collapsing chained projections ([#22389]), +avoiding re-inlining expensive common subexpressions ([#23459]), faster pushdown +filter rule ([#20002]), in place modifications ([#21668]), making planning 1.3x +faster for filter-heavy queries such as TPC-H Q19. +Thanks to [@adriangb], [@Dandandan], [@fordN], and [@joroKr21] for this work. ### Scan Improvements @@ -223,6 +224,16 @@ file-level Parquet row selections ([#22940]), and lowers the default small files ([#22439]). Thanks to [@alamb], [@haohuaijin], and [@adriangb]. +## Stability Improvements 🛡️ + +The community also improved DataFusion's handling of larger than memory +aggregate workloads ([#23657], [#23965], [#24061]). Sorts under memory pressure are more +resilient: when a spill merge cannot reserve enough memory, DataFusion now +re-spills the largest stream in smaller batches rather than failing ([#22945]), +and caps the merge fan-in to bound memory use ([#23066]). Thanks to +[@2010YOUY01], [@EmilyMatt], [@yinli-systems], and [@pepijnve] (who fixed a +subtle lost-wakeup bug in the spill pool, [#23522]) for this work. + ## New Features ✨ ### `file_row_index()` and `input_file_name()` @@ -250,28 +261,6 @@ Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from [@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. -### `unnest_outer` - -DataFusion's `unnest` drops rows whose input list is empty or `NULL`. The new -[`unnest_outer`][unnest_outer] function ([#22100]) preserves them as `NULL` rows instead, -matching Spark's `explode_outer` semantics: - -```sql -SELECT id, unnest_outer(tags) -FROM VALUES (1, ['a', 'b']), (2, []), (3, NULL) AS t(id, tags); -+----+----------------------+ -| id | UNNEST_OUTER(t.tags) | -+----+----------------------+ -| 1 | a | -| 1 | b | -| 2 | NULL | -| 3 | NULL | -+----+----------------------+ -``` - -Thanks to [@athlcode] for implementing this feature, with reviews from -[@comphead]. - ### Support for Range Partitioning Range partitioning assigns rows to partitions by ordered key ranges, so @@ -503,8 +492,10 @@ for this work. **SQL and Scalar Functions**: DataFusion 55 adds new array math functions [`array_scale`][array_scale], [`array_add`][array_add], [`array_subtract`][array_subtract], [`array_sum`][array_sum], and [`array_avg`][array_avg], plus the higher-order -[`array_first`][array_first] function. -Thanks to [@crm26], [@SubhamSinghal], and [@EdsonPetry] for these +[`array_first`][array_first] function. It also include the new [`unnest_outer`][unnest_outer] +function ([#22100]) that preserves rows whose input is empty or `NULL` as `NULL` rows rather +than dropping them. +Thanks to [@crm26], [@SubhamSinghal], [@EdsonPetry], and [@athlcode] for these contributions. **Spark-Compatible Functions**: @@ -665,6 +656,7 @@ end-to-end query speedups. | Skip subquery traversal, rewrite in place | [#22298](https://github.com/apache/datafusion/pull/22298) | [23% faster TPC-DS planning: 220ms → 170ms](https://github.com/apache/datafusion/pull/22298) | | Don't re-inline CSE'd expensive expressions | [#23459](https://github.com/apache/datafusion/pull/23459) | [40% faster on repeated `power(a, 2)`](https://github.com/apache/datafusion/pull/23459) | | Skip `ensure_distribution` rebuild for unchanged children | [#22521](https://github.com/apache/datafusion/pull/22521) | [2.87x speedup, 65% less CPU per call](https://github.com/apache/datafusion/pull/22521) | +| Fewer allocations in `PushDownFilter` | [#20002](https://github.com/apache/datafusion/issues/20002), [#21668](https://github.com/apache/datafusion/pull/21668) | [TPC-H Q19 planning 1.3x faster](https://github.com/apache/datafusion/pull/21668) | | Unified `EnsureRequirements` pass | [#21976](https://github.com/apache/datafusion/pull/21976) | [TPC-H: 8 queries faster, 0 slower](https://github.com/apache/datafusion/pull/21976#issuecomment-4521276189) | | Predicate reordering heuristic | [#22343](https://github.com/apache/datafusion/pull/22343) | [ClickBench Q21 10-13% faster](https://github.com/apache/datafusion/pull/22343#issuecomment-4483681627) | @@ -692,6 +684,7 @@ end-to-end query speedups. [@2010YOUY01]: https://github.com/2010YOUY01 [@AdamGS]: https://github.com/AdamGS [@Dandandan]: https://github.com/Dandandan +[@EmilyMatt]: https://github.com/EmilyMatt [@EdsonPetry]: https://github.com/EdsonPetry [@JSOD11]: https://github.com/JSOD11 [@JeelRajodiya]: https://github.com/JeelRajodiya @@ -715,6 +708,7 @@ end-to-end query speedups. [@haohuaijin]: https://github.com/haohuaijin [@jayshrivastava]: https://github.com/jayshrivastava [@jkylling]: https://github.com/jkylling +[@joroKr21]: https://github.com/joroKr21 [@kosiew]: https://github.com/kosiew [@kumarUjjawal]: https://github.com/kumarUjjawal [@lyne7-sc]: https://github.com/lyne7-sc @@ -726,6 +720,7 @@ end-to-end query speedups. [@neilconway]: https://github.com/neilconway [@niebayes]: https://github.com/niebayes [@pantShrey]: https://github.com/pantShrey +[@pepijnve]: https://github.com/pepijnve [@peterxcli]: https://github.com/peterxcli [@saadtajwar]: https://github.com/saadtajwar [@simonvandel]: https://github.com/simonvandel @@ -735,6 +730,7 @@ end-to-end query speedups. [@timsaucer]: https://github.com/timsaucer [@tohuya6]: https://github.com/tohuya6 [@wirybeaver]: https://github.com/wirybeaver +[@yinli-systems]: https://github.com/yinli-systems [@zhuqi-lucas]: https://github.com/zhuqi-lucas [find_in_set]: https://github.com/apache/datafusion/pull/23460 @@ -774,7 +770,9 @@ end-to-end query speedups. [`ObjectStore`]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html [#19241]: https://github.com/apache/datafusion/issues/19241 +[#20002]: https://github.com/apache/datafusion/issues/20002 [#20763]: https://github.com/apache/datafusion/pull/20763 +[#21668]: https://github.com/apache/datafusion/pull/21668 [#21768]: https://github.com/apache/datafusion/pull/21768 [#21767]: https://github.com/apache/datafusion/pull/21767 [#21882]: https://github.com/apache/datafusion/pull/21882 @@ -800,6 +798,7 @@ end-to-end query speedups. [#22768]: https://github.com/apache/datafusion/pull/22768 [#22777]: https://github.com/apache/datafusion/pull/22777 [#22940]: https://github.com/apache/datafusion/pull/22940 +[#22945]: https://github.com/apache/datafusion/pull/22945 [#22962]: https://github.com/apache/datafusion/pull/22962 [#22978]: https://github.com/apache/datafusion/pull/22978 [#22988]: https://github.com/apache/datafusion/pull/22988 @@ -807,6 +806,7 @@ end-to-end query speedups. [#23012]: https://github.com/apache/datafusion/pull/23012 [#23014]: https://github.com/apache/datafusion/pull/23014 [#23036]: https://github.com/apache/datafusion/issues/23036 +[#23066]: https://github.com/apache/datafusion/pull/23066 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 [#23184]: https://github.com/apache/datafusion/pull/23184 @@ -819,6 +819,7 @@ end-to-end query speedups. [#23484]: https://github.com/apache/datafusion/pull/23484 [#23487]: https://github.com/apache/datafusion/pull/23487 [#23494]: https://github.com/apache/datafusion/issues/23494 +[#23522]: https://github.com/apache/datafusion/pull/23522 [#23523]: https://github.com/apache/datafusion/pull/23523 [#23566]: https://github.com/apache/datafusion/pull/23566 [#23583]: https://github.com/apache/datafusion/pull/23583 @@ -826,6 +827,7 @@ end-to-end query speedups. [#23623]: https://github.com/apache/datafusion/pull/23623 [#23645]: https://github.com/apache/datafusion/issues/23645 [#23646]: https://github.com/apache/datafusion/pull/23646 +[#23657]: https://github.com/apache/datafusion/pull/23657 [#23680]: https://github.com/apache/datafusion/pull/23680 [#23743]: https://github.com/apache/datafusion/pull/23743 [#23783]: https://github.com/apache/datafusion/pull/23783 @@ -836,10 +838,12 @@ end-to-end query speedups. [#23888]: https://github.com/apache/datafusion/pull/23888 [#23930]: https://github.com/apache/datafusion/pull/23930 [#23954]: https://github.com/apache/datafusion/pull/23954 +[#23965]: https://github.com/apache/datafusion/pull/23965 [#23993]: https://github.com/apache/datafusion/issues/23993 [#24018]: https://github.com/apache/datafusion/pull/24018 [#24028]: https://github.com/apache/datafusion/pull/24028 [#24035]: https://github.com/apache/datafusion/pull/24035 +[#24061]: https://github.com/apache/datafusion/pull/24061 [#24068]: https://github.com/apache/datafusion/pull/24068 [#24090]: https://github.com/apache/datafusion/pull/24090 [#24100]: https://github.com/apache/datafusion/pull/24100 From 47f732ce3e35e2ed69729b594467cf80fc76ce19 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 09:57:01 -0400 Subject: [PATCH 15/31] update --- content/blog/2026-08-16-datafusion-55.0.0.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 38f1ed4f..2596e46b 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -179,11 +179,12 @@ Thanks to [@lyne7-sc] for implementing this feature, with reviews from [@alamb] and [@kosiew]. **Faster Optimizer**: -The optimizer continues to get faster, with improvements such as selective subquery -traversal and rewrites ([#22298]), collapsing chained projections ([#22389]), -avoiding re-inlining expensive common subexpressions ([#23459]), faster pushdown -filter rule ([#20002]), in place modifications ([#21668]), making planning 1.3x -faster for filter-heavy queries such as TPC-H Q19. +The optimizer continues to get faster, with improvements such as selective +subquery traversal and in-place rewrites ([#22298]), collapsing chained +projections ([#22389]), avoiding re-inlining expensive common subexpressions +([#23459]), and a faster `PushDownFilter` rule that modifies plans in place +([#20002], [#21668]), making planning 1.3x faster for filter-heavy queries +such as TPC-H Q19. Thanks to [@adriangb], [@Dandandan], [@fordN], and [@joroKr21] for this work. ### Scan Improvements @@ -492,9 +493,9 @@ for this work. **SQL and Scalar Functions**: DataFusion 55 adds new array math functions [`array_scale`][array_scale], [`array_add`][array_add], [`array_subtract`][array_subtract], [`array_sum`][array_sum], and [`array_avg`][array_avg], plus the higher-order -[`array_first`][array_first] function. It also include the new [`unnest_outer`][unnest_outer] -function ([#22100]) that preserves rows whose input is empty or `NULL` as `NULL` rows rather -than dropping them. +[`array_first`][array_first] function. It also includes the new +[`unnest_outer`][unnest_outer] function ([#22100]), which preserves +empty input as `NULL`s rather than dropping them. Thanks to [@crm26], [@SubhamSinghal], [@EdsonPetry], and [@athlcode] for these contributions. From 1243ee6d934ba5b516f22527db2d95c48994a78d Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 10:14:05 -0400 Subject: [PATCH 16/31] trim range partitioning --- content/blog/2026-08-16-datafusion-55.0.0.md | 75 +++----------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 2596e46b..d593371b 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -29,7 +29,7 @@ limitations under the License. We are proud to announce the release of [DataFusion 55.0.0]. This post highlights some of the major improvements since [DataFusion 54.0.0]. Notable -additions include as always significant performance improvements, as well as +additions include significant performance improvements, range partitioning, `MERGE INTO` support, and runtime row-group pruning for TopK queries. The complete list of changes is available in the [changelog]. This release represents roughly 9 weeks @@ -264,13 +264,13 @@ Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work ### Support for Range Partitioning -Range partitioning assigns rows to partitions by ordered key ranges, so -partition 0 holds the lowest keys, partition 1 the next range, and so on. It is -the layout of time-series tables written one file per day or hour, and of tables -partitioned by ID range. +DataFusion 55 adds native *range partitioning* support, which maps rows to partitions by key ranges (rather than hash +values). The inputs to queries are often range partitioned in real-world scenarios such as time-series where the data +is written one file per day or hour. DataFusion uses range partitioning information to +avoid expensive repartitioning operations and push more specific dynamic filters +to scans. -DataFusion 55 adds *range partitioning* ([#22395], design discussion -[#21992]). A range partitioning declares an ordering and a list of split +Data that is range partitioned declares an ordering and a list of split points. Partition `i` holds the keys that fall between split point `i-1` and split point `i`: @@ -283,63 +283,9 @@ partition 1: 2022-01-01 <= date < 2023-01-01 partition 2: date >= 2023-01-01 ``` -Compound keys work the same way, with split points compared lexicographically. -DataFusion does not validate the layout: a source that declares range -partitioning is responsible for placing every row in the partition its split -points describe. Wrong split points produce skew or missing join matches rather -than an error. The physical `Partitioning::Range` variant landed in [#22207], -the logical representation in [#22777], and execution plus planning in -[#23231] and [#23617]. - -**Declaring a layout.** There is no SQL syntax for this yet. A table declares -its partitioning through `ListingOptions::with_output_partitioning` or -`FileScanConfig::with_output_partitioning` ([#22657]): - -```rust -let output_partitioning = Partitioning::Range(RangePartitioning::try_new( - vec![col("range_key").sort(true, true)], - vec![ - SplitPoint::new(vec![ScalarValue::Int32(Some(10))]), - SplitPoint::new(vec![ScalarValue::Int32(Some(20))]), - SplitPoint::new(vec![ScalarValue::Int32(Some(30))]), - ], -)?); - -let options = ListingOptions::new(Arc::new(ParquetFormat::default())) - .with_output_partitioning(Some(output_partitioning)); -``` - -**What it buys you.** The payoff is the `RepartitionExec` the planner no longer -inserts. A declared range layout now satisfies `Distribution::KeyPartitioned` -([#23680]), so aggregates ([#23239]), partitioned hash joins for inner -([#23184]), left ([#23487]), right ([#23484]), and full ([#23583]) types, sort -merge and symmetric hash joins ([#23480]), window functions ([#23416]), -and `InterleaveExec` ([#23623]) can all run -directly on the declared partitions: - -```text -> EXPLAIN SELECT range_key, SUM(value) FROM range_partitioned GROUP BY range_key; - -AggregateExec: mode=SinglePartitioned, gby=[range_key@0 as range_key], aggr=[sum(range_partitioned.value)] - DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet -``` - -Joins need both sides on the same layout, not merely on some range layout: - -```text -> EXPLAIN SELECT l.range_key, l.value, r.value - FROM range_partitioned l JOIN range_partitioned r ON l.range_key = r.range_key; - -HashJoinExec: mode=Partitioned, join_type=Inner, on=[(range_key@0, range_key@0)] - DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet - DataSourceExec: output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=parquet -``` - -Dynamic filter pushdown also now works for range-partitioned joins, routing -build-side filters to the correct probe partition using the range split points -([#23854]). - -Thanks to [@gene-bordegaray], [@saadtajwar], [@peterxcli], [@stuhood], +For more details, please see the documentation for +[`Partitioning::Range`][Partitioning::Range], the planning epic ([#22395]), and +the design discussion ([#21992]). Thanks to [@gene-bordegaray], [@saadtajwar], [@peterxcli], [@stuhood], [@gmhelmold], [@mattp5657], [@mithuncy], [@JSOD11], [@EdsonPetry], [@Rich-T-kid], and [@blinding-pixels] for driving this substantial community effort. @@ -762,6 +708,7 @@ end-to-end query speedups. [concat_ws]: https://github.com/apache/datafusion/pull/20928 [appendix]: #appendix-clickbench-results +[Partitioning::Range]: https://docs.rs/datafusion/55.0.0/datafusion/physical_expr/struct.RangePartitioning.html [TableProvider]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html [TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#tymethod.merge_into [SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html From c2a5ceb4d263847872eab596d009c47f367b6f19 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 10:21:39 -0400 Subject: [PATCH 17/31] trim more --- content/blog/2026-08-16-datafusion-55.0.0.md | 89 +++++++------------- 1 file changed, 32 insertions(+), 57 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index d593371b..38b51a13 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -94,24 +94,17 @@ reviews from [@adriangb]. **Complete Multi-Column `GROUP BY` Type Coverage**: DataFusion's column-wise `GROUP BY` storage (`GroupValuesColumn`) has type-specific fast paths, but previously any unsupported column type dragged -the entire grouping onto a slower row-encoded fallback. - -For example, a query like this to deduplicate a table of UUIDs used to -fall back to the slow path. +the entire grouping onto a slower row-encoded fallback. For example, this +query to deduplicate a table of UUIDs used to hit the slow path: ```sql - SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); +SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); ``` - -DataFusion 55 completes the type coverage ([#22715]) with new specializations for -`FixedSizeBinary`, `Decimal256`, `Float16`, `Duration`, and `Interval` ([#23646], -[#23849], [#23785], [#23783], [#23786]), and a new generic -`Rows`-backed `GroupColumn` ([#23523]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. This results in significant -performance improvements for queries that previously fell back to the row-encoded path, -such as a 1.06x speedup and much less memory use for queries of 200M UUIDs, which previously could -also run out of memory entirely ([#23645], [#23646]). Specializing `Dictionary` -and run-end encoded group keys remains as future work ([#23993]). +DataFusion 55 completes the type coverage ([#22715]) with new specializations, +and a generic `Rows`-backed `GroupColumn`. The query above now runs 6% faster +on 200M UUIDs and uses much less memory (see [#23645]). Thanks to +[@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. **Split Aggregation Streams**: We are in the process of refactoring the DataFusion aggregation path into dedicated streams ([#22729]) as the first step @@ -122,31 +115,29 @@ Thanks to [@2010YOUY01] for this work, with reviews from [@Rachelint] and ### Faster Functions DataFusion ships hundreds of built-in functions, so speeding them up pays off -across many workloads. This release optimizes many, including [`find_in_set`][find_in_set] -(up to 24x faster), [`trunc`][trunc] (10x), [`replace`][replace] (2x), [`regexp_instr`][regexp_instr] (40%), -[`regexp_match`][regexp_match], [`round`][round], [`date_trunc`][date_trunc], [`date_part`][date_part], [`get_field`][get_field], [`upper`][upper], and -[`string_trim`][string_trim], plus dictionary-encoding preservation for many string functions -([#23743], [#23930], [#24100]) and a 100x improvement to [`approx_distinct`][approx_distinct] for -low-cardinality inputs with many groups ([#22768]). The `approx_distinct` -aggregate also gained support for many more types, including `Decimal`, -`Interval`, `Duration`, `Struct`, `Map`, and `Union`, thanks to [@mkleen]. -Thanks to the many contributors who drove this work, especially [@andygrove], -[@neilconway], [@lyne7-sc], [@theirix], and [@haohuaijin]. +across many workloads. This release optimizes dozens of them — up to 24x faster +for [`find_in_set`][find_in_set] and 100x for [`approx_distinct`][approx_distinct] +with low-cardinality inputs and many groups ([#22768]). It also includes +dictionary-encoding preservation for many string functions ([#23743], +[#23930], [#24100]). See the [full list in the appendix][perf appendix]. +Thanks to the many contributors who drove this work, especially +[@andygrove], [@neilconway], [@lyne7-sc], [@theirix], and [@haohuaijin]. -**Faster `IN` List Evaluation**: +[perf appendix]: #appendix-full-list-of-performance-improvements -Many queries use `IN` list membership checks, often with large lists of values. -For example to find data for a particular set of hosts, a query might look like: +**Faster `IN` List Evaluation**: +Many queries check membership in an `IN` list, for example to find data for a +particular set of hosts: ```sql SELECT ... WHERE host_id IN (12321321, ... 18568924) ``` -DataFusion's general `IN` list implementation is already very fast, but 55 adds -additional specializations selected by type and list size, including bitmap -filters for small integer types and branchless filters for small primitive lists -([#19241]). Thanks to [@geoffreyclaude] for driving this work, with -contributions from [@alamb]. Related PRs: [#23012], [#23014], [#23299] +DataFusion's `IN` list implementation was already fast, and 55 adds further +specializations selected by type and list size, including bitmap filters for +small integer types and branchless filters for small primitive lists +([#19241], [#23012], [#23014], [#23299]). Thanks to [@geoffreyclaude] for +driving this work, with contributions from [@alamb]. ### Planner Improvements @@ -243,8 +234,7 @@ DataFusion 55 adds [`file_row_index`][file_row_index] ([#22604]), and [`input_f to expose Parquet virtual columns: ```sql -COPY (SELECT * from values (100), (200), (300)) to '/tmp/foo.parquet'; - +> COPY (SELECT * from values (100), (200), (300)) to '/tmp/foo.parquet'; > select *, input_file_name(), file_row_index() from '/tmp/foo.parquet'; +---------+-------------------+------------------+ | column1 | input_file_name() | file_row_index() | @@ -253,8 +243,6 @@ COPY (SELECT * from values (100), (200), (300)) to '/tmp/foo.parquet'; | 200 | tmp/foo.parquet | 1 | | 300 | tmp/foo.parquet | 2 | +---------+-------------------+------------------+ -3 row(s) fetched. -Elapsed 0.003 seconds. ``` Such functions are useful for building change data capture, debugging, and @@ -262,7 +250,7 @@ Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from [@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. -### Support for Range Partitioning +### Range Partitioning DataFusion 55 adds native *range partitioning* support, which maps rows to partitions by key ranges (rather than hash values). The inputs to queries are often range partitioned in real-world scenarios such as time-series where the data @@ -388,7 +376,7 @@ each plan node, use this: SELECT region_id, sum(amount) FROM orders GROUP BY region_id; AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amount)], metrics=[output_rows=5] - DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet, metrics=[output_rows=100.0 K, files_ranges_pruned_statistics=1 total → 1 matched, row_groups_pruned_statistics=1 total → 1 matched, row_groups_pruned_bloom_filter=1 total → 1 matched, page_index_pages_pruned=0 total → 0 matched, limit_pruned_row_groups=0 total → 0 matched, scan_efficiency_ratio=7.83% (28.30 K/361.5 K)] + DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet, metrics=[output_rows=100.0 K, row_groups_pruned_statistics=1 total → 1 matched, scan_efficiency_ratio=7.83% (28.30 K/361.5 K), ...] ``` Adding `FORMAT pgjson` renders the same physical plan and its live metrics as @@ -405,23 +393,9 @@ such as [Dalibo] or PEV2: { "Plan": { "Node Type": "AggregateExec", - "Details": "AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amount)]", "Actual Rows": 5, "Plans": [ - { - "Node Type": "DataSourceExec", - "Details": "DataSourceExec: file_groups={1 group: [[orders.parquet]]}, projection=[region_id, amount], file_type=parquet", - "Actual Rows": 100000, - "Extras": { - "files_ranges_pruned_statistics": "1 total → 1 matched", - "row_groups_pruned_statistics": "1 total → 1 matched", - "row_groups_pruned_bloom_filter": "1 total → 1 matched", - "page_index_pages_pruned": "0 total → 0 matched", - "limit_pruned_row_groups": "0 total → 0 matched", - "scan_efficiency_ratio": "7.83% (28.30 K/361.5 K)" - }, - "Plans": [] - } + { "Node Type": "DataSourceExec", "Actual Rows": 100000, ... } ] } } @@ -440,10 +414,11 @@ for this work. DataFusion 55 adds new array math functions [`array_scale`][array_scale], [`array_add`][array_add], [`array_subtract`][array_subtract], [`array_sum`][array_sum], and [`array_avg`][array_avg], plus the higher-order [`array_first`][array_first] function. It also includes the new -[`unnest_outer`][unnest_outer] function ([#22100]), which preserves -empty input as `NULL`s rather than dropping them. -Thanks to [@crm26], [@SubhamSinghal], [@EdsonPetry], and [@athlcode] for these -contributions. +[`unnest_outer`][unnest_outer] function ([#22100]), which preserves +empty input as `NULL`s rather than dropping them, and +[`approx_distinct`][approx_distinct] supports more types. +Thanks to [@crm26], [@SubhamSinghal], [@EdsonPetry], [@athlcode], and +[@mkleen] for these contributions. **Spark-Compatible Functions**: The [datafusion-spark crate] gains new or improved Spark-compatible functions, From c5104b6437ea58cf6f9c77b619dc85f7ba523dff Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 10:29:11 -0400 Subject: [PATCH 18/31] tighen --- content/blog/2026-08-16-datafusion-55.0.0.md | 108 ++++++------------- 1 file changed, 32 insertions(+), 76 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 38b51a13..5c90165a 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -28,10 +28,10 @@ limitations under the License. [TOC] We are proud to announce the release of [DataFusion 55.0.0]. This post -highlights some of the major improvements since [DataFusion 54.0.0]. Notable -additions include significant performance improvements, -range partitioning, `MERGE INTO` support, and runtime row-group pruning for -TopK queries. The complete list of +highlights some of the many improvements since [DataFusion 54.0.0] such as +significant performance improvements, range partitioning, +`MERGE INTO` support, and runtime row-group pruning for Top-K queries. The +complete list of changes is available in the [changelog]. This release represents roughly 9 weeks of development and 877 commits. Thanks to the [175 contributors] (a new record!) for making it possible. @@ -57,7 +57,7 @@ in DataFusion 55. This release, we focused our optimizations on making DataFusion faster across the board rather than further optimizing our already great ClickBench numbers -(DataFusion is already the fastest on ClickBench for bare metal see the +(DataFusion is already the fastest on ClickBench for bare metal — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no PageIndex, and has only integers and string columns). @@ -78,13 +78,11 @@ Here is a representative sample of the performance improvements in this release. The multi-release [Sort Pushdown effort] continues to optimize `ORDER BY` and `ORDER BY ... LIMIT` (TopK) queries. In DataFusion 55, as a dynamic filter threshold tightens, the Parquet reader re-evaluates the threshold against the -remaining row groups and drops those that can no longer contribute ([#22450]). -This is a powerful optimization but required careful engineering to avoid -regressions. See our [Optimizing for Almost Sorted Data] blog post for more -details. DataFusion 55 also supports compound `ORDER BY` queries. 5 of 11 -queries in the `topk_tpch` got 3-4x faster with no regressions, reducing total -suite runtime by 44%. Thanks to [@zhuqi-lucas] for driving this work, with -reviews from [@adriangb]. +remaining row groups and drops those that can no longer contribute ([#22450]), +and compound `ORDER BY` queries are now supported. Together these reduced +total `topk_tpch` suite runtime by 44% with no regressions; see our +[Optimizing for Almost Sorted Data] blog post for more details. Thanks to +[@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. [Sort Pushdown effort]: https://github.com/apache/datafusion/issues/23036 [Optimizing for Almost Sorted Data]: https://datafusion.apache.org/blog/2026/07/20/sort-pushdown/ @@ -101,17 +99,11 @@ query to deduplicate a table of UUIDs used to hit the slow path: SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); ``` -DataFusion 55 completes the type coverage ([#22715]) with new specializations, +DataFusion 55 completes the type coverage ([#22715]) with new specializations and a generic `Rows`-backed `GroupColumn`. The query above now runs 6% faster on 200M UUIDs and uses much less memory (see [#23645]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. -**Split Aggregation Streams**: We are in the process of refactoring the -DataFusion aggregation path into dedicated streams ([#22729]) as the first step -towards better memory management for large aggregates (part of epic [#22710]). -Thanks to [@2010YOUY01] for this work, with reviews from [@Rachelint] and -[@alamb]. - ### Faster Functions DataFusion ships hundreds of built-in functions, so speeding them up pays off @@ -188,8 +180,7 @@ subfields the query needs. For example, given a file whose `events` column physically holds four subfields, a table might declare only two of them: ```sql --- events.parquet physically stores --- events: ARRAY> +-- events column is ARRAY> -- Table definition only refers to the first two subfields id, and name CREATE EXTERNAL TABLE events ( events ARRAY> @@ -198,11 +189,11 @@ STORED AS PARQUET LOCATION 'events.parquet'; ``` DataFusion correctly reconciles these schemas, but prior to DataFusion 55 all -four leaves were read from the file, including the large `payload` and `trace` -subfields, decoded them, and then threw them away in memory. DataFusion 55 does -not read `id` and `name` leaves from the file at all, resulting in a report from -the Comet project that a production query went from reading 1.35 TB to reading -30.9 GB ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from +four leaves were read from the file and decoded, including the large `payload` +and `trace` subfields, which were then thrown away. DataFusion 55 does not +read the undeclared `payload` and `trace` leaves from the file at all, +resulting in a report from the Comet project that a production query went from +reading 1.35 TB to reading 30.9 GB ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from [@adriangb]. [DataFusion Comet]: https://datafusion.apache.org/comet/ @@ -219,18 +210,20 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ## Stability Improvements 🛡️ The community also improved DataFusion's handling of larger than memory -aggregate workloads ([#23657], [#23965], [#24061]). Sorts under memory pressure are more -resilient: when a spill merge cannot reserve enough memory, DataFusion now -re-spills the largest stream in smaller batches rather than failing ([#22945]), -and caps the merge fan-in to bound memory use ([#23066]). Thanks to -[@2010YOUY01], [@EmilyMatt], [@yinli-systems], and [@pepijnve] (who fixed a -subtle lost-wakeup bug in the spill pool, [#23522]) for this work. +aggregate workloads (e.g. [#23657], [#23965], [#24061]), building on a +refactoring of the aggregation path into dedicated streams ([#22729], part of +epic [#22710]). Sorts under memory pressure are more resilient: when a spill +merge cannot reserve enough memory, DataFusion now re-spills the largest stream +in smaller batches rather than failing ([#22945]), and caps the merge fan-in to +bound memory use ([#23066]). Thanks to [@2010YOUY01], [@EmilyMatt], +[@yinli-systems], [@Rachelint], and [@pepijnve] (who fixed a subtle lost-wakeup +bug in the spill pool, [#23522]) for this work. ## New Features ✨ ### `file_row_index()` and `input_file_name()` -DataFusion 55 adds [`file_row_index`][file_row_index] ([#22604]), and [`input_file_name`][input_file_name] ([#22978]) functions +DataFusion 55 adds [`file_row_index`][file_row_index] ([#22604]) and [`input_file_name`][input_file_name] ([#22978]) functions to expose Parquet virtual columns: ```sql @@ -309,20 +302,15 @@ DataFusion spills to disk when a query exceeds its memory budget, but the spill infrastructure was previously hardwired to OS-level temporary files. DataFusion 55 introduces a pluggable [`SpillFile`][SpillFile] trait and [`TempFileFactory`][TempFileFactory] ([#21882], -[#22230]) so hosts can route spill data through their own storage layers, for -example, extensions like ParadeDB can now integrate spilling into the +[#22230]) so hosts can route spill data through their own storage layers — for +example, extensions like ParadeDB can now integrate spilling into the Postgres buffer pool. Implement [`TempFileFactory`][TempFileFactory] and install it on the [`RuntimeEnv`][RuntimeEnv]: ```rust -impl TempFileFactory for MyTempFileFactory { - fn create_temp_file(&self, description: &str) -> Result> { - Ok(Arc::new(MySpillFile::new(description))) - } -} - let runtime = RuntimeEnvBuilder::new() .with_disk_manager_builder( + // register a custom TempFileFactory DiskManagerBuilder::default() .with_temp_file_factory(Arc::new(MyTempFileFactory::new())), ) @@ -492,8 +480,8 @@ width="100%" class="img-fluid" alt="DataFusion clickbench performance on c6a.4xlarge" /> -**Figure 2**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion is the second -fastest engine for processing parquet files on this VM type. See the[ClickBench results page] for latest results +**Figure 3**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion is the second +fastest engine for processing parquet files on this VM type. See the [ClickBench results page] for latest results [ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- @@ -504,7 +492,7 @@ class="img-fluid" alt="DataFusion Performance over time" /> -**Figure 2**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. +**Figure 4**: Average and median normalized execution times for DataFusion 55.0.0 on ClickBench queries, compared to previous releases. Query times are normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. @@ -656,16 +644,6 @@ end-to-end query speedups. [@zhuqi-lucas]: https://github.com/zhuqi-lucas [find_in_set]: https://github.com/apache/datafusion/pull/23460 -[trunc]: https://github.com/apache/datafusion/pull/23593 -[replace]: https://github.com/apache/datafusion/pull/23589 -[regexp_instr]: https://github.com/apache/datafusion/pull/23540 -[regexp_match]: https://github.com/apache/datafusion/pull/23547 -[round]: https://github.com/apache/datafusion/pull/23471 -[date_trunc]: https://github.com/apache/datafusion/pull/23542 -[date_part]: https://github.com/apache/datafusion/pull/23491 -[get_field]: https://github.com/apache/datafusion/pull/23537 -[upper]: https://github.com/apache/datafusion/pull/23588 -[string_trim]: https://github.com/apache/datafusion/pull/23541 [approx_distinct]: https://github.com/apache/datafusion/pull/22768 [file_row_index]: https://datafusion.apache.org/user-guide/sql/scalar_functions.html#file-row-index [input_file_name]: https://datafusion.apache.org/user-guide/sql/scalar_functions.html#input-file-name @@ -701,9 +679,7 @@ end-to-end query speedups. [#21882]: https://github.com/apache/datafusion/pull/21882 [#21976]: https://github.com/apache/datafusion/pull/21976 [#21992]: https://github.com/apache/datafusion/issues/21992 -[#22026]: https://github.com/apache/datafusion/pull/22026 [#22100]: https://github.com/apache/datafusion/pull/22100 -[#22207]: https://github.com/apache/datafusion/pull/22207 [#22230]: https://github.com/apache/datafusion/pull/22230 [#22298]: https://github.com/apache/datafusion/pull/22298 [#22343]: https://github.com/apache/datafusion/pull/22343 @@ -714,12 +690,10 @@ end-to-end query speedups. [#22534]: https://github.com/apache/datafusion/pull/22534 [#22604]: https://github.com/apache/datafusion/pull/22604 [#22652]: https://github.com/apache/datafusion/pull/22652 -[#22657]: https://github.com/apache/datafusion/pull/22657 [#22715]: https://github.com/apache/datafusion/issues/22715 [#22710]: https://github.com/apache/datafusion/issues/22710 [#22729]: https://github.com/apache/datafusion/pull/22729 [#22768]: https://github.com/apache/datafusion/pull/22768 -[#22777]: https://github.com/apache/datafusion/pull/22777 [#22940]: https://github.com/apache/datafusion/pull/22940 [#22945]: https://github.com/apache/datafusion/pull/22945 [#22962]: https://github.com/apache/datafusion/pull/22962 @@ -732,37 +706,19 @@ end-to-end query speedups. [#23066]: https://github.com/apache/datafusion/pull/23066 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 -[#23184]: https://github.com/apache/datafusion/pull/23184 -[#23231]: https://github.com/apache/datafusion/pull/23231 -[#23239]: https://github.com/apache/datafusion/pull/23239 [#23299]: https://github.com/apache/datafusion/pull/23299 -[#23416]: https://github.com/apache/datafusion/pull/23416 [#23459]: https://github.com/apache/datafusion/pull/23459 -[#23480]: https://github.com/apache/datafusion/pull/23480 -[#23484]: https://github.com/apache/datafusion/pull/23484 -[#23487]: https://github.com/apache/datafusion/pull/23487 [#23494]: https://github.com/apache/datafusion/issues/23494 [#23522]: https://github.com/apache/datafusion/pull/23522 [#23523]: https://github.com/apache/datafusion/pull/23523 [#23566]: https://github.com/apache/datafusion/pull/23566 -[#23583]: https://github.com/apache/datafusion/pull/23583 -[#23617]: https://github.com/apache/datafusion/pull/23617 -[#23623]: https://github.com/apache/datafusion/pull/23623 [#23645]: https://github.com/apache/datafusion/issues/23645 [#23646]: https://github.com/apache/datafusion/pull/23646 [#23657]: https://github.com/apache/datafusion/pull/23657 -[#23680]: https://github.com/apache/datafusion/pull/23680 [#23743]: https://github.com/apache/datafusion/pull/23743 -[#23783]: https://github.com/apache/datafusion/pull/23783 -[#23785]: https://github.com/apache/datafusion/pull/23785 -[#23786]: https://github.com/apache/datafusion/pull/23786 -[#23849]: https://github.com/apache/datafusion/pull/23849 -[#23854]: https://github.com/apache/datafusion/pull/23854 -[#23888]: https://github.com/apache/datafusion/pull/23888 [#23930]: https://github.com/apache/datafusion/pull/23930 [#23954]: https://github.com/apache/datafusion/pull/23954 [#23965]: https://github.com/apache/datafusion/pull/23965 -[#23993]: https://github.com/apache/datafusion/issues/23993 [#24018]: https://github.com/apache/datafusion/pull/24018 [#24028]: https://github.com/apache/datafusion/pull/24028 [#24035]: https://github.com/apache/datafusion/pull/24035 From af72558cf86675f04bf4aa4fc5d39982ff43db08 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 10:37:07 -0400 Subject: [PATCH 19/31] Trim --- content/blog/2026-08-16-datafusion-55.0.0.md | 53 ++++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 5c90165a..71efe199 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -28,9 +28,9 @@ limitations under the License. [TOC] We are proud to announce the release of [DataFusion 55.0.0]. This post -highlights some of the many improvements since [DataFusion 54.0.0] such as +highlights some of the many improvements since [DataFusion 54.0.0], such as significant performance improvements, range partitioning, -`MERGE INTO` support, and runtime row-group pruning for Top-K queries. The +`MERGE INTO` support, and runtime row-group pruning for TopK queries. The complete list of changes is available in the [changelog]. This release represents roughly 9 weeks of development and 877 commits. Thanks to the [175 contributors] (a new record!) @@ -55,11 +55,11 @@ in DataFusion 55. ## Performance Improvements 🚀 -This release, we focused our optimizations on making DataFusion faster across +In this release, we focused our optimizations on making DataFusion faster across the board rather than further optimizing our already great ClickBench numbers (DataFusion is already the fastest on ClickBench for bare metal — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users -do (e.g. its files have no PageIndex, and has only integers and string columns). + do (e.g. its files have no page index and contain only integer and string columns). Here is a representative sample of the performance improvements in this release. @@ -111,26 +111,15 @@ across many workloads. This release optimizes dozens of them — up to 24x faste for [`find_in_set`][find_in_set] and 100x for [`approx_distinct`][approx_distinct] with low-cardinality inputs and many groups ([#22768]). It also includes dictionary-encoding preservation for many string functions ([#23743], -[#23930], [#24100]). See the [full list in the appendix][perf appendix]. +[#23930], [#24100]) and new `IN` list specializations selected by type and +list size, such as bitmap filters for small integer types ([#19241]). See the +[full list in the appendix][perf appendix]. Thanks to the many contributors who drove this work, especially -[@andygrove], [@neilconway], [@lyne7-sc], [@theirix], and [@haohuaijin]. +[@andygrove], [@geoffreyclaude], [@neilconway], [@lyne7-sc], [@theirix], and +[@haohuaijin]. [perf appendix]: #appendix-full-list-of-performance-improvements -**Faster `IN` List Evaluation**: -Many queries check membership in an `IN` list, for example to find data for a -particular set of hosts: - -```sql -SELECT ... WHERE host_id IN (12321321, ... 18568924) -``` - -DataFusion's `IN` list implementation was already fast, and 55 adds further -specializations selected by type and list size, including bitmap filters for -small integer types and branchless filters for small primitive lists -([#19241], [#23012], [#23014], [#23299]). Thanks to [@geoffreyclaude] for -driving this work, with contributions from [@alamb]. - ### Planner Improvements **Unified Distribution and Sorting Enforcement**: @@ -180,8 +169,8 @@ subfields the query needs. For example, given a file whose `events` column physically holds four subfields, a table might declare only two of them: ```sql --- events column is ARRAY> --- Table definition only refers to the first two subfields id, and name +-- events column is ARRAY> +-- Table definition only refers to the first two subfields, id and name CREATE EXTERNAL TABLE events ( events ARRAY> ) @@ -209,7 +198,7 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. ## Stability Improvements 🛡️ -The community also improved DataFusion's handling of larger than memory +The community also improved DataFusion's handling of larger-than-memory aggregate workloads (e.g. [#23657], [#23965], [#24061]), building on a refactoring of the aggregation path into dedicated streams ([#22729], part of epic [#22710]). Sorts under memory pressure are more resilient: when a spill @@ -227,18 +216,16 @@ DataFusion 55 adds [`file_row_index`][file_row_index] ([#22604]) and [`input_fil to expose Parquet virtual columns: ```sql -> COPY (SELECT * from values (100), (200), (300)) to '/tmp/foo.parquet'; > select *, input_file_name(), file_row_index() from '/tmp/foo.parquet'; +---------+-------------------+------------------+ | column1 | input_file_name() | file_row_index() | +---------+-------------------+------------------+ | 100 | tmp/foo.parquet | 0 | | 200 | tmp/foo.parquet | 1 | -| 300 | tmp/foo.parquet | 2 | +---------+-------------------+------------------+ ``` -Such functions are useful for building change data capture, debugging, and +Such functions are useful for change data capture, debugging, and Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work (reviving earlier work from [@jkylling]), with reviews from [@adriangb], [@comphead], and [@niebayes]. @@ -246,8 +233,8 @@ Spark-compatible workloads. Thanks to [@mbutrovich] and [@AdamGS] for this work ### Range Partitioning DataFusion 55 adds native *range partitioning* support, which maps rows to partitions by key ranges (rather than hash -values). The inputs to queries are often range partitioned in real-world scenarios such as time-series where the data -is written one file per day or hour. DataFusion uses range partitioning information to +values). Query inputs are often range partitioned in real-world scenarios, such as time-series data +written as one file per day or hour. DataFusion uses range partitioning information to avoid expensive repartitioning operations and push more specific dynamic filters to scans. @@ -466,10 +453,10 @@ object storage. Plenty left to optimize, of course. src="/blog/images/datafusion-55.0.0/clickbench.c7a.metal-48xlarge.2026_08_24.png" width="100%" class="img-fluid" -alt="DataFusion clickbench performance on c7a.metal-48xlarge" +alt="DataFusion ClickBench performance on c7a.metal-48xlarge" /> **Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]; DataFusion is the fastest engine for -processing parquet files. See the [ClickBench results page] for latest results +processing parquet files. See the [ClickBench results page] for the latest results. [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ae-&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- [ClickBench results page]: https://benchmark.clickhouse.com/ @@ -478,10 +465,10 @@ processing parquet files. See the [ClickBench results page] for latest results src="/blog/images/datafusion-55.0.0/clickbench.c6a.4xlarge.2026_08_24.png" width="100%" class="img-fluid" -alt="DataFusion clickbench performance on c6a.4xlarge" +alt="DataFusion ClickBench performance on c6a.4xlarge" /> **Figure 3**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion is the second -fastest engine for processing parquet files on this VM type. See the [ClickBench results page] for latest results +fastest engine for processing parquet files on this VM type. See the [ClickBench results page] for the latest results. [ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- @@ -538,7 +525,7 @@ end-to-end query speedups. | Improvement | Issue / PR | Representative Result | |--------------------------------------------------------------------------| --- | --- | | Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | -| "Faster `IN` list (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | +| Faster `IN` list (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | | Faster `IN` list (`Int8` / `Int16`) | [#23299](https://github.com/apache/datafusion/pull/23299) | [4.4x faster for `i16`, 4-element list](https://github.com/apache/datafusion/pull/23299#issuecomment-4875954295) *(micro)* | | Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | | Preserve dictionary encoding for trim family | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | From 2a80ac78c29bec2a8fa6fd408f69fe67cc6e1577 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 11:14:22 -0400 Subject: [PATCH 20/31] tighten --- content/blog/2026-08-16-datafusion-55.0.0.md | 144 ++++++++++--------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-16-datafusion-55.0.0.md index 71efe199..2a84fbca 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-16-datafusion-55.0.0.md @@ -32,7 +32,7 @@ highlights some of the many improvements since [DataFusion 54.0.0], such as significant performance improvements, range partitioning, `MERGE INTO` support, and runtime row-group pruning for TopK queries. The complete list of -changes is available in the [changelog]. This release represents roughly 9 weeks +changes is available in the [changelog]. This release represents roughly 10 weeks of development and 877 commits. Thanks to the [175 contributors] (a new record!) for making it possible. @@ -57,7 +57,8 @@ in DataFusion 55. In this release, we focused our optimizations on making DataFusion faster across the board rather than further optimizing our already great ClickBench numbers -(DataFusion is already the fastest on ClickBench for bare metal — see the +(DataFusion is already the fastest on partitioned Parquet files on ClickBench's +combined metric for bare metal — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no page index and contain only integer and string columns). @@ -66,11 +67,11 @@ Here is a representative sample of the performance improvements in this release. | Improvement | Issue / PR | Representative Result | Area | | --- | --- | --- | --- | | Runtime row-group pruning for TopK | [#23036], [#22450] | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | -| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | -| Prune unread Parquet leaves for nested columns | [#24090] | [1.35 TB read → 30.9 GB for a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | -| Fewer object store requests for CSV | [#22962] | [1.70x faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | -| Faster `SortPreservingMerge` tie-breaker | [#23107] | [1.08x faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | -| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), and [no longer runs out of memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | +| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | +| Prune unread Parquet leaves for nested columns | [#24090] | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | +| Fewer object store requests for CSV | [#22962] | [70% faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | +| Faster `SortPreservingMerge` tie-breaker | [#23107] | [8% faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [~5% faster: 1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), addressing a [reported out-of-memory crash](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | ### Sort Pushdown + TopK Pruning @@ -80,7 +81,7 @@ The multi-release [Sort Pushdown effort] continues to optimize `ORDER BY` and threshold tightens, the Parquet reader re-evaluates the threshold against the remaining row groups and drops those that can no longer contribute ([#22450]), and compound `ORDER BY` queries are now supported. Together these reduced -total `topk_tpch` suite runtime by 44% with no regressions; see our +total `topk_tpch` suite runtime by ~43%; see our [Optimizing for Almost Sorted Data] blog post for more details. Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. @@ -100,8 +101,9 @@ SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); ``` DataFusion 55 completes the type coverage ([#22715]) with new specializations -and a generic `Rows`-backed `GroupColumn`. The query above now runs 6% faster -on 200M UUIDs and uses much less memory (see [#23645]). Thanks to +and a generic `Rows`-backed `GroupColumn`. The query above now runs about 5% +faster on 200M UUIDs, and addresses a reported out-of-memory crash when +grouping on `FixedSizeBinary` (see [#23645]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. ### Faster Functions @@ -155,8 +157,7 @@ The optimizer continues to get faster, with improvements such as selective subquery traversal and in-place rewrites ([#22298]), collapsing chained projections ([#22389]), avoiding re-inlining expensive common subexpressions ([#23459]), and a faster `PushDownFilter` rule that modifies plans in place -([#20002], [#21668]), making planning 1.3x faster for filter-heavy queries -such as TPC-H Q19. +rather than copying them ([#20002], [#21668]). Thanks to [@adriangb], [@Dandandan], [@fordN], and [@joroKr21] for this work. ### Scan Improvements @@ -179,10 +180,11 @@ STORED AS PARQUET LOCATION 'events.parquet'; DataFusion correctly reconciles these schemas, but prior to DataFusion 55 all four leaves were read from the file and decoded, including the large `payload` -and `trace` subfields, which were then thrown away. DataFusion 55 does not -read the undeclared `payload` and `trace` leaves from the file at all, -resulting in a report from the Comet project that a production query went from -reading 1.35 TB to reading 30.9 GB ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from +and `trace` subfields, which were then thrown away. The Comet project reported +a production query where this extra decoding caused 1.35 TB of reads, where +plain Spark read only 30.9 GB for the same pruned schema. DataFusion 55 closes +that gap by not reading the undeclared `payload` and `trace` leaves from the +file at all ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from [@adriangb]. [DataFusion Comet]: https://datafusion.apache.org/comet/ @@ -443,7 +445,8 @@ can find out how to reach us on the [communication doc]. We try not to get too excited by benchmarks, though it is hard not to. As noted above, ClickBench covers only a tiny fraction of what our users actually do, and reads local files rather than object storage. Even so, DataFusion now sits at -the top of the ClickBench leaderboard for processing parquet files. Results vary +the top of the ClickBench leaderboard for processing partitioned Parquet files, +as measured by ClickBench's combined metric. Results vary slightly by engine and instance type, but DataFusion matches any other state-of-the-art engine on local files and often significantly exceeds them on object storage. Plenty left to optimize, of course. @@ -456,7 +459,7 @@ class="img-fluid" alt="DataFusion ClickBench performance on c7a.metal-48xlarge" /> **Figure 2**: [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]; DataFusion is the fastest engine for -processing parquet files. See the [ClickBench results page] for the latest results. +processing partitioned Parquet files, by ClickBench's combined metric. See the [ClickBench results page] for the latest results. [ClickBench results for c7a.metal-48xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ae-&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- [ClickBench results page]: https://benchmark.clickhouse.com/ @@ -468,7 +471,7 @@ class="img-fluid" alt="DataFusion ClickBench performance on c6a.4xlarge" /> **Figure 3**: [ClickBench results for c6a.4xlarge as of 2026-08-24]; DataFusion is the second -fastest engine for processing parquet files on this VM type. See the [ClickBench results page] for the latest results. +fastest engine for processing partitioned Parquet files (combined metric) on this VM type. See the [ClickBench results page] for the latest results. [ClickBench results for c6a.4xlarge as of 2026-08-24]: https://benchmark.clickhouse.com/#system=+hqa|curp|ti%20rud|Distt|kBP%20t|rsoP%20t|Sili|feeid|traan|Tutt&type=-&machine=+ca4e&cluster_size=-&opensource=-&hardware=+c&tuned=+n&metric=combined&queries=- @@ -489,71 +492,70 @@ for more details. The tables below list the performance improvements in this release along with a representative measurement for each. Results marked *(micro)* come from Criterion microbenchmarks and are not expected to translate directly into -end-to-end query speedups. +end-to-end query speedups. Speedups are reported as ratios of old to new +runtime: "n% faster" means the old runtime was (100+n)% of the new, and +speedups of 2x or more are reported as a multiple. ### Sort / TopK -| Improvement | Issue / PR | Representative Result | -| --- | --- | --- | -| Runtime row-group pruning for TopK | [#23036](https://github.com/apache/datafusion/issues/23036), [#22450](https://github.com/apache/datafusion/pull/22450) | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | -| " (suite-level) | " | [total `topk_tpch` runtime −44%, no regressions](https://github.com/apache/datafusion/issues/23036) | -| Faster `SortPreservingMerge` tie-breaker | [#23107](https://github.com/apache/datafusion/pull/23107) | [1.08x faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | +| Improvement | Issue / PR | Representative Result | +|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------| +| Runtime row-group pruning for TopK | [#23036](https://github.com/apache/datafusion/issues/23036), [#22450](https://github.com/apache/datafusion/pull/22450) | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | +| Faster `SortPreservingMerge` tie-breaker | [#23107](https://github.com/apache/datafusion/pull/23107) | [8% faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | ### Window Functions -| Improvement | Issue / PR | Representative Result | -| --- | --- | --- | -| `LEAD` / `LAG` with `IGNORE NULLS` | [#23711](https://github.com/apache/datafusion/pull/23711) | [21.7x faster for `List`, 10.5x for `Utf8View`](https://github.com/apache/datafusion/pull/23711) *(micro)* | -| Sliding-window `MIN` / `MAX` monotonic deques | [#23827](https://github.com/apache/datafusion/pull/23827) | [3.5x faster](https://github.com/apache/datafusion/pull/23827#issuecomment-5067789139) *(micro)* | -| Skip fully calculated window partitions | [#24127](https://github.com/apache/datafusion/pull/24127) | [33% faster with 32,768 sparse partitions](https://github.com/apache/datafusion/pull/24127) | -| Skip re-slicing quiet window partitions | [#24047](https://github.com/apache/datafusion/pull/24047) | [21% faster with 32,768 sparse partitions](https://github.com/apache/datafusion/pull/24047) | +| Improvement | Issue / PR | Representative Result | +|-----------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| `LEAD` / `LAG` with `IGNORE NULLS` | [#23711](https://github.com/apache/datafusion/pull/23711) | [21.7x faster for `List`, 10.5x for `Utf8View`](https://github.com/apache/datafusion/pull/23711) *(micro)* | +| Sliding-window `MIN` / `MAX` monotonic deques | [#23827](https://github.com/apache/datafusion/pull/23827) | [up to 3.5x faster (strings; ~2x for numerics)](https://github.com/apache/datafusion/pull/23827#issuecomment-5067789139) *(micro)* | +| Skip fully calculated window partitions | [#24127](https://github.com/apache/datafusion/pull/24127) | [49% faster with 32,768 sparse partitions: 161ms → 108ms](https://github.com/apache/datafusion/pull/24127) | +| Skip re-slicing quiet window partitions | [#24047](https://github.com/apache/datafusion/pull/24047) | [26% faster with 32,768 sparse partitions: 209ms → 166ms](https://github.com/apache/datafusion/pull/24047) | ### Aggregation -| Improvement | Issue / PR | Representative Result | -| --- | --- | --- | -| `approx_distinct` with many groups | [#22768](https://github.com/apache/datafusion/pull/22768) | [101x faster: 1723ms → 17ms (Int64, 50K groups)](https://github.com/apache/datafusion/pull/22768#issuecomment-4627539820) | -| " (end-to-end) | " | [32.6s → 0.12s, on par with DuckDB](https://github.com/apache/datafusion/pull/22768) | -| `array_agg(DISTINCT ...)` | [#23716](https://github.com/apache/datafusion/pull/23716) | [4.0x faster at high cardinality](https://github.com/apache/datafusion/pull/23716) *(micro)* | -| `percentile_cont` / `median` | [#23954](https://github.com/apache/datafusion/pull/23954) | [63% faster: 247µs → 92µs (`median`, window=256)](https://github.com/apache/datafusion/pull/23954) *(micro)* | -| Multi-column `GROUP BY` type coverage | [#22715](https://github.com/apache/datafusion/issues/22715), [#23523](https://github.com/apache/datafusion/pull/23523) | [46% less memory for mixed-schema keys: 1096KB → 594KB](https://github.com/apache/datafusion/pull/23523) | -| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645](https://github.com/apache/datafusion/issues/23645), [#23646](https://github.com/apache/datafusion/pull/23646) | [1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), and [no longer runs out of memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | -| Semi / anti join index alignment | [#22794](https://github.com/apache/datafusion/pull/22794) | [TPC-DS Q15 1.16x faster](https://github.com/apache/datafusion/pull/22794#issuecomment-4640131159) | +| Improvement | Issue / PR | Representative Result | +|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `approx_distinct` with many groups | [#22768](https://github.com/apache/datafusion/pull/22768) | [101x faster: 1723ms → 17ms (Int64, 50K groups)](https://github.com/apache/datafusion/pull/22768#issuecomment-4627539820) | +| `array_agg(DISTINCT ...)` | [#23716](https://github.com/apache/datafusion/pull/23716) | [4.0x faster at high cardinality](https://github.com/apache/datafusion/pull/23716) *(micro)* | +| `percentile_cont` / `median` | [#23954](https://github.com/apache/datafusion/pull/23954) | [2.7x faster: 247µs → 92µs (`median`, window=256)](https://github.com/apache/datafusion/pull/23954) *(micro)* | +| Multi-column `GROUP BY` type coverage | [#22715](https://github.com/apache/datafusion/issues/22715), [#23523](https://github.com/apache/datafusion/pull/23523) | [46% less memory for mixed-schema keys: 1096KB → 594KB](https://github.com/apache/datafusion/pull/23523) | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645](https://github.com/apache/datafusion/issues/23645), [#23646](https://github.com/apache/datafusion/pull/23646) | [~5% faster: 1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), addressing a [reported out-of-memory crash](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | +| Semi / anti join index alignment | [#22794](https://github.com/apache/datafusion/pull/22794) | [TPC-DS Q15 16% faster](https://github.com/apache/datafusion/pull/22794#issuecomment-4640131159) | ### Expressions and Functions -| Improvement | Issue / PR | Representative Result | -|--------------------------------------------------------------------------| --- | --- | -| Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | -| Faster `IN` list (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | +| Improvement | Issue / PR | Representative Result | +|--------------------------------------------------------------------------| --- |----------------------------------------------------------------------------------------------------------------------------| +| Faster `IN` list evaluation | [#19241](https://github.com/apache/datafusion/issues/19241), [#23014](https://github.com/apache/datafusion/pull/23014) | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014) *(micro)* | +| Faster `IN` list (`UInt8` bitmap) | [#23011](https://github.com/apache/datafusion/pull/23011) | [9.7x faster: 38.4µs → 4.0µs](https://github.com/apache/datafusion/pull/23011) *(micro)* | | Faster `IN` list (`Int8` / `Int16`) | [#23299](https://github.com/apache/datafusion/pull/23299) | [4.4x faster for `i16`, 4-element list](https://github.com/apache/datafusion/pull/23299#issuecomment-4875954295) *(micro)* | -| Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | -| Preserve dictionary encoding for trim family | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | -| Preserve dictionary encoding for (`ascii`, `bit_length`, `octet_length`) | [#23743](https://github.com/apache/datafusion/pull/23743) | [28.7x faster `ascii`](https://github.com/apache/datafusion/pull/23743) *(micro)* | -| `find_in_set` | [#23460](https://github.com/apache/datafusion/pull/23460) | [24x faster: 1.25ms → 52µs](https://github.com/apache/datafusion/pull/23460) *(micro)* | -| `array_has` with array needle | [#23337](https://github.com/apache/datafusion/pull/23337) | [15.1x faster; join query 0.95s → 0.059s](https://github.com/apache/datafusion/pull/23337) | -| `trunc` with scalar precision | [#23593](https://github.com/apache/datafusion/pull/23593) | [12.9x faster: 9.5µs → 735ns](https://github.com/apache/datafusion/pull/23593) *(micro)* | -| `encode` / hex | [#23456](https://github.com/apache/datafusion/pull/23456) | [5.2x faster: 200µs → 39.5µs](https://github.com/apache/datafusion/pull/23456) *(micro)* | -| `arrays_zip` perfect-list fast path | [#22285](https://github.com/apache/datafusion/pull/22285) | [4.2x faster](https://github.com/apache/datafusion/pull/22285#issuecomment-4537748133) *(micro)* | -| `overlay` | [#22182](https://github.com/apache/datafusion/pull/22182) | [81% faster on high-null input](https://github.com/apache/datafusion/pull/22182) *(micro)* | -| `translate` | [#22171](https://github.com/apache/datafusion/pull/22171) | [77% faster: 265µs → 60µs](https://github.com/apache/datafusion/pull/22171) *(micro)* | -| `date_trunc` | [#23542](https://github.com/apache/datafusion/pull/23542) | [76% faster for `week`](https://github.com/apache/datafusion/pull/23542) *(micro)* | -| `replace` | [#23589](https://github.com/apache/datafusion/pull/23589) | [60% faster: 228µs → 91µs](https://github.com/apache/datafusion/pull/23589) *(micro)* | -| `regexp_instr` | [#23540](https://github.com/apache/datafusion/pull/23540) | [47% faster](https://github.com/apache/datafusion/pull/23540) *(micro)* | -| `left` / `right` | [#23762](https://github.com/apache/datafusion/pull/23762) | [45% faster on `string_view` long results](https://github.com/apache/datafusion/pull/23762) *(micro)* | -| `round` | [#23471](https://github.com/apache/datafusion/pull/23471) | [44% faster](https://github.com/apache/datafusion/pull/23471) *(micro)* | -| `regexp_match` with literal pattern | [#23547](https://github.com/apache/datafusion/pull/23547) | [37% faster with literal pattern and flags](https://github.com/apache/datafusion/pull/23547) *(micro)* | -| `date_part` `isodow` | [#23491](https://github.com/apache/datafusion/pull/23491) | [38% faster](https://github.com/apache/datafusion/pull/23491#issuecomment-4962484527) *(micro)* | +| Preserve dictionary encoding for string functions | [#23930](https://github.com/apache/datafusion/pull/23930) | [360x faster `initcap` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/23930) *(micro)* | +| Preserve dictionary encoding for trim family | [#24100](https://github.com/apache/datafusion/pull/24100) | [151x faster `ltrim` on cardinality-10 dictionaries](https://github.com/apache/datafusion/pull/24100) *(micro)* | +| Preserve dictionary encoding for (`ascii`, `bit_length`, `octet_length`) | [#23743](https://github.com/apache/datafusion/pull/23743) | [28.7x faster `ascii`](https://github.com/apache/datafusion/pull/23743) *(micro)* | +| `find_in_set` | [#23460](https://github.com/apache/datafusion/pull/23460) | [24x faster: 1.25ms → 52µs](https://github.com/apache/datafusion/pull/23460) *(micro)* | +| `array_has` with array needle | [#23337](https://github.com/apache/datafusion/pull/23337) | [15.1x faster; join query 0.95s → 0.059s](https://github.com/apache/datafusion/pull/23337) | +| `trunc` with scalar precision | [#23593](https://github.com/apache/datafusion/pull/23593) | [12.9x faster: 9.5µs → 735ns](https://github.com/apache/datafusion/pull/23593) *(micro)* | +| `encode` / hex | [#23456](https://github.com/apache/datafusion/pull/23456) | [5.1x faster: 200µs → 39.5µs](https://github.com/apache/datafusion/pull/23456) *(micro)* | +| `arrays_zip` perfect-list fast path | [#22285](https://github.com/apache/datafusion/pull/22285) | [4.2x faster](https://github.com/apache/datafusion/pull/22285#issuecomment-4537748133) *(micro)* | +| `overlay` | [#22182](https://github.com/apache/datafusion/pull/22182) | [5.3x faster on high-null input: 401µs → 75µs](https://github.com/apache/datafusion/pull/22182) *(micro)* | +| `translate` | [#22171](https://github.com/apache/datafusion/pull/22171) | [4.4x faster: 265µs → 60µs](https://github.com/apache/datafusion/pull/22171) *(micro)* | +| `date_trunc` | [#23542](https://github.com/apache/datafusion/pull/23542) | [4.2x faster for `week`: 23.3µs → 5.6µs](https://github.com/apache/datafusion/pull/23542) *(micro)* | +| `replace` | [#23589](https://github.com/apache/datafusion/pull/23589) | [2.5x faster: 228µs → 91µs](https://github.com/apache/datafusion/pull/23589) *(micro)* | +| `regexp_instr` | [#23540](https://github.com/apache/datafusion/pull/23540) | [90% faster: 49.2µs → 26.0µs](https://github.com/apache/datafusion/pull/23540) *(micro)* | +| `left` / `right` | [#23762](https://github.com/apache/datafusion/pull/23762) | [81% faster on `string_view` long results: 94µs → 52µs](https://github.com/apache/datafusion/pull/23762) *(micro)* | +| `round` | [#23471](https://github.com/apache/datafusion/pull/23471) | [79% faster: 1486ns → 831ns](https://github.com/apache/datafusion/pull/23471) *(micro)* | +| `regexp_match` with literal pattern | [#23547](https://github.com/apache/datafusion/pull/23547) | [59% faster with literal pattern and flags: 280µs → 176µs](https://github.com/apache/datafusion/pull/23547) *(micro)* | +| `date_part` `isodow` | [#23491](https://github.com/apache/datafusion/pull/23491) | [reported 38% improvement](https://github.com/apache/datafusion/pull/23491#issuecomment-4962484527) *(micro)* | ### Planning | Improvement | Issue / PR | Representative Result | | --- | --- | --- | -| Collapse chained projections | [#22389](https://github.com/apache/datafusion/pull/22389) | [75% faster planning: 623ms → 155ms](https://github.com/apache/datafusion/pull/22389) | -| Skip subquery traversal, rewrite in place | [#22298](https://github.com/apache/datafusion/pull/22298) | [23% faster TPC-DS planning: 220ms → 170ms](https://github.com/apache/datafusion/pull/22298) | -| Don't re-inline CSE'd expensive expressions | [#23459](https://github.com/apache/datafusion/pull/23459) | [40% faster on repeated `power(a, 2)`](https://github.com/apache/datafusion/pull/23459) | -| Skip `ensure_distribution` rebuild for unchanged children | [#22521](https://github.com/apache/datafusion/pull/22521) | [2.87x speedup, 65% less CPU per call](https://github.com/apache/datafusion/pull/22521) | -| Fewer allocations in `PushDownFilter` | [#20002](https://github.com/apache/datafusion/issues/20002), [#21668](https://github.com/apache/datafusion/pull/21668) | [TPC-H Q19 planning 1.3x faster](https://github.com/apache/datafusion/pull/21668) | +| Collapse chained projections | [#22389](https://github.com/apache/datafusion/pull/22389) | [4.0x faster planning: 623ms → 155ms](https://github.com/apache/datafusion/pull/22389) | +| Skip subquery traversal, rewrite in place | [#22298](https://github.com/apache/datafusion/pull/22298) | [29% faster TPC-DS optimization: 220ms → 170ms](https://github.com/apache/datafusion/pull/22298) | +| Don't re-inline CSE'd expensive expressions | [#23459](https://github.com/apache/datafusion/pull/23459) | [67% faster on repeated `power(a, 2)`](https://github.com/apache/datafusion/pull/23459) | +| Skip `ensure_distribution` rebuild for unchanged children | [#22521](https://github.com/apache/datafusion/pull/22521) | [2.9x faster per call: 171µs → 59µs](https://github.com/apache/datafusion/pull/22521) | | Unified `EnsureRequirements` pass | [#21976](https://github.com/apache/datafusion/pull/21976) | [TPC-H: 8 queries faster, 0 slower](https://github.com/apache/datafusion/pull/21976#issuecomment-4521276189) | | Predicate reordering heuristic | [#22343](https://github.com/apache/datafusion/pull/22343) | [ClickBench Q21 10-13% faster](https://github.com/apache/datafusion/pull/22343#issuecomment-4483681627) | @@ -561,10 +563,10 @@ end-to-end query speedups. | Improvement | Issue / PR | Representative Result | | --- | --- | --- | -| Prune unread Parquet leaves for nested columns | [#24090](https://github.com/apache/datafusion/pull/24090) | [1.35 TB read → 30.9 GB for a production Comet query](https://github.com/apache/datafusion/pull/24090) | -| Fewer object store requests for CSV | [#22962](https://github.com/apache/datafusion/pull/22962) | [1.70x faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | -| Lower `repartition_file_min_size` to 1 MiB | [#22439](https://github.com/apache/datafusion/pull/22439) | [TPC-H Q22 1.68x faster](https://github.com/apache/datafusion/pull/22439#issuecomment-4511995613) | -| Skip page index load when the file has none | [#24149](https://github.com/apache/datafusion/issues/24149), [#24150](https://github.com/apache/datafusion/pull/24150) | [ClickBench (single file) Q1 1.38x faster](https://github.com/apache/datafusion/pull/24150#issuecomment-5226213508) | +| Prune unread Parquet leaves for nested columns | [#24090](https://github.com/apache/datafusion/pull/24090) | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | +| Fewer object store requests for CSV | [#22962](https://github.com/apache/datafusion/pull/22962) | [70% faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | +| Lower `repartition_file_min_size` to 1 MiB | [#22439](https://github.com/apache/datafusion/pull/22439) | [TPC-H Q22 68% faster](https://github.com/apache/datafusion/pull/22439#issuecomment-4511995613) | +| Skip page index load when the file has none | [#24149](https://github.com/apache/datafusion/issues/24149), [#24150](https://github.com/apache/datafusion/pull/24150) | [ClickBench (single file) Q1 38% faster](https://github.com/apache/datafusion/pull/24150#issuecomment-5226213508) | [Apache DataFusion]: https://datafusion.apache.org/ @@ -650,7 +652,7 @@ end-to-end query speedups. [appendix]: #appendix-clickbench-results [Partitioning::Range]: https://docs.rs/datafusion/55.0.0/datafusion/physical_expr/struct.RangePartitioning.html [TableProvider]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html -[TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#tymethod.merge_into +[TableProvider::merge_into]: https://docs.rs/datafusion/55.0.0/datafusion/catalog/trait.TableProvider.html#method.merge_into [SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html [TempFileFactory]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.TempFileFactory.html [RuntimeEnv]: https://docs.rs/datafusion/55.0.0/datafusion/execution/runtime_env/struct.RuntimeEnv.html From b85fb1d8064a211a33b8a204fb193515f2edea96 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 11:19:03 -0400 Subject: [PATCH 21/31] Update dates, tweak --- ...0.0.md => 2026-08-25-datafusion-55.0.0.md} | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) rename content/blog/{2026-08-16-datafusion-55.0.0.md => 2026-08-25-datafusion-55.0.0.md} (94%) diff --git a/content/blog/2026-08-16-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md similarity index 94% rename from content/blog/2026-08-16-datafusion-55.0.0.md rename to content/blog/2026-08-25-datafusion-55.0.0.md index 2a84fbca..568212df 100644 --- a/content/blog/2026-08-16-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -1,7 +1,7 @@ --- layout: post title: Apache DataFusion 55.0.0 Released -date: 2026-08-16 +date: 2026-08-25 author: pmc categories: [release] --- @@ -28,13 +28,12 @@ limitations under the License. [TOC] We are proud to announce the release of [DataFusion 55.0.0]. This post -highlights some of the many improvements since [DataFusion 54.0.0], such as -significant performance improvements, range partitioning, -`MERGE INTO` support, and runtime row-group pruning for TopK queries. The -complete list of -changes is available in the [changelog]. This release represents roughly 10 weeks -of development and 877 commits. Thanks to the [175 contributors] (a new record!) -for making it possible. +highlights some of the many improvements since [DataFusion 54.0.0], such as +significant performance increases, range partitioning, `MERGE INTO` support, and +runtime row-group pruning. The complete list of changes is available in the +[changelog]. This release represents roughly 10 weeks of development and 877 +commits. Thanks to the [175 contributors] (a new record!) for making it +possible. [DataFusion 55.0.0]: https://crates.io/crates/datafusion/55.0.0 [DataFusion 54.0.0]: https://datafusion.apache.org/blog/2026/06/12/datafusion-54.0.0/ @@ -45,13 +44,12 @@ for making it possible. src="/blog/images/datafusion-55.0.0/commits_contributors.svg" width="100%" class="img-fluid" -alt="Bar charts showing total commits, commits per day, and unique contributors for DataFusion releases 53.0.0, 54.0.0, and 55.0.0. All three measures grew in each release." +alt="Bar charts showing total commits, commits per day, and unique contributors for DataFusion releases 53.0.0, 54.0.0, and 55.0.0." /> **Figure 1**: Development activity over the last three DataFusion releases: total commits, commits per day, and unique contributors, computed from each -release's [changelog] and release dates. All three measures reached new records -in DataFusion 55. +release's [changelog] and release dates. ## Performance Improvements 🚀 @@ -60,17 +58,17 @@ the board rather than further optimizing our already great ClickBench numbers (DataFusion is already the fastest on partitioned Parquet files on ClickBench's combined metric for bare metal — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users - do (e.g. its files have no page index and contain only integer and string columns). +do (e.g. its files have no page index and contain only integer and string columns). Here is a representative sample of the performance improvements in this release. -| Improvement | Issue / PR | Representative Result | Area | -| --- | --- | --- | --- | -| Runtime row-group pruning for TopK | [#23036], [#22450] | [4.2x faster on `topk_tpch` Q8; 5 of 11 queries 3.6-4.2x faster](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | -| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | -| Prune unread Parquet leaves for nested columns | [#24090] | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | -| Fewer object store requests for CSV | [#22962] | [70% faster on TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | -| Faster `SortPreservingMerge` tie-breaker | [#23107] | [8% faster on `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | +| Improvement | Issue / PR | Representative Result | Area | +| --- | --- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | +| Runtime row-group pruning for TopK | [#23036], [#22450] | [4.2x faster `topk_tpch` Q8](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | +| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | +| Prune unread Parquet leaves for nested columns | [#24090] | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | +| Fewer object store requests for CSV | [#22962] | [70% faster TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | +| Faster `SortPreservingMerge` tie-breaker | [#23107] | [8% faster `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | | Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [~5% faster: 1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), addressing a [reported out-of-memory crash](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | @@ -102,8 +100,8 @@ SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); DataFusion 55 completes the type coverage ([#22715]) with new specializations and a generic `Rows`-backed `GroupColumn`. The query above now runs about 5% -faster on 200M UUIDs, and addresses a reported out-of-memory crash when -grouping on `FixedSizeBinary` (see [#23645]). Thanks to +faster on 200M UUIDs, and the change also addresses a reported out-of-memory +crash when grouping on `FixedSizeBinary` (see [#23645]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. ### Faster Functions @@ -178,10 +176,10 @@ CREATE EXTERNAL TABLE events ( STORED AS PARQUET LOCATION 'events.parquet'; ``` -DataFusion correctly reconciles these schemas, but prior to DataFusion 55 all +DataFusion correctly reconciles these schemas, but prior to DataFusion 55, all four leaves were read from the file and decoded, including the large `payload` and `trace` subfields, which were then thrown away. The Comet project reported -a production query where this extra decoding caused 1.35 TB of reads, where +a production query where this extra decoding caused 1.35 TB of reads, whereas plain Spark read only 30.9 GB for the same pruned schema. DataFusion 55 closes that gap by not reading the undeclared `payload` and `trace` leaves from the file at all ([#24090]). Thanks to [@mbutrovich] for this work, with reviews from @@ -265,7 +263,7 @@ effort. `MERGE INTO` (SQL:2003) is a widely used DML statement for upsert and conditional update workloads, and a key building block for table formats such as Apache Iceberg and Delta Lake. DataFusion 55 adds the logical plan types -([#20763]) along with SQL planner, physical planner, and a new +([#20763]) along with SQL planner and physical planner support, and a new [`TableProvider::merge_into`][TableProvider::merge_into] hook ([#22988]) so table implementations can execute merge operations: @@ -392,8 +390,8 @@ DataFusion 55 adds new array math functions [`array_scale`][array_scale], [`arra [`array_subtract`][array_subtract], [`array_sum`][array_sum], and [`array_avg`][array_avg], plus the higher-order [`array_first`][array_first] function. It also includes the new [`unnest_outer`][unnest_outer] function ([#22100]), which preserves -empty input as `NULL`s rather than dropping them, and -[`approx_distinct`][approx_distinct] supports more types. +empty inputs as `NULL`s rather than dropping them, and +[`approx_distinct`][approx_distinct] now supports more types. Thanks to [@crm26], [@SubhamSinghal], [@EdsonPetry], [@athlcode], and [@mkleen] for these contributions. @@ -689,24 +687,20 @@ speedups of 2x or more are reported as a multiple. [#22978]: https://github.com/apache/datafusion/pull/22978 [#22988]: https://github.com/apache/datafusion/pull/22988 [#23011]: https://github.com/apache/datafusion/pull/23011 -[#23012]: https://github.com/apache/datafusion/pull/23012 [#23014]: https://github.com/apache/datafusion/pull/23014 [#23036]: https://github.com/apache/datafusion/issues/23036 [#23066]: https://github.com/apache/datafusion/pull/23066 [#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 -[#23299]: https://github.com/apache/datafusion/pull/23299 [#23459]: https://github.com/apache/datafusion/pull/23459 [#23494]: https://github.com/apache/datafusion/issues/23494 [#23522]: https://github.com/apache/datafusion/pull/23522 -[#23523]: https://github.com/apache/datafusion/pull/23523 [#23566]: https://github.com/apache/datafusion/pull/23566 [#23645]: https://github.com/apache/datafusion/issues/23645 [#23646]: https://github.com/apache/datafusion/pull/23646 [#23657]: https://github.com/apache/datafusion/pull/23657 [#23743]: https://github.com/apache/datafusion/pull/23743 [#23930]: https://github.com/apache/datafusion/pull/23930 -[#23954]: https://github.com/apache/datafusion/pull/23954 [#23965]: https://github.com/apache/datafusion/pull/23965 [#24018]: https://github.com/apache/datafusion/pull/24018 [#24028]: https://github.com/apache/datafusion/pull/24028 From 5880b0cf9ee73331722866d426133a42d53f487f Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 11:36:04 -0400 Subject: [PATCH 22/31] obsess --- content/blog/2026-08-25-datafusion-55.0.0.md | 85 ++++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 568212df..c6e4a154 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -55,21 +55,21 @@ release's [changelog] and release dates. In this release, we focused our optimizations on making DataFusion faster across the board rather than further optimizing our already great ClickBench numbers -(DataFusion is already the fastest on partitioned Parquet files on ClickBench's -combined metric for bare metal — see the +(DataFusion is already the fastest in some cases — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no page index and contain only integer and string columns). -Here is a representative sample of the performance improvements in this release. +Here is a representative sample of the performance improvements in this release; +see the [full list in the appendix][perf appendix]. -| Improvement | Issue / PR | Representative Result | Area | -| --- | --- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- | -| Runtime row-group pruning for TopK | [#23036], [#22450] | [4.2x faster `topk_tpch` Q8](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | -| Faster `IN` list evaluation | [#19241], [#23011], [#23014] | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | -| Prune unread Parquet leaves for nested columns | [#24090] | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | -| Fewer object store requests for CSV | [#22962] | [70% faster TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | -| Faster `SortPreservingMerge` tie-breaker | [#23107] | [8% faster `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | -| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [#23645], [#23646] | [~5% faster: 1.13s → 1.07s grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), addressing a [reported out-of-memory crash](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | +| Improvement | Representative Result | Area | +|-----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| Runtime row-group pruning for TopK | [4.2x faster `topk_tpch` Q8](https://github.com/apache/datafusion/pull/22450#issuecomment-4720594338) | Sort / TopK | +| Faster `IN` list evaluation | [up to 14.7x faster for small primitive lists](https://github.com/apache/datafusion/pull/23014), [9.7x faster for `UInt8`](https://github.com/apache/datafusion/pull/23011) | Expressions | +| Prune unread Parquet leaves for nested columns | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | +| Fewer object store requests for CSV | [70% faster TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | +| Faster `SortPreservingMerge` tie-breaker | [8% faster `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [~5% faster grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), [much less memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | ### Sort Pushdown + TopK Pruning @@ -78,7 +78,7 @@ The multi-release [Sort Pushdown effort] continues to optimize `ORDER BY` and `ORDER BY ... LIMIT` (TopK) queries. In DataFusion 55, as a dynamic filter threshold tightens, the Parquet reader re-evaluates the threshold against the remaining row groups and drops those that can no longer contribute ([#22450]), -and compound `ORDER BY` queries are now supported. Together these reduced +and compound `ORDER BY` queries are now supported. Together these reduce the total `topk_tpch` suite runtime by ~43%; see our [Optimizing for Almost Sorted Data] blog post for more details. Thanks to [@zhuqi-lucas] for driving this work, with reviews from [@adriangb]. @@ -90,7 +90,7 @@ total `topk_tpch` suite runtime by ~43%; see our **Complete Multi-Column `GROUP BY` Type Coverage**: DataFusion's column-wise `GROUP BY` storage (`GroupValuesColumn`) has -type-specific fast paths, but previously any unsupported column type dragged +type-specific fast paths, but previously any unsupported column type forced the entire grouping onto a slower row-encoded fallback. For example, this query to deduplicate a table of UUIDs used to hit the slow path: @@ -98,21 +98,18 @@ query to deduplicate a table of UUIDs used to hit the slow path: SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); ``` -DataFusion 55 completes the type coverage ([#22715]) with new specializations -and a generic `Rows`-backed `GroupColumn`. The query above now runs about 5% -faster on 200M UUIDs, and the change also addresses a reported out-of-memory -crash when grouping on `FixedSizeBinary` (see [#23645]). Thanks to +DataFusion 55 completes the type coverage ([#22715]), so the query above now runs about 5% +faster on 200M UUIDs, and uses much less memory (see [#23645]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. ### Faster Functions -DataFusion ships hundreds of built-in functions, so speeding them up pays off -across many workloads. This release optimizes dozens of them — up to 24x faster +DataFusion ships hundreds of built-in functions, so speeding them up improves performance +for many workloads. This release optimizes dozens of funnctions — up to 24x faster for [`find_in_set`][find_in_set] and 100x for [`approx_distinct`][approx_distinct] with low-cardinality inputs and many groups ([#22768]). It also includes dictionary-encoding preservation for many string functions ([#23743], -[#23930], [#24100]) and new `IN` list specializations selected by type and -list size, such as bitmap filters for small integer types ([#19241]). See the +[#23930], [#24100]) and new `IN` list specializations, such as bitmap filters for small integer types ([#19241]). See the [full list in the appendix][perf appendix]. Thanks to the many contributors who drove this work, especially [@andygrove], [@geoffreyclaude], [@neilconway], [@lyne7-sc], [@theirix], and @@ -200,8 +197,8 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. The community also improved DataFusion's handling of larger-than-memory aggregate workloads (e.g. [#23657], [#23965], [#24061]), building on a -refactoring of the aggregation path into dedicated streams ([#22729], part of -epic [#22710]). Sorts under memory pressure are more resilient: when a spill +refactoring of the aggregation path into dedicated streams (epic [#22710]). +Sorts under memory pressure are more resilient: when a spill merge cannot reserve enough memory, DataFusion now re-spills the largest stream in smaller batches rather than failing ([#22945]), and caps the merge fan-in to bound memory use ([#23066]). Thanks to [@2010YOUY01], [@EmilyMatt], @@ -305,8 +302,8 @@ let runtime = RuntimeEnvBuilder::new() let ctx = SessionContext::new_with_config_rt(SessionConfig::new(), runtime); ``` -See the [`object_store_spill.rs`] example for a complete implementation that -spills to an [`ObjectStore`] such as S3. Thanks to [@pantShrey] for this work, +See the [`object_store_spill.rs`][object_store_spill.rs] example for a complete implementation that +spills to an [`ObjectStore`][ObjectStore] such as S3. Thanks to [@pantShrey] for this work, with reviews from [@alamb]. ### Extensibility for Distributed Engines @@ -316,18 +313,17 @@ Several new APIs make it easier to build distributed systems such as top of DataFusion: - **Dynamic filter propagation across network boundaries**: new - `ExecutionPlan::apply_expressions` and - `ExecutionPlan::dynamic_expressions_produced` methods let engines discover + [`ExecutionPlan::apply_expressions`][ExecutionPlan::apply_expressions] and + [`ExecutionPlan::dynamic_expressions_produced`][ExecutionPlan::dynamic_expressions_produced] methods let engines discover which plan nodes produce dynamic filters and re-wire them across stage boundaries ([#24018], [#24068]). Thanks to [@jayshrivastava]. - **`FFI_QueryPlanner`**: foreign libraries can now provide a custom query planner over the FFI boundary — for example, connecting a distributed - planner to a `SessionContext` in Python ([#24028]). Thanks to [@timsaucer]. -- **Self-serializing execution plans**: built-in `ExecutionPlan`s were ported - to per-type `try_to_proto` / `try_from_proto` hooks ([#23494]), eliminating - the central downcast chain in `datafusion-proto` and putting built-in and + planner to a [`SessionContext`][SessionContext] in Python ([#24028]). Thanks to [@timsaucer]. +- **Self-serializing execution plans**: built-in [`ExecutionPlan`][ExecutionPlan]s were ported + to per-type [`try_to_proto`][try_to_proto] / [`try_from_proto`][try_from_proto] hooks ([#23494]), putting built-in and third-party plans on the same code path. Thanks to [@adriangb]. -- **Window accumulator state access**: `BoundedWindowAggExec` can now expose +- **Window accumulator state access**: [`BoundedWindowAggExec`][BoundedWindowAggExec] can now expose finalized accumulator state to an observer callback, enabling incremental / prefix-scan use cases ([#24035]). Thanks to [@avantgardnerio], with reviews from [@alamb] and [@timsaucer]. @@ -335,13 +331,22 @@ top of DataFusion: [datafusion-distributed]: https://github.com/datafusion-contrib/datafusion-distributed [DataFusion Ballista]: https://datafusion.apache.org/ballista/ [DataFusion Python]: https://datafusion.apache.org/python/ +[ExecutionPlan::apply_expressions]: https://docs.rs/datafusion/55.0.0/datafusion/physical_plan/trait.ExecutionPlan.html#tymethod.apply_expressions +[ExecutionPlan::dynamic_expressions_produced]: https://docs.rs/datafusion/55.0.0/datafusion/physical_plan/trait.ExecutionPlan.html#method.dynamic_expressions_produced +[SessionContext]: https://docs.rs/datafusion/55.0.0/datafusion/execution/context/struct.SessionContext.html +[ExecutionPlan]: https://docs.rs/datafusion/55.0.0/datafusion/physical_plan/trait.ExecutionPlan.html +[try_to_proto]: https://docs.rs/datafusion-physical-plan/55.0.0/datafusion_physical_plan/filter/struct.FilterExec.html#method.try_to_proto +[try_from_proto]: https://docs.rs/datafusion-physical-plan/55.0.0/datafusion_physical_plan/filter/struct.FilterExec.html#method.try_from_proto +[BoundedWindowAggExec]: https://docs.rs/datafusion/55.0.0/datafusion/physical_plan/windows/struct.BoundedWindowAggExec.html ### `EXPLAIN` Improvements DataFusion 55 adds a Postgres-style `EXPLAIN (...)` option list ([#21768]) and -a `pgjson` output format for `EXPLAIN ANALYZE` ([#21767]), making plan output +a `pgjson` output format for [`EXPLAIN ANALYZE`][EXPLAIN ANALYZE] ([#21767]), making plan output easier to consume with existing Postgres tooling such as plan visualizers. +[EXPLAIN ANALYZE]: https://datafusion.apache.org/user-guide/explain-usage.html#execution-counters-explain-analyze + `EXPLAIN ANALYZE` can produce many metrics, and you can narrow them down explicitly with the `METRICS` option. For example, to see only row counts for each plan node, use this: @@ -356,7 +361,7 @@ AggregateExec: mode=Single, gby=[region_id@0 as region_id], aggr=[sum(orders.amo Adding `FORMAT pgjson` renders the same physical plan and its live metrics as Postgres-compatible JSON, which can be pasted straight into plan visualizers -such as [Dalibo] or PEV2: +such as [Dalibo]: ```sql > EXPLAIN (ANALYZE, FORMAT pgjson, METRICS 'rows', LEVEL summary) @@ -381,6 +386,7 @@ See the [EXPLAIN usage guide] for the full option list. Thanks to [@adriangb] for this work. [Dalibo]: https://explain.dalibo.com/ +[PEV2]: https://github.com/dalibo/pev2 [EXPLAIN usage guide]: https://datafusion.apache.org/user-guide/explain-usage.html ### New Functions @@ -654,8 +660,8 @@ speedups of 2x or more are reported as a multiple. [SpillFile]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.SpillFile.html [TempFileFactory]: https://docs.rs/datafusion/55.0.0/datafusion/execution/spill_file/trait.TempFileFactory.html [RuntimeEnv]: https://docs.rs/datafusion/55.0.0/datafusion/execution/runtime_env/struct.RuntimeEnv.html -[`object_store_spill.rs`]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs -[`ObjectStore`]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html +[object_store_spill.rs]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs +[ObjectStore]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html [#19241]: https://github.com/apache/datafusion/issues/19241 [#20002]: https://github.com/apache/datafusion/issues/20002 @@ -679,25 +685,18 @@ speedups of 2x or more are reported as a multiple. [#22652]: https://github.com/apache/datafusion/pull/22652 [#22715]: https://github.com/apache/datafusion/issues/22715 [#22710]: https://github.com/apache/datafusion/issues/22710 -[#22729]: https://github.com/apache/datafusion/pull/22729 [#22768]: https://github.com/apache/datafusion/pull/22768 [#22940]: https://github.com/apache/datafusion/pull/22940 [#22945]: https://github.com/apache/datafusion/pull/22945 -[#22962]: https://github.com/apache/datafusion/pull/22962 [#22978]: https://github.com/apache/datafusion/pull/22978 [#22988]: https://github.com/apache/datafusion/pull/22988 -[#23011]: https://github.com/apache/datafusion/pull/23011 -[#23014]: https://github.com/apache/datafusion/pull/23014 -[#23036]: https://github.com/apache/datafusion/issues/23036 [#23066]: https://github.com/apache/datafusion/pull/23066 -[#23107]: https://github.com/apache/datafusion/pull/23107 [#23148]: https://github.com/apache/datafusion/pull/23148 [#23459]: https://github.com/apache/datafusion/pull/23459 [#23494]: https://github.com/apache/datafusion/issues/23494 [#23522]: https://github.com/apache/datafusion/pull/23522 [#23566]: https://github.com/apache/datafusion/pull/23566 [#23645]: https://github.com/apache/datafusion/issues/23645 -[#23646]: https://github.com/apache/datafusion/pull/23646 [#23657]: https://github.com/apache/datafusion/pull/23657 [#23743]: https://github.com/apache/datafusion/pull/23743 [#23930]: https://github.com/apache/datafusion/pull/23930 From 4b98f880c38bc109f9cac674bf28d4853883c098 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 11:38:43 -0400 Subject: [PATCH 23/31] bencmaxx --- content/blog/2026-08-25-datafusion-55.0.0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index c6e4a154..6e41e8c0 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -446,7 +446,8 @@ can find out how to reach us on the [communication doc]. # Appendix: ClickBench Results -We try not to get too excited by benchmarks, though it is hard not to. As noted +We try not to get too excited by benchmarks, though it is hard not to get caught up +in Benchmaxxing. As noted above, ClickBench covers only a tiny fraction of what our users actually do, and reads local files rather than object storage. Even so, DataFusion now sits at the top of the ClickBench leaderboard for processing partitioned Parquet files, From a740ff64af069f221212a4b778aa981e397d377e Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 11:46:54 -0400 Subject: [PATCH 24/31] obsess --- content/blog/2026-08-25-datafusion-55.0.0.md | 21 ++++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 6e41e8c0..a4531199 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -49,13 +49,13 @@ alt="Bar charts showing total commits, commits per day, and unique contributors **Figure 1**: Development activity over the last three DataFusion releases: total commits, commits per day, and unique contributors, computed from each -release's [changelog] and release dates. +release's [changelog] and release dates. ## Performance Improvements 🚀 In this release, we focused our optimizations on making DataFusion faster across the board rather than further optimizing our already great ClickBench numbers -(DataFusion is already the fastest in some cases — see the +(DataFusion is already the fastest in some cases — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no page index and contain only integer and string columns). @@ -69,7 +69,7 @@ see the [full list in the appendix][perf appendix]. | Prune unread Parquet leaves for nested columns | [Reduces reads from 1.35 TB to 30.9 GB in a production Comet query](https://github.com/apache/datafusion/pull/24090) | Scan / IO | | Fewer object store requests for CSV | [70% faster TPC-H CSV with simulated latency](https://github.com/apache/datafusion/pull/22962#issuecomment-4721729807) | Scan / IO | | Faster `SortPreservingMerge` tie-breaker | [8% faster `sort_tpch` Q6](https://github.com/apache/datafusion/pull/23107#issuecomment-4776877963) | Sorting | -| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [~5% faster grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), [much less memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | +| Native `GROUP BY` on `FixedSizeBinary` (e.g. UUIDs) | [~5% faster grouping 200M UUIDs](https://github.com/apache/datafusion/pull/23646#pullrequestreview-4900163566), [much less memory](https://github.com/apache/datafusion/pull/23646#issuecomment-4996436765) | Aggregation | ### Sort Pushdown + TopK Pruning @@ -99,13 +99,13 @@ SELECT count(*) FROM (SELECT uuid, id FROM 'uuids.parquet' GROUP BY uuid, id); ``` DataFusion 55 completes the type coverage ([#22715]), so the query above now runs about 5% -faster on 200M UUIDs, and uses much less memory (see [#23645]). Thanks to +faster on 200M UUIDs, and uses much less memory (see [#23645]). Thanks to [@zhuqi-lucas], [@tohuya6], and [@maxburke] for this work. ### Faster Functions DataFusion ships hundreds of built-in functions, so speeding them up improves performance -for many workloads. This release optimizes dozens of funnctions — up to 24x faster +for many workloads. This release optimizes dozens of functions — up to 24x faster for [`find_in_set`][find_in_set] and 100x for [`approx_distinct`][approx_distinct] with low-cardinality inputs and many groups ([#22768]). It also includes dictionary-encoding preservation for many string functions ([#23743], @@ -197,7 +197,7 @@ Thanks to [@alamb], [@haohuaijin], and [@adriangb]. The community also improved DataFusion's handling of larger-than-memory aggregate workloads (e.g. [#23657], [#23965], [#24061]), building on a -refactoring of the aggregation path into dedicated streams (epic [#22710]). +refactoring of the aggregation path into dedicated streams (epic [#22710]). Sorts under memory pressure are more resilient: when a spill merge cannot reserve enough memory, DataFusion now re-spills the largest stream in smaller batches rather than failing ([#22945]), and caps the merge fan-in to @@ -386,7 +386,6 @@ See the [EXPLAIN usage guide] for the full option list. Thanks to [@adriangb] for this work. [Dalibo]: https://explain.dalibo.com/ -[PEV2]: https://github.com/dalibo/pev2 [EXPLAIN usage guide]: https://datafusion.apache.org/user-guide/explain-usage.html ### New Functions @@ -444,7 +443,7 @@ contribute suggestions, documentation, bug reports, or a PR with documentation, tests, or code. A list of open issues suitable for beginners is [here], and you can find out how to reach us on the [communication doc]. -# Appendix: ClickBench Results +## Appendix: ClickBench Results We try not to get too excited by benchmarks, though it is hard not to get caught up in Benchmaxxing. As noted @@ -452,8 +451,8 @@ above, ClickBench covers only a tiny fraction of what our users actually do, and reads local files rather than object storage. Even so, DataFusion now sits at the top of the ClickBench leaderboard for processing partitioned Parquet files, as measured by ClickBench's combined metric. Results vary -slightly by engine and instance type, but DataFusion matches any other -state-of-the-art engine on local files and often significantly exceeds them on +slightly by engine and instance type, but DataFusion matches other +state-of-the-art engines on local files and often significantly exceeds them on object storage. Plenty left to optimize, of course. @@ -492,7 +491,7 @@ Query times are normalized using the ClickBench definition. See the [DataFusion Benchmarking Page](https://alamb.github.io/datafusion-benchmarking/) for more details. -# Appendix: Full List of Performance Improvements +## Appendix: Full List of Performance Improvements The tables below list the performance improvements in this release along with a representative measurement for each. Results marked *(micro)* come from From 1aed4ff093c86993605e454bc896ba13870d9871 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 12:16:15 -0400 Subject: [PATCH 25/31] mention strictly order preserving --- content/blog/2026-08-25-datafusion-55.0.0.md | 22 +++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index a4531199..9a4c42f5 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -139,13 +139,17 @@ intermediate projections in outer join elimination ([#22534]), and reorders predicates in conjunctions using a cost heuristic ([#22343]). Thanks to [@neilconway] and [@simonvandel] for driving this work. -**Scalar UDF Strictness Metadata**: -Scalar UDFs can now declare that they are *strict* (they return `NULL` when any -input is `NULL`) ([#23148]). The optimizer uses this metadata to prove that -filters reject `NULL`s, unlocking outer join elimination for queries that -filter on the result of a function call. -Thanks to [@lyne7-sc] for implementing this feature, with reviews from [@alamb] -and [@kosiew]. +**Better Scalar UDF Metadata APIs**: +New APIs let Scalar UDFs describe more of their behavior to the optimizer. +UDFs can now declare that they are *strict* (they return `NULL` when any input +is `NULL`) ([#23148]), which the optimizer uses to prove that filters reject +`NULL`s, unlocking outer join elimination for queries that filter on the result +of a function call. UDFs can also declare that they are *strictly order +preserving* via [`ScalarUDFImpl::strictly_order_preserving`][strictly_order_preserving] ([#23807]): if the +input is sorted, the output is sorted the same way, so data ordered by `col` is +also ordered by `f(col)` and redundant sorts can be eliminated. +Thanks to [@lyne7-sc] and [@rluvaton] for implementing these features, with +reviews from [@alamb], [@kosiew], and [@getChan]. **Faster Optimizer**: The optimizer continues to get faster, with improvements such as selective @@ -605,6 +609,7 @@ speedups of 2x or more are reported as a multiple. [@crm26]: https://github.com/crm26 [@davidlghellin]: https://github.com/davidlghellin [@fordN]: https://github.com/fordN +[@getChan]: https://github.com/getChan [@gene-bordegaray]: https://github.com/gene-bordegaray [@geoffreyclaude]: https://github.com/geoffreyclaude [@gmhelmold]: https://github.com/gmhelmold @@ -625,6 +630,7 @@ speedups of 2x or more are reported as a multiple. [@pantShrey]: https://github.com/pantShrey [@pepijnve]: https://github.com/pepijnve [@peterxcli]: https://github.com/peterxcli +[@rluvaton]: https://github.com/rluvaton [@saadtajwar]: https://github.com/saadtajwar [@simonvandel]: https://github.com/simonvandel [@sjhddh]: https://github.com/sjhddh @@ -662,6 +668,7 @@ speedups of 2x or more are reported as a multiple. [RuntimeEnv]: https://docs.rs/datafusion/55.0.0/datafusion/execution/runtime_env/struct.RuntimeEnv.html [object_store_spill.rs]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs [ObjectStore]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html +[strictly_order_preserving]: https://docs.rs/datafusion/55.0.0/datafusion/logical_expr/trait.ScalarUDFImpl.html#method.strictly_order_preserving [#19241]: https://github.com/apache/datafusion/issues/19241 [#20002]: https://github.com/apache/datafusion/issues/20002 @@ -699,6 +706,7 @@ speedups of 2x or more are reported as a multiple. [#23645]: https://github.com/apache/datafusion/issues/23645 [#23657]: https://github.com/apache/datafusion/pull/23657 [#23743]: https://github.com/apache/datafusion/pull/23743 +[#23807]: https://github.com/apache/datafusion/pull/23807 [#23930]: https://github.com/apache/datafusion/pull/23930 [#23965]: https://github.com/apache/datafusion/pull/23965 [#24018]: https://github.com/apache/datafusion/pull/24018 From f1dc80f61546b7b00191f9039b3b4f3088709193 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 12:18:17 -0400 Subject: [PATCH 26/31] Shorten --- content/blog/2026-08-25-datafusion-55.0.0.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 9a4c42f5..1c4d45fc 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -140,16 +140,13 @@ predicates in conjunctions using a cost heuristic ([#22343]). Thanks to [@neilconway] and [@simonvandel] for driving this work. **Better Scalar UDF Metadata APIs**: -New APIs let Scalar UDFs describe more of their behavior to the optimizer. -UDFs can now declare that they are *strict* (they return `NULL` when any input -is `NULL`) ([#23148]), which the optimizer uses to prove that filters reject -`NULL`s, unlocking outer join elimination for queries that filter on the result -of a function call. UDFs can also declare that they are *strictly order -preserving* via [`ScalarUDFImpl::strictly_order_preserving`][strictly_order_preserving] ([#23807]): if the -input is sorted, the output is sorted the same way, so data ordered by `col` is -also ordered by `f(col)` and redundant sorts can be eliminated. -Thanks to [@lyne7-sc] and [@rluvaton] for implementing these features, with -reviews from [@alamb], [@kosiew], and [@getChan]. +Scalar UDFs can now declare that they are *strict* (they return `NULL` when any +input is `NULL`) ([#23148]), letting the optimizer eliminate outer joins for +queries that filter on a function result, and *strictly order preserving* +(sorted input yields identically sorted output) ([#23807]), letting the +optimizer eliminate redundant sorts on expressions such as custom casts. +Thanks to [@lyne7-sc] and [@rluvaton] for this work, with reviews from +[@alamb], [@kosiew], and [@getChan]. **Faster Optimizer**: The optimizer continues to get faster, with improvements such as selective @@ -668,7 +665,6 @@ speedups of 2x or more are reported as a multiple. [RuntimeEnv]: https://docs.rs/datafusion/55.0.0/datafusion/execution/runtime_env/struct.RuntimeEnv.html [object_store_spill.rs]: https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/data_io/object_store_spill.rs [ObjectStore]: https://docs.rs/object_store/latest/object_store/trait.ObjectStore.html -[strictly_order_preserving]: https://docs.rs/datafusion/55.0.0/datafusion/logical_expr/trait.ScalarUDFImpl.html#method.strictly_order_preserving [#19241]: https://github.com/apache/datafusion/issues/19241 [#20002]: https://github.com/apache/datafusion/issues/20002 From 0170e87226d24790bc6bf31f7b8bb1fcd025e248 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 17:29:14 -0400 Subject: [PATCH 27/31] Update content/blog/2026-08-25-datafusion-55.0.0.md Co-authored-by: Stu Hood --- content/blog/2026-08-25-datafusion-55.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 1c4d45fc..05298901 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -54,7 +54,7 @@ release's [changelog] and release dates. ## Performance Improvements 🚀 In this release, we focused our optimizations on making DataFusion faster across -the board rather than further optimizing our already great ClickBench numbers +the board rather than further optimizing our already very satisfying ClickBench numbers (DataFusion is already the fastest in some cases — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no page index and contain only integer and string columns). From 024399ba3cd6bfff97f90fed0c368165c098cca5 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Tue, 25 Aug 2026 17:31:04 -0400 Subject: [PATCH 28/31] Add reference to ballista use --- content/blog/2026-08-25-datafusion-55.0.0.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 05298901..afbc1072 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -326,8 +326,8 @@ top of DataFusion: third-party plans on the same code path. Thanks to [@adriangb]. - **Window accumulator state access**: [`BoundedWindowAggExec`][BoundedWindowAggExec] can now expose finalized accumulator state to an observer callback, enabling incremental / - prefix-scan use cases ([#24035]). Thanks to [@avantgardnerio], with reviews - from [@alamb] and [@timsaucer]. + prefix-scan use cases ([#24035]; see [how it is used in Ballista]). Thanks to + [@avantgardnerio], with reviews from [@alamb] and [@timsaucer]. [datafusion-distributed]: https://github.com/datafusion-contrib/datafusion-distributed [DataFusion Ballista]: https://datafusion.apache.org/ballista/ @@ -339,6 +339,7 @@ top of DataFusion: [try_to_proto]: https://docs.rs/datafusion-physical-plan/55.0.0/datafusion_physical_plan/filter/struct.FilterExec.html#method.try_to_proto [try_from_proto]: https://docs.rs/datafusion-physical-plan/55.0.0/datafusion_physical_plan/filter/struct.FilterExec.html#method.try_from_proto [BoundedWindowAggExec]: https://docs.rs/datafusion/55.0.0/datafusion/physical_plan/windows/struct.BoundedWindowAggExec.html +[how it is used in Ballista]: https://github.com/apache/datafusion-ballista/pull/2211 ### `EXPLAIN` Improvements From 0c1af5763044a8f6a7a6bd625319bf41713efd49 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Wed, 26 Aug 2026 17:08:58 -0400 Subject: [PATCH 29/31] Update content/blog/2026-08-25-datafusion-55.0.0.md Co-authored-by: Qi Zhu <821684824@qq.com> --- content/blog/2026-08-25-datafusion-55.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index afbc1072..82373f52 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -54,7 +54,7 @@ release's [changelog] and release dates. ## Performance Improvements 🚀 In this release, we focused our optimizations on making DataFusion faster across -the board rather than further optimizing our already very satisfying ClickBench numbers +the board rather than further optimizing our very satisfying ClickBench numbers (DataFusion is already the fastest in some cases — see the [appendix]), as ClickBench represents only a tiny fraction of what our actual users do (e.g. its files have no page index and contain only integer and string columns). From 6b8dfb86f62940660ededdf4dc9b18898e58572b Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Wed, 26 Aug 2026 17:09:09 -0400 Subject: [PATCH 30/31] Update content/blog/2026-08-25-datafusion-55.0.0.md Co-authored-by: Qi Zhu <821684824@qq.com> --- content/blog/2026-08-25-datafusion-55.0.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index 82373f52..7791f9c8 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -583,7 +583,7 @@ speedups of 2x or more are reported as a multiple. [Python library]: https://datafusion.apache.org/python/ [command-line SQL tool]: https://datafusion.apache.org/user-guide/cli/ [Upgrade Guide]: https://datafusion.apache.org/library-user-guide/upgrading/55.0.0.html -[here]: https://github.com/apache/arrow-datafusion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 +[here]: https://github.com/apache/datafusion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 [communication doc]: https://datafusion.apache.org/contributor-guide/communication.html [@2010YOUY01]: https://github.com/2010YOUY01 From 00cc646ecdf05d38d66d02aa53604c6d0bc27cb8 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Thu, 27 Aug 2026 09:43:07 -0400 Subject: [PATCH 31/31] Mention more merge is planned --- content/blog/2026-08-25-datafusion-55.0.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/blog/2026-08-25-datafusion-55.0.0.md b/content/blog/2026-08-25-datafusion-55.0.0.md index afbc1072..544172a4 100644 --- a/content/blog/2026-08-25-datafusion-55.0.0.md +++ b/content/blog/2026-08-25-datafusion-55.0.0.md @@ -276,7 +276,8 @@ WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name); Built-in table providers do not yet implement the hook, but custom [`TableProvider`][TableProvider] implementations (such as lakehouse table formats) can now plug -in their own merge execution. +in their own merge execution. More `MERGE INTO` improvements are planned for +upcoming releases; see [#20746] for details. Thanks to [@wirybeaver] for implementing this feature, with reviews from [@alamb] and [@kosiew]. @@ -669,6 +670,7 @@ speedups of 2x or more are reported as a multiple. [#19241]: https://github.com/apache/datafusion/issues/19241 [#20002]: https://github.com/apache/datafusion/issues/20002 +[#20746]: https://github.com/apache/datafusion/issues/20746 [#20763]: https://github.com/apache/datafusion/pull/20763 [#21668]: https://github.com/apache/datafusion/pull/21668 [#21768]: https://github.com/apache/datafusion/pull/21768