ruvnet / ruvnet/RuVector

bug(rvf-runtime): delete() bitmap is permanent — re-ingest does not undelete; compact drops re-ingested data

Open
#748 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
4.5k
Forks
603
Avg merge
23h 32m
Merged PRs (30d)
59

Description

Summary

RvfStore::delete(ids) sets bits in deletion_bitmap. Those bits:

  1. Survive re-ingest of the same idingest_batch inserts vector data but never clears the deletion bit.
  2. Cause query() (and index paths) to skip the id while the bit remains set.
  3. Cause compact() to treat the id as dead: it physically removes the vector — including a freshly re-ingested payload — then clears the bitmap.

So “delete then re-insert under the same id” is broken, and “delete → re-insert → compact” destroys the re-inserted data.

DeletionBitmap::clear_ids already exists in deletion.rs but is not called from the re-ingest path.

Affected versions

  • Confirmed: 0.2.0 (store.rs delete / ingest_batch / query / compact)
  • Same interaction present in 0.3.0 (delete sets bitmap; query filters deleted; compact removes deleted_ids)

Repro

use rvf_runtime::{DistanceMetric, QueryOptions, RvfOptions, RvfStore};

fn main() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("del.rvf");
    let opts = RvfOptions {
        dimension: 2,
        metric: DistanceMetric::L2,
        ..Default::default()
    };
    let mut s = RvfStore::create(&path, opts).unwrap();
    let v = [1.0f32, 0.0];
    s.ingest_batch(&[&v], &[42], None).unwrap();
    s.delete(&[42]).unwrap();

    let v2 = [0.0f32, 1.0];
    s.ingest_batch(&[&v2], &[42], None).unwrap();

    let hits = s.query(&[0.0, 1.0], 1, &QueryOptions::default()).unwrap();
    // Actual: 42 not returned (still soft-deleted) — BUG
    // Expected: 42 present with v2

    s.compact().unwrap();
    let hits2 = s.query(&[0.0, 1.0], 1, &QueryOptions::default()).unwrap();
    // Actual: still absent — re-ingested payload reclaimed as dead — BUG
    let _ = (hits, hits2);
}

Expected

On successful ingest_batch / upsert of an id, clear that id from deletion_bitmap (e.g. clear_ids). Document soft-delete vs re-ingest-as-undelete. compact should only drop ids still soft-deleted at compact time.

Downstream impact

WeftOS BranchableMemory::delete never calls RvfStore::delete on working tips; it uses a crate-level tombstone set so delete→re-ingest works and chain-walk query masking is correct. Promote-to-base still hits this sticky-bitmap path — a real correctness hazard.

Reported from WeftOS WEFT-662.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in store.rs with RvfStore::ingest_batch, delete, query, and compact, then inspect DeletionBitmap::clear_ids in deletion.rs. Run the provided delete→re-ingest→query→compact reproduction. Done means re-ingested ids are queryable with the new vector and remain present after compact, while ids still soft-deleted are removed.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.