Quiver is a Cargo workspace: a from-scratch storage engine, index structures, SIMD distance kernels, and a query planner, with a thin gRPC/REST shell and a TUI client. The C4 views — system context and container view — map the system at a glance; the crate map below and the index design that follows are the deep dive. Every significant decision is captured as an ADR.
Quiver — Architecture Overview
Quiver is a native-Rust vector database. It stores vectors and structured payloads, builds approximate-nearest-neighbour (ANN) indexes over them, and answers top-k similarity queries with optional metadata filtering — over gRPC, REST, an embeddable library API, and an MCP server, with a retro terminal cockpit for operators.
The wedge (and the non-goals)
Quiver competes on a narrow, defensible edge, not on raw scale or feature count:
- Security-first, by default. Encryption-at-rest is on out of the box with secure defaults; payloads can be client-side-encrypted so the server never sees plaintext; strong authN/Z, audit, and crypto-shredding. Only audited cryptography (
rustls, RustCrypto/ring) — never a primitive of our own. - Memory frugality. Disk-resident graph/inverted indexes plus quantization (product, scalar, binary) let large datasets serve from a laptop’s RAM budget. The headline benchmark metric is memory footprint at a fixed recall, not just QPS.
- Developer experience. A single static binary; embeddable and server modes; a
ratatuicockpit; idiomatic Python and TypeScript SDKs; an MCP server so agents can drive it.
Explicit non-goals for v1 (stated to keep the project honest): out-scaling Milvus/Pinecone; distributed clustering (single-node excellence first; replication is a clearly-labelled stretch); homomorphic-encrypted search in core (only a published distance-comparison-preserving scheme, behind an experimental flag, with honest leakage caveats). Embeddings are produced by the caller — Quiver stays model-agnostic.
Workspace map
The core (storage, indexes, kernels, query planner, on-disk format, wire protocol) is built from scratch. A minimal set of vetted crates is used only where reinventing would be reckless (async runtime, TLS, crypto, serialization, TUI). No embedded database engine is used (no RocksDB/LMDB/sqlite).
| Crate | Responsibility | Notable external deps |
|---|---|---|
quiver-simd | SIMD distance kernels (cosine/L2/dot/hamming), runtime CPU-feature dispatch, scalar fallback | none (uses std/core::arch) |
quiver-crypto | Thin wrappers over audited crypto: envelope encryption, AEAD, KDF, key hierarchy, TLS config | ring/RustCrypto, rustls |
quiver-core | Storage engine: segments, mmap + page/buffer manager, WAL, manifest, compaction, snapshots; the collection/payload model | memmap2, crc32c |
quiver-index | HNSW (in-mem), DiskANN/Vamana (disk), IVF; quantization (PQ/scalar/binary) | — |
quiver-query | Query planner; hybrid filtered search (vector + metadata predicate + optional BM25); top-k merge & re-rank | — |
quiver-proto | Wire types: gRPC service (tonic/prost), REST DTOs, OpenAPI generation | tonic, prost, serde |
quiver-embed | Embeddable in-process database handle — the clean Rust API over core+index+query+crypto | — |
quiver-providers | Edge embedding/rerank adapters (OpenAI-compatible/Cohere/fake) shared by the network and MCP servers (ADR-0047/0058) | ureq, figment |
quiver-server | The daemon: axum REST + tonic gRPC, auth, RBAC, audit, query cost limits (ADR-0040), config, observability | axum, tonic, tokio, tracing |
quiver-tui | The ratatui cockpit (API client; works local or remote) | ratatui, crossterm |
quiver-mcp | MCP server exposing Quiver as agent tools | MCP SDK / rmcp |
quiver-cli | Single binary entrypoint: serve, tui, mcp, admin, bench | clap |
Dependency DAG (acyclic by construction)
flowchart TD
simd[quiver-simd]
crypto[quiver-crypto]
proto[quiver-proto]
core[quiver-core] --> crypto
index[quiver-index] --> core
index --> simd
query[quiver-query] --> index
query --> core
embed[quiver-embed] --> query
embed --> crypto
providers[quiver-providers]
server[quiver-server] --> embed
server --> proto
server --> crypto
server --> providers
tui[quiver-tui] --> proto
mcp[quiver-mcp] --> embed
mcp --> crypto
mcp --> providers
cli[quiver-cli] --> server
cli --> tui
cli --> mcp
cli --> embed
Domain logic lives in the lower crates (core/index/query/crypto); framework code (HTTP, gRPC, TUI, MCP) stays at the edges. This keeps the engine testable in isolation and reusable in embedded mode.
Operating modes
- Embedded library —
quiver_embed::Database::open(path, config)?gives an in-process handle: no network, no auth surface, encryption-at-rest still on. For tests, notebooks, and apps that want a local vector store. - Server —
quiver serveexposes gRPC + REST with auth, RBAC, multi-tenant namespaces, audit, query cost limits (ADR-0040), and observability. The TUI and MCP server are API clients of it.
Both ship in one static binary; quiver-server is a thin network/policy shell over quiver-embed.
Request lifecycles
Write (upsert): client → server (TLS terminate → authenticate → authorize scope → cost-limit check → idempotency check) → quiver-embed → quiver-core appends a WAL record (encrypted, checksummed) and stages the vector + payload into the active segment → quiver-index inserts the vector into the live index → ack after WAL fsync (durability boundary). Payload secondary indexes updated transactionally with the segment.
Query (top-k + filter): client → server (authn/z) → quiver-query plans the filter strategy (pre-filter via metadata bitmap when selective; post-filter otherwise) → quiver-index runs ANN search (HNSW/Vamana/IVF), using quiver-simd kernels on quantized vectors → candidate set re-ranked with exact distances against full-precision vectors fetched & decrypted from quiver-core → top-k assembled (payloads decrypted at rest; returned as-is if client-side-encrypted) → response with a cursor for pagination.
Cross-cutting concerns
- Security layers: TLS/mTLS in transit (
quiver-crypto/rustls); envelope encryption at rest (per-collection DEK wrapped by a master key, AEAD on segment pages); optional client-side payload encryption (opaque ciphertext to the server); API-key scopes + RBAC + tenant isolation; append-only audit log; crypto-shredding by DEK destruction. See../security/threat-model.mdand../security/crypto.md. - Concurrency: single-writer, many-reader. The server guards the engine with a reader–writer lock (ADR-0057): searches take the shared lock and run in parallel, writes take the exclusive lock. A read that finds a collection’s index stale (a prior write deferred its rebuild) serves the prior snapshot and schedules an off-lock rebuild (ADR-0062) — the inputs are captured under the shared lock, the new index is built with no lock held, and it is swapped in under a brief write lock — so a rebuild never stalls concurrent readers (measured: a 100k-vector rebuild that blocked every read for ~77 s now keeps reads in the sub-millisecond tail). Reads are then snapshot-isolated and eventually consistent across a rebuild window; embedded
&mutcallers still rebuild synchronously for read-your-writes. Durability and thekill -9crash gate are unchanged. - Observability: OpenTelemetry-compatible
tracingspans across server→query→index→core; Prometheus/metrics; structured logs;/healthz+/readyz. - Configuration: typed, validated config with secure defaults; secrets via env + KMS pattern (see ADR-0013).
Where to read next
- C4 system context —
c4-context.md; container view —c4-container.md. - Per-subsystem diagrams (storage, indexing, retrieval, distributed, security, ops) —
../diagrams.md. - Decisions —
../adr/. Roadmap & DoDs —../roadmap.md. Risks —../risk-register.md. - The bridge to implementation —
../repo-scaffold-plan.md.
Index Design
The index engine (quiver-index) is the centerpiece. Indexes are pluggable per collection behind a common trait, so a collection picks the point on the recall / latency / memory surface it needs. The decisions are recorded in ADR-0007 (index roadmap) and ADR-0008 (quantization); the distance math is in distance-kernels.md.
The tradeoff surface
| Index | RAM resident | Disk | Recall | Latency | Build | Best for |
|---|---|---|---|---|---|---|
| HNSW (Phase 1) | graph + vectors | — | very high | lowest | medium | small/hot collections in RAM |
| Vamana / DiskANN (Phase 2) | PQ codes + node cache | graph + full vectors | high | low–med (SSD-bound) | slow | large collections, frugal RAM |
| IVF (+PQ / SPANN) (Phase 2) | centroids (+ codes) | posting lists | med–high | med | fast | predictable RAM, fast build, fallback |
Memory frugality is the headline; the disk-resident path is risk R1 and is de-risked with the analytical budget below before any code is written.
HNSW — Phase 1, in-memory
Hierarchical Navigable Small World graphs (Malkov & Yashunin, IEEE TPAMI 2020; arXiv:1603.09320). A multi-layer proximity graph: greedy descent through sparse upper layers to an entry region, then an ef-bounded best-first search at the dense base layer.
- Parameters:
M(neighbors/node/layer; base layer2M),efConstruction,efSearch, level factormL = 1/ln(M). Recall/latency tuned byefSearchat query time. - Neighbor selection: the paper’s heuristic (keep diverse neighbors, not merely the nearest) — materially better recall on clustered data than naive top-
M. - Memory layout: base-layer adjacency in a flat arena of fixed
2Mu32slots per node for cache locality; upper-layer lists (held only by the few nodes promoted above L0) in a compact side structure. Vectors are referenced by row id into the columnar store — full-precision for exact distance, or quantized codes when compressed. - Concurrency: built by the single writer; traversed lock-free by readers via atomic adjacency publication + EBR (see
../concurrency/model.md).
Vamana / DiskANN — Phase 2, disk-resident (the memory-frugal core)
DiskANN (Subramanya et al., NeurIPS 2019). A single flat Vamana graph (degree R, build list L, prune slack α≈1.2) laid out on SSD so each node co-locates its adjacency and its full-precision vector in one disk block — one random read per hop.
- RAM holds only PQ-compressed vectors (to navigate with approximate distances) plus a hot-node cache; the graph and full vectors stay on SSD. The candidate set returned by the beam search is re-ranked with exact distances by fetching full-precision vectors from SSD.
- Parameters:
R(e.g. 64–128),L,α, beam widthW(parallel SSD reads/hop).
IVF (+ PQ / SPANN) — Phase 2, the predictable-memory fallback
Inverted file: a coarse k-means quantizer partitions space into nlist Voronoi cells; a query probes the nprobe nearest cells. Combined with PQ (IVFADC; Jégou et al., IEEE TPAMI 2011) or with on-disk posting lists (SPANN; Chen et al., NeurIPS 2021, which keeps centroids in RAM and balanced posting lists on SSD). IVF gives a tighter, more predictable RAM profile (essentially just centroids) and fast builds, at somewhat lower recall-per-IO than a good graph — hence its role as the R1 fallback if a Vamana RAM budget slips.
Quantization (ADR-0008)
- Scalar (SQ): f32 → int8 per-dim min/max. 4× smaller, fast, good default for light compression.
- Product (PQ): split
dimintomsubspaces, k-means (256 centroids → 1 byte/subspace); asymmetric distance via precomputed lookup tables. The workhorse for RAM-resident codes. - Binary (BQ): 1 bit/dim (sign); Hamming via SIMD popcount as a fast pre-filter, then exact re-rank. ~32× smaller; strong for high-dim normalized embeddings.
- Re-rank flow: approximate distance (SQ/PQ/BQ) → candidate set → exact full-precision distance → final top-k. The candidate multiplier is the recall ↔ latency/memory knob.
Analytical memory budget — de-risking R1 (768-dim f32 embeddings)
| Representation | Bytes / vector | vs full |
|---|---|---|
| Full precision (f32) | 3072 | 1× |
| SQ int8 | 768 | 4× |
| PQ, m=192 | 192 | 16× |
| PQ, m=96 | 96 | 32× |
| Binary | 96 | 32× |
| HNSW base adjacency (M=16) | 128 | — |
10M × 768-dim: full vectors = 30.7 GB (won’t fit a laptop). DiskANN keeps PQ codes (m=96) ≈ 0.96 GB in RAM + a node cache, with full vectors + graph (~32 GB) on SSD → serve 10M from ~1–2 GB RAM. 100M ⇒ ~9.6 GB PQ in RAM (a 32 GB workstation) + ~320 GB SSD. Honest scope: billion-scale DiskANN needs a server (~64 GB RAM); on a 16–32 GB laptop/workstation the disk path comfortably serves tens to a few hundred million vectors. SIFT1M (128-dim) full-precision HNSW is ~0.5 GB of vectors + ~0.13 GB adjacency — trivially in RAM.
De-risk plan: (1) this budget + a recall model from the cited papers (now); (2) a .scratch spike on a public 1–10M set measuring real recall vs RAM for chosen (R, m, W) before Phase 2 implementation; (3) prove at 10M+ in Phase 2 benchmarks. Fallback: IVF+PQ.
Filtered search
The planner (in quiver-embed) decomposes the Filter into the predicates the secondary indexes can answer (And intersects, Or unions, negation/existence/undeclared fields widen to unconstrained — always a sound superset) and resolves a candidate id set via Store::matching_ids. When that set is selective (at or below a full-scan threshold) it scans those rows exactly — perfect recall, and immune to the filtered-ANN recall cliff that bites when a selective predicate starves an over-fetched candidate list; an empty candidate set short-circuits to no results. When the filter is broad (or not indexable) it post-filters the ANN candidates instead. Both arms re-check the full Filter, so results are exact regardless of path. This is the selectivity-based strategy for v1; constraining graph traversal to an allowed-node bitmap, and specialized filtered-graph search (e.g. Filtered-DiskANN, Gollapudi et al. WWW 2023; ACORN, Patel et al. SIGMOD 2024), are later enhancements.
Incremental updates (Phase 4 — ADR-0023)
Through v0.3.0 the index is a derived artifact: HNSW absorbs a brand-new id in place, but an update, a delete, or any write to a batch index (Vamana / IVF / DiskVamana) marks the collection stale and the next search rebuilds it from the store — which is also how every index is reconstructed on open. This is simple and correct (the store is the single source of truth, ADR-0020/0021, so the index never has to be crash-consistent), but the update cost is O(N), which does not suit streaming workloads.
Phase 4 closes the gap with SpFresh’s LIRE protocol (Lightweight Incremental REbalancing; Xu et al., SOSP 2023), applied first to IVF — the inverted-list structure LIRE was designed for (SPANN; Chen et al., NeurIPS 2021). Inserts append to the nearest centroid’s posting list; deletes mark a per-index deletion set; and local split / merge / reassign rebalancing keeps posting lists balanced as the distribution drifts, maintaining the invariant that each vector sits in its nearest centroid’s list — which is what preserves recall — without a global rebuild. The first increment (v0.4.0) kept the index in memory and derived, so the kill -9 crash gate (ADR-0005) was untouched by construction. v0.6.0 then made the IVF index durable: it is snapshotted at checkpoint, referenced by the manifest under the same atomic swap as the segments, and recovered on open by loading the snapshot and replaying only the post-checkpoint WAL tail instead of rebuilding — with the crash gate extended to cover it (ADR-0025). v0.13.0 closes the last gap with FreshDiskANN’s StreamingMerge for the graph family (Vamana / DiskVamana; Singh et al., 2021 — a different algorithm than LIRE): the batch-built graph becomes a read-only base, recent inserts land in a small in-memory delta graph (Vamana::insert) searched alongside it, deletes are O(1) tombstones filtered with live-fraction beam widening, and consolidation is a derived rebuild from the store once pending work passes a churn threshold — so the index stays in memory and derived, the on-disk artifact keeps its write-once contract, and the crash gate is untouched by construction (ADR-0033). The decisions and full scope are in ADR-0023, ADR-0025, and ADR-0033.
Multi-vector / late interaction (Phase 4 — ADR-0028)
Single-vector retrieval pools a document into one embedding; late-interaction models (ColBERT) keep a document as a set of token embeddings and score a query — also a set — by MaxSim: each query token takes its best-matching document token, and the maxima are summed. It is stronger out of domain, at the cost of storing 100–200 vectors per document — the large, low-dimensional pool that Quiver’s PQ and disk-resident index were built to compress, so late interaction showcases the memory-frugality wedge rather than straining it.
Quiver models a multi-vector document as a group of ordinary rows — one row per token vector — over the existing row-addressed store (ADR-0020), with no on-disk format change, so the kill -9 crash gate stays untouched by construction. The token pool is the set the collection’s ANN index serves, so candidate generation is a normal nearest-neighbour search (the PLAID shape): for each query token, find its nearest token rows, union their parent documents, then re-rank those candidates by the full MaxSim and apply the document-level Filter. Document grouping (doc-id → token rows) is derived in memory on open, like the existing id maps; the store stays the single source of truth. Late interaction requires a similarity metric (Cosine or Dot); the token pool compresses under the same IVF+PQ / disk path as any collection, with exact vectors read for the re-rank. The decision and full scope are in ADR-0028.
v0.14.0 takes two of ADR-0028’s three deferred follow-ups (ADR-0034). Incremental maintenance: document upsert/delete now dispatch to the token-pool index incrementally through the same per-point insert/delete path single-vector collections use, instead of marking the collection stale for a full rebuild — so a document write is size-independent, closing the last write-then-rebuild path. ColBERTv2 / PLAID compression: an opt-in colbert index holds coarse kmeans centroids plus per-token (centroid id, PQ residual code) in RAM (residuals trained under L2 so reconstruction is faithful; the collection metric is applied only at scoring) and prunes candidate generation by scoring centroids first — the exact token vectors stay on the encrypted store for the MaxSim re-rank. Both stay in memory and derived (rebuilt from the store on open), so neither changes the on-disk format or the crash gate. The third follow-up — native variable-stride document rows — is deferred: its benefit is storage locality / id-space only (candidate generation still needs the token-pool index, so it removes no work), and ADR-0034 gates the on-disk change on a measured locality win that is owner-reference-hardware-only, so it is not shipped on faith.
References
- Malkov, Yashunin. Efficient and robust ANN search using HNSW graphs. IEEE TPAMI, 2020.
- Subramanya et al. DiskANN: Fast, accurate billion-point NN search on a single node. NeurIPS, 2019.
- Chen et al. SPANN: Highly-efficient billion-scale ANN search. NeurIPS, 2021.
- Jégou, Douze, Schmid. Product quantization for nearest neighbor search. IEEE TPAMI, 2011.
- Xu et al. SpFresh: Incremental in-place update for billion-scale vector search. SOSP, 2023.
- Gollapudi et al. Filtered-DiskANN. WWW, 2023. · Patel et al. ACORN. SIGMOD, 2024.
- Singh et al. FreshDiskANN: A fast and accurate graph-based ANN index for streaming similarity search. 2021.
- Khattab, Zaharia. ColBERT: Efficient and effective passage search via contextualized late interaction over BERT. SIGIR, 2020. · Santhanam et al. ColBERTv2 (NAACL 2022) · PLAID (CIKM 2022).