Skip to content

Fix README.md image - #1

Merged
jghoman merged 2 commits into
mainfrom
fix-readme-image
Mar 21, 2026
Merged

jghoman merged 2 commits into
mainfrom
fix-readme-image

Conversation

@jghoman

@jghoman jghoman commented Mar 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fix README image reference
  • Add inline image file
  • Remove .claude/settings.local.json from tracking and add to .gitignore

Test plan

  • Verify README image renders correctly on GitHub

@jghoman
jghoman merged commit 35fc546 into main Mar 21, 2026
10 checks passed
jghoman added a commit that referenced this pull request Mar 21, 2026
pa.concat_tables(pending) raises ArrowInvalid when Arrow tables from
different convert() batches have different schemas (different key sets).
Add promote_options="default" to handle missing columns by filling with
nulls. Applied to both the main flush path and the final flush on shutdown.
jghoman added a commit that referenced this pull request Mar 21, 2026
…view (#4)

* Fix concat_tables crash on heterogeneous schemas (#1)

pa.concat_tables(pending) raises ArrowInvalid when Arrow tables from
different convert() batches have different schemas (different key sets).
Add promote_options="default" to handle missing columns by filling with
nulls. Applied to both the main flush path and the final flush on shutdown.

* Fix JDBC prefix in k8s manifest that breaks urlparse (#2)

DUCKLAKE_METADATA_URL was set to "jdbc:postgresql://..." but ducklake.py
parses it with Python's urlparse(), which doesn't understand JDBC URLs.
With the jdbc: prefix, scheme becomes 'jdbc' and hostname/port/path are
mangled. The docker-compose already uses the correct postgresql:// form.

* Fix SQL injection via env vars in DuckDB SET statements (#3)

S3 config values from environment variables were interpolated directly
into SET SQL statements without validation. Add whitelist regex
validation via _sanitize_setting_value() that rejects values containing
quotes, semicolons, or other dangerous characters.

* Validate table name against safe identifier regex at config load (#4)

DUCKLAKE_TABLE was interpolated directly into SQL in ducklake.py and
schema.py without validation. Add _SAFE_TABLE_NAME regex check at
config load time to reject table names with unsafe characters.

* Improve arrow converter: preserve int precision, optimize schema inference (#5/#7/#8/#9)

- Cast integers to int64 (not float64) to preserve precision for values
  > 2^53 like Snowflake IDs and nanosecond timestamps. Floats still go
  to float64. Cross-batch type differences handled by promote_options.
- Single-pass _build_schema: collect all keys and first non-null samples
  in one loop instead of O(keys x records) nested scan.
- Use pa.array([sample]).type for type inference instead of creating a
  pa.Table per field.
- Build all columns in one pass instead of N table.set_column() calls.

* Normalize DuckDB type names to prevent schema evolution thrash (#6)

DuckDB's information_schema returns "TIMESTAMP WITH TIME ZONE" while
our _ARROW_TO_DUCKDB mapping uses "TIMESTAMPTZ". This mismatch caused
spurious ALTER TABLE SET DATA TYPE on every flush. Add normalization
when loading schema from information_schema.

* Cache _ensure_table to avoid CREATE TABLE IF NOT EXISTS every flush (#10)

After the first successful call, _ensure_table is a wasted metadata-DB
round-trip. Cache table names in a module-level set and skip SQL on
subsequent calls.

* Add write retry with backoff, move lag sampling to periodic (#11/#12)

- Add _write_with_retry(): 3 attempts with exponential backoff (1s, 2s)
  to survive transient S3/Postgres errors without killing the pod.
- Move watermark offset queries from every-flush to every-60s to avoid
  blocking the main loop. For 512 partitions, this reduces worst-case
  blocking from ~42 minutes to a single 60s-gated sample.
- Split committed offset tracking (always, cheap) from watermark queries
  (periodic, network RPC).

* Fix health check for idle topics, add readiness/liveness separation (#14/#15)

- Health check required recent flush, but idle topics never flush. After
  max_flush_age_s (600s) the pod went unhealthy and K8s killed it. Now
  only check flush recency if at least one flush has occurred.
- Split is_healthy() into is_alive() (liveness) and is_ready() (readiness).
  Liveness: started + recently polling. Readiness: alive + flush ok.
- Add /readyz endpoint. K8s livenessProbe uses /healthz (is_alive),
  readinessProbe uses /readyz (is_ready).

* Fix ArrowInvalid crash on nested structs with null inner fields

When _build_schema's first non-null sample for a dict field contained
null inner values (e.g. {"referrer": null, "screen_width": 1920}),
pa.array() inferred a null-typed struct field. Later records with
actual string values for that field then crashed with ArrowInvalid.

Fix: when collecting samples for type inference, prefer dict samples
where all inner values are non-null. Falls back to the first non-null
sample if no fully-populated sample exists.

Found via docker-compose integration testing — pod 0 crashed on
partitions where the first consumed batch had null inner struct fields.

* Fix DuckLake ATTACH to use Postgres for metadata storage

The ATTACH string was 'ducklake:host=... dbname=...' which causes
DuckLake to use a local file for metadata instead of Postgres. The
correct syntax requires a 'postgres:' prefix in the connection string:
'ducklake:postgres:host=... dbname=...'. Without this, metadata was
ephemeral to each pod's in-memory DuckDB connection.

Ref: https://ducklake.select/docs/stable/duckdb/usage/connecting
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