Skip to content

Experimental image: raster + zarr DuckDB extensions on a separate tag and endpoint - #358

Merged
cboettig merged 3 commits into
mainfrom
experimental/raster-zarr
Aug 7, 2026
Merged

Experimental image: raster + zarr DuckDB extensions on a separate tag and endpoint#358
cboettig merged 3 commits into
mainfrom
experimental/raster-zarr

Conversation

@cboettig

@cboettig cboettig commented Aug 7, 2026

Copy link
Copy Markdown
Member

Groundwork for #354 (streams: raster/zarr reads), kept off the default image.

Approach

Rather than adding extensions to the production image, this makes the extra-extension set a deployment knob and ships a second image/endpoint that turns it on:

  • EXTRA_DUCKDB_EXTENSIONS (comma-separated) is LOADed per connection, alongside the existing SETUP_SQL statements. Unset on the default image, so duckdb-mcp behaves exactly as before — same extensions, same tool description, same bytes of injected guidance.
  • Dockerfile.experimental installs raster + zarr and sets the variable; docker-experimental.yml publishes it under its own tag.
  • k8s/experimental-{deployment,service,ingress}.yaml run it as a separate endpoint, so an experiment can't take down the production head.
  • experimental-extensions.md carries the guidance for the extras and is injected only when the extras are actually loaded, so the default deployment's query tool description is unchanged.

Safety notes

  • Extension names are validated against ^[A-Za-z0-9_]+$ before interpolation — LOAD takes no bind parameters, so this is the guard against injection through the env var.
  • A failed LOAD warns to stderr rather than raising: a missing extension degrades that connection's capability instead of failing every query the replica serves. Same posture as the existing SETUP_SQL loop.

Status

Not wired into any production path. The default image and the duckdb-mcp deployment are untouched; this only adds files plus the opt-in block in server.py. Merging it does not change what prod serves.

Split out of the branch these commits were already sitting on so they can be reviewed on their own.

…dpoint

Adds a second image (Dockerfile.experimental -> :experimental) layered on the
default one, carrying the duckdb raster and zarr community extensions, plus a
testing-only endpoint at experimental-duckdb-mcp.nrp-nautilus.io.

raster gives RT_ReadCells/RT_Read (GDAL) so a small GeoTIFF can be joined to
catalog GeoParquet directly, without a hex ingest first; zarr gives read_zarr.
Both stay off the default endpoint: raster pulls GDAL into the query path, and
RT_ReadCells materializes one row per pixel, which is only safe for small
rasters.

One codebase serves both tags. server.py LOADs whatever EXTRA_DUCKDB_EXTENSIONS
names per connection (unset on the default image, so behaviour is unchanged) and
injects experimental-extensions.md into the query tool description only where the
extras are actually loaded. Extension names are restricted to identifier
characters since LOAD takes no parameters.

The guidance carries the two traps found while testing: RT_ReadCells emits
GEOMETRY('EPSG:4326') while catalog GeoParquet is GEOMETRY('OGC:CRS84'), so the
join needs ST_SetCRS; and unfamiliar rasters need a cols*rows check before read,
with the hex asset as the answer for anything large.
The raster extension vendors a GDAL whose libcurl is built with the RedHat CA
path (/etc/pki/tls/certs/ca-bundle.crt), which does not exist on the python:slim
Debian base. Every /vsicurl/ read failed with 'CURL error: error setting
certificate file' while RT_* functions themselves loaded fine, so the extension
looked healthy right up until it touched the network.
The area advice was wrong in the direction that matters. It told models to assign
each pixel an H3 cell and sum h3_cell_area(), which charges every pixel a whole
hex's area rather than the pixel's own — inflating the test fire by ~29% (110.5 ha
against a true 85.9). h3_cell_area is for hex rows, not raster rows.

Replaced with the pixel size from the raster's own geotransform, including the
element order, since $.transform is [originX, dx, rotX, originY, rotY, dy] and the
pixel size is at indices 1 and 5 — indexing 0 and 4 silently yields zero area.

Found by running the documented recipe against the Eureka fire on the live
endpoint and reconciling with the published R analysis (87.16 ha).
@cboettig
cboettig merged commit bf74991 into main Aug 7, 2026
2 checks passed
@cboettig
cboettig deleted the experimental/raster-zarr branch August 7, 2026 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant