Two years ago, we introduced Sail, our open-source drop-in replacement for Apache Spark written in Rust. At the time, it ran nearly 4x faster than Spark in our first published benchmark. We have now rerun the derived TPC-H benchmark on the latest versions of both engines. This time, Sail completes all 22 queries in 52.8 seconds, while Spark takes 534.8 seconds on the same machine, a 10x speed-up.
The number we watch most closely is Sail’s own. In 2024, Sail finished this workload in 102.8 seconds. The engine work since then has cut that time roughly in half. Not all of that work happened in our repository: Sail builds on Apache DataFusion and Apache Arrow, and two years of improvements from those communities are in these numbers too. A huge thank you to the contributors of both projects!
Benchmark Results
Each of the 22 queries was run once and timed end to end, with memory and disk activity sampled across the whole run. The chart and table below give the totals for each engine: wall-clock time for the full suite, peak memory, and bytes written to disk. The two charts after them break the same run down query by query, first as raw times and then as the speed-up between the engines. The full setup and the resource utilization charts can be found in the appendix.
| Metric | Spark | Sail |
|---|---|---|
| Total Query Time | 534.78 seconds | 52.81 seconds |
| Query Speed-Up | 1x (baseline) | 2.8x to 29.2x |
| Peak Memory Usage | 72 GB | 26 GB |
| Disk Write (Shuffle Spill) | 115 GB | 0 GB |
Sail is faster on all 22 queries. Twelve run 10x faster or better, nineteen run at least 5x faster, and the smallest improvement in the suite is 2.8x (q19). The largest is 29.2x (q16), where Spark takes 13.5 seconds and Sail takes under half a second. Across the run, Sail wrote nothing to disk while Spark wrote 115 GB, and Sail peaked at roughly a third of Spark’s memory.
The largest gains cluster among multi-join queries: q16, q4, q11, q22, and q2 all run 18x faster or better. These are the plans where join order matters most. The smallest gains are on scan- and filter-heavy queries such as q6 and q19, where both engines spend most of their time reading Parquet files and there is less for an optimizer to do.
Why Sail Is Blazing Fast
Sail’s speed starts with how data is laid out and processed. Data stays in the Arrow columnar format from the scan through to the result, and the operators working on it are compiled Rust code that processes whole arrays of values at once. The columnar layout is what makes that possible: values of the same type sit together in memory, so the CPU streams them through cache efficiently and SIMD instructions process multiple records per cycle. Spark reads Parquet files into columnar batches too, but converts them to rows to execute. The gain here comes from keeping the columnar memory layout end to end.
Memory management is the second piece. Rust manages memory deterministically, so no garbage collector decides when to reclaim it and no collection pauses interleave with compute. A query takes the memory it needs and releases it when it finishes, which is the shape of Sail’s resource chart: a 26 GB peak that falls back between queries rather than a heap held for the length of the run. We wrote about the runtime trade-offs in more depth in Rust vs. the JVM.
The third is what happens between operators. Sail passes Arrow batches directly by sharing pointers within the same address space, so intermediate results never have to be written down and read back at a stage boundary. That is why the entire benchmark completed without writing anything to disk. Spark always persists shuffle data, even in local mode. Sail supports both streaming and blocking shuffle for scalability, but you don’t have to pay the data persistence cost if it’s just an ad hoc query that completes within a few seconds.
Spark accelerators such as Photon, Comet, and Velox introduce native operators in Spark’s existing runtime to speed up query execution. But the JVM stays in the execution path for everything they do not cover, so the performance gain has a ceiling. Sail replaces the runtime entirely and outperforms both Spark and the popular Spark accelerators. You can see the differences on ClickBench, where results are refreshed regularly.
Getting Started
Sail 0.7 is available now. Install the pysail package and point your existing PySpark code at a Sail server. The Getting Started guide covers the setup. If you run any other benchmarks yourself, we would love to hear about your results! You can share your findings on GitHub or by joining our Slack Community.
Managed Sail in Your Cloud
Want to run Sail with managed infrastructure? The LakeSail Platform offers fully managed Sail with built-in governance, observability, BYOC deployment, and enterprise controls. Get started with a free trial and see how it can improve performance and reduce costs for your team.
Appendix
A. Benchmark Setup
TPC-H is an industry-standard analytics benchmark: 22 SQL queries over a wholesale-supplier dataset, covering scans, joins, aggregations, and subqueries. We run a derived version using data of the standard schema and the same queries, timed directly but without the official TPC audit. These are not official TPC-H results.
- Dataset: derived TPC-H, scale factor 100 (100 GB of raw data; about 39 GB stored as Parquet)
- Data generation: tpchgen-cli, 64 partitions per table
- Hardware: AWS EC2
r8g.4xlarge(16 vCPU, 128 GB memory) - Disk: separate EBS volumes for data and temporary files (4,000 IOPS, 1,000 MiB/s throughput)
- Versions: Sail 0.7.0 and Spark 4.2.0
- Configuration: Spark in local mode (
local[*]) with 128 GB driver memory and temporary files on the dedicated volume; Sail as a Spark Connect server - Timing: one timed run per query
- Metrics: AWS CloudWatch at 1-second resolution
Here are two methodology notes.
- Sail ran with the
optimizer.enable_join_reorderoption turned on. This option is currently marked experimental, and we plan to enable it by default once the optimizer implementation stabilizes. - We switched data generators since 2024, from
dbgenplus Parquet conversion totpchgen-cli, so year-over-year comparisons are approximate rather than exact. Spark’s total is higher here than in the 2024 run. The dataset change could be the reason, though we have not isolated the cause. We observed similar timings from Spark 3.5 and 4.2 on this dataset, so the Spark version does not explain the difference. Within this run, both engines ran the same queries on the same data and hardware.
Credit: as in 2024, we conducted the experiments with the help of the DataFusion benchmark scripts.
B. Resource Utilization
Spark peaked at 72 GB of memory and wrote 115 GB to the volume provisioned for temporary files over the course of the run. Between queries, its resident memory stayed in the tens of gigabytes.
Sail peaked at 26 GB, released memory after each query, and wrote nothing to disk.
Both charts are plotted from AWS CloudWatch metrics at 1-second resolution, with query start times marked.