Skip to content

Vector search

Vector indexes

The simplest way to search through your vector embeddings and data in SurrealDB is the brute force approach.

Brute force search compares a query vector against all vectors in the dataset to find the closest match. As this is a brute-force approach, you do not create an index for this approach.

The brute force approach for finding the nearest neighbour is generally preferred in the following use cases:

  • Small datasets / limited query vectors: For applications with small datasets, the overhead of building and maintaining an index might outweigh its benefits. In such cases, the brute force approach is optimal.

  • Guaranteed accuracy: Since the brute force method compares the query vector against every vector in the dataset, it guarantees finding the exact nearest vectors based on the chosen distance metric (like Euclidean, Manhattan, etc.).

  • Benchmarking models: The brute force approach can be used as a reference to help benchmark the performance of other approximate alternatives like HNSW or DISKANN.

While brute force can give you exact results, it's computationally expensive for large datasets.

In most cases, you do not need a 100% exact match, and you can give it up for faster, high-dimensional searches to find the approximate nearest neighbour to a query vector.

Vector indexes provide this kind of approximate search.

SurrealDB offers two approximate graph indexes for k-nearest-neighbour search:

IndexBest forStorage
HNSW (DEFINE INDEX … HNSW)Low-latency ANN when the graph fits comfortably in memory with headroom for the bounded vector cacheIn-memory hot graph + persistence
DISKANN (DEFINE INDEX … DISKANN) (SurrealDB 3.1+)Very large corpora where RAM cannot hold the full graph - full-precision vectors and graph stay in the key-value store and are paged through a bounded cacheKey-value-backed graph + bounded in-memory cache (64-bit platforms and WebAssembly builds)

Both are proximity graph-style indexes. Queries use the same <|K, …|> KNN operator shapes; the optimiser picks the index when distances and types line up.

  • HNSW - efficient in-memory approximation for high dimensions or large in-RAM datasets.

  • DISKANN - disk-oriented approximation for embeddings that exceed practical memory for a pure HNSW graph.

  • Brute force - when you do not define an index, when you want exact nearest neighbours, or when you pass an explicit distance function to the query that does not route to your index.

ParameterDefaultOptionsDescription
DIMENSIONSize of the vector
DISTEUCLIDEANEUCLIDEAN, COSINE, MANHATTANDistance function
TYPEF32F64, F32, I64, I32, I16Vector type
EFC150EF construction
M12Max connections per element
M024Max connections in the lowest layer
LM0.40242960438184466fMultiplier for level generation. This value is automatically calculated with a value considered as optimal.

An HNSW index stores its graph in layers. Every vector is in layer 0, and each higher layer holds a smaller sample of the layer below, with links that span longer distances. M limits the links per node in the upper layers and M0 in layer 0. A search enters at the top, walks towards the query in each layer and drops to the next, then keeps the EF nearest nodes it finds in layer 0 and returns the K nearest of them:

Three panels for the layers of a small HNSW graph. Layer 2 has three nodes; the search starts at the entry point and moves one step towards the query. Layer 1 has eight nodes; the search continues from where layer 2 ended and moves one step closer. Layer 0 holds every node with more links; the search walks to the nodes nearest the query, keeps the six nearest it finds as EF, and returns the two nearest as K.Three panels for the layers of a small HNSW graph. Layer 2 has three nodes; the search starts at the entry point and moves one step towards the query. Layer 1 has eight nodes; the search continues from where layer 2 ended and moves one step closer. Layer 0 holds every node with more links; the search walks to the nodes nearest the query, keeps the six nearest it finds as EF, and returns the two nearest as K.

Examples:

-- User statement:
DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4;
-- Defaults to:
DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4
    DIST EUCLIDEAN
    TYPE F32
    EFC 150
    M 12
    M0 24
    LM 0.40242960438184466f;
-- Users are strongly suggested not to set an LM value, as
-- it is computed based on other parameters. Only users
-- completely versed in the field should manually set it

For more details, see the DEFINE INDEX statement documentation.

Available since: v3.1.0

ParameterDefaultOptionsDescription
DIMENSIONVector dimension
DISTEUCLIDEANEUCLIDEAN, COSINE, INNER_PRODUCT, COSINE_NORMALIZEDDistance (narrower set than HNSW)
TYPEF32F32, F16, I8, U8Element encoding (COSINE_NORMALIZED requires F32 or F16)
DEGREE64> 0Target maximum graph degree
L_BUILD100> 0Construction search-list size
ALPHA1.2DiskANN pruning parameter
HASHED_VECTORoffOptional hash-stabilised vector keys
DEFINE INDEX diskann_idx
  ON pts FIELDS point DISKANN DIMENSION 4 DIST COSINE TYPE F32;

DISKANN keeps its graph and full-precision vectors in the key-value store and answers queries through a bounded in-memory cache, so resident memory is bounded by the cache rather than by the dataset. (DISKANN does not apply product quantisation; per-vector size is governed only by the chosen TYPE.) The cache defaults to 256 MiB and is shared across all DISKANN indexes; cap it with SURREAL_DISKANN_CACHE_SIZE.

DISKANN is available on 64-bit platforms and in WebAssembly builds, including the Wasm engine in a browser. Other 32-bit platforms, such as 32-bit x86 and 32-bit Arm, use HNSW instead. See DEFINE INDEX → DISKANN for the full memory model.

DEFINE INDEX hnsw_idx ON pts FIELDS point HNSW DIMENSION 4;

LET $vector = [2,3,4];
SELECT
    id,
    vector::distance::knn() as dist  -- distance from $vector
                                     -- knn reuses the value computed during
                                     -- the query, in this case the euclidean
                                     -- distance
FROM pts
WHERE point
    <|2|>  -- return 2, in this case using the distance function defined in the
           -- index: euclidean
    $vector;

With a DISKANN index defined on point, the same <|K, EF|> approximate form applies; the second number bounds the dynamic candidate list for search (see the KNN operator).

Functions
vector::distance::knn()reuses the value computed during the query
vector::distance::chebyshev(point, $vec)
vector::distance::cosine(point, $vec)1 - vector::similarity::cosine(point, $vec)
vector::distance::euclidean(point, $vec)
vector::distance::hamming(point, $vec)
vector::distance::manhattan(point, $vec)
vector::distance::minkowski(point, $vec, 3)third param is 𝑝
vector::distance::mahalanobis(point, $vec, $cov)third param is the covariance matrix, not an index distance
vector::similarity::cosine(point, $vec)
vector::similarity::jaccard(point, $vec)
vector::similarity::pearson(point, $vec)
vector::similarity::spearman(point, $vec)

WHERE statement

Query

HNSW index

DISKANN index

<|2|>

uses distance function defined in index

same when the index distance matches

<|2, EUCLIDEAN|>

brute force method

brute force method

<|2, COSINE|>

brute force method

brute force method

<|2, MANHATTAN|>

brute force method

brute force method

<|2, MINKOWSKI, 3|>

brute force method (third param is

𝑝

)

brute force method

<|2, CHEBYSHEV|>

brute force method

brute force method

<|2, HAMMING|>

brute force method

brute force method

<|2, 10|>

second param is effort*

same approximate form - second value bounds the candidate list

\* effort - for HNSW and DISKANN, the second number in <|K, N|> tells the engine how far to search along the graph. Both algorithms are approximate and may miss some vectors.

ORDER BY statement

From SurrealDB 4.0, an HNSW or DISKANN index also answers ORDER BY vector::distance::<metric>(point, $vec) LIMIT K when the function matches the distance the index was defined with, so ORDER BY vector::distance::euclidean(point, $vec) LIMIT 2 uses the hnsw_idx index above. The search runs with the same width as <|2|>, which is the larger of K and the index's EFC for HNSW or L_BUILD for DISKANN. See ordering by distance for the conditions.

Available since: v4.0.0

From SurrealDB 4.0, a KNN query on an index defined with DIST JACCARD or DIST PEARSON ranks records by the distance 1 - similarity, so the nearest vector comes first. Before 4.0, these two metrics ordered the results by the similarity itself. An existing HNSW index with either metric was built on the earlier ordering and must be rebuilt with REBUILD INDEX before it returns nearest-first results.

Rebuilding an HNSW or DISKANN index also makes KNN queries with a selective filter faster, as the rebuilt index lets those queries score candidates from the index. The upgrade page lists which indexes to rebuild and when: see upgrading from 3.3 to 4.0.

  • Verify index utilisation in queries using the EXPLAIN FULL clause. E.g: SELECT id FROM pts WHERE point <|10|> [2,3,4,5] EXPLAIN FULL;

  • 𝑝 values: (more about 𝑝 in Minkowski distance)

    • 20 = 1 → manhattan/diamond ◇

    • 21 = 2 → euclidean/circle ○

    • 22 = 4 → squircle ▢

    • 2∞ = ∞ → square □

Was this page helpful?