SQL and Python are the same kind of node
Not a Python escape hatch bolted onto a SQL tool. A .py model and a .sql model are interchangeable in one DAG — either can depend on the other, and the
planner does not care which you wrote.
published as interlaced; the import name and CLI are interlace
One graph
A Python model in the hot path
Ten models over 25 million rows, from the benchmark project. Click any node to see its
source — user_ltv.py sits between two SQL models, and nothing around it acknowledges
the difference.
elsewhere in the same graph
from interlace import model
@model(depends_on=["by_user"], strategy="merge", key=["user_id"])
def user_ltv(by_user):
for batch in by_user.reader():
score = pc.add(pc.multiply(batch.column("spend"), 0.1), batch.column("events"))
yield pa.RecordBatch.from_arrays(
[batch.column("user_id"), batch.column("spend"), pc.round(score, 2)],
names=["user_id", "spend", "ltv"],
)Python, in the hot path. Its upstream is SQL. It is a generator, so 100k rows stream through Arrow with bounded memory — and merge is the same keyed upsert a SQL model gets.
Materialisation
Two axes, not one
materialise decides where a result lands and who owns it. strategy decides how it is written. They compose — and every combination above comes
from one real project.
virtual · view · ephemeral
Interlace builds an immutable fingerprinted snapshot and serves it through an environment
view. That ownership is what makes rebuild-skip, sandboxes, atomic promotion, rollback and gc possible.
table · file
A destination Interlace does not own. It delivers into an external table or overwrites a file, evolves the target additively, and never drops it — so grants, indexes and RLS survive. Environment-gated to production by default.
A breaking change cannot apply to a terminal target: there is no old version to serve during the build and no atomic cutover. That constraint is why the two planes exist.
Build
One run, every plane
A single command builds owned snapshots, delivers into external tables and writes files — each with its own strategy, in dependency order. This is the benchmark: 25M synthetic events fanned out through every strategy, start to finish in seconds.
$ interlace run
Build results
Model Output Strategy Engine Depends on Rows Time
───────────────────────────────────────────────────────────────────────────────────────────────
events virtual replace default — +25,000,000 3.85s
daily_revenue virtual incremental_by_time default events +29 0.28s
daily_feed table append default daily_revenue +29 0.30s
revenue_report file replace default daily_revenue +29 0.07s
by_day virtual replace default enriched +30 0.08s
by_device virtual replace default enriched +4 0.11s
by_product virtual replace default enriched +15,000 0.21s
by_user virtual replace default enriched +100,000 0.45s
product_catalog virtual full_merge default by_product +15,000 0.34s
top_products view replace default by_product — 0.36s
user_history virtual scd default by_user +100,000 0.09s
user_ltv virtual merge default by_user +100,000 0.15s
Checks: 2/2 passed
Ran 12 model(s) (12 task(s)); promoted 13 to 'prod'.Preview every change
interlace plan classifies each model as breaking, non-breaking or forward-only before anything is built.
Reuse instead of rebuild
Column-level impact analysis proves when a downstream output is unchanged, so its existing table is reused.
Checks gate promotion
An error-severity check failure blocks the apply before the environment view ever moves.
The Daemon
One process, not a stack
interlace serve runs the web UI, HTTP API, scheduler and stream ingestion together.
There is no separate orchestrator to deploy and no broker to operate.
Durable streams
POST an event and it is fsynced before the 200, deduplicated by idempotency key, and materialised exactly once — the watermark commits in the same warehouse transaction as the data.
Free sandboxes
An environment is a set of views over fingerprinted tables, so a dev environment reuses production’s for free. Promotion is an atomic view swap; rollback is the same move backwards.
Built-in scheduling
Cron and interval triggers over a durable run queue with leases, retries and cooperative cancellation. No Airflow, no broker, no second deployment.
Pin models to engines
DuckDB/DuckLake by default, with Postgres, Snowflake, BigQuery, Redshift, MotherDuck and Spark behind an engine: pin. Cross-engine dependencies move as Arrow, or over a federated ATTACH.
Ready to simplify your data pipelines?
Get started with Interlace in minutes. Install, define your first model, and run.