Skip to content

The DuckLake catalog answers a call that exits as an error, so retention, GC and the compactor keep their state (T-464) - #329

Merged
chasers merged 2 commits into
t-462-claim-test-telemetryfrom
t-464-catalog-try-layer
Sep 11, 2026
Merged

The DuckLake catalog answers a call that exits as an error, so retention, GC and the compactor keep their state (T-464)#329
chasers merged 2 commits into
t-462-claim-test-telemetryfrom
t-464-catalog-try-layer

Conversation

@chasers

@chasers chasers commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Tracker: T-464. Stacked on #328 (T-462).

Every Smolquery.Catalog callback already promised {:error, term()}, but every statement the DuckLake implementation ran was a bare GenServer.call: a read that timed out behind a busy connection, or found the connection gone, exited its caller. The compactor had grown its own catch for that (T-460); Retention.run/1 and GC.run/1 still crashed, and GC's crash reset its grace-period candidates, so a catalog engine timing out each sweep meant GC never deleted anything.

  • Engine.try_transaction/3 joins try_query/4.
  • DuckLake's query/4 and its five transaction sites go through the try layer, so an exit comes back as {:error, %CallExited{}} to every caller.
  • The compactor drops its listing and read wrappers; the swap still tags {:swap_failed, ...} so the log names the phase, and one catch stays for an exit the catalog never sees (a store put whose HTTP pool died).

Tests: try_transaction on a missing and a busy connection; the catalog answering {:error, %CallExited{}} from a read and a commit; a retention sweep failing instead of crashing on a dead catalog connection; the compactor's existing exit tests re-shaped.

🤖 Generated with Claude Code

https://claude.ai/code/session_014K63Kxq4pf7eD9K1416Eju

Chase Granberry and others added 2 commits September 11, 2026 21:15
…ion, GC and the compactor keep their state (T-464)

Every Catalog callback already promised {:error, term()}, but every
statement the DuckLake implementation ran was a bare GenServer.call: a
read that timed out behind a busy connection, or found the connection
gone, exited its caller. The compactor had grown its own catch for that
(T-460); Retention.run/1 and GC.run/1 still crashed, and GC's crash reset
its grace-period candidates, so a catalog engine timing out each sweep
meant GC never deleted anything.

Engine.try_transaction/3 joins try_query/4, and DuckLake's query/4 and its
five transaction sites go through the try layer, so an exit comes back as
{:error, %CallExited{}} to every caller. The compactor's listing and read
wrappers go; its swap tags the error {:swap_failed, ...} so the log names
the phase, and one catch stays for an exit the catalog never sees — a
store put whose HTTP pool died mid-upload.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014K63Kxq4pf7eD9K1416Eju
…ention stops at the first exit, the swap's exit keeps its bare shape, and the engine's two try functions share one catch

With the catalog answering exits as errors, PgCatalog.listed_tables/1
collapsed any error to [] and rebuilt pg_namespace and pg_class empty, so a
psql client asking \dt during a wedge saw a database with no tables and
the empty build was cached for the TTL. A refresh that cannot read the
catalog now answers {:error, {:pg_catalog_unavailable, _}} and leaves the
generated tables and the timestamp as they were, which is what main's
crash-and-restart gave the client.

Retention.run/1 mapped every table and turned each exit into a per-table
failure, so a wedged connection cost one timeout per table instead of one;
it now stops at the first exit and reports the rest as deferred, like the
compactor. The compactor's replaced/4 existed only to re-tag the swap's
exit as :swap_failed, which nothing branched on; the bare struct is the
one shape. Engine.try_query/4 and try_transaction/3 share catching_exit/1.

Smolquery.Test.ExitingCatalog wraps a real catalog and answers chosen
calls with %CallExited{}, since a real wedge cannot be staged
deterministically.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014K63Kxq4pf7eD9K1416Eju
@chasers
chasers force-pushed the t-464-catalog-try-layer branch from 145d22a to 4fada2c Compare September 11, 2026 21:15
@chasers
chasers merged commit d62040c into main Sep 11, 2026
11 of 13 checks passed
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