fix: make FTS metadata loading retry-safe#7838
Conversation
📝 WalkthroughWalkthroughThe changes improve cache error propagation and concurrent loader coalescing, add not-found detection across wrapped errors, namespace index metadata caches by object-store identity, preserve inverted-index metadata errors during legacy fallback, and strengthen related tests. ChangesCache and index behavior
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| Ok(serde_json::from_str::<InvertedIndexParams>(params)?) | ||
| } | ||
| Err(_) => { | ||
| Err(error) if error.is_not_found() => { |
There was a problem hiding this comment.
Legacy FTS indexes can still fail to open on S3 readers that have s3:GetObject but not s3:ListBucket. These indexes do not contain metadata.lance, and S3 returns 403 rather than 404 for a missing object when ListBucket is unavailable, so is_not_found() is false even though tokens.lance is readable. Could we probe the legacy tokens file when opening metadata fails, use the fallback only if that succeeds, and otherwise preserve the original metadata error? https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html
| None => { | ||
| let mut indices = self | ||
| .index_cache | ||
| .get_or_insert_with_key(metadata_key, || async { |
There was a problem hiding this comment.
get_or_insert_with_key does not preserve loader errors for coalesced waiters. Moka runs one initializer, but each caller owns a separate oneshot; when the loader fails, only the initializer receives the original timeout or permission error and the other callers return Internal("Failed to retrieve error from cache loader"). A five-caller repro produced one Timeout and four Internal errors. Could we fan out the same fallible result and add a concurrent failure test before using this path here?
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
rust/lance-core/src/error.rs (1)
931-970: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a short comment on
DisplayError's purpose.The clone logic itself is correct: wrapping not-found causes in
DisplayErrorbefore boxing (rather than boxing theErrordirectly) is what keepsformat!("{:?}", ...)on the cloned error human-readable (DisplayError'sDebugforwards toDisplay), which downstream consumers rely on (e.g.fragment.rstests assertmessage.contains("miss.lance")onformat!("{err:?}")). This is non-obvious — a future refactor could "simplify" away theDisplayErrorwrapper and silently break Debug-format matching in callers. A one-line comment on the struct would prevent that.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@rust/lance-core/src/error.rs` around lines 931 - 970, Add a concise comment above DisplayError documenting that its Debug implementation forwards to Display so wrapped not-found errors remain human-readable in debug output; preserve the existing CloneableError cloning logic and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@rust/lance-core/src/error.rs`:
- Around line 523-536: Update Error::is_not_found to explicitly handle the
DatasetNotFound variant by inspecting its boxed source with
error_source_is_not_found, matching the existing IO and Wrapped source
classification. Preserve the current handling for NotFound, IO, Wrapped, and all
other variants.
---
Nitpick comments:
In `@rust/lance-core/src/error.rs`:
- Around line 931-970: Add a concise comment above DisplayError documenting that
its Debug implementation forwards to Display so wrapped not-found errors remain
human-readable in debug output; preserve the existing CloneableError cloning
logic and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: QUIET
Plan: Pro Plus
Run ID: 35e41ea4-97a6-4854-9789-133ff803b157
📒 Files selected for processing (9)
rust/lance-core/src/cache/mod.rsrust/lance-core/src/cache/moka.rsrust/lance-core/src/error.rsrust/lance-index/src/scalar/inverted/index.rsrust/lance/src/dataset.rsrust/lance/src/dataset/fragment.rsrust/lance/src/index.rsrust/lance/src/io/commit.rsrust/lance/src/session/index_caches.rs
Distributed FTS global-stats planning needs one immutable set of index segments and parsed tokenizer parameters per table snapshot. Concurrent cold callers currently race while reading index metadata, and `InvertedIndex::load_params` treats every `metadata.lance` open failure as a legacy index. A transient object-store error can therefore become a successful default-tokenizer fallback and be retained by an upper-layer cache. Make index-metadata cache misses use the existing fallible singleflight path and include the object-store identity in the cache key. Restrict the legacy tokens fallback to typed not-found errors, preserving that variant through shared-reader error cloning so timeouts and permission failures remain retryable errors. This is the Lance-side prerequisite for the Sophon QN-owned FTS index snapshot cache in [lancedb/sophon#6759](lancedb/sophon#6759). (cherry picked from commit 654f0d4)
Distributed FTS global-stats planning needs one immutable set of index segments and parsed tokenizer parameters per table snapshot. Concurrent cold callers currently race while reading index metadata, and
InvertedIndex::load_paramstreats everymetadata.lanceopen failure as a legacy index. A transient object-store error can therefore become a successful default-tokenizer fallback and be retained by an upper-layer cache.Make index-metadata cache misses use the existing fallible singleflight path and include the object-store identity in the cache key. Restrict the legacy tokens fallback to typed not-found errors, preserving that variant through shared-reader error cloning so timeouts and permission failures remain retryable errors.
This is the Lance-side prerequisite for the Sophon QN-owned FTS index snapshot cache in lancedb/sophon#6759.