Skip to content

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

Description

@aepod

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions