WeaveDocs
Basis

Insert and Search

Append vectors to a Basis instance and run k-nearest-neighbor queries.

What this page covers

The two operations a developer reaches for first: add to insert a vector under a UUID, and search to return the k nearest matches for a query vector.

Note

HNSW is an approximate index. Recall depends on ef_search and graph topology — for high-precision workloads, retrieve a larger k and re-rank with an exact distance computation or a cross-encoder.

Construction

Basis::new(vector_strand, index_strand) takes two Arc<RwLock<Strand>> handles. The vector strand stores the canonical write log; the index strand reserves space for HNSW snapshots.

use std::sync::Arc;
use tokio::sync::RwLock;
use strand::{Strand, StrandConfig};
use basis::Basis;
use uuid::Uuid;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;

    let vec_cfg = StrandConfig::new().with_storage(dir.path().join("vectors"));
    let idx_cfg = StrandConfig::new().with_storage(dir.path().join("index"));

    let vector_strand = Arc::new(RwLock::new(Strand::new(vec_cfg).await?));
    let index_strand = Arc::new(RwLock::new(Strand::new(idx_cfg).await?));

    let basis = Basis::new(vector_strand, index_strand);

    // Insert three 4-dimensional vectors.
    let red    = Uuid::new_v4();
    let green  = Uuid::new_v4();
    let blue   = Uuid::new_v4();

    basis.add(red,   &[1.0, 0.0, 0.0, 0.0]).await?;
    basis.add(green, &[0.0, 1.0, 0.0, 0.0]).await?;
    basis.add(blue,  &[0.0, 0.0, 1.0, 0.0]).await?;

    // Query for the 2 nearest to [0.9, 0.1, 0.0, 0.0] — should be red, then green.
    let hits = basis.search(&[0.9, 0.1, 0.0, 0.0], 2).await?;
    assert_eq!(hits[0].0, red);

    println!("nearest: {} at L2 distance {}", hits[0].0, hits[0].1);
    Ok(())
}

What add does

  1. Serializes VectorEntry::Add { id, vector } with serde_json.
  2. Appends the bytes to the vector strand. The Strand returns the new sequence number.
  3. Inserts the vector into the in-memory HNSW graph and records the internal index in a DashMap<Uuid, usize>.
  4. Calls update_index_strand() (currently a no-op stub — see Snapshot and Recovery).

The add call is O(log N) on average for the HNSW insertion plus one Strand append.

What search does

  1. Builds a Point { id: Uuid::nil(), vec: query } as the search probe.
  2. Walks the HNSW graph with ef = max(k, 24) to gather candidates.
  3. Returns up to k (Uuid, f32) pairs, sorted by ascending L2 distance.
ParameterMeaning
queryQuery vector. Must have the same dimensionality as inserted vectors.
kMaximum number of results.
ef (internal)Search width. Always ≥ 24 to keep recall stable on small k.

Errors

VariantCauseRecovery
BasisError::Strand(String)Underlying Strand append/read failedRetry; check disk and quorum
BasisError::Serialization(serde_json::Error)VectorEntry could not be encodedVerify vector length matches the rest of the corpus
BasisError::VectorNotFound(Uuid)Removal target absentCheck id_to_internal_index map

Practical sizing

The HNSW graph is held entirely in memory. With M=12, M0=24 and 32-bit floats, a 768-dimensional embedding consumes ≈ 3.1 KB per vector (vector + neighbor lists + struct overhead). 1 M vectors fits in ≈ 3 GB of RAM. Larger corpora require sharding across multiple Basis instances.