vindex

package
v2.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package vindex implements a btree-resident HNSW vector index for any-store.

Unlike the experimental top-level vector package (which keeps an in-memory arena), this index owns NO graph state: the HNSW graph lives entirely in btree namespaces and every operation runs against a transaction. That is what makes it correct across processes — a reader sees a consistent MVCC snapshot of the graph, a writer's mutations commit atomically with the document, and there is no per-process in-memory layer to invalidate or sync.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildOf added in v2.3.0

func BuildOf(rtx *btree.ReadTx, vmeta *btree.Namespace) (uint64, error)

BuildOf returns the build the index's meta record in rtx's view names (meta.build); 0 for a record written before builds were identified.

func SetBuildConcurrency

func SetBuildConcurrency(n int)

SetBuildConcurrency sets the process-wide budget of concurrent build/compaction workers. n < 1 is treated as 1. In-flight builds keep the budget they captured.

Types

type Candidate

type Candidate struct {
	DocID    []byte
	Distance float32
}

Candidate is one ANN result: the document id and its distance to the query.

type DistanceFunc

type DistanceFunc func(a, b []float32) float32

DistanceFunc returns the distance between two equal-length vectors.

func DistanceFor

func DistanceFor(m Metric) DistanceFunc

DistanceFor returns the best distance function for the metric on this CPU — the same kernel the index uses, so brute-force search ranks identically to ANN.

type Hit

type Hit struct {
	DocID    []byte
	Distance float32
}

Hit is a search result.

type Index

type Index struct {
	// contains filtered or unexported fields
}

Index is a btree-resident HNSW. It holds no graph state — only the namespace handles, the (immutable) parameters, the distance function, and an RNG for level generation. Every operation reads/writes through the passed transaction.

func BulkBuild

func BulkBuild(wtx *btree.WriteTx, prefix string, p Params, seed int64, ids [][]byte, vecs [][]float32) (*Index, error)

BulkBuild creates a fresh index over (ids, vecs) by constructing the HNSW graph in RAM and serializing every record to the namespaces in ONE bulk pass, in label (insertion) order. This is the "build in memory, flush once" pattern every other engine uses (pgvector/Lance/DiskANN/usearch); it avoids the per-edge copy-on-write B-tree churn of Insert-per-document, the dominant cost of an Insert-built graph.

The in-RAM build mirrors Insert's algorithm exactly — same level RNG draw order, same beam search, same neighbour-selection heuristic, same back-link pruning — so for a given (seed, order, vectors) it produces a graph byte-identical to the per-insert path. The serialized records use the same encoders (encodeVec / encodeAdj / meta), so search, incremental Insert, Delete and Compact all work against a bulk-built index unchanged.

ids must be unique and len(ids)==len(vecs); each vec must have length p.Dim. It runs entirely inside wtx (atomic: on error the tx rolls back).

func BulkBuildParallel

func BulkBuildParallel(wtx *btree.WriteTx, prefix string, p Params, seed int64, ids [][]byte, vecs [][]float32, threads int) (*Index, error)

BulkBuildParallel is BulkBuild with a concurrent in-RAM graph construction phase, then the same single-threaded bulk flush. This is the technique hnswlib and Lance use: many workers insert into one shared in-RAM graph guarded by per-node locks. It trades the byte-identical determinism of BulkBuild for near-linear build speedup (the graph-construction CPU, not the btree writes, is the dominant build cost). The resulting graph is a valid HNSW of equivalent recall, not byte-identical (it depends on insertion interleaving).

threads <= 0 uses GOMAXPROCS. Levels are drawn sequentially from the index RNG (deterministic), so only the connection order is nondeterministic.

func Compact

func Compact(wtx *btree.WriteTx, prefix string, seed int64) (*Index, error)

Compact rebuilds the index from its live vectors, dropping every tombstone and re-densifying labels to [0, live). It runs entirely inside wtx: the index's namespaces are deleted and recreated, then the surviving vectors are re-inserted into a fresh HNSW graph. Because it is one transaction it is atomic — on commit the clean graph replaces the old one; on any error the tx rolls back with the original graph intact.

It reclaims the storage that Delete/replace only tombstoned: dead :adj nodes, superseded :vec records, and stale :lbl entries. The rebuilt graph is also of higher quality than a tombstone-laden one (deleted nodes no longer route or dilute neighbour lists), so recall improves or holds.

Returns a NEW *Index bound to the rebuilt namespaces; the caller must publish it in place of the old one and re-apply SetHybrid/SetVectorCache (the returned index defaults to plain btree mode). When there is nothing to reclaim (no tombstones and labels already dense) it is a no-op and returns an index over the existing graph unchanged.

Relabeling safety: live nodes get fresh labels, so any label-keyed RAM cache (the layer-0 mirror, the vector tier) built before the compaction would be invalid. Recreating the namespaces moves their btree root pages, so the collection's reconcile reopens the Index with fresh handles and empty caches (the same root-moved path that handles a range index's drop+recreate). The old Index object is only ever read at the pre-compaction MVCC snapshot, where its caches are still correct, so no in-place cache versioning is needed.

func Create

func Create(wtx *btree.WriteTx, prefix string, p Params, seed int64) (*Index, error)

Create initialises a new index: creates the namespaces and writes the initial meta record. Idempotent only if the namespaces don't already hold an index.

func Open

func Open(db *btree.DB, prefix string, seed int64) (*Index, error)

Open resolves an existing index, reading its parameters from the meta record.

func OpenTx

func OpenTx(rtx *btree.ReadTx, prefix string, seed int64) (*Index, error)

OpenTx resolves an existing index using a caller-provided read transaction (no nested BeginRead) — used when opening/reconciling inside another tx.

func (*Index) Build added in v2.3.0

func (ix *Index) Build() uint64

Build returns the identity of the build this object serves (meta.build).

func (*Index) Counts

func (ix *Index) Counts(rtx *btree.ReadTx) (live, deleted, total int, err error)

Counts returns the live, tombstoned, and total-ever node counts from the meta record alone — one btree read, no namespace walk. Cheap enough for a per-write compaction-threshold check (deleted/live ratio). total is the physical label high-water mark (live + tombstones).

func (*Index) Delete

func (ix *Index) Delete(wtx *btree.WriteTx, docID []byte) (bool, error)

Delete tombstones the node for docID. Returns false if docID is not indexed.

func (*Index) Dim

func (ix *Index) Dim() int

Dim returns the index dimension.

func (*Index) EfSearch

func (ix *Index) EfSearch() int

EfSearch returns the index's default query-time candidate-list size.

func (*Index) Insert

func (ix *Index) Insert(wtx *btree.WriteTx, docID []byte, vec []float32) error

Insert adds (or replaces) the vector for docID. If docID is already present its old node is tombstoned and a fresh node inserted (HNSW-standard update).

func (*Index) MetaRoot

func (ix *Index) MetaRoot() uint32

MetaRoot returns the btree root page of the index's meta namespace. The collection compares it against the on-disk namespace to detect that a peer compaction recreated the index (root moved) and the Index must be reopened.

func (*Index) Roots added in v2.3.0

func (ix *Index) Roots() map[string]uint32

Roots returns the root page of every namespace of the index, by the namespace's suffix: what a view must have for this object to serve it.

func (*Index) Search

func (ix *Index) Search(rtx *btree.ReadTx, query []float32, k, efSearch int) ([]Hit, error)

Search returns the k nearest live documents to query, ranked closest-first. efSearch <= 0 uses the index default. It is a thin ranking wrapper over SearchCandidates (which yields the beam in closest-first order); the live query pipeline uses SearchCandidates directly and owns its own sort+limit.

func (*Index) SearchCandidates

func (ix *Index) SearchCandidates(rtx *btree.ReadTx, query []float32, ef int) ([]Candidate, error)

SearchCandidates runs the ANN beam search and returns up to ef live candidates with their distances, in (distance, docId) ascending order — a TOTAL order, so identical queries on identical snapshots return identical sequences (query verb coherence). Membership of the ef-cut is likewise deterministic: the beam admits at the boundary by (dist, label), so the result is the unique (dist,label)-smallest ef of the visited set. Unlike Search it does NOT rank/truncate to k — the query pipeline's k-cut/sort/limit own the final windowing.

func (*Index) SetHybrid

func (ix *Index) SetHybrid(h bool)

SetHybrid enables (or disables) the RAM layer-0 mirror on the read path. Call once right after Create/Open, before concurrent use. The btree stays the source of truth; the mirror is a derived, snapshot-aligned read cache.

func (*Index) SetInsertCacheSize

func (ix *Index) SetInsertCacheSize(n int)

SetInsertCacheSize configures the per-batch insert vector cache (lever 1): during a write batch, repeatedly-read node vectors (entry point and hubs) are served from a capped RAM arena instead of the btree. n is the max cached vectors (RAM ≈ n*dim*4 bytes per active write searcher); n <= 0 disables it. Call once right after Create/Open, before concurrent use.

func (*Index) SetVectorCache

func (ix *Index) SetVectorCache(v bool)

SetVectorCache enables (or disables) the RAM vector tier (hybrid only): layer-0 vector reads come from a RAM slab instead of the btree, making a hop pure-RAM. It caches the btree's exact :vec bytes (int8 if the index is int8), so results are unchanged. RAM cost ≈ nextLabel × vecRecordSize, where nextLabel is the label high-water mark — every delete/replace allocates a fresh label, so this grows with write churn (NOT just the live set) until a Compact resets it. Use int8 to keep it down. Call once right after Create/Open, before concurrent use.

func (*Index) Stats

func (ix *Index) Stats(rtx *btree.ReadTx) (Stats, error)

Stats reads the index meta and walks each namespace to produce a storage and occupancy summary for the index as seen by rtx's snapshot.

type Metric

type Metric uint8

Metric selects the distance measure. Smaller is closer for all of them.

const (
	L2 Metric = iota
	Cosine
	Dot
)

func (Metric) String

func (m Metric) String() string

type Params

type Params struct {
	Dim            int
	M              int     // max neighbours on layers >= 1 (default 16)
	EfConstruction int     // candidate list at insert (default 200)
	EfSearch       int     // candidate list at query (default 64)
	Ml             float64 // level factor (default 0.25)
	Metric         Metric
	Quantization   Quantization
}

Params configure a freshly created index. Zero fields take defaults.

type Quantization

type Quantization uint8

Quantization selects how vectors are stored in the :vec namespace. It is a pure storage choice: vectors are quantized on write and dequantized to float32 on read, so the graph, distance functions, and cross-process consistency are all unchanged — only the bytes on disk (and thus the page cache footprint) shrink.

const (
	// QuantNone stores raw float32 vectors (dim*4 bytes), read zero-copy.
	QuantNone Quantization = iota
	// QuantInt8 stores a per-vector scale + int8 components (4+dim bytes), ~4x
	// smaller, at a small recall cost. Symmetric scalar quantization.
	QuantInt8
)

func (Quantization) String

func (q Quantization) String() string

type Stats

type Stats struct {
	Dim          int
	Metric       Metric
	Quantization Quantization
	M            int
	M0           int
	EfSearch     int
	NodeCount    int // physical nodes ever allocated (incl. tombstones)
	LiveCount    int // non-deleted nodes
	DeletedCount int // tombstones (compaction reclaims these)
	TopLayer     int

	Vec  btree.NamespaceSize // vectors (immutable)
	Adj  btree.NamespaceSize // graph adjacency
	Doc  btree.NamespaceSize // docId -> label
	Lbl  btree.NamespaceSize // label -> docId
	Meta btree.NamespaceSize // params + entry point
}

Stats summarises a vector index: graph parameters, node counts (live vs tombstoned), and the physical size of each backing namespace.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL