Skip to content
All articles

Inside the engine

Building a live ANN index on object storage

Built for billion-scale vector search, with fast queries and continuous updates.

At a billion vectors, scoring the entire collection for every query is an expensive way to find a handful of neighbours. Approximate nearest-neighbour (ANN) search aims to recover most of those neighbours while reading and comparing much less data.

Our index narrows the search in stages: choose promising regions, compare candidates cheaply, then score a shortlist with full vectors.

ANN search · narrow the candidates
Query
RouteFind promising regions
ShortlistCompare candidates cheaply
RescoreRank the shortlist
Each stage reduces the work for the next. A neighbour discarded along the way cannot be recovered by later scoring.

The harder part is keeping those shortcuts useful as the data changes. Inserts crowd some regions, deletes leave others sparse, and a changing distribution can make yesterday’s partition a poor guide. We designed our ANN layer to keep that partition useful over time, with object storage as its durable home.

Give each vector a home

Nearby vectors are grouped around a representative centre, or centroid. A query can then visit a few promising groups instead of searching the whole collection. Good placement matters: a true neighbour in a group the query skips is lost before scoring begins.

Our implementation, Cellstore, calls each group a cell. We train centroids on a sample and assign every vector one home cell (R = 1), aiming for its nearest centroid. That same cell becomes the unit we search and repair.

From vectors to cells

Use the steps below to explore the figure. On a narrow screen, scroll the diagram horizontally.

From vectors to cells. Illustrative two-dimensional geometry.Cells and routing groups

Super-centroids group the cells, letting routing narrow the search in two levels.

Figure 1 · Vectors form cells, and cells form routing groups. Dots are document vectors, diamonds are cell centroids, and open circles are super-centroids. Geometry is illustrative.

Routing works in two levels. A small set of super-centroids groups the cells; a query chooses promising groups, then selects cells within them. We keep this routing information in memory, so the query can decide where to look before reading the candidate data.

The design builds on partition-based search in SPANN and local partition maintenance in SPFresh, adapted to an index stored as immutable objects.

Spend reads on promising candidates

Choosing a few cells still leaves many vectors to compare. Each vector has a compact binary code for a cheap first pass and a separate full-vector representation for rescoring. The scan keeps a shortlist; full-vector scores determine its final order.

A query through the index

Use the steps below to explore the figure. On a narrow screen, scroll the diagram horizontally.

A query through the index. Illustrative two-dimensional geometry.123queryChoose routing groupsRead selected cellsKeep a shortlistReturn the nearest results

Fetch the shortlisted vectors, rescore them, and return the nearest results.

Figure 2 · One query, from routing to final results. Each step narrows the candidates. Geometry and counts are illustrative.

This separation matters on object storage. The query first reads codes from the selected cells, then fetches vectors for the shortlist. We batch related reads and cache frequently used data. A query pays for the regions and candidates it explores, rather than pulling every vector in those regions through the expensive scoring path.

Searching more cells or keeping a longer shortlist can recover more neighbours, at the cost of more work. Rescoring only helps candidates that survive the earlier stages, so we measure the quality of routing and code filtering as well as the final results.

Keep the partition useful as it changes

As vectors arrive and disappear, cell sizes and assignments become uneven. Crowded cells make queries scan more candidates; poor placement can hide useful neighbours. Maintenance repairs the affected regions so the partition continues to serve the search path.

A split also changes its neighbourhood

When a cell grows too large, we split it into two smaller cells. Their new centroids may be better homes for vectors in nearby cells too. We revisit those neighbours and move eligible vectors, repairing the boundary as well as the original cell.

Insert, split, reassign

Use the steps below to explore the figure. On a narrow screen, scroll the diagram horizontally.

Insert, split, reassign. Illustrative two-dimensional geometry.splitneighbours re-checked rows in the cellbound

Recheck nearby vectors against the new centres and move eligible vectors to better homes.

Figure 3 · A split creates new centres and changes nearby boundaries. Local reassignment gives affected vectors better homes.

Nearby splits share a repair pass, so overlapping neighbourhoods are handled together. The amount of neighbourhood work is a quality tradeoff: too little can leave vectors in poor homes. Placement needs to be checked over repeated updates.

Retire a cell after moving its survivors

Deletes can leave a cell too sparse to keep. We reassign its surviving vectors to nearby cells, potentially to different destinations, before retiring it. Every live vector keeps one home; a move removes the old membership and installs the new one in the same index version.

Delete, reassign, publish

Use the steps below to explore the figure. On a narrow screen, scroll the diagram horizontally.

Delete, reassign, publish. Illustrative two-dimensional geometry.retire rows in the cellfloor
PublicationIndex v2The updated index is ready for new queries.

Publish the updated index once its parts are ready. New queries can adopt it; in-flight queries keep their existing view.

Figure 4 · Move the surviving vectors before retiring their old cell. Readers adopt the updated index as a coherent version.

Publish a coherent view, then clear the history

Updates are recorded before background work folds them into the partition. Readers incorporate recent changes as they catch up, while maintenance prepares the next durable version of the index.

The update path
Maintain
ApplyInserts and deletes
RepairAdjust affected regions
PublishA consistent version
Queries keep using a coherent index while maintenance prepares the next version.

Existing objects remain immutable. The writer prepares new data and matching routing information, then publishes the version once its parts are ready. Each query holds one consistent view; later queries can adopt the new version without interrupting work already in flight.

Small incremental writes leave a history of additions and deletions. Background compaction consolidates that history so future queries have fewer pieces to read. Compaction itself rewrites data, so its cadence balances maintenance cost against the extra work carried by queries.

Local repair and compaction address different kinds of drift: one keeps the geometry useful, while the other keeps accumulated history from slowing down the read path.

Track quality and cost through updates

A live index needs to be evaluated throughout its lifetime. We track three things together:

  1. Search quality.

    Compare results with exact nearest neighbours. Check whether routing and compact codes retain useful candidates as the vector distribution changes.

  2. Placement.

    Measure how many vectors remain in their nearest-centroid cell after repeated inserts and deletes. Being present exactly once is a separate property from being placed well.

  3. Work over time.

    Track query latency, data read, and maintenance writes as updates accumulate. These show when local repair or compaction is falling behind.

On our 8.84-million-vector evaluation corpus, the finer partition reached 93.6% ANN Recall@10 at 7.55 ms warm median latency on one CPU core. The curves compare our two partition sizes with LanceDB on the same vectors and queries.

Search quality and query cost
AriseLabs and LanceDBWarm queries · one CPU core · 8.84M vectors

Scroll horizontally to see the full chart on a narrow screen.

AriseLabs and LanceDB: ANN Recall at 10 versus median warm query latency. Five curves compare our two partition sizes with LanceDB IVF_PQ, with and without refinement, and IVF_HNSW_SQ. Our finer partition reaches 93.6 percent recall at 7.55 milliseconds; LanceDB IVF_PQ with refinement reaches 94.8 percent at 77.40 milliseconds. image/svg+xml AriseLabs · matplotlib
Figure 5 · Markers show tested settings; lines connect each series’ observed quality–latency frontier. The latency axis is logarithmic. MS MARCO passages, 1,024-d Cohere v3 embeddings, top 10 results. Recall uses exact inner-product neighbours on 500 queries; latency uses 6,980 queries. LanceDB 0.38.0 uses f32 vectors; our vector store uses f16. Index layouts and search settings differ. Recorded 7 September 2026.

In a separate update check, we added one million duplicate vectors under new row IDs to the finer partition. At unchanged query settings, recall stayed close, from 88.5% to 88.1%, while warm median latency rose from 3.50 to 5.76 ms before compaction. The index absorbed those additions incrementally; the extra query work shows why maintaining the read path remains part of the job.

Fast ANN search comes from keeping work selective: visit promising regions, compare compact representations, and spend full-vector reads on a shortlist. A live index has to preserve that selectivity as documents arrive, change, and disappear.

That is why we design the query path and the update path together. The same partition that narrows a query also gives maintenance a local scope. It is the foundation of our approach to billion-scale search: make each query touch less data, and keep that advantage as the corpus changes.

Figure detail