Benchmarks

10x Faster Than Spark: TPC-H Benchmark, Two Years Later

Sail now completes the derived TPC-H SF100 benchmark 2x faster than itself two years ago, and 10x faster than the latest Spark version.

6 min read Sep 2026

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.

Total query time, all 22 queries
0100200300400500600Spark534.8 sSail52.8 sTotal query time (seconds)
MetricSparkSail
Total Query Time534.78 seconds52.81 seconds
Query Speed-Up1x (baseline)2.8x to 29.2x
Peak Memory Usage72 GB26 GB
Disk Write (Shuffle Spill)115 GB0 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.

Execution time per query
020406080Query time (seconds)SparkSailHover a query for exact timingsq1q2q3q4q5q6q7q8q9q10q11q12q13q14q15q16q17q18q19q20q21q22q1: Spark 52.25 s, Sail 3.50 s, 14.9x fasterq2: Spark 11.92 s, Sail 0.66 s, 18.1x fasterq3: Spark 20.65 s, Sail 1.90 s, 10.9x fasterq4: Spark 19.05 s, Sail 0.91 s, 20.9x fasterq5: Spark 37.91 s, Sail 2.80 s, 13.5x fasterq6: Spark 2.93 s, Sail 1.01 s, 2.9x fasterq7: Spark 19.29 s, Sail 2.35 s, 8.2x fasterq8: Spark 31.80 s, Sail 3.10 s, 10.2x fasterq9: Spark 55.98 s, Sail 4.31 s, 13.0x fasterq10: Spark 14.71 s, Sail 2.21 s, 6.6x fasterq11: Spark 10.01 s, Sail 0.49 s, 20.3x fasterq12: Spark 9.58 s, Sail 1.78 s, 5.4x fasterq13: Spark 17.51 s, Sail 2.40 s, 7.3x fasterq14: Spark 5.64 s, Sail 1.27 s, 4.4x fasterq15: Spark 13.68 s, Sail 2.35 s, 5.8x fasterq16: Spark 13.47 s, Sail 0.46 s, 29.2x fasterq17: Spark 70.43 s, Sail 5.24 s, 13.4x fasterq18: Spark 50.76 s, Sail 6.53 s, 7.8x fasterq19: Spark 5.97 s, Sail 2.16 s, 2.8x fasterq20: Spark 10.14 s, Sail 2.02 s, 5.0x fasterq21: Spark 52.37 s, Sail 4.89 s, 10.7x fasterq22: Spark 8.76 s, Sail 0.47 s, 18.8x faster
Speed-up per query, sorted
0x10x20x30xSpeed-up vs Sparkq16q4q11q22q2q1q5q17q9q3q21q8q7q18q13q10q15q12q20q14q6q1929.2x20.9x20.3x18.8x18.1x14.9x13.5x13.4x13.0x10.9x10.7x10.2x8.2x7.8x7.3x6.6x5.8x5.4x5.0x4.4x2.9x2.8xq16: 29.2x faster (Spark 13.47 s, Sail 0.46 s)q4: 20.9x faster (Spark 19.05 s, Sail 0.91 s)q11: 20.3x faster (Spark 10.01 s, Sail 0.49 s)q22: 18.8x faster (Spark 8.76 s, Sail 0.47 s)q2: 18.1x faster (Spark 11.92 s, Sail 0.66 s)q1: 14.9x faster (Spark 52.25 s, Sail 3.50 s)q5: 13.5x faster (Spark 37.91 s, Sail 2.80 s)q17: 13.4x faster (Spark 70.43 s, Sail 5.24 s)q9: 13.0x faster (Spark 55.98 s, Sail 4.31 s)q3: 10.9x faster (Spark 20.65 s, Sail 1.90 s)q21: 10.7x faster (Spark 52.37 s, Sail 4.89 s)q8: 10.2x faster (Spark 31.80 s, Sail 3.10 s)q7: 8.2x faster (Spark 19.29 s, Sail 2.35 s)q18: 7.8x faster (Spark 50.76 s, Sail 6.53 s)q13: 7.3x faster (Spark 17.51 s, Sail 2.40 s)q10: 6.6x faster (Spark 14.71 s, Sail 2.21 s)q15: 5.8x faster (Spark 13.68 s, Sail 2.35 s)q12: 5.4x faster (Spark 9.58 s, Sail 1.78 s)q20: 5.0x faster (Spark 10.14 s, Sail 2.02 s)q14: 4.4x faster (Spark 5.64 s, Sail 1.27 s)q6: 2.9x faster (Spark 2.93 s, Sail 1.01 s)q19: 2.8x faster (Spark 5.97 s, Sail 2.16 s)

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_reorder option 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 dbgen plus Parquet conversion to tpchgen-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.

Spark: memory and disk utilization
0326496Memory (GB)00.511.5Disk write (GB/s)q1q6q11q16q21q22124 GB usable72 GB peak0100200300400500Seconds from benchmark start541 s total

Sail peaked at 26 GB, released memory after each query, and wrote nothing to disk.

Sail: memory and disk utilization
0326496Memory (GB)00.511.5Disk write (GB/s)q1q6q11q16q21q22124 GB usable26 GB peakzero disk writes for the whole run01020304050Seconds from benchmark start54 s total

Both charts are plotted from AWS CloudWatch metrics at 1-second resolution, with query start times marked.

Questions about your Spark setup?

Book 30 minutes with a LakeSail engineer. Real answers, no pitch.