Skip to content

fix: resolve vector prewarm name collisions from segment metadata - #97

Merged
yanghua merged 2 commits into
lance-format:mainfrom
Gabriel39:fix/prewarm-index-field-collision
Oct 20, 2026
Merged

yanghua merged 2 commits into
lance-format:mainfrom
Gabriel39:fix/prewarm-index-field-collision

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

lance_dataset_prewarm_index() can fail for a valid vector index when its name matches another schema column. The C++ Dataset::prewarm_index() wrapper consequently throws lance::Error. Resolve the indexed field from segment metadata for this collision so prewarming succeeds on a fresh session and subsequent queries reuse the warmed partitions.

Trigger and impact

A concrete reproducer is:

  1. Create a dataset with id: Int32 and embedding: FixedSizeList<Float32, 8>.
  2. Build an IVF_FLAT index on embedding, but name the index id. The regression uses two fragments and two index segments.
  3. Open the dataset with a fresh session, before any vector query has warmed that session.
  4. Call lance_dataset_prewarm_index(dataset, "id").

Before this fix, the call returns -1 with an error containing Data type is not a vector ... but Int32. Index construction and nearest-neighbor queries on embedding succeed for the same dataset. Consequently, callers cannot complete an explicit warmup before serving queries; applications that continue after the error still incur cold index reads. Applications that require warmup to succeed can reject an otherwise usable dataset. The demonstrated failure is in prewarm, without changing persisted data or index contents.

The name collision matters: an index name that is not a schema column already resolves through index metadata. The test uses a separate session for the cold control query, so that query cannot populate the session used to reproduce prewarm.

Cause and fix

Reproduced against lance-c 2089f6f (current main), which pins Lance SDK 68c12df. This is a correctness follow-up to #94.

The pinned Lance SDK passes the logical index name to open_generic_index(). Its vector field resolver checks for a schema column with that name before consulting the segment's field ID, so id incorrectly selects the scalar column instead of embedding.

For a name that matches a different schema field, the binding loads the named index's segment metadata and opens each affected vector segment using the indexed field path and segment UUID. It prewarms that opened index on the same dataset snapshot and session. The complete open-and-prewarm operation for each affected segment runs concurrently via try_join_all, preserving the SDK’s fail-fast error propagation and cancellation of pending segment futures. Other paths continue through the SDK, including missing-index errors and scalar/full-text prewarm with its aggregate residency validation.

This keeps the existing C/C++ API and dependency revision. It does not include the Doris-specific Foyer patch. Found while integrating upstream prewarm into apache/doris#69277.

Validation

Linux, Rust 1.97.0, using the unchanged lockfile and default features:

  • Before the fix: the new cold-session collision test failed with -1 and the Int32/vector type error, after its independent cold-query control succeeded.
  • cargo test --locked --test c_api_test test_prewarm_index: all 6 prewarm tests passed. The vector cases cover non-colliding names, names matching the indexed field, and names matching a different scalar field. Both segments return correct nearest neighbors with zero partition reloads after prewarm and a shared-session reopen.
  • cargo test --locked --lib index::tests::: all 3 scheduling tests passed. Deterministic gates verify that all 8 segment opens start before any may finish, and all 8 prewarms start before any may finish. Separate checks verify immediate error propagation/cancellation during open or prewarm, and scalar/unresolved-segment fallback to the SDK. The concurrency tests failed against the serial implementation (1 segment started instead of 8); no timing thresholds or sleeps are used.
  • Coverage also includes B-tree/FTS name collisions, invalid arguments, missing indices (including names matching an existing column), pinned snapshots, repeated calls, and dataset/session handle lifetimes.
  • cargo test --locked: 492 passed, 0 failed; the 3 ignored C/C++ cases were run separately. Doc-tests also completed successfully.
  • cargo test --locked --test compile_and_run_test -- --ignored --test-threads=1: all 3 C/C++ compilation/runtime cases passed.
  • cargo clippy --locked --all-targets -- -D warnings: passed.
  • cargo fmt -- --check and git diff --check: passed.

lance-gatekeeper[bot]

This comment was marked as outdated.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 20, 2026

@yanghua yanghua left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Left one comment.

Comment thread src/index.rs Outdated
Comment on lines +315 to +335
for metadata in indices {
let Some(&field_id) = metadata.fields.first() else {
sdk_segments.push(metadata.uuid);
continue;
};
if field_id == named_field.id {
sdk_segments.push(metadata.uuid);
continue;
}
// The pinned SDK interprets an index name as a schema column first.
// Open colliding vector segments by their indexed field, even on a cold cache.
let field_path = dataset.schema().field_path(field_id)?;
let index = dataset
.open_generic_index(&field_path, &metadata.uuid, &NoOpMetricsCollector)
.await?;
if index.index_type().is_vector() {
index.prewarm().await?;
} else {
sdk_segments.push(metadata.uuid);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This makes the collision path prewarm every vector segment sequentially. The SDK implementation uses try_join_all, so a logical index with many distributed-build segments currently warms concurrently. Since open_generic_index() and prewarm() can both perform substantial object-store I/O, the new path changes warmup latency from roughly the slowest segment to the sum of all segments. The two-segment local test cannot expose that regression. Please collect the colliding-vector futures and await them concurrently—ideally preserving the SDK path’s error/concurrency semantics—and add a deterministic multi-segment concurrency test.

Run each segment open and prewarm operation together under try_join_all,
matching the SDK concurrency and error handling. Keep scalar and unresolved
segments on the SDK validation path.

Add deterministic gate-based tests for concurrent opens and prewarms,
error cancellation, and SDK fallback routing.
@lance-gatekeeper lance-gatekeeper Bot removed the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 20, 2026

@lance-gatekeeper lance-gatekeeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Gate recommendation: approve.

Resolving colliding vector segments by their stored field IDs remains a reasonable workaround for the pinned SDK. Scalar/FTS validation and snapshot/session behavior are preserved.

The revised path runs segment opens and prewarms concurrently and cancels pending work on error, addressing the concurrency concern. All three scheduling tests and all six C API prewarm tests pass, including cold-session collision handling and reuse of both warmed vector segments without partition reloads.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 20, 2026

@yanghua yanghua left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@yanghua
yanghua merged commit 4fafcd6 into lance-format:main Oct 20, 2026
8 checks passed
Gabriel39 added a commit to apache/doris that referenced this pull request Oct 21, 2026
…69332)

### What problem does this PR solve?

Related PRs: lance-format/lance-c#97,
lance-format/lance-c#96

Update the pinned lance-c revision from
`8e9e09ecc795c91ce7f97513db94c2d91eec799d` to
`4fafcd64525b274b113208390bbcca86bbbc093b`, the latest upstream `main`
commit when this PR was prepared, and update the archive checksum.

The new revision includes the upstream fix for cold-session vector
prewarm when an index name matches a different schema column. It
resolves the indexed field from segment metadata while preserving
concurrent segment opening/prewarming. The revision also includes the
row-address allowlist/blocklist prefilter APIs from lance-c #96.

Refresh one C++ test context in the existing Foyer patch to accommodate
the test added by #96. The patch's added/deleted code lines are
unchanged; no local prewarm patch is introduced.

### Release note

Fix Lance index prewarm failures when a vector index name matches a
different schema column.

### Check List (For Author)

- Test
    - [x] Manual test
- Downloaded the exact pinned archive and verified its checksum through
the Doris downloader.
- `bash thirdparty/test/lance-prefilter-patch-test.sh thirdparty/src`:
passed all scenarios, including fresh extraction, idempotence, cache
invalidation, and patch-failure handling.
- `cargo metadata --locked --offline --format-version 1` on the patched
source: passed dependency resolution, including Foyer.
- Compiled the patched upstream C and C++ test sources with
`-fsyntax-only`: passed.
- `bash -n` on the dependency scripts and `git diff --check`: passed.
- Full Doris build and runtime regression tests were not run locally;
requested through `run buildall`.
- Behavior changed:
    - [x] Yes. Includes the upstream prewarm fix and new prefilter APIs.
- Does this need documentation?
    - [x] No.

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

K-approved Latest Gatekeeper recommendation permits acceptance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants