The Night Rust Met Spark at Adyen
On the evening of 9 September 2026, Adyenâs Amsterdam office hosted the Rust AI Europe meetup, the evening before PyData Amsterdam. The theme was that Rust is becoming a foundation for data and AI infrastructure, and the evening had three talks: Zemin Piao on evolving Spark at Adyen, the LakeSail team on how they rebuilt Spark in Rust, and Robin Everaars (HoistIT) on data sovereignty and Rust.
The Venue: Adyenâs Amsterdam Event Space
The talks ran in a large open-plan event space with a stage, a wide LED screen and rows of white chairs. Before the start, the screen showed Adyenâs own house rules for its rooms: âMaking the most of our meeting spacesâ.

Adyenâs Amsterdam event space filling up before the first talk.
Zemin Piao: Evolving Spark at Adyen
Zemin Piao (Big Data Platform at Adyen, as his slides put it) opened with âEvolving Spark at Adyenâ, with the subtitle âScheduling, Kubernetes, Observability, AI, Rustâ.

Zemin Piaoâs opening slide, âEvolving Spark at Adyenâ, seen from the audience.
His introduction slide was titled âMy life, in one numberâ: âEverything about me comes in sixes.â Six years at Adyen, six years of Apache Spark, and a cat who is six years old.

âEverything about me comes in sixesâ: six years at Adyen and six years of Apache Spark.
Data Has Consequences: Why Compute Matters
The âWhy compute mattersâ slide set the scene for one financial technology platform, where the data platform feeds payment decisions, finance and reconciliation, pricing and billing, merchant insights, and compliance. It summed up the requirements in three words: correct, on time, and at PB scale.

The three requirements for production data: correct, on time, at PB scale.
From there the talk walked through how Spark at Adyen has changed:
- Scheduling: low-criticality work is useful until demand collides, so YARN application priority was used to âuse spare capacity, protect critical workâ.
- Kubernetes: compute and storage used to compete on the same YARN + HDFS worker hosts. The move was âMove compute. Keep shared dataâ: Spark workloads go through a shared platform launcher to either YARN or Kubernetes, with resource boundaries between critical, standard and backfill workloads.
- Remote shuffle: local shuffle constrains elastic compute, so a Celeborn remote-shuffle experiment (marked work in progress) targets decoupling compute from shuffle.
- Observability and optimisation: âManual tuning does not scaleâ. The approach was deterministic analysis first, then a Spark History MCP to âask the event logâ, producing âsuggestions, not automatic tuningâ, with a human who approves or rejects each one.

Before Kubernetes: Spark executors and HDFS DataNodes shared the same hosts, so compute load turned into storage latency.

The Celeborn remote-shuffle experiment, marked âwork in progressâ on the slide.
A Sail Proof of Concept at Adyen
The last part of Zeminâs talk was the Rust experiment. Spark Connect was âthe seam that made the experiment possibleâ: a PySpark client talks a protocol to a server, and that server can be Apache Spark on the JVM or Sail, the Rust-native server. The slide title said it plainly: âKeep PySpark. Change the server.â
The test used a production-shaped ingestion path: âSame DAG. Different lifecycle.â An Airflow task with a LakeSail operator started a single Kubernetes container holding the PySpark client and the Sail server, reading from Postgres over JDBC and writing to Delta Lake on HDFS, while keeping the orchestration and platform contracts as they were.
The result slide was careful: âCorrectâand directionally faster.â Row counts and values matched, and task wall-clock time went down. The slide also called it a bounded signal: a bounded sample, with deployment and engine effects not isolated. It was âenough evidence to continue exploringâ, not a production migration. The closing slide listed three parts of the journey: share capacity intentionally, ground suggestions in evidence, and test Sail without rewriting PySpark.
The Rust Stack: Why Language Matters at Scale
LakeSailâs talk explained why youâd build the whole Spark-compatible stack in Rust. The architecture, as the slides described it:
- Rust runtime replaces the JVM. Sail is â100% Rust, JVM-freeâ.
- Spark Connect protocol, so existing PySpark code keeps working: âswitch your endpoint, keep your codeâ.
- Apache Arrow and Apache DataFusion end to end, columnar throughout, with no row-to-columnar conversion tax.
- PyO3 for UDFs: Python UDFs run in-process and share Arrow memory with the engine.
Inside Sail: How LakeSail Rebuilt Spark in Rust
The LakeSail team (two presenters) gave the second talk, âInside Sail: Rebuilding Spark in Rustâ, subtitled âA technical tour of Spark compatibility, Arrow-native execution, query planning, distributed runtime, Python UDFs, and Rust-native lakehouse formats.â The deck was 48 pages long. These are the parts I photographed.

The opening slide of LakeSailâs âInside Sailâ talk.
Accelerate vs. replace
The first slides explained why Sail rebuilds the engine instead of bolting native operators onto Spark. Accelerators such as Photon, Comet, Gluten/Velox and Auron intercept the physical plan and offload operators to native code, but stay inside the JVM boundary. Sail replaces the JVM with a Rust-native engine built on Arrow and DataFusion. The slideâs summary: âSpark compatibility is the contract and the execution runtime is the opportunity.â
A drop-in Spark Connect server
âWhat Is Sailâ described a drop-in Spark Connect server in Rust. You point SparkSession.builder.remote() at a Sail endpoint (the slide used sc://sail-host:50051) and the SQL and DataFrame APIs stay the same. It targets Spark 3.5.x and Spark 4.x and has two deployment modes: a single node (simpler ops, smaller data, local dev and testing) and a distributed cluster on Kubernetes with stateless, elastic workers. The Sail README says the same: compatible with the Spark Connect protocol, with âno code rewrites requiredâ, and designed for Spark 3.5.x, Spark 4.x and later.
Under the hood, the Spark Connect client is a thin gRPC stub that builds an unresolved logical plan. Sail decodes the protobuf plan and runs it in Rust on DataFusion and Arrow, behind the same wire format. SQL goes through Sailâs own parser, built with parser combinators into a type-safe, compile-time-checked AST.

âSwitch your endpoint, keep your codeâ: Sail is Spark Connect compatible, on one node or on Kubernetes.
Python UDFs in-process
The slides called Python UDFs âthe largest single source of speedupâ. In Spark, Python sits outside the engine: every batch crosses a process boundary to a Python worker and pays a SerDe tax. In Sail, the engine and CPython share one address space through PyO3: the engine reaches the UDF node, invokes Python inline on the Arrow batch, and returns with zero copy and zero serialisation. Because Python is part of the execution plan, fusion and vectorisation still apply, across UDF, UDAF, UDWF and UDTF (Python, Pandas and Arrow). One slide also cited RedNote, where, the slide said, Spark and the accelerators they evaluated could not run a Python-heavy AI workload that now runs on Sail.
The actor model
The distributed runtime is built on actors: âConcurrency without locks: donât communicate by sharing memory, share memory by communicating.â The driver and the workers are actors, and each one processes messages on a single thread, so there are no shared-memory races and no mutexes. The framework is purpose-built on Tokio, âwith no actor hierarchy, no remote actors, and no JVM/Pekko weightâ. Workers hold no durable state, so the driver can reschedule a failed region onto any available worker.

Sailâs actor model: one thread per actor, state changed by messages, and stateless workers.
Job graphs, task regions and pipelined shuffle
A job is a DAG of stages, broken into tasks and attempts. The driver manages the job graph, and workers run tasks through a task runner. The unit of scheduling and failure recovery is a task region: a set of tasks across stages.
With pipelined shuffle, partitions stream between stages in memory within one task region. Arrow batches travel over Arrow Flight between live tasks, and nothing is durably materialised. Thatâs the fast path, but it has a trade-off the slide named âcoupled failureâ: if one task fails, in-flight data is gone and the whole region retries. Tasks from different stages can share a task slot (âslot sharingâ).

Pipelined shuffle: one task region where everything streams and runs together.
For blocking shuffle, a boundary separates task regions and data is written to object storage or Celeborn before downstream tasks read it. On object storage, Sail inserts a no-op reducer stage so that mapper chunks merge into one object per reduce partition, instead of every mapper writing one tiny file per partition. The slide compared this to the push-based shuffle merging in the Magnet paper. âPersistence is not overheadâ, another slide said: it buys resiliency, elasticity and the ability to run big jobs on small clusters, because only one task region runs at a time.
Apache Celeborn, native to Sail
Apache Celeborn is âa second blocking shuffle backend, supported from Sail 0.7.1â. Sailâs driver actor registers with the Celeborn masters and gets slot allocation, and Sailâs worker actors push and fetch data to Celeborn workers, which store, merge and serve shuffle data. The Celeborn client in Sail is written in Rust and handles TCP, protobuf and binary framing, so no JVM runs inside Sail. It reuses Sailâs actor model and existing gRPC channel. The configuration on the slide was SAIL_CLUSTER__SHUFFLE_BACKEND__CELEBORN__MASTER_ENDPOINTS.
I checked this against the project. The Sail v0.7.1 release notes list âadd Celeborn shuffle backendâ, and the shuffle backends documentation describes three options: Arrow Flight (the default pipelined shuffle), a storage backend and Apache Celeborn, the last two being blocking shuffle.

Celeborn as a second blocking shuffle backend, built on Sailâs actors.
Lakehouse formats and MERGE on Delta Lake
The lakehouse part started with the difference between listing tables (a directory layout, where updates mean a full table rewrite) and lakehouse tables (atomic manifest writes, where updates are efficient). A support matrix showed Delta Lake, Iceberg and DuckLake. Delta Lake had ticks for read (partition pruning, time travel), write (schema evolution, column mapping), delete, merge and update. Iceberg had ticks for read, write and merge. DuckLake was marked as a pull request in progress. The note under the table: âUpdate is simpler than Merge, but we tackled Merge first to enable a more extensible DML architecture.â
The Delta Lake journey went from adding the delta-rs and delta-kernel-rs dependencies to removing them, because âlakehouse contract needs deep engine integrationâ. Metadata processing moved to the physical execution phase.
The MERGE case study was âthe most complex DML operation, tackled first for informed architecture decisionsâ. At a high level, Sail removes all files touched by the ON and MATCHED AND conditions, writes new files for the UPDATE and INSERT results, and writes a manifest at the end for added and removed files. In the query planning pipeline, a SQL AST or Spark Connect plan becomes a MergeIntoNode, is expanded into a logical plan, and is turned into a physical plan by a custom planner extension. The simplified physical plan ends in a DeltaCommitExec.

MERGE on Delta Lake, in three steps.
The Delta Lake features page in the Sail docs now lists MERGE with matched, insert and WHEN NOT MATCHED BY SOURCE clauses, plus UPDATE and DELETE with copy-on-write and deletion vectors. The key takeaways slide said that lakehouse formats need deep engine integration, that âmetadata is dataâ, and that composable plan nodes enable operations across formats. Future work was reusable plan nodes for Delta Lake, Iceberg and DuckLake, and testing and benchmarking distributed DML at scale.
Catalogs and the system catalog
Sailâs CatalogProvider is a single, flat, async trait for databases, tables and views. It covers catalog objects only (create, get, list and drop) and keeps query planning separate, and being async makes it easier to integrate external systems such as cloud catalogs. The comparison slide set this against DataFusionâs hierarchical CatalogProvider, SchemaProvider and TableProvider, which are not async (for performance, so there are no network requests in planning) and also carry query planning. The system catalog exposes internal execution state through SQL: âObservability Through SQLâ. The slide showed SELECT * FROM system.session.sessions WHERE status = 'RUNNING' and listed system tables for sessions, jobs, stages, tasks and workers. It said this complements the OpenTelemetry integration, so metrics, traces and logs correlate with catalog queries.
Under the hood, a system table is a TableScan with a custom TableSource that supports filter pushdown. When execute() runs, it sends a message to the session manager actor and awaits the result on a Tokio channel, with no locks thanks to the single-threaded actor event loop. The system catalog documentation uses the same system.session.sessions example.

The system catalog: Sailâs internal state, queryable with SQL.
My take: being able to query sessions, jobs and workers with SQL is the kind of feature platform teams notice. It means the same dashboards and alerting that read your data can also read the state of the engine.
The Benchmarks, as LakeSail Presented Them
The last section of the deck was benchmarks. These are LakeSailâs own numbers from its slides, not independent measurements, and Iâm reporting them exactly as shown.
TPC-H (derived), âSail Speed-up Over Spark (2026 Derived TPC-H)â:
- 10x faster on average
- 98% total cost reduction
- 0 GB shuffle spill, versus more than 115 GB for Spark
- 26 GB peak memory, versus 72 GB for Spark
- Per-query speed-ups from 2,819% (q16) down to 176% (q19), where 100% means 2x faster
The benchmark results page in the Sail docs gives the setup: TPC-H-derived queries at scale factor 100 on an AWS EC2 r8g.4xlarge (16 vCPU, 128 GB RAM), with the 22 queries taking 52.81 seconds on Sail against 534.78 seconds on Spark.

LakeSailâs TPC-H-derived results: 10x faster on average and 98% total cost reduction, according to its slide.
ClickBench, âSail vs. Spark & Accelerators (2026 ClickBench)â, as relative time (lower is better) on c8g.4xlarge:
| Run | Sail (Parquet) | Spark | Spark (Comet) |
|---|---|---|---|
| Combined run | x1.00 | x6.90 | x7.08 |
| Hot run | x1.00 | x15.04 | x14.24 |
ClickBench, âSail vs. Databricks (2026 ClickBench)â, best hot run: Sail (Parquet) on c8g.4xlarge at x1.00 (16 vCPU, 32 GiB memory) against Databricks (32xXL) at x2.06 (256 vCPU, 1,952 GiB memory). The slide explained that it reported hot-run numbers because the cold run is dominated by data load time (Databricks 41 s against Sail 2 s, about a 20x gap), which reflects data loading and setup rather than query engine performance. ClickBench is ClickHouseâs open analytical benchmark.


LakeSailâs ClickBench slides against Spark, Comet and Databricks.
The final slide was two lines: âStop tuning the JVM. Replace it.â
My take: vendor benchmarks are a reason to run your own test, not a replacement for one. What I liked is that the two talks fitted together. LakeSail showed big numbers on standard benchmarks, and Adyen showed a small, honest proof of concept on a production-shaped pipeline: correct, directionally faster, and with the caveats written on the slide.
Data Sovereignty and Rust
The third talk of the evening was Robin Everaars (HoistIT) on âData Sovereignty and Rustâ. It covered 27 Dutch mental-healthcare record systems, ownership as a Rust feature, and how to âdestroy it and prove itâ. I wrote it up separately: Data Sovereignty and Rust: Robin Everaars at Rust AI.
What This Means for Production AI
I spend a lot of time thinking about the gap between AI prototypes and production deployments. The most common failure mode I see isnât in the model layer. Itâs in the data layer. Teams get a model working, then hit a wall when they try to feed it production-scale data through a pipeline that was never designed for the throughput, the latency, or the reliability requirements.
Projects like LakeSailâs Sail, the DataFusion ecosystem, and the broader push to rebuild data infrastructure without the JVM arenât only about faster benchmarks. Theyâre about a data foundation that can serve AI applications. When Python UDFs run in the same process as the engine, and the engine can be swapped behind a protocol your code already speaks, the cost of trying it drops a lot.
Key Takeaways
- Spark Connect is the seam. Both talks relied on it: the protocol boundary lets you change the server while keeping PySpark code as it is.
- Replace, donât accelerate. LakeSailâs argument is that a Rust-native engine on Arrow and DataFusion removes costs that JVM accelerators canât, especially for Python UDFs.
- The runtime design matters as much as the language. Actors without locks, task regions, and a choice between pipelined shuffle, object storage and Celeborn are what make the distributed mode work.
- Treat benchmarks as a starting point. LakeSailâs slides claim 10x faster and 98% lower cost on TPC-H-derived queries. Adyenâs proof of concept found âcorrect and directionally fasterâ on a bounded sample.
- Python stays. The architecture is âRust inside, Python outsideâ, with PyO3 bridging the two without serialisation overhead.
The PyData Preview
The timing was no accident. The next day at PyData Amsterdam, Shehab Amin (LakeSail co-founder and CEO) and Santosh Pingale (Adyen) were giving the joint talk âModernizing Spark: Performance Boost without Rewriteâ.
Resources
- LakeSail: Rust-native data and AI platform
- Sail on GitHub: drop-in Apache Spark replacement written in Rust
- Sail documentation: benchmark results
- Sail documentation: shuffle backends
- PyData Amsterdam 2026: âModernizing Spark: Performance Boost without Rewriteâ by Shehab Amin & Santosh Pingale
- rust.ai: Rust AI Europe meetup series
Recorded at the Rust AI Europe meetup at Adyen, Amsterdam, 9 September 2026, the evening before PyData Amsterdam.
Discussion questions:
- If your Spark workloads are CPU-bound, not I/O-bound, whatâs the cost of keeping the JVM?
- Would you trust a vendorâs 10x claim, or run a bounded proof of concept like Adyenâs first?