Published

F0 · DuckDB ↔ Lakekeeper ↔ R2 smoke

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...

F0 · DuckDB ↔ Lakekeeper ↔ R2 smoke

Primer paso del major DuckDB = motor de cómputo+escritura del Warehouse (detrás de Junction). Spec completo: docs/architecture/duckdbengine.md.

Objetivo de F0 (inerte, cero cambio en el editor): probar que DuckDB puede ATTACH el catálogo Lakekeeper vivo, servir un information_schema real del catálogo, y leer data de una tabla Iceberg en R2. Eso valida catalog-binding + creds R2 + motor DML-capable en nuestra infra antes de F1+.

Decisiones que encarna (§7 del spec)

  • Deployment = servicio dedicado (duck-server), F1+. F0 es agnóstico → basta este script.
  • Creds R2 = llaves estáticas interim. ⚠️ El credential-vending Lakekeeper→DuckDB está roto hoy (DuckDB reutiliza creds scoped a metadata_path para el data_path403 en data files; PyIceberg no). Issues: duckdb-iceberg #792, #670, duckdb #19185. Por eso SECRET s3 estático + ATTACH … ACCESS_DELEGATION_MODE 'none'. El vending SOTA queda diferido, gated en esos issues.

Cómo correr

Ruta recomendada — job one-off en Railway (junto a ml-runner)

Ahí ya viven los LAKEHOUSE_* y hay red al Lakekeeper. En un one-off del servicio ml-runner:

pip install duckdb pytz       # duckdb ≥1.5.3 (validado con 1.5.4); pytz para columnas tz
python scripts/duckdb/f0_smoke.py

Local

Requiere DuckDB ≥1.5.3 y los ~7 secretos exportados (o en un .env que cargues antes). .env.local del repo es el spike SQLite — no trae REST URI / OIDC / llaves R2.

pip install duckdb pytz
# exporta las LAKEHOUSE_* (ver más abajo) …
python scripts/duckdb/f0_smoke.py --smoke-table lake.datasets.ds_<uuid>

Env requerido (contrato = services/ml-runner/.env.example)

VarQué es
LAKEHOUSE_REST_URIendpoint Iceberg REST (…/catalog)
LAKEHOUSE_REST_WAREHOUSEwarehouse (prod: lakehouse)
LAKEHOUSE_CATALOG_OAUTH2_SERVER_URItoken endpoint Keycloak
LAKEHOUSE_CATALOG_CREDENTIAL<client-id>:<client-secret>
LAKEHOUSE_CATALOG_SCOPEscope OIDC
LAKEHOUSE_S3_ENDPOINThttps://<account>.r2.cloudflarestorage.com
LAKEHOUSE_S3_ACCESS_KEY_ID / LAKEHOUSE_S3_SECRET_ACCESS_KEYllaves R2 estáticas
LAKEHOUSE_S3_REGIONdefault auto
SMOKE_TABLE (opc.)FQN a contar; si falta, se autoelige la 1ª tabla

El runner no imprime secretos (enmascara los CREATE SECRET).

Qué valida (salida esperada)

  1. count(*) de information_schema.tables > 0 → catalog-binding + info_schema real (esto arregla de raíz el bug character_maximum_length, F2).
  2. Muestra de 10 tablas del catálogo vivo.
  3. count(*) sobre 1 tabla main.default.ds_*lectura de data en R2 con creds estáticas.

✅ Estado: F0 VERDE (validado 2026-07-09, DuckDB 1.5.4)

Ejecutado contra el Lakekeeper de prod: ATTACH OK (OIDC keycloak), 23 tablas en el catálogo, lake.bench.ds_* → 1.500 filas y SELECT * LIMIT 5 devolvió data real de R2sin 403, el workaround de creds estáticas funciona. Hallazgos que ya están reflejados en el .sql/runner:

  • information_schema de un catálogo Iceberg REST adjunto NO existe (lake.information_schema → error). La introspección real va por duckdb_tables() / duckdb_columns() (esto es lo que F2 usará para arreglar el bug character_maximum_length).
  • Los namespaces de Lakekeeper se ven como schemas planos (bench, datasets, main), no main.default.
  • INSTALL icu; LOAD icu; es obligatorio — las tablas ds_* nativas llevan __created_at TIMESTAMP WITH TIME ZONE.
  • Materializar esa columna tz a Python requiere pytz (pip install pytz). El duck-server real (F1) debe devolver Arrow, que maneja tz sin pytz — este smoke usa el cliente Python, por eso lo necesita.
  • count(*) en Iceberg se responde por metadata → NO prueba lectura de data files. El Smoke 4 (SELECT *) es el que fuerza el read real de R2.
  • 403 en el Smoke 4 pese a creds estáticas = firma del bug de vending: verificar ACCESS_DELEGATION_MODE 'none' en el ATTACH y el endpoint/URL_STYLE del SECRET r2.

Siguiente tras F0 verde

F1 (read shadow en el SQL Editor internal-mode) → F2 (information_schema real) → F3 (flip read, retirar lib/warehouse/query/dialect.ts) → F4 (DML) → F5 (Junction formal). Ver spec §5.