Published

duck-server

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

duck-server

DuckDB como motor de cómputo+escritura del Warehouse, detrás de Junction. Cáscara fina (FastAPI) que ATTACHea el catálogo Lakekeeper (Iceberg REST) y lee/computa sobre Iceberg en R2. Espeja el patrón de services/ml-runner. Spec: docs/architecture/duckdbengine.md.

Fase: F1 (seed read-first). El setup DuckDB↔Lakekeeper↔R2 quedó validado en F0 (scripts/duckdb). /query bloquea DML por defecto; abrir writes es F4 (gobernado por la facade/Junction).

Endpoints

MétodoRutaAuthQué hace
GET/healthpública200 si el motor está adjunto; ?deep=1 sonda el catálogo.
POST/queryBearer*Ejecuta SQL → Arrow IPC (format:"json" opc.). Guard read-only (DML necesita allow_writes:true).
GET/tablesBearer*Lista tablas del catálogo (duckdb_tables()).
GET/configBearer*MODE auto-descriptivo (warehouse, schemas, vending off).

* Bearer solo si DUCK_SERVER_TOKEN está seteado; en F1 puede ir abierto en la red privada.

POST /query

{ "sql": "SELECT * FROM lake.datasets.ds_toydemo001 LIMIT 10",
  "format": "arrow",        // "arrow" (default) | "json"
  "max_rows": 1000,          // tope (default MAX_QUERY_ROWS=100k)
  "allow_writes": false }    // true → permite DML (F4)

Respuesta arrow: application/vnd.apache.arrow.stream + headers X-Row-Count, X-Truncated. La respuesta Arrow es la canónica (maneja TIMESTAMPTZ sin pytz).

Diseño (decisiones que encarna)

  • Creds R2 estáticas + ACCESS_DELEGATION_MODE 'none' — el vending Lakekeeper→DuckDB está roto (#792); ver spec §7.2.
  • INSTALL/LOAD iceberg+httpfs+icu al boot (icu obligatorio por __created_at TIMESTAMPTZ). Extensiones pre-descargadas en la imagen (Dockerfile) → boot sin egress.
  • Introspección por duckdb_tables()/duckdb_columns() (un catálogo REST adjunto no expone information_schema).
  • Una conexión serializada (lock) + queries en threadpool. Migrar a cursor-por-query cuando la concurrencia lo pida.
  • Motor stateless: las tablas viven en Iceberg/R2, no en disco local → escalable/reciclable.

Deploy en Railway

Servicio dedicado (espeja karma/ml-runner), monorepo Carbon:

  1. Servicio "Duck" → Root Directory = services/duck-server (Railway detecta el Dockerfile).
  2. Variables: el set LAKEHOUSE_* (ver .env.example) — idealmente reference vars de ml-runner (${{ml-runner.LAKEHOUSE_S3_SECRET_ACCESS_KEY}}…). PORT lo inyecta Railway.
  3. (Opcional) DUCK_SERVER_TOKEN para exigir Bearer.

Local

pip install -r requirements.txt
# exporta las LAKEHOUSE_* (o pon un .env local — NO lo commitees)
uvicorn app.main:app --reload --port 8000
curl localhost:8000/health
curl -s localhost:8000/tables | head

Siguiente (spec §5)

F2 (information_schema real vía duckdb_columns() → arregla character_maximum_length) · F3 (flip read, retirar lib/warehouse/query/dialect.ts) · F4 (DML gobernado) · F5 (Junction formal, lib/compute/gateway.ts).