feat(tools): ensure-sort-keys — canonical DuckLake sort keys for the tenant column - #122
feat(tools): ensure-sort-keys — canonical DuckLake sort keys for the tenant column#122fuziontech wants to merge 4 commits into
Conversation
Codifies the DuckLake catalog secondary indexes as an idempotent routine. The stock catalog schema has no indexes on the listing/compaction predicates; on megaduck (677M stats rows, 499k snapshots) a per-query file listing seq-scanned for minutes. The set is the union of the two ad-hoc indexing efforts applied there (snapshot-visibility reads, compaction/metrics scans, ClickHouse-reader per-file fetches), named to match what is already live so adoption is a no-op. Implementation notes: - direct psycopg connection with autocommit: duckdb postgres_execute wraps statements in BEGIN, and CREATE INDEX CONCURRENTLY cannot run inside one (verified against megaduck); - every statement is CONCURRENTLY + IF NOT EXISTS: no write blocking, safe on every maintenance pass; - dry-run needs no catalog connection. Co-authored-by: Shelley <shelley@exe.dev>
This reverts commit 45be177.
Adds ducklake_file_column_stats_table_file_idx and ducklake_file_partition_value_table_file_idx ((table_id, data_file_id)) to the bootstrap-indexes umbrella, so the tenant maintenance CronJob applies them fleet-wide on its next pass. These serve the ClickHouse DuckLake readers table-scoped per-file fetches (WHERE table_id = ? AND data_file_id IN (...)); the shapes were validated live on megaduck, where they took the ClickHouse file listing from minutes to seconds. Both names already exist on megaduck, so adoption there is a no-op. This replaces the python-subcommand approach previously on this branch: the justfile bootstrap section (with psql + CONCURRENTLY) is the established home for catalog index recipes. Co-authored-by: Shelley <shelley@exe.dev>
…tenant column Sets SET SORTED BY (team_id[, timestamp]) on the megaduck tenant tables, idempotently (skips matching tables, RESETs+SETs changed ones). The DuckLake extension sorts new writes per file and honors the sort in compaction merges, so parquet row-group min/max stats become tight for the tenant column — the ClickHouse DuckLake reader row-group pruning then skips most of a file for `team_id = X` queries. Before this, every file spanned the full team_id range (verified live: avg per-file span 563k of ~566k teams) and team filters were full table scans (one OOM-killed a 26Gi pod). - ensure-sort-keys subcommand (duckdb-side DDL; safe inside the extension txn semantics, unlike the CONCURRENTLY index recipes which stay on psql) - just ensure-sort-keys recipe for the maintenance CronJob chain - integration tests on a real local DuckLake catalog (apply, idempotence, change-of-key RESET, dry-run, canonical-set pin) Already applied to megaduck imperatively (2026-08-19); this codifies it. Catalog version stays 1.0 (no reader-compat impact; verified with duckdb 1.5.5, the version viaduck and the maintenance image run). Co-authored-by: Shelley <shelley@exe.dev>
|
@fuziontech What about the row_id bug we saw? |
|
@jghoman good question — I traced the row-lineage interaction before calling this safe, and I think sort keys are clean here. Analysis (happy to be corrected if you saw something different): Deletes are physical-position based, so write-time sorting cannot invalidate them. DuckDB's delete machinery records Row lineage is stamped after the sort. Compaction is lineage-safe by construction: merge-adjacent skips delete-bearing files outright, tracks row-id adjacency across merge inputs (concatenates only adjacent ranges), writes explicit Verified empirically before the prod ALTER: post-ALTER inserts produce tight, disjoint row-group team_id spans ([0..31],[31..61],[61..92],[92..100]); plain sorted inserts carry no lineage columns; catalog version stays 1.0. If the bug you two saw was the reserved-field-id crash on millpond-written lineage columns (the one the reader hit pre-v4): orthogonal — that was reader strictness about Iceberg reserved-range field ids, already exempted on our port and upstream master. Worth noting sorted compaction will write explicit row_id columns more often (non-adjacent merges), so that exemption matters — and it's covered by tests. If it was a different row_id bug (e.g. something in the |
bill-ph
left a comment
There was a problem hiding this comment.
Reviewed current head 55b8261. No P0 blockers found.\n\nNon-blocking finding:\n- [P1] The change adds the ensure-sort-keys command and just recipe, but the repository documents that the maintenance sequence is assembled in the external composition. Please confirm the production CronJob composition invokes this new recipe; otherwise the sort keys remain a manual operation and the stated runtime benefit will not be applied automatically.\n\nThe index/sort-key implementation is idempotent and tests are green.\n\n— Robo Bill
Summary
Sets
SET SORTED BY (team_id[, timestamp])on the megaduck tenant tables — idempotently, from a newensure-sort-keyssubcommand +just ensure-sort-keysrecipe for the maintenance CronJob chain.Why
team_idis the universal filter column, but every DuckLake file spans the full team range (verified live on megaduck: avg per-file stats span = 563,336 of ~566k teams), so min/max pruning eliminates nothing and ateam_id = Xquery is a full table scan — one of these OOM-killed the 26Gi reader pod.With sort keys set, the DuckLake extension sorts new writes per file and honors the sort in compaction merges, so parquet row-group min/max stats become tight (verified locally: 4/4 row groups reduce to 1 relevant group for a single-team filter). The ClickHouse DuckLake reader already prunes row groups from embedded parquet stats — no reader change needed. Existing unsorted files improve progressively as compaction churns them.
Notes
ALTER TABLE … SET SORTED BY), safe inside the extension's transaction semantics — unlike theCONCURRENTLYindex recipes, which correctly stay on psql in the justfile.SET SORTED BYappends a sort spec rather than replacing, and the live spec is theend_snapshot IS NULLrow ofducklake_sort_info(both covered by tests).Testing
New integration tests on a real local DuckLake catalog: apply, idempotence, change-of-key RESET, dry-run, and a pin of the canonical set. Full suite: 744 passed.
Agent-authored (Shelley on exe.dev).