# v0.16.0: Persisted HNSW Graph — 5–25× Faster Cold Load > v0.16.0 embeds prebuilt HNSW graphs directly into .feather files (format v9). A 40k×128-dim dataset goes from 2.7s rebuild to 48ms cold load — and recall improves from 0.963 to 0.988. - **Category**: Architecture - **Read time**: 6 min read - **Date**: June 18, 2026 - **Author**: Feather DB (Engineering) - **URL**: https://getfeather.store/theory/feather-db-v016-persisted-hnsw-graph --- Scenario 40k×128-dim cold load Recall@10 Serial rebuild (pre-v0.16.0 baseline) 13.4s 0.988 Parallel rebuild (pre-v0.16.0 fast path) 2.7s 0.963 **Persisted graph (v0.16.0)** **48ms** **0.988** Across typical workloads the speedup is **5–25×** depending on index size and hardware. Recall is preserved at serial-build quality (0.988) because the exact graph that was built at insert time is what gets reloaded — no approximation on the way back in. File sizes increase roughly **25%** due to the embedded link lists. That is the only cost. ## Before and after ### Before v0.16.0 — rebuild every cold start ```python import featherdb db = featherdb.open("embeddings.feather") db.add(vectors, ids) db.save() # saves vectors + metadata, no graph # --- next process start --- db = featherdb.open("embeddings.feather") # always triggers parallel HNSW rebuild: ~2.7s for 40k vecs results = db.search(query, k=10) ``` ### After v0.16.0 — graph persisted, instant load ```python import featherdb db = featherdb.open("embeddings.feather") db.add(vectors, ids) db.save() # now embeds HNSW graph into the file (format v9) # --- next process start --- db = featherdb.open("embeddings.feather") # graph loaded from file: ~48ms for 40k vecs, no rebuild results = db.search(query, k=10) ``` No API changes. No flags to set. If the conditions for persistence are met, `save()` writes the graph and `load()` restores it. ## When the graph persists vs. when it falls back Persistence has three hard requirements. If any are violated, `save()` skips the graph and `load()` falls back to parallel rebuild automatically. - **Index holds exactly the live set.** No pending `forget()` or `purge()` calls that have not been flushed. The graph must be structurally consistent with the vector set on disk. - **No on-disk quantization.** Quantized modalities compress the stored vectors; the link lists reference positions in the full-precision space, so they cannot be trivially round-tripped through a quantization codec. - **No pending forget/purge.** Soft-deleted vectors leave ghost nodes in the graph. Persisting a graph with ghosts would restore stale routing paths on reload. In-RAM `int8` modalities are explicitly supported. The int8 graph is persisted with exact round-trip fidelity — positions are stored as int8, loaded as int8, no precision shift. Each modality gets its own `persist_graph` flag in the v9 format, so a multi-modal index can persist some modalities and fall back on others independently. ## The compact() workflow After a `forget()` or `purge()` the graph is invalidated and persistence is disabled until the index is compacted. `compact()` rebuilds a clean graph over the surviving vectors and re-enables fast load for subsequent saves. ```python import featherdb db = featherdb.open("embeddings.feather") # remove stale entries — disables graph persistence db.forget(old_ids) # compact rebuilds a clean graph over surviving vectors db.compact() # save() now persists the clean graph again db.save() # --- next process start: fast load restored --- db2 = featherdb.open("embeddings.feather") # 48ms, not 2.7s results = db2.search(query, k=10) ``` The compact/save/fast-load cycle is the recommended pattern for long-running indexes that periodically evict stale data. ## Why recall improves The previous fast path was parallel HNSW construction. Parallel build trades some graph quality for wall-clock speed — edges are inserted with less global visibility, producing a graph with slightly worse navigability. Recall@10 settled at 0.963. Serial build has full global visibility at edge insertion time and reaches 0.988 recall. The persisted graph is the serial-build graph, frozen and reloaded exactly. You get serial-build quality at sub-100ms cold load time. The only way that was previously possible was to accept the 13.4s serial rebuild on every restart. ## File format v9 v9 adds a per-modality header section that records: - `persist_graph: bool` — whether the link list block is present for this modality - Link list block — HNSW layer-0 and upper-layer neighbour arrays, length-prefixed per node - Entry point and `ef_construction` metadata needed to resume future inserts v3–v8 files are detected on open and loaded with the existing rebuild path. No migration step required. If you want fast load on an existing file, add vectors, call `compact()`, then `save()` — the next write produces a v9 file. ## What 30+ test cases cover - Persist → load → search round-trip, single and multi-modality - Fallback triggers: pending forget, pending purge, on-disk quantization - compact() re-enables persistence after each invalidation path - int8 graph round-trip fidelity (no precision shift) - Backward compat: v3, v5, v7, v8 files load and rebuild correctly under v0.16.0 - Recall regression: persisted graph must match or exceed parallel-rebuild recall on reference datasets - File size delta: assert embedded graph stays within 30% overhead bound ## Upgrade ```bash pip install --upgrade feather-db ``` No code changes required for existing projects. The first `save()` after upgrade on an eligible index writes v9. Cold loads from that point on skip the rebuild. If you need the old parallel-rebuild behaviour for any reason (e.g., you are size-constrained and cannot absorb the 25% file overhead), pass `persist_graph=False` to `save()`: ```python db.save(persist_graph=False) # opt out; falls back to parallel rebuild on load ``` ]]> --- *This is the machine-readable mirror of the theory post at [getfeather.store/theory/feather-db-v016-persisted-hnsw-graph](https://getfeather.store/theory/feather-db-v016-persisted-hnsw-graph). For the full Feather DB documentation, see [getfeather.store/llms-full.txt](https://getfeather.store/llms-full.txt).*