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| Field | Role |
|---|---|
vector_strand | Canonical write log of VectorEntry blocks |
index_strand | Reserved for HNSW snapshots (stub today) |
hnsw | In-memory graph used by search |
id_to_internal_index | Lookup from caller-supplied UUID to HNSW node index |
did_to_internal_index | Lookup 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:
- Construct a fresh
BasisHnswwith a new RNG. - Allocate a fresh
DashMap<Uuid, usize>. - Read the vector strand from sequence 0 to
len() - 1. - For each
VectorEntry::Add, record(id, vector)in aDashMap. - For each
VectorEntry::Remove, drop the id from the map. - Insert every surviving
(id, vector)into the new HNSW. - 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
| Operation | Locks held | Lock kind |
|---|---|---|
add | vector strand, HNSW, id-map shard | Write guards can overlap |
search | HNSW only | read |
remove | vector strand write, id-map shard, then rebuild | Present-id path can wait for its own strand lock |
rebuild_index | vector 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.appendto simulate a slow disk; verify thatsearchcontinues to serve. - Corrupt a single block in the vector strand;
rebuild_indexmust 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
TODOinupdate_index_strand). - Configurable distance function (cosine, inner-product) — today L2 is hard-coded.
- SIMD-accelerated
MetricPoint::distance— straightforward viawideorstd::simd. - Dimensionality enforcement at the
Basislevel rather than at the caller.