Repository navigation
Conversation
A filtered scan now derives its read schemas once per distinct read projection instead of per fragment, and shares a FragmentOpenCache through FragReadConfig so the per-file schema, field-to-column map and ReaderProjection are computed once per data-file layout rather than once per fragment. Scan opens also pass the ReaderProjection as the base projection on the full-metadata path, and FileReadCore no longer builds a whole-schema projection it then discards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified in cache isolation, concurrent insertion, schema reuse, or projection handling.
No open issues 🎉
What changed in this PR
Reduces repeated setup when filtered scans open many fragments, building on the shared filter planning in #93. The optimization reuses scan-local schemas and reader-open state without changing the file format.
Changes:
- Shares derived read schemas across fragments with the same read projection.
- Caches reader-open state for up to 16 layouts, guarded by dataset and projection identity.
- Avoids unused whole-file projection construction and adds cache, concurrency, and schema-evolution tests.
| File | Description |
|---|---|
rust/lance/src/io/exec/filtered_read.rs |
Shares schemas and caches across scan fragments; adds integration coverage. |
rust/lance/src/dataset/fragment.rs |
Adds the bounded layout cache and optional FragReadConfig::open_cache field. |
rust/lance-file/src/reader.rs |
Constructs the whole-file projection only when needed. |
Review effort: Balanced
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related
Problem
A filtered scan opens every fragment it can't rule out with an index, and each open repeats the same setup.
For every data file, it projects the full table schema down to the file's fields (
data_file.schema), intersects that with the read projection, builds a field-id → column map, and derives aReaderProjection.read_fragmentalso rebuilds the output and read schemas for every fragment.None of this depends on the fragment.
It depends only on the table schema, the read projection and the file's layout (field ids, column indices, format version), and fragments written the same way share a layout.
On tables with many small unindexed fragments, this per-fragment setup costs far more than reading the data: about 0.35 ms of CPU per fragment even after #93.
Solution
Compute that state once per layout per scan, and reuse it for every fragment with the same layout.
plan_to_scoped_fragmentsbuilds the read schemas once per distinct read projection and shares them across fragments.FragmentOpenCache, carried inFragReadConfig.open_cache, memoizes the per-file state for each data-file layout.BaseSchedulerscarries a per-scan cache inFragReadConfig.Wins
CPU per filtered scan on top of #93 (4-column projection, release build, 6 cores):
With #93 that's −35% to −43%.
On a dataplatform stack, incremental registration used 19% less writer CPU per segment.
Safety
Arc, the same projectionArc, and a data file with equal field ids, column indices and format version. Those are the only inputs to the cached values. The cache holds bothArcs, so a different object can never be mistaken for them.Testing
lance --lib2699 passed;lance-file141 passed;lance-datafusion85 passed. Clippy (-D warnings) and rustfmt are clean.Compatibility
FragReadConfig::open_cache, defaulting toNone. Callers that buildFragReadConfigwith struct-literal syntax need to add it.Agent
🤖 This PR was opened by a coding agent (Claude Opus 5.5).