Building an HA,
Distributed
Query Engine
on Apache DataFusion Ballista
Phillip LeBlanc
@phillipleblanc

Ecosystem work


Single-node ceilings.
Scale query compute independently of storage—without Spark-class operational tax, and without giving up a simple single-node deploy.
100GB+ lakes
outgrow one process.
Memory and compute hit a hard wall. Analytical work needs stage boundaries, placement, and elastic executors.
Stages fail.
Processes fail too.
Resume stages from intermediate data. Losing the process that accepted the query should be invisible to the caller—accepted work keeps running elsewhere.
Why Apache Ballista?
Apache Ballista
datafusion-distributed
Custom build
What we built on Ballista.
Stock Ballista is the execution foundation. Production Spice needed control, catalog, security, shuffle choices, and codecs.
Poll scheduling
Executor poll loop · no slot leasing · PollNow for latency
Catalog + UDFs
Scheduler is source of truth · one spicepod for the cluster
mTLS by default
Privileged RPCs on an internal port · CLI cert tooling
Shuffle backends
Local disk · memory · object store for ephemeral nodes
Vortex shuffle
Smaller intermediates · less network and disk I/O
Custom codecs
Serialize Spice plans · UDTFs · Cayenne · Iceberg scans
Multi-active HA. Object store is all you need.
Every scheduler is active. Shared state lives in the object store—no etcd, ZooKeeper, or Redis.
Poll for work. Don't lease slots.
Ballista supports both. We pick poll: executors self-regulate—no shared live workload view across schedulers.
Scheduler pushes tasks
Push needs an accurate shared view of free slots—or you oversubscribe. Slot leasing and coordination become the tax.
- Scheduler assigns work onto executors
- Needs a shared slot / workload view
- Multi-active → lease, lock, or oversubscribe
Executors ask for work
Each executor knows its own capacity. It polls when free—no leasing, no shared workload ledger across schedulers.
- Executor-initiated poll loop to every scheduler
- Self-regulates concurrency locally
- Fits multi-active without slot leasing
Secure by default. One spicepod.
Privileged cluster RPCs require mTLS. Catalog and UDFs sync from the scheduler—no manual registration drift.
Protect privileged RPCs
GetAppDefinitionfull cluster configExpandSecretsecret valuesGetSchedulersscheduler membership / topologyScheduler is source of truth
Executors join and receive catalog + UDF registry via GetCatalog / GetFunctions.
Shuffle is a deployment choice.
Disk, memory, or object store—plus optional Vortex encoding for smaller intermediates.
Local disk
Default durable path: materialize stage output on the executor.
Memory
Keep intermediates in RAM when recomputation is acceptable.
Object store
Decouple shuffle from executor disks—essential for ephemeral cloud nodes.
DEMO
Scheduler failover for an accepted async query.
General fixes flow back.
We maintain spiceai/datafusion-ballista—and upstream TLS hooks, shuffle backends, and scheduling fixes to Apache.
Cluster API + hooks4 merged fork PRs
Scheduling + HA14 merged fork PRs
Shuffle + transport16 merged fork PRs
Planning + correctness8 merged fork PRs
Observability4 merged fork PRs
Maintenance4 merged fork PRs
Apache path
Apache BallistaStatus snapshot · 2026-07-23
Get started.
Install Spice, start cluster roles, then follow the async-queries cookbook.
curl https://install.spiceai.org | /bin/bashspiced --role schedulerspiced --role executor
--scheduler-address https://host:50052Async queries walkthrough
TLS, scheduler + executor setup, submit/poll, and spice query.
github.com/spiceai/cookbook/tree/trunk/async-queriesOpen the recipe →Ballista for execution.
Object store for HA.
Multi-active schedulers, mTLS, deployable shuffle, and an async API—on the same binary that still runs standalone.
Executors dial out. Schedulers never dial in.
Control streams are executor-initiated. Object store is the shared plane both sides can reach—including final results.
Object store
One stream, two directions
Each executor keeps a long-lived outbound stream to every scheduler—commands ride back on that same connection; heartbeats and slot state go up.
PollNowWake the pull loop—cut wait from the poll interval to near-zero.CancelTasksCancellation rides the open stream—no scheduler dial-out.MetricsOn-demand executor metrics over the same stream.HeartbeatSlot availability · liveness from executor to every scheduler.Distribute or accelerate.
TPC-H SF100 against federated S3 Parquet—no local acceleration—one node vs a three-executor Ballista cluster.