Skip to content

fix(mem_wal): push LIMIT/OFFSET down to fresh-tier scan sources#7256

Merged
hamersaw merged 1 commit into
lance-format:mainfrom
hamersaw:bug/wal-pushdown-limit
Jun 16, 2026
Merged

fix(mem_wal): push LIMIT/OFFSET down to fresh-tier scan sources#7256
hamersaw merged 1 commit into
lance-format:mainfrom
hamersaw:bug/wal-pushdown-limit

Conversation

@hamersaw

Copy link
Copy Markdown
Contributor

Summary

The LSM scan planner (LsmScanPlanner) applied a query's LIMIT/OFFSET above the per-source union, so every flushed generation and the base table were fully scanned before the limit was imposed at the top. For a bounded read over a fresh tier with many flushed generations this materialized the entire tier on every query.

This threads an overfetch_factor through LsmScannerLsmScanPlanner and pushes a per-source fetch of (offset + limit) * overfetch into each Base/Flushed source scan. The active memtable is exempt (its within-source dedup needs the full match set). Block-listed sources over-fetch by overfetch_factor so cross-generation PK dedup still leaves ~limit live rows; a LocalLimit re-imposes the exact skip/fetch cap above the merge. Unbounded reads (no limit) are unchanged.

Changes

  • scanner/planner.rs: overfetch_factor field + with_overfetch_factor on LsmScanPlanner; per-source limit pushdown in plan_scan; fetch parameter on build_source_scan (pushed into Base/Flushed scans, ActiveMemTable exempt).
  • scanner/builder.rs: overfetch_factor on LsmScanner + with_overfetch_factor, threaded into both the scan and FTS planners.

Validation

Validated end-to-end against a WAL benchmark on minikube with object storage behind a latency proxy: a bounded offset+limit read over an 18-generation fresh tier dropped from a full-tier scan (~360 generation scans) to a bounded per-source read.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the bug Something isn't working label Jun 12, 2026
The LSM scan planner applied a query's LIMIT/OFFSET above the per-source
union, so every flushed generation and the base table were fully scanned
before the limit was imposed at the top. For a bounded read over a many-
generation fresh tier this materialized the entire tier per query.

Thread an `overfetch_factor` through `LsmScanner`/`LsmScanPlanner` and push
a per-source fetch of `(offset + limit) * overfetch` into each Base/Flushed
source scan (the active memtable is exempt). Block-listed sources over-fetch
so cross-generation PK dedup still leaves ~limit live rows; a `LocalLimit`
re-imposes the exact cap above the merge. Unbounded reads are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hamersaw hamersaw force-pushed the bug/wal-pushdown-limit branch from fccd679 to 1211c17 Compare June 15, 2026 16:00
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 36.00000% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
rust/lance/src/dataset/mem_wal/scanner/builder.rs 16.66% 8 Missing and 2 partials ⚠️
rust/lance/src/dataset/mem_wal/scanner/planner.rs 53.84% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

flushed_cache: Option<Arc<FlushedMemTableCache>>,
/// Over-fetch multiple for block-listed sources in search plans
/// (see [`super::LsmFtsSearchPlanner::with_overfetch_factor`]).
overfetch_factor: Option<f64>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We call it refine factor?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh better to understand whether it's similar to refine factors in vector / fts
not saying we should use refine factor

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point. I'm trying to keep these separate because they are logically different things. IIUC the refine_factor is used because IVF distances may not be exact, so we over retrieve so that we can become more accurate. The proposed overfetch_factor is strictly for probabilistically ensuring we have K values after we remove duplicates. This is a simple solution for the problem, but ultimately we will probably want to fix it in another way. So by separating them, we can remove the overfetch_factor more easily.

@hamersaw hamersaw merged commit 3c2e36e into lance-format:main Jun 16, 2026
31 checks passed
@hamersaw hamersaw deleted the bug/wal-pushdown-limit branch June 16, 2026 08:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants