Weave documentation
Basis

Internals

How Basis stores, indexes, and rebuilds vectors under the hood.

What this page covers

A walk through the private state and helper paths so you can audit, extend, or debug the implementation.

State

pub struct Basis {
    vector_strand: Arc<RwLock<Strand>>,
    index_strand: Arc<RwLock<Strand>>,
    hnsw: Arc<RwLock<BasisHnsw>>,
    id_to_internal_index: Arc<DashMap<Uuid, usize>>,
    did_to_internal_index: Arc<DashMap<String, usize>>,
}

type BasisHnsw = Hnsw<Point, StdRng, M, M0>; // M=12, M0=24
FieldRole
vector_strandCanonical write log of VectorEntry blocks
index_strandReserved for HNSW snapshots (stub today)
hnswIn-memory graph used by search
id_to_internal_indexLookup from caller-supplied UUID to HNSW node index
did_to_internal_indexLookup from DID string to HNSW node index, used by membership checks

VectorEntry encoding

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VectorEntry {
    Add { id: Uuid, vector: Vec<f32> },
    Remove { id: Uuid },
}

Entries are encoded with serde_json::to_vec. JSON keeps the encoding human-inspectable at the cost of size. No versioned migration or forward-compatibility guarantee is established by this encoding alone.

Insert path

add(id, vector)
   │
   ├─► VectorEntry::Add → serde_json::to_vec → strand.append
   │   (await on vector_strand.write())
   │
   ├─► Point { id, vec } → hnsw.insert(point, &mut searcher)
   │   returns the HNSW internal index
   │
   ├─► id_to_internal_index.insert(id, internal_index)
   │
   └─► update_index_strand()  // stub: returns Ok(())

The vector strand write guard remains in scope while add acquires the HNSW write guard. The locks can therefore overlap. Searches need an HNSW read guard and can wait behind insertion.

Search path

search(query, k)
   │
   ├─► Build Point { id: nil, vec: query.to_vec() }
   │
   ├─► ef = max(k, 24)
   │
   ├─► dest = vec![Neighbor { index: 0, distance: u32::MAX }; k]
   │
   ├─► hnsw.nearest(&probe, ef, &mut searcher, &mut dest)
   │   returns filled subslice of dest
   │
   └─► For each neighbor:
           point = hnsw.feature(neighbor.index)
           push (point.id, f32::from_bits(neighbor.distance))

Searcher is allocated per call. For high-throughput query loads, pool searchers to cut allocation pressure (not yet exposed).

Rebuild path

The private rebuild_index helper is intended to run when remove finds an entry in id_to_internal_index. That caller currently retains the strand write guard while the helper requests a read guard, so this path can deadlock. The helper body describes the intended reconstruction:

  1. Construct a fresh BasisHnsw with a new RNG.
  2. Allocate a fresh DashMap<Uuid, usize>.
  3. Read the vector strand from sequence 0 to len() - 1.
  4. For each VectorEntry::Add, record (id, vector) in a DashMap.
  5. For each VectorEntry::Remove, drop the id from the map.
  6. Insert every surviving (id, vector) into the new HNSW.
  7. Swap the new graph and id map into place under write locks.

Cost: O(N · log N) time, O(N) memory transient overhead.

Concurrency model

OperationLocks heldLock kind
addvector strand, HNSW, id-map shardWrite guards can overlap
searchHNSW onlyread
removevector strand write, id-map shard, then rebuildPresent-id path can wait for its own strand lock
rebuild_indexvector strand (read), then HNSW (write), then id map (write)mixed

DashMap is internally sharded; the id map locks are fine-grained.

Failure injection points

If you are stress-testing the implementation, the most useful synthetic faults are:

  • Block strand.append to simulate a slow disk; verify that search continues to serve.
  • Corrupt a single block in the vector strand; rebuild_index must skip malformed entries (if let Ok(entry) = serde_json::from_slice ...).
  • Reproduce present-id removal with an explicit timeout in an isolated runtime test before attempting a runtime repair; it can deadlock in the current source.

Where to extend

  • HNSW snapshot serialization (currently a TODO in update_index_strand).
  • Configurable distance function (cosine, inner-product) — today L2 is hard-coded.
  • SIMD-accelerated MetricPoint::distance — straightforward via wide or std::simd.
  • Dimensionality enforcement at the Basis level rather than at the caller.

On this page