Commit d8542b5
perf!: run scheduler initialize eagerly in async read_tasks (#6710)
BREAKING CHANGE: the rust file reader's read methods are now async. This
is to allow the caller control over when the scheduler initialization
and inline scheduling occurs so that they can parallelize this work
across fragments, if appropriate.
## Summary
Fix the v6→v7 inline-scheduling regression by running the decode
scheduler's `initialize` I/O eagerly on the awaiting task instead of
smuggling it into the returned stream's first poll.
This is offered as an alternative to #6709 (which reverted #6637
entirely): we keep the inline-scheduling optimization for small reads,
but make the work explicit and properly parallelized across fragments.
## Background
#6637 introduced an "inline scheduling" path that, for small reads,
attached the scheduler future to the front of the returned stream via
`flatten_stream` and only ran it on first poll. Combined with the
per-fragment `try_flatten` in `FilteredReadExec`
(`rust/lance/src/io/exec/filtered_read.rs:455`) and `LanceScanExec` —
both of which poll one inner stream at a time — this serialized the
scheduler's `initialize` I/O across fragments.
`StructuralPrimitiveFieldScheduler::initialize`
(`rust/lance-encoding/src/encodings/logical/primitive.rs:3422`) does a
real `io.submit_request(...).await` for chunk metadata. The cache is
per-file (the `FieldDataCacheKey` is column-scoped within a file's
metadata cache), so every fragment open misses. With 800 small fragments
× tens of ms of S3 latency, the inline path was catastrophic.
## Repro
[gist](https://gist.github.com/wkalt/e080fc9ddff6edd8eaee5ab50a069fbe) —
400k rows / 800 fragments × 500 rows, KNN brute force, no index:
| | before fix | after fix |
|-----------------------|---------------|-----------------|
| default | ~60–66 ms | **~48–52 ms** |
| `LANCE_INLINE_SCHEDULING_THRESHOLD=0` (spawn) | ~46 ms | ~50–52 ms |
Default and spawn are now matched. Cross-fragment-count ablation shows
no regression at any scale (default tracks spawn ±2 ms across
rpf=500/2000/8000/50000).
## Approach
The goal was to make the scheduling work explicit, not "smuggled into
the poll of the first batch."
1. **`schedule_and_decode` is now `async`**
(`rust/lance-encoding/src/decoder.rs`). It awaits
`DecodeBatchScheduler::try_new` (which runs `initialize`) before
returning. For the inline branch, it then runs the synchronous
`schedule_ranges` / `schedule_take` work in line, leaving a fully primed
decode stream. The non-inline branch still spawns the scheduling task so
it can overlap with decoding.
2. **Cascade async through the file-reader surface.** All of
`FileReader::read_tasks`, `read_range`, `read_ranges`, `read_stream`,
and `read_stream_projected` are now `async`. Each got a "Why is this
async?" doc paragraph explaining that the decode scheduler's metadata
I/O happens on the awaiting task rather than on the consumer that polls
the stream.
3. **`GenericFileReader` trait methods return `BoxFuture<'_,
Result<ReadBatchTaskStream>>`.** V1Reader, the v2 adapter `Reader`, and
`NullReader` updated. `FragmentReader::{read_range, read_all,
read_ranges, take_range}` and `new_read_impl` are now async;
`new_read_impl` uses `try_join_all` so per-data-file `initialize` I/Os
run concurrently within a fragment.
4. **Callers updated** in `scan.rs` (v1 + v2 paths), `filtered_read.rs`,
`dataset/updater.rs` (`Updater::try_new` made async), `lance-index`
(shufflers, distributed index merger, scalar lance_format, vector
storage), benches, and `python/src/file.rs`.
The fix relies on the existing `SpawnedTask::spawn` of `read_fragment`
in `FilteredReadExec` and the `tokio::spawn` of the open task in
`LanceScanExec`: the per-fragment task now also drives `initialize`, so
all fragments' scheduling I/Os run in parallel up to
`fragment_readahead`.
## Behavior change
Errors from `initialize` (e.g. corrupted metadata, transient I/O) now
surface from the `read_*` await instead of from the first stream item.
Existing callers that match on the result of `read_*` keep working;
callers that previously assumed the construction was infallible and only
the stream could error will now see the error one step earlier.
## Test plan
- [x] `cargo check --workspace --tests --benches` — clean
- [x] Repro gist — regression resolved (numbers above)
- [x] Cross-fragment-count ablation — no regression at any scale
- [x] Python tests: 526 passed across `test_dataset`,
`test_scalar_index`, `test_blob`, `test_filter`, `test_file`,
`test_fragment`, `test_vector_index` (minus the timing-fragile test
below)
- [ ] Cloud / S3 verification — would expect a much larger improvement
than local
## One known test failure to flag
`test_create_index_progress_callback_error_before_completion_propagates`
fails after this change. It is a **pre-existing timing race exposed by
the speedup**, not a correctness break:
- The test registers `fail_on_tag="start:train_ivf"` and expects
`create_index` to raise.
- Mechanism: Rust calls `progress.stage_start("train_ivf").await`, which
only does a sync channel send — the callback's error surfaces later,
when Python's `block_on_pumping` (`python/src/executor.rs:200-247`)
calls `pump()` between 100 ms `tokio::time::sleep`s.
- After this change the default-mode operation completes inside the
first 100 ms slice often enough that pump doesn't get a chance to
surface the error mid-flight. It hits the post-completion branch at
`executor.rs:238-244`, which logs and ignores errors from the final pump
(`"Ignoring progress callback error after operation completed
successfully"`). Running with `LANCE_INLINE_SCHEDULING_THRESHOLD=0`
(spawn) makes the test pass.
- Earlier perf commits on main may already have exposed variants of this
on some platforms; commit 87ef5e2 fixed a related case.
The clean fix is in `block_on_pumping` (propagate the final pump's error
rather than ignoring it), but that's outside this refactor's scope and
changes the contract of "operation succeeded but a callback later
errored". Happy to land that as a separate PR if reviewers want.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent b7ff253 commit d8542b5
17 files changed
Lines changed: 476 additions & 278 deletions
File tree
- python/src
- rust
- lance-encoding/src
- lance-file
- benches
- src
- lance-index/src
- scalar
- vector
- distributed
- ivf
- v3
- lance
- benches
- src
- dataset
- index/vector/ivf
- io/exec
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
734 | 734 | | |
735 | 735 | | |
736 | 736 | | |
737 | | - | |
738 | | - | |
739 | 737 | | |
740 | 738 | | |
741 | 739 | | |
| |||
745 | 743 | | |
746 | 744 | | |
747 | 745 | | |
| 746 | + | |
748 | 747 | | |
749 | 748 | | |
750 | 749 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
266 | 271 | | |
267 | 272 | | |
268 | 273 | | |
269 | 274 | | |
270 | 275 | | |
271 | 276 | | |
272 | 277 | | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
273 | 288 | | |
274 | 289 | | |
275 | 290 | | |
| |||
1956 | 1971 | | |
1957 | 1972 | | |
1958 | 1973 | | |
| 1974 | + | |
| 1975 | + | |
| 1976 | + | |
| 1977 | + | |
| 1978 | + | |
| 1979 | + | |
| 1980 | + | |
| 1981 | + | |
| 1982 | + | |
| 1983 | + | |
| 1984 | + | |
| 1985 | + | |
| 1986 | + | |
| 1987 | + | |
| 1988 | + | |
| 1989 | + | |
| 1990 | + | |
| 1991 | + | |
1959 | 1992 | | |
1960 | 1993 | | |
1961 | 1994 | | |
1962 | 1995 | | |
1963 | 1996 | | |
1964 | 1997 | | |
1965 | 1998 | | |
| 1999 | + | |
1966 | 2000 | | |
1967 | 2001 | | |
1968 | 2002 | | |
| |||
2089 | 2123 | | |
2090 | 2124 | | |
2091 | 2125 | | |
2092 | | - | |
| 2126 | + | |
2093 | 2127 | | |
2094 | 2128 | | |
2095 | 2129 | | |
| |||
2120 | 2154 | | |
2121 | 2155 | | |
2122 | 2156 | | |
2123 | | - | |
2124 | | - | |
2125 | | - | |
2126 | | - | |
2127 | | - | |
2128 | | - | |
2129 | | - | |
2130 | | - | |
2131 | | - | |
2132 | | - | |
2133 | | - | |
2134 | | - | |
2135 | | - | |
2136 | | - | |
2137 | | - | |
2138 | | - | |
2139 | | - | |
2140 | | - | |
2141 | | - | |
2142 | | - | |
2143 | | - | |
| 2157 | + | |
| 2158 | + | |
| 2159 | + | |
| 2160 | + | |
| 2161 | + | |
| 2162 | + | |
| 2163 | + | |
| 2164 | + | |
| 2165 | + | |
| 2166 | + | |
| 2167 | + | |
| 2168 | + | |
| 2169 | + | |
| 2170 | + | |
| 2171 | + | |
| 2172 | + | |
| 2173 | + | |
2144 | 2174 | | |
| 2175 | + | |
| 2176 | + | |
| 2177 | + | |
| 2178 | + | |
| 2179 | + | |
| 2180 | + | |
| 2181 | + | |
| 2182 | + | |
| 2183 | + | |
| 2184 | + | |
| 2185 | + | |
2145 | 2186 | | |
2146 | 2187 | | |
2147 | 2188 | | |
| |||
2150 | 2191 | | |
2151 | 2192 | | |
2152 | 2193 | | |
2153 | | - | |
2154 | | - | |
2155 | | - | |
| 2194 | + | |
| 2195 | + | |
| 2196 | + | |
| 2197 | + | |
| 2198 | + | |
| 2199 | + | |
| 2200 | + | |
| 2201 | + | |
| 2202 | + | |
| 2203 | + | |
| 2204 | + | |
| 2205 | + | |
| 2206 | + | |
| 2207 | + | |
| 2208 | + | |
| 2209 | + | |
| 2210 | + | |
| 2211 | + | |
2156 | 2212 | | |
2157 | 2213 | | |
2158 | | - | |
2159 | | - | |
| 2214 | + | |
| 2215 | + | |
| 2216 | + | |
| 2217 | + | |
| 2218 | + | |
| 2219 | + | |
| 2220 | + | |
2160 | 2221 | | |
2161 | | - | |
2162 | | - | |
2163 | | - | |
| 2222 | + | |
| 2223 | + | |
| 2224 | + | |
| 2225 | + | |
| 2226 | + | |
| 2227 | + | |
| 2228 | + | |
2164 | 2229 | | |
2165 | 2230 | | |
2166 | 2231 | | |
2167 | 2232 | | |
2168 | 2233 | | |
2169 | 2234 | | |
2170 | | - | |
| 2235 | + | |
2171 | 2236 | | |
2172 | | - | |
| 2237 | + | |
2173 | 2238 | | |
2174 | 2239 | | |
2175 | 2240 | | |
| |||
2178 | 2243 | | |
2179 | 2244 | | |
2180 | 2245 | | |
2181 | | - | |
2182 | | - | |
2183 | | - | |
2184 | | - | |
| 2246 | + | |
2185 | 2247 | | |
2186 | 2248 | | |
2187 | 2249 | | |
2188 | 2250 | | |
2189 | 2251 | | |
2190 | 2252 | | |
2191 | | - | |
2192 | | - | |
2193 | | - | |
2194 | | - | |
2195 | | - | |
2196 | | - | |
2197 | | - | |
2198 | | - | |
2199 | | - | |
2200 | | - | |
2201 | | - | |
| 2253 | + | |
| 2254 | + | |
| 2255 | + | |
| 2256 | + | |
| 2257 | + | |
| 2258 | + | |
2202 | 2259 | | |
2203 | 2260 | | |
2204 | 2261 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| 92 | + | |
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
| |||
305 | 306 | | |
306 | 307 | | |
307 | 308 | | |
| 309 | + | |
308 | 310 | | |
309 | 311 | | |
310 | 312 | | |
| |||
0 commit comments