Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

The vector index

Prev Next

Applies to VAST Cluster 5.5.

This page covers the ideas the rest of the Vector Search docs rely on: what you define when you create a vector-indexed table, what happens to the rows afterwards, and the rules a search has to follow to use the index. For how the index works inside the cluster, see architecture.

Objects

Object What it is
Vector column A column of SQL type VECTOR(<element type>, <dimension>), for example VECTOR(FLOAT, 768). These docs use FLOAT elements; FLOAT16 (alias HALF) also works. The length is fixed: inserting a vector of another length fails.
Vector index Clustering of the table's data on one vector column. You create it with the table (VECTOR CLUSTER BY) and can't add it later. You can't drop it or re-create it. A table has at most one.
Distance metric How "near" is computed, both for clustering and for search: l2sq (Euclidean), cosine or ip (inner product). You can't change it after the table is created.
Cluster quantization The encoding the index uses when it builds and stores its clusters: none or scalar_8bit. Set when the table is created.
Search quantization The encoding used for distances while a search walks the index: none or scalar_8bit. Set when the table is created.
Recent rows Rows inserted since the last indexing pass, which aren't in the index yet. Every search reads them directly, by brute force, so they're found straight away.

An index is designated when the table is created, can't be designated afterwards and can't be removed, and only one vector column per table can be indexed (see Vector database index). Every decision you make at creation, and what each one rules out, is in design a vector table.

Both quantization settings default to scalar_8bit, and not every pair is allowed. See quantization.

Avoid naming a column vector. It's a reserved word in the VAST Query Engine, and unquoted it's a syntax error.

In the Python SDK, a vector column is a fixed-size Arrow list of float32 whose items aren't nullable: pa.list_(pa.field("item", pa.float32(), nullable=False), dim).

Terms

Term Meaning
Approximate nearest-neighbour (ANN) search A search that uses the vector index. It returns close matches quickly, but it can miss some of the exact nearest neighbours.
Probe Opening a cluster of the index during a search. How many clusters a search opens at each level is its probe budget: more probes usually means better recall and more latency. The search session settings set the budget.
Recall@K The fraction of the exact top K rows that a search with the index also returns in its top K. See measure recall and latency.
Query Engine VIP pool A virtual IP pool with the Query Engine role. Your client reaches the VAST Query Engine through its addresses (VAST Query Engine). See setup.
Clustering grade How far the index has caught up with the table's rows, from C up to A+. A grade of C doesn't mean the index isn't used. See performance and sizing.

Lifecycle

Your writes, with SQL INSERT, ADBC bulk ingest, or the Python SDK's insert() or import_files(), are searchable once they commit. New rows land as recent rows, which aren't in the index yet and are read directly by every search. In the background, initial indexing places recent rows into the index, a hierarchy of clusters, and reclustering keeps refining that hierarchy. A search reads both: it walks the index's clusters and reads the recent rows directly, so new rows are always found, but a large backlog of recent rows makes every search do more work and run slower. An UPDATE of the vector column is rejected: to change a vector, delete the row and insert it again, and the new row joins the recent rows. With the session setting vector_search_skip_recent_non_indexed set to 1, a search reads only the index, so recent rows can be missing from its results.

New rows are searchable at once as recent rows; background indexing moves them into the index.

  1. Create an empty table with VECTOR CLUSTER BY. See create an index.
  2. Ingest with SQL INSERT, the SDK's insert() or import_files(), or ADBC bulk ingest. See ingest.
  3. Initial indexing. In the background, the cluster takes the recent rows and places them into clusters of the index.
  4. Reclustering. Also in the background, the cluster refines the hierarchy with k-means clustering.
  5. Query at any time after the insert commits. Until indexing has caught up, a search reads more rows directly, so it's slower. Judge speed and recall once indexing has caught up with your load: the clustering grade shows how far it has got. With the session setting vector_search_skip_recent_non_indexed set to "1", a search reads only the index, so recent rows can be missing from its results (search session settings).
  6. Change rows. UPDATE of non-vector columns and DELETE work. You can't update a vector value: the cluster rejects it. To change a vector, delete the row and insert it again, so the new vector is placed in the right cluster.

What distance values mean

An embedding model turns a piece of data, such as a text chunk, an image or a video frame, into a vector: a fixed-length array of numbers that captures its meaning, not its exact content. Vectors that are near each other represent data with similar meaning.

The vectors embedding models produce for semantic search are dense: most of their elements hold a value. Sparse vectors, where nearly every element is zero (used for keyword-style, lexical search), aren't supported in VAST Cluster 5.5: there's no sparse vector type, and a VECTOR column holds a fixed-length list of numbers (supported data types).

How "near" is measured depends on the table's metric, and each metric has a distance function you use in SQL.

All three distance functions follow one rule: smaller is more similar. That's why every search sorts by distance ascending.

Metric Distance function What it measures Range 0 means
l2sq array_distance Euclidean distance: the straight-line distance between the two vectors. It depends on both their direction and their length. 0 to +∞ The vectors are identical.
cosine array_cosine_distance 1 − cosine similarity. Cosine similarity compares only the angle between the vectors, not their length, and runs from −1 (opposite) through 0 (unrelated) to 1 (same direction). 0 to 2 Same direction. 1 means perpendicular (unrelated) and 2 means opposite.
ip array_negative_inner_product The inner (dot) product, with its sign flipped. The inner product is the vectors' lengths multiplied together and by the cosine of the angle between them, so it rewards both direction and length. −∞ to +∞ Nothing special: values are negative for similar vectors and positive for dissimilar ones. Only the order matters.

For example, with the query vector [1, 1], the stored vector [3, 3] points the same way but is three times as long. Its distances are 2.83 (Euclidean), 0 (cosine) and −6 (negative inner product): cosine ignores the difference in length, Euclidean distance counts it against the match, and inner product counts it in its favour. The stored vector [1, 0] is at Euclidean distance 1 and cosine distance 0.29 (an angle of 45°), and its negative inner product is −1.

What this means in practice:

  • Use the metric your embedding model was trained for. Its documentation says which. Many text-embedding models are meant for cosine. If a model produces vectors of length 1 (normalised), cosine, Euclidean and inner product rank results the same way.
  • Compare distances only within one search. A distance has no fixed meaning across metrics or models: 0.3 can be close for one model and far for another. If you need a cut-off, such as "only return chunks closer than X", find it by testing with your own data and model.
  • array_distance returns the Euclidean distance itself, not its square, even though the metric is called l2sq. Ranking is the same either way.
  • The similarity forms sort the other way. array_cosine_similarity and array_inner_product grow as vectors get more similar. Sorting them descending doesn't use the index, and sorting them ascending returns the least similar rows first. Search with the distance functions above (query design).

Rules for a search that uses the index

A search on the index is an approximate nearest-neighbour search. The Query Engine walks the index from the top, level by level, and opens only the clusters nearest the query vector; architecture explains the walk. For the walk to happen, the query must have this shape:

SELECT <id>, <payload>,
       <distance_fn>(vec::FLOAT[d], ARRAY[...]::FLOAT[d]) AS dist
FROM "bucket/schema".table
WHERE <optional filters>
ORDER BY dist ASC
LIMIT k;
  • Use the distance function that matches the index's metric.

    Metric Distance function
    l2sq array_distance
    cosine array_cosine_distance
    ip array_negative_inner_product

    A query with a different distance function still runs, but it doesn't use the index: it computes the distance for every row.

  • Smaller is nearer. ORDER BY dist ASC. For inner product, use the negative function, so ascending order still puts the best match first: array_inner_product sorted ascending returns the least similar rows first. The SDK's vector_search() has the same problem on ip tables; see Python SDK.

  • One sort key and a LIMIT, no OFFSET. Sort by the distance only, ascending, with a LIMIT. A search with OFFSET runs, but it doesn't use the index.

  • Match the dimension. Cast both sides to the column's width, FLOAT[d] (or FLOAT16[d]).

  • Filters in WHERE work alongside the ranking, and row and column security applies to the user running the search (Vector search).

  • Order by a distance, not a similarity. A similarity sorted descending, as in some slides and webinars, runs as a full scan and doesn't use the index; use the distance ascending. (There's no cosine_similarity function; array_cosine_similarity exists, but doesn't use the index.) See query design.

EXPLAIN shows whether a search uses the index: its plan contains a VAST_VEC_INDEX_SCAN step. Query design has the full rules, with captured examples of each.

What "indexed" doesn't mean

  • It isn't HNSW, IVF or another vendor's index. It's hierarchical clustering, with recent rows read directly until they're indexed. See architecture.
  • It isn't a separate namespace. You search the same table you insert into.
  • It isn't self-training. The clustering is a data structure, not a model.

See also