Beta — API stabilizing toward 1.0

The embedded pipeline engine

Batch and streaming in one Python decorator surface. One binary that scales to a peer mesh — no cluster service. The fastest single-node engine we've measured, at every scale.

Measured on AWS, not a laptop

TPC-H on c7i.4xlarge (16 vCPU / 32 GB), July 2026 — shipped pip install ematix-flow defaults, no flags, results value-validated. Full benchmarks →

Faster

Distributed, TPC-H SF=100

8× faster than Trino on the same 4-node cluster.

ematix-flow (auto mesh) 61.9 s
Trino 497 s
PySpark DNF — 18/22 queries failed

4× c7i.4xlarge · SF=10: 5.6× vs Trino, 6.5× vs PySpark · SF=1: 7.2× / 19×

Cheaper

One node vs their cluster, SF=10

One ematix node outruns a 4-node Trino cluster 12×.

ematix-flow · 1 node 4.8 s
Trino · 4 nodes 56.4 s
PySpark · 4 nodes 65.0 s

Same queries, same S3 parquet — a quarter of the hardware, ~12–14× the speed

Simpler

Single node, pip install, zero tuning

Fastest single-node engine at SF=1 and SF=10.

ematix-flow 4.8 s
DuckDB 5.8 s
Polars SF=1 only: 2.45 s vs ematix 0.68 s

SF=100 totals: DuckDB 59 s, ematix 82 s — ematix faster on 15/22 queries; the total is one memory-cliff outlier (Q09), fix in flight

Documentation

Start the tutorial →

Featured concepts

All concepts →

Quick peek

Full tutorial →

A workflow with a composite trigger (event + cron) plus within-DAG ordering.

from ematix_flow import ematix, ManagedTable, Annotated, BigInt, Text, pk

@ematix.connection
class warehouse:
    kind = "postgres"
    url = "${WAREHOUSE_URL}"

class OrdersExtracted(ManagedTable):
    __schema__ = "analytics"; __tablename__ = "orders_extracted"
    order_id: Annotated[BigInt, pk()]
    customer_id: BigInt
    amount_cents: BigInt

class OrdersEnriched(ManagedTable):
    __schema__ = "analytics"; __tablename__ = "orders_enriched"
    order_id: Annotated[BigInt, pk()]
    amount_bucket: Text

@ematix.job(name="extract_orders",
            target=OrdersExtracted, target_connection="warehouse",
            mode="merge", keys=("order_id",))
def extract_orders(conn):
    return "SELECT order_id, customer_id, amount_cents FROM raw.orders"

@ematix.job(name="enrich_orders",
            target=OrdersEnriched, target_connection="warehouse",
            mode="merge", keys=("order_id",),
            depends_on=["extract_orders"])
def enrich_orders(conn):
    return "SELECT order_id, CASE WHEN amount_cents < 10000 THEN 'small' ELSE 'large' END AS amount_bucket FROM analytics.orders_extracted"

# Workflow declares the trigger; member jobs declare their DAG position.
ematix.workflow(
    name="orders_etl",
    triggered_by=["upstream_workflow"],
    schedule="0 21 * * *",
    timezone="America/New_York",
    jobs=["extract_orders", "enrich_orders"],
)
Currently in beta

On PyPI as ematix-flow. The core API is settling toward 1.0; pinning a minor version is still recommended. Bug reports and design pushback during the beta window are exactly what we want — file issues on GitHub.