Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/skade

.nornir/serve-object-store-guide.md

Serving a MinIO/S3 warehouse over Iceberg-REST — guide

skade-katalog serve fronts a RedbCatalog over Iceberg-REST and/or gRPC. By default it assumes the Iceberg blobs live on the local filesystem. Build it with --features object-store and it can front a warehouse whose blobs live in S3/MinIO — i.e. the warehouse skade::Warehouse::open_with_store writes.

cargo build --release --features object-store   # in skade/serve

The two things that must match the writer

A warehouse is a redb file plus a place the blobs live. serve needs both, and both must be what the writer used:

writer (skade::Warehouse) serve
open_with_store(db_path, warehouse_uri, store) --db db_path
↑ 2nd argument, e.g. s3://red/skade-warehouse --warehouse-uri …
the S3Config behind store --s3-bucket/--s3-endpoint/…
catalog name (always skade) --catalog (already defaults to skade)

Mind the argument order. Warehouse::open_with_store takes (db_path, warehouse_uri, store) — the local redb file first. Passing them the other way round is silent: the redb lands in a directory literally named s3:/<bucket>/… under the writer's working directory, and the blobs go to a prefix named after the redb path. serve --db then opens a different, empty redb and reports {"namespaces":[]}.

Running it

export MINIO_ACCESS_KEY=…        # or SKADE_S3_ACCESS_KEY_ID / AWS_ACCESS_KEY_ID
export MINIO_SECRET_KEY=…        # or SKADE_S3_SECRET_ACCESS_KEY / AWS_SECRET_ACCESS_KEY

skade-katalog serve \
  --db          /var/lib/skade/warehouse.redb \
  --warehouse-uri s3://red/skade-warehouse \
  --s3-bucket   red \
  --s3-endpoint https://minio.example:9000 \
  --rest        0.0.0.0:8181

Credentials are taken from the environment only — never a flag, so they stay out of ps, shell history and systemd unit files. Path-style addressing is the default (MinIO needs it); pass --s3-virtual-hosted for real AWS-style hosts. --warehouse ROOT is not needed in this shape; it stays required when you serve a plain local file:// warehouse, exactly as before.

Check it:

curl -s localhost:8181/v1/namespaces
curl -s localhost:8181/v1/namespaces/main/tables
curl -s localhost:8181/v1/namespaces/main/tables/personer | head -c 400

loadTable returning a metadata document with an s3://… metadata-location means the whole path is live.

Or let the shipped check do all three and tell you PASS/FAIL. It writes a small table into a throwaway prefix, serves it, loads it back over REST and deletes what it wrote:

export MINIO_ACCESS_KEY=… MINIO_SECRET_KEY=…
export SKADE_LIVE_S3_ENDPOINT=https://minio.example:9000 SKADE_LIVE_S3_BUCKET=red
cargo run --release --features object-store --example live_object_store

It skips with one line (exit 0) when those variables are unset, so it is safe to wire into any pipeline. SKADE_LIVE_S3_KEEP=1 leaves the objects for you to inspect.

serve refuses to start rather than half-work

The failure this feature exists to remove is a catalog that lists every table and loads none. So the two pairings that produce it are checked before the socket binds:

--warehouse-uri file://… is fine in both shapes: relocating a local root is not the same as needing a store.

Pointing Apache Doris at it

CREATE CATALOG skade PROPERTIES (
  'type'                = 'iceberg',
  'iceberg.catalog.type'= 'rest',
  'uri'                 = 'http://<serve-host>:8181',
  'warehouse'           = 's3://red/skade-warehouse',
  's3.endpoint'         = 'https://minio.example:9000',
  's3.region'           = 'us-east-1',
  's3.access_key'       = '…',
  's3.secret_key'       = '…',
  'use_path_style'      = 'true'
);

Doris fetches the table pointer and metadata from serve and then reads the Parquet directly from MinIO with its own credentials — which is why the S3 properties appear on both sides.

Troubleshooting

symptom cause
{"namespaces":[]} --db points at a redb that isn't the writer's (redb creates an empty one silently). Check the file exists and is non-empty before starting serve.
namespaces list, loadTable 404 "No such file or directory" serving an object-store warehouse without --features object-store / without the --s3-* flags: the front is looking on the local disk for an s3:// path.
HTTP 403 … InvalidAccessKeyId credentials not in the environment, or the wrong ones.
s3 put … HTTP 403 while writing the same, on the write side. It used to be silent — every put "succeeded" and the breakage surfaced later as an unparseable metadata document.
HTTP 404 … NoSuchKey --warehouse-uri/--s3-bucket don't match what the writer used.
Failed to parse json string … expected value at line 1 column 1 you are on a pre-2026-08 skade: that is S3's XML error document being read as Iceberg metadata. Upgrade and the same run names the status, the operation and the key.
--warehouse-uri … is not on this filesystem: it needs --s3-bucket exactly what it says; serve refuses rather than come up half-working.
redb "already open" another process (often a still-running writer) holds the write lock. serve is the single lock owner by design.