- A cluster only while a job runs. A stopped sandbox starts in about a second, so the job script starts the nodes, runs the job, and stops them again. In our runs, the cluster was ready 5 seconds after the script started, and the whole job took about 30 seconds once Archil had cached the data.
- Your bucket, read in place. Spark reads the bucket’s Parquet files as ordinary files, with no S3 connector or
credentials to configure, and Archil caches what it reads. Each node sees the same files at
/mnt/overtureand/mnt/lake, so a Delta table is a directory of ordinary files that every executor writes to directly. - Faster and cheaper than Databricks serverless. In our runs, the cluster finished the same jobs 2 to 5 times sooner than Databricks serverless in performance-optimized mode, for a fifth to a half of the cost, even on a first run with nothing cached.
Prerequisites
The Overture bucket is in AWSus-west-2, so this guide uses the aws-us-west-2 Archil region. Export an API key created in
that region, then install the SDK for the language you’ll orchestrate from. The cluster uses 128 vCPU across four
sandboxes, so you need the Developer plan or above; see Plans and limits.
npx tsx <name>.ts and Python
scripts with python <name>.py.
How the nodes connect
Spark’s master, workers, and driver open connections to each other on fixed ports. Sandboxes don’t share a private network, so each node runs wstunnel, an open-source tunnel over WebSockets:- A tunnel server on port 9000. The setup script creates a port token for each node’s port 9000, so only the other nodes, which hold that token, can reach it. Nothing else in the sandbox is exposed.
- A tunnel to every other node. Each node gets an address of its own,
127.0.1.1for the first node,127.0.1.2for the second, and so on. Spark listens only on its node’s address, and on every other node, a tunnel forwards that address’s Spark ports to the node through its port 9000.
Create the disks and the nodes
You need two disks:overture-mapsis backed by the public bucket, using therelease/2026-09-23.0/prefix as its root. Its buildings theme is 512 Parquet files, 258 GiB in total. Archil reads them from the bucket as Spark asks for them.spark-lakeis a regular Archil disk for the tables Spark writes.
overture-maps immutable with archil set-immutable. An
immutable disk’s files can’t change, so any number of sandboxes can read them at full speed at the same time. Marking a
disk takes a writable mount, so the script runs the command once in a small sandbox and deletes it.
This script runs once in every node, during setup. It downloads the tunnel and Delta Lake to the node’s root filesystem,
so no job waits on a download:
install.sh
node.sh
--cache-expiry-secs 1, so each node sees the files other nodes write within a second. Delta
Lake needs that: after the driver commits a table, executors on every node read the commit’s files. The script runs four
tasks per vCPU, because tasks reading uncached data mostly wait on object storage, not compute.
The setup script creates both disks and a token for the lake, then creates the four nodes, each with 32 vCPU and 64 GiB of
memory and overture-maps mounted read-only. It gives every node its settings, the other nodes’ tunnel addresses, and the
scripts, runs install.sh, and stops the node:
Run the job
This is the job. It reads only the four columns it needs from every building, counts the buildings in each 1-degree grid cell, with their average height where it’s known, and saves the result as a Delta table. Spark spreads the files across every executor in the cluster:buildings.py
Run the job from any scheduler that can run a script, such as cron, GitHub Actions, or Airflow. If the script dies
before it stops the nodes,
idleTtlSeconds pauses each node after 10 minutes without a connection, which also stops
compute charges.
Query the table
The table is an ordinary Delta table, so Spark SQL can query it by path. This query finds the five grid cells with the most buildings:query.sql
spark-lake can read the table too.
Running it in production
- Size the cluster per job. Change
NODESin the setup script for more or fewer nodes. Because the nodes run only while a job runs, twice the nodes for half the time costs about the same, so give big jobs more nodes. A sandbox can have up to 32 vCPU and 64 GiB of memory, andnode.shsizes Spark to the sandbox. Spark writes shuffle and spill files to each node’s root filesystem. - Mark data immutable once it’s final. Overture publishes each release under its own prefix and never changes it. When you back a disk with your own bucket, give each finished dataset, such as a dated partition, its own immutable disk, and keep data that’s still being written on a regular disk. Objects your other systems add to a bucket can take minutes to appear on a disk; see From the bucket to the disk.
- Share the lake. Any number of clusters, servers, and sandboxes can mount
spark-lakein conditional mode at the same time, and read or write tables there. - Keep the tokens to the nodes. The port tokens and the lake token are stored only on the nodes’ root filesystems. Deleting a node revokes its port token, and removing the lake’s token user revokes the lake token; see Managing disks.
- Pin your versions. The nodes run the official
sparkimage with Spark 4.2.1, Delta Lake 4.4.1, and wstunnel 11.1.0. To start faster and control when they change, build an image with them installed; see Images.
Compared with Databricks serverless
Databricks serverless compute includes the instances in its DBU rate, and Databricks chooses how much compute each job gets, so we measured it. We ranbuildings.py as a Databricks serverless job on the Premium tier in AWS us-east-2,
reading a copy of the same Overture release from an S3 bucket in that region, in both of its modes. We also ran a second
job that reads every column of every building, 252 GB. Each Databricks job’s cost is its DBU usage times the Premium list
price of $0.35 per DBU. This cluster's cost is the time its four nodes ran, at Archil's list [sandbox rates](/administration/billing#persistent-sandboxes-and-services), $12.16 an hour for the four nodes. Each time is from start to finish, including startup: for this cluster, starting and
stopping every node, submitting the job, and Spark’s startup. A first run reads from the bucket; later runs on this
cluster read what Archil cached, while Databricks serverless starts each run on fresh compute:
Archil samples running sandboxes once a minute, so a single run this short costs a minute of the nodes or nothing, and
the time they ran on average.
Against performance-optimized mode, the cluster finished 2 to 5 times sooner, for a fifth to a half of the cost. Against
standard mode, it finished 6 to 14 times sooner, for 40% less on the scan of every column. Most of the difference is size:
Databricks’ query history shows that serverless ran these jobs 7 to 24 tasks at a time, while this cluster starts 128 vCPU
on the job at once.
To price your own serverless jobs, add up each job’s rows in the
system.billing.usage table, which Databricks fills in
over the few hours after a job runs:
Clean up
Delete the nodes and both disks:overture-maps doesn’t touch the public bucket.
Next steps
Sandboxes
Create, start, and stop sandboxes, and reach their ports.
Sharing disks
Mount one disk on many sandboxes and servers, and choose between shared and conditional mode.
Analytics with DuckDB
Query the same Overture data with DuckDB, and serve results over HTTPS.
ClickHouse
Run ClickHouse on an Archil disk, and branch the database for heavy queries.