Skip to content

Query parquet/csv pins in place via DuckDB views - #232

Draft
cpsievert wants to merge 3 commits into
feat/prewarm-cachingfrom
feat/duckdb-pin-views
Draft

Query parquet/csv pins in place via DuckDB views#232
cpsievert wants to merge 3 commits into
feat/prewarm-cachingfrom
feat/duckdb-pin-views

Conversation

@cpsievert

Copy link
Copy Markdown
Collaborator

What changes

Board-backed data sources previously loaded every pin eagerly into the in-process DuckDB: first use of a table meant pin_read() into R followed by dbWriteTable() — a full copy of the data through R's memory for formats DuckDB could have read itself. This PR registers parquet and CSV pins as zero-copy views over the downloaded file in the pins cache instead. Formats DuckDB doesn't read with built-in table functions (RDS, qs2, arrow, uploads) keep the eager path unchanged.

What this means in practice

  • First use of a large parquet pin: a CREATE VIEW (milliseconds) instead of a full read + dbWriteTable() copy.
  • Queries against pin-backed tables read only the columns and row groups they need, via DuckDB's pushdown against the file.
  • With the pins cache warmed ahead of time (prewarm_sources()), first use is just view registration — there's nothing left to load in-process.

The key ideas

Views are as stable as the pin version they point at. A table reflects the pin version resolved at first use, same as the eager path. When the underlying file disappears (a rewrite on a non-versioned board, cache pruning), the next failed query re-resolves the pin's latest version and retries once, transparently; a persistent failure surfaces the original error.

The lockdown relaxes to exactly one directory. The source's DuckDB connection stays locked down (no extensions, no filesystem access), but now allowlists the board's pin root via allowed_directories so views can read pin files — and nothing else. Boards with an unknown layout skip the allowlist and load eagerly as before.

The pins cache becomes single-writer. pins has no cache locking, so a background prewarm download racing a first-use pin_read() could leave a truncated cache entry. Both sides now take an exclusive filelock per board cache + pin (the new filelock dependency); lock files are left behind deliberately, since unlinking one a waiter holds would break the exclusion.

Also in this PR

  • source_describe() gets the same dangling-view retry as source_query().
  • Format changes across pin versions are handled in both directions (view replacing an eager table and vice versa).
  • Tests cover view registration (parquet/CSV), the eager fallback (RDS), and transparent re-resolution after a non-versioned board rewrite.
  • Review fixes on top: the cache_hit span attribute now probes the store path without filesystem side effects, cache-state-mutating tests snapshot/restore that state, and the view tests go through data_source_state() (sources are R6 since Make data_source(), semantic_layer(), and context_layer() output R6 #228).

First use of a pinned table used to pin_read() the data into R and
dbWriteTable() a copy into DuckDB. For the formats DuckDB reads natively
with built-in table functions (parquet, csv), first use now registers a
view over the pin's versioned file instead: no copy, no R round-trip,
and queries get column and predicate pushdown. The view references the
resolved version's path, so it is as stable as the version itself.

The locked-down connection gains an allowed_directories exception scoped
to exactly the board's pin root (its cache for remote boards, its
directory for folder boards), set before external access is disabled --
pin files become the only files the agent's queries can read. Extension
readers (e.g. read_json_auto) stay off the view path because autoload is
disabled; json/rds/qs2/arrow pins keep the eager path.

A view dangles if its file disappears after registration (a rewrite on a
non-versioned board deletes the old version's directory, or the cache is
pruned). source_query()'s error-driven retry loop and source_describe()'s
sample now re-resolve the latest version and re-register -- view again,
or an eager load if the format changed -- before surfacing an error.
- The cache_hit span attribute in prewarm_context() probes the store path with the side-effect-free context_cache_dir() resolver (context_store_path() gains an injectable cache_dir), so recording telemetry cannot create directories or trigger the fallback warning.
- Add local_context_cache_state() to snapshot/restore the package-level cache housekeeping state in tests that poke it.
- Fix view tests to reach connection/pending state via data_source_state() (sources are R6 since #228).
- Document the deliberate cache-before-path field order in board_pin_root().
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