Skip to content

fix: recover blob reads racing WAL reclamation - #411

Merged
beinan merged 1 commit into
mainfrom
codex/blob-read-merge-race
Oct 20, 2026
Merged

beinan merged 1 commit into
mainfrom
codex/blob-read-merge-race

Conversation

@beinan

@beinan beinan commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

A rollout blob read can open a WAL generation, then lose its data files when a concurrent merge commits and reclaims that generation. The subsequent ID scan or payload read currently returns a missing-file error to HTTP as a 500; the existing final-miss refresh does not handle errors.

Retry missing-file failures at most twice from a refreshed, cloned base snapshot. Successful base hits keep their existing fast path, historical reads stay pinned, and persistent missing data remains an error. The resident dataset is unchanged and no writer lock or merge scheduling behavior changes.

Validation:

  • Deterministic regression on main 3be40eef: pause immediately after opening the generation, merge and reclaim it, then resume the read. Confirmed failure with NotFound on the generation data file (not a timing-only stress result).
  • Regression covers a present payload and an absent ID; a separate test preserves errors when data is deleted without a base commit.
  • Post-fix cargo test --locked -p lance-context-core --lib blob -- --nocapture: 20 passed, including the deterministic race, persistent-loss guard, WAL read cap, and pinned historical snapshot checks.
  • cargo test --locked -p lance-context-core --lib point_lookup_ -- --nocapture: 2 passed, covering base-first lookup and immutable-ID precedence.
  • cargo fmt --all and git diff --check passed.

An isolated Azure workload also reproduced this race on the older image. This is not yet an attribution of production slow reads or proof of production performance improvement.

@beinan
beinan marked this pull request as ready for review October 20, 2026 19:25
@beinan
beinan merged commit 07e97ba into main Oct 20, 2026
11 checks passed
beinan added a commit that referenced this pull request Oct 20, 2026
Blob misses currently reacquire an exclusive store guard to refresh the
resident dataset. A concurrent append/flush holds a shared guard, so one
absent-ID lookup can wait behind the write and queue later blob hits
behind itself.

Keep blob lookups under a shared guard and refresh cloned base snapshots
around WAL fallback. After a complete WAL miss, check the latest base
again so a row merged and drained between the initial base check and WAL
discovery remains readable. Preserve resident and pinned versions, read
caps, the base-hit fast path, and the bounded missing-file retries from
#411.

Validation:
- Before the fix, the server regression times out while an independent
shared guard is held.
- Before the fix, the deterministic core regression returns `None` for a
row flushed before the read and merged during the base-to-WAL
transition.
- After the fix: 31 blob tests (21 core, 10 server) and 4 point-lookup
tests (2 core, 2 server) pass, including both regressions. `cargo fmt
--all -- --check` also passes. Full CI remains in progress.
- Isolated Azure staging with a 291,593,422-byte same-table write,
53,248-byte blob reads, 32 pending generations, and concurrent hit/miss
streams produced the maximum header latencies below. Each run used a
fresh storage prefix; no OOM or container restart occurred. These are
three sequential synthetic runs, not production p99 measurements.

| Image/run | Hit maximum | Miss maximum |
|---|---:|---:|
| c9aa control | 4.046 s | 4.149 s |
| Patched run 1 | 0.0021 s | 0.159 s |
| Patched run 2 | 0.0058 s | 2.100 s |

The second patched miss still took 2.1 s: this does not resolve every
source of read latency. The runtime image IDs matched the immutable
image built from this PR's exact head (`768495e`) in services build
[2159160](https://dev.azure.com/MicrosoftAI/a05dc5e9-6da2-404a-8da3-c43822735c75/_build/results?buildId=2159160).

A separate isolated five-worker fixture with 100,586,357-byte payloads
passed six status checks and four full payload comparisons during five
concurrent writes. All three lookup types (base hit, WAL hit, absent ID)
overlapped actual merges; the four writer shards reclaimed 32
generations. Maximum read header latency was 0.849 s, with no
OOM/restarts. This is a bounded correctness run, not a soak test. All
four fixture Pods were deleted and verified absent. Production has not
been changed.

Co-authored-by: Beinan Wang <beinanwang@microsoft.com>
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