MiniLake: De Databricks API op je eigen laptop

Installatie

Je kunt MiniLake installeren via pip:

pip install minilake && minilake --port 8000

Of draaien via Docker:

docker run -p 8000:8000 ghcr.io/dmux/minilake:latest

Na de installatie kun je de ingebouwde SQL-workspace openen via http://localhost:8000/ui/. De image downloadt tijdens runtime niets, waardoor het ook in air-gapped omgevingen werkt.

Drop-in vervanging voor:

  • databricks-sdk (Python)
  • Databricks CLI
  • Terraform provider
  • Asset Bundles
  • PySpark + Delta
  • MCP agents

Kerncijfers:

  • 30+ geëmuleerde Databricks API-groepen, van Unity Catalog tot SCIM.
  • 67 MCP-tools voor LLM-agents, geserveerd op dezelfde poort.
  • 378+ tests aangedreven door de echte Databricks SDK (geen mocks).
  • $0 cloud-uitgaven — alles draait op je eigen machine.

Wat MiniLake biedt

MiniLake is geen eenvoudige mock-server, maar een echte engine achter een API die is vormgegeven naar Databricks. Waar fake endpoints vaak foutieve code laten passeren, voert MiniLake SQL uit op DuckDB, schrijft echte Delta-bestanden en draait echte spark-submit jobs in containers. Een geslaagde lokale test is hierdoor daadwerkelijk representatief.

Unity Catalog voor het echt

Catalogs, schema's, tabellen en volumes met volledige CRUD-functionaliteit. Elke catalog is een eigen DuckDB-databasebestand, waardoor de adressering via catalog.schema.table native is en niet via een regex-rewrite gebeurt.

Echte uitvoering

  • SQL die echt werkt: Statement-uitvoering op DuckDB met INLINE en EXTERNALLINKS dispositions, JSONARRAY / ARROW_STREAM / CSV-formaten en een resultaatmanifest met correcte kolomtypes.
  • Echte Delta Lake-bestanden: EXTERNAL Delta-tabellen zijn genuine Parquet-bestanden inclusief een deltalog/. INSERT, UPDATE en DELETE verlopen via een echte Spark-job; leesacties gaan via DuckDB's delta_scan().
  • Jobs die echt worden uitgevoerd: sparkpythontask voert spark-submit uit in een sibling container en retourneert de werkelijke stdout. Echte DAG-scheduling respecteert dependson en runif, waarbij onafhankelijke branches parallel lopen.
  • Unity Catalog protocol voor Spark: spark.table("cat.sch.tbl") wordt via de officiële UCSingleCatalog connector opgelost tegen MiniLake — hetzelfde protocol dat Spark gebruikt voor een echte workspace.

Naadloze compatibiliteit

  • Ingebouwde SQL-workspace: Een Athena-stijl editor op /ui met Monaco (inclusief catalog-aware completion), een gevirtualiseerd resultaatgrid, grafieken, CSV/JSON-export, opgeslagen queries, query-historie en een embedded JupyterLab.
  • Terraform & Asset Bundles: Richt de databricks provider op localhost en terraform apply creëert echte catalogs. Dit is geverifieerd in elke testrun: de echte provider wordt aangestuurd via apply, plan en destroy. Ook databricks bundle deploy en bundle run werken end-to-end.
  • MCP-server voor agents: 67 tools, 8 resources en 4 prompts beschikbaar op /mcp — inclusief composites die een sequentie van vijf calls samenvoegen tot één, en een SQL-dialect resource die voorkomt dat agents Spark SQL schrijven.
  • Ontworpen voor CI: Eén POST /_minilake/reset geeft elke test een schone workspace zonder de container te herstarten. De control plane wordt compleet gemaakt door /health, /ready en /services.
  • Workspace, DBFS, Files, Secrets: Bestandsgebaseerde notebook- en bestandopslag met chunked uploads. Secrets hebben echte CRUD-functionaliteit, maar de waarden zijn alleen oplosbaar binnen de omgeving van een job, precies zoals bij Databricks.
  • Air-gapped design: DuckDB's delta-extensie en de Delta / Unity Catalog jars zijn tijdens de build-tijd ingebakken. De image downloadt niets tijdens runtime, wat bewezen kan worden met docker run --network none.
  • Databricks-stijl foutmeldingen: Fouten worden teruggegeven als {"errorcode", "message"}, zodat de SDK de normale typed exceptions kan raising. Alles wat buiten scope valt, geeft een duidelijke 501 NOTIMPLEMENTED terug in plaats van een overtuigende leugen.

Geen codeaanpassingen nodig

De migratie naar MiniLake bestaat enkel uit het wijzigen van de host. Omdat MiniLake de Databricks REST API op wire-niveau spreekt, blijven alle bestaande clients werken met dezelfde calls, response shapes en typed errors.

  • Authenticatie: Elk token wordt geaccepteerd. Geen aanmelding, geen PAT, geen OAuth-proces nodig.
  • Type-mapping: STRING, LONG, DECIMAL(10,2), ARRAY\<T>, MAP en STRUCT mappen naar DuckDB en keren terug als de ColumnInfo van de SDK.
  • Fysieke waarheid: Verklaarde kolomtypes die afwijken van de bestanden verliezen; mismatches zijn dus zichtbaar in plaats van dat ze worden aangenomen.
  • Persistentie: Met MINILAKE_PERSIST=1 wordt de status bij afsluiten gesnapped en bij opstarten hersteld.

Codevoorbeelden

Python SDK

from databricks.sdk import WorkspaceClient
from databricks.sdk.service.catalog import ColumnInfo, TableType

# De enige regel die afwijkt van productie
w = WorkspaceClient(host="http://localhost:8000", token="dev")

w.catalogs.create(name="sales")
w.schemas.create(name="store", catalog_name="sales")
w.tables.create(
    name="orders", catalog_name="sales", schema_name="store",
    table_type=TableType.MANAGED,
    data_source_format="DELTA", storage_location="/data/sales/store/orders",
    columns=[
        ColumnInfo(name="id", type_text="BIGINT"),
        ColumnInfo(name="customer", type_text="STRING"),
        ColumnInfo(name="amount", type_text="DECIMAL(10,2)"),
    ],
)

wh = w.warehouses.create(name="dev", cluster_size="Small")
w.statement_execution.execute_statement(
    warehouse_id=wh.id,
    statement="INSERT INTO sales.store.orders VALUES (1, 'Ana', 459.90)",
)

result = w.statement_execution.execute_statement(
    warehouse_id=wh.id,
    statement="SELECT customer, amount FROM sales.store.orders",
)
print(result.result.data_array)   # [['Ana', '459.90']] — echt opgeslagen

Spark & Delta

from pyspark.sql import SparkSession

UC = "http://localhost:8000"
spark = (
    SparkSession.builder
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")
    # Unity Catalog connector — three-part names resolve tegen MiniLake
    .config("spark.sql.catalog.spark_catalog", "io.unitycatalog.spark.UCSingleCatalog")
    .config("spark.sql.catalog.spark_catalog.uri", UC)
    .config("spark.sql.catalog.sales", "io.unitycatalog.spark.UCSingleCatalog")
    .config("spark.sql.catalog.sales.uri", UC)
    .getOrCreate()
)

# Echte Delta-bestanden: parquet + _delta_log/, geschreven door echte Spark
spark.createDataFrame([(1, "a"), (2, "b")], ["id", "name"]) \
    .write.format("delta").mode("overwrite").save("/data/delta/sales/store/events")

spark.sql("INSERT INTO sales.store.events VALUES (3, 'c')")
print(spark.table("sales.store.events").count())   # 3

Terraform

provider "databricks" {
  host  = "http://localhost:8000"
  token = "dev"
}

resource "databricks_catalog" "sandbox" {
  name    = "sandbox"
  comment = "Local sandbox catalog"
}

resource "databricks_schema" "things" {
  catalog_name = databricks_catalog.sandbox.name
  name         = "things"
}

resource "databricks_sql_endpoint" "compute" {
  name         = "local-compute"
  cluster_size = "Small"
}
# terraform apply  -> creëert echte catalogs en echte DuckDB databases
# terraform destroy -> verwijdert deze

Asset Bundles

bundle:
  name: my_pipeline
  targets:
    dev:
      mode: development
      workspace:
        host: http://localhost:8000
      resources:
        jobs:
          daily_load:
            name: daily_load
            tasks:
              - task_key: main
                spark_python_task:
                  python_file: ../src/load.py
# $ databricks bundle deploy -t dev
# $ databricks bundle run daily_load -t dev

Curl

# Check of de service up is
curl http://localhost:8000/_minilake/health

# Catalog aanmaken via de API
curl -X POST http://localhost:8000/api/2.1/unity-catalog/catalogs \
     -H 'Content-Type: application/json' \
     -d '{"name": "sales"}'

# SQL uitvoeren en rijen terugkrijgen
curl -X POST http://localhost:8000/api/2.0/sql/statements \
     -H 'Content-Type: application/json' \
     -d '{"warehouse_id": "dev", "statement": "SELECT 42 AS answer"}'

# Workspace opschonen voor de volgende test
curl -X POST http://localhost:8000/_minilake/reset

Hoe het werkt

MiniLake bestaat uit één proces op één poort met echte engines eronder. Een FastAPI-applicatie dient de Databricks REST-interface, de web-UI en het MCP-endpoint. Daarachter bevinden zich DuckDB, echte Delta-bestanden op schijf en Docker voor Spark.

De architectuur:

  1. Clients: (SDK, CLI, Terraform, PySpark, LLM-agents, Browser)
  2. MiniLake (FastAPI): Beheert de REST-surface, SQL-workspace en MCP-tools.
  3. Real Engines:
  • DuckDB: Eén databasebestand per catalog.
  • Delta Lake on disk: Parquet + deltalog/.
  • Docker → Apache Spark: spark-submit in sibling containers.

Spark schrijft de bestanden — DuckDB leest exact dezelfde bytes.

Voor LLM-agents

MiniLake biedt agents een lakehouse dat ze niet kunnen slopen. Een normale Databricks workspace is lastig aan te sturen voor een agent: acties vereisen vaak vier calls, de SQL-dialecten kloppen vaak niet en fouten zijn soms stil. De MCP-server van MiniLake lost dit op zonder kosten bij fouten.

Beschikbare tools voor agents:

  • setup_fixture(): Catalog, schema en warehouse in één idempotente call.
  • seed_table(): Tabel inclusief exact de gevraagde rijen.
  • describecatalogtree(): De volledige hiërarchie in één keer, in plaats van tientallen calls.
  • runpythonscript(): Stage, submit naar echte Spark, poll en retourneer stdout.

Daarnaast zijn er 8 resources (waaronder een SQL-dialect gids om te voorkomen dat agents Spark SQL schrijven naar DuckDB) en 4 prompts die de regels vooraf definiëren.

Configuratie voor Claude Code:

claude mcp add --transport http minilake http://localhost:8000/mcp

Overzicht van Ondersteuning (Coverage)

Elke functionaliteit wordt ofwel echt uitgevoerd, of duidelijk gelabeld. Alles buiten scope geeft een 501 NOT_IMPLEMENTED terug.

ServiceStatusBetekenis
Unity CatalogRealCatalogs, schema's, tabellen, volumes, functies — elke catalog is een eigen DuckDB database.
UC grantsUnenforced CRUDMet echte inheritie, maar privileges worden niet gecontroleerd.
UC functionsRealSQL-functies worden echte DuckDB-macro's.
SQL statementsRealUitgevoerd op DuckDB; alle drie de formaten en beide dispositions.
EXTERNAL Delta tablesRealEchte Delta-bestanden; writes via Spark-job, reads via delta_scan().
JobsRealspark-submit in sibling containers, echte DAG-scheduling en logs.
SQL warehousesRealVolledige CRUD en lifecycle; statements draaien ongeacht de status.
Query history & saved queriesRealServer-backed, inclusief fouten.
AlertsRealCondities worden geëvalueerd tegen echte query-resultaten.
Workspace, DBFS & FilesRealBestandsgebaseerde opslag met chunked upload.
SecretsRealEchte CRUD; waarden alleen oplosbaar binnen job environment variables.
Web UI & notebooksRealAthena-stijl SQL-workspace op /ui en JupyterLab op /jupyter.
MCP serverRealOpt-in via MINILAKE_MCP=1: 67 tools, 8 resources, 4 prompts.
PersistenceRealMINILAKE_PERSIST=1 snapshot state als JSON bij afsluiten.
ClustersState machineCRUD plus timed lifecycle transitions; geen Spark compute (dit zit in Jobs).
Policies & instance poolsUnenforced CRUDValidatie werkt zodat bundles deployen, maar er is geen compute om te beperken.
PermissionsAllow-all CRUDEchte create/read/update, maar niets wordt afgedwongen.
Workspace adminUnenforced CRUDGit credentials, IP lists, etc. — wat een terraform apply aanraakt.
Identity & SCIMUnenforced CRUDGebruikers, groepen en service principals; records, geen credentials.
TokensUnenforced CRUDWaarde komt één keer terug, maar authenticeert niets.
Legacy SQL (preview/sql)RealQueries, alerts en dashboards via adapters over de moderne stores.
DLT, Model Registry, etc.Not implementedRetourneert 501 NOT_IMPLEMENTED.

Beperkingen en Ontwerpopzetten (Straight Talk)

MiniLake is specifiek ontworpen voor één ontwikkelaar die lokaal werkt, niet als een shared, multi-tenant server.

  • Geen authenticatie: Elk token wordt geaccepteerd en er is slechts één gebruiker. Stel dit nooit bloot buiten localhost.
  • Geen toegangscontrole: De Permissions API is real CRUD die altijd alles toestaat. Een geslaagde test hier zegt niets over grants in een echte workspace.
  • DuckDB, geen Spark SQL: Statements draaien op het dialect van DuckDB. Hiervoor is een dialect-resource in de MCP-server opgenomen.
  • Geen Spark op clusters: De cluster-lifecycle is een state machine. Echte Spark-executie vindt plaats via Jobs in sibling containers.
  • Single process, geen HA: DuckDB is single-writer, dus gelijktijdige load wordt geserialiseerd via een lock. Geschikt voor dev en CI, niet als deployment target.
  • MANAGED tables zijn onzichtbaar voor Spark: Dit zijn DuckDB-tabellen zonder bestanden. Gebruik EXTERNAL Delta wanneer Spark de data moet kunnen zien.