<a id="vs-working-with"></a>

# Working with Vector Search

This page provides a technical overview of how to work with Vector Search in
ScyllaDB. It covers the vector data type, vector indexes, similarity
functions, index tuning, ANN queries, and driver integration.

#### NOTE
This page describes Vector Search through CQL. Vector Search is also
available through the DynamoDB-compatible Alternator API, storing embeddings
as lists of numbers. For the Alternator workflow, Python examples, and a
feature-availability comparison with CQL, see
[Vector Search with Alternator](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-alternator.md).

<a id="vs-workflow"></a>

## Workflow

1. Create a keyspace.
2. Create a table with a vector-typed column to store your embedding vectors.
3. Insert vector data (embeddings) into the table.
4. Create a vector index on the vector column to enable efficient similarity
   search.
5. Perform similarity searches using the `ANN OF` query to find vectors most
   similar to your input.

<a id="work-with-vs-vector-type"></a>

## Vector Data Type

The `VECTOR` data type allows you to store fixed-length numeric vectors as
a native column type in ScyllaDB tables. These vectors can represent embedding
vectors or other high-dimensional numeric data used for similarity search.

* **Syntax:** `vector<element_type, dimension>` (e.g. `vector<float, 768>`)
* **Element types:** Typically floating-point types (e.g., `float`).
* **Dimensions:** Supports vectors with dimensionality ranging from 1 up to 16,000.

This vector data type integrates with ScyllaDB’s native protocol (v5) and is
fully supported by the CQL interface.

See [Data Types - Vectors](https://docs.scylladb.com/manual/branch-2026.1/cql/types#vectors)
in the ScyllaDB documentation for details.

<a id="vs-table-with-vector"></a>

## Table with Vector Column

You can store and query vectors in a table that contains a vector-typed column
(e.g., `vector<float, 768>`).

In the following example, a `comments` table is created in the `myapp` keyspace.
In addition to columns for storing and identifying comments (commenter name, comment text,
comment ID, etc.), it has a `comment_vector` vector-typed column for storing
vectors of `float` type.

#### NOTE
This example uses 5-dimensional vectors for clarity. In production, you
will typically use higher dimensions (384-1536) to match your embedding
model’s output.

```cql
CREATE TABLE IF NOT EXISTS myapp.comments (
  record_id timeuuid,
  id uuid,
  commenter text,
  comment text,
  comment_vector vector<float, 5>,
  created_at timestamp,
  PRIMARY KEY (id, created_at)
);
```

<a id="vector-search-tablets-info"></a>

### Tablets Requirement

All ScyllaDB versions currently used by ScyllaDB Cloud enable tablets by
default. This means every new keyspace automatically uses tablets-based data
distribution, and no additional configuration is required:

```cql
CREATE KEYSPACE myapp;
```

<a id="vs-embeddings"></a>

## Embeddings

Embeddings are fixed-length numeric vectors that represent data — such as text,
images, or audio — in a high-dimensional space, capturing their semantic or
structural meaning. They are typically generated by external machine learning
or deep learning models trained for tasks like semantic search, recommendation,
or classification.

The embedding pipeline works as follows:

1. Your application sends raw data (text, image, etc.) to an **embedding
   model** (e.g., OpenAI, Cohere, or an open-source sentence-transformer).
2. The model returns a **fixed-length vector** of floating-point numbers
   (e.g., 768 floats for `all-MiniLM-L6-v2`).
3. Your application **inserts the vector** into a ScyllaDB table.
4. At query time, your application embeds the query text using the **same
   model** and runs an `ANN OF` query against the stored vectors.

ScyllaDB does not generate embeddings — it stores and indexes the vectors
produced by your embedding pipeline. For background on how embeddings encode
meaning, see [How Embeddings Work](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-concepts.md#vs-concepts-embeddings) in the
Concepts page.

To insert an embedding vector into a ScyllaDB table, use a standard `INSERT`
statement with a list of numeric values matching the vector column’s defined
dimension and data type.

Example:

```cql
INSERT INTO myapp.comments (
    record_id, id, commenter, comment, comment_vector, created_at
) VALUES (
    now(), uuid(), 'Alice', 'I like vector search in ScyllaDB.',
    [0.12, 0.34, 0.56, 0.78, 0.91], toTimestamp(now())
);
```

* The vector must match the dimension and element type declared in
  the table schema, e.g., `vector<float, 5>`.
* All vector values must be numeric (e.g., `float`), and enclosed in
  square brackets.

<a id="vs-embedding-models"></a>

### Choosing an Embedding Model

When selecting an embedding model, consider the following trade-offs:

* **Dimensions** — higher dimensions capture more nuance but use more memory
  and increase query latency. Common choices: 384 (lightweight), 768
  (general-purpose), 1536 (high accuracy).
* **Model family** — popular options include OpenAI embeddings
  (`text-embedding-3-small`, `text-embedding-3-large`), Cohere Embed,
  and open-source sentence-transformers (e.g., `all-MiniLM-L6-v2`).
* **Normalization** — some models output unit-normalized vectors (suitable
  for cosine similarity), while others do not (use dot product instead).

ScyllaDB supports dimensions from 1 to 16,000, so it is compatible with all
major embedding models.

<a id="vs-vector-index"></a>

## Vector Index Type

Before you query the data, you need to create a vector index to enable fast
similarity search over vector columns. Without an index, a similarity query
would need to compare the query vector against every stored vector — a
brute-force scan that does not scale. The vector index pre-organizes vectors
into a navigable graph for $O(\log N)$ search. See
[Why You Need an Index](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-concepts.md#vs-concepts-why-index) for details.

This index type is based on the HNSW
(Hierarchical Navigable Small World) algorithm and supports Approximate Nearest
Neighbor (ANN) search with configurable similarity functions.

* **Creation:** Use a custom index on a vector column.
* **Similarity functions supported:** `DOT_PRODUCT`, `COSINE` (default),
  and `EUCLIDEAN`.
* **Index parameters:** Tunable HNSW parameters such as `m`
  (maximum node connections), `ef_construct` (construction beam width),
  and `ef_search` (search beam width).

Example:

```cql
CREATE CUSTOM INDEX IF NOT EXISTS ann_idx
ON myapp.comments(comment_vector)
USING 'vector_index'
WITH OPTIONS = { 'similarity_function': 'DOT_PRODUCT' };
```

See
[Global Secondary Indexes - Vector Index](https://docs.scylladb.com/manual/branch-2026.1/cql/secondary-indexes.html#vector-index-scylladb-cloud)
in the ScyllaDB documentation for details.

<a id="vs-similarity-functions"></a>

### Choosing a Similarity Function

The similarity function determines how distances between vectors are measured
during search. Choose based on your embedding model and use case:

| Function           | When to use                                                                                                                     | Notes                                                                                                                                                  |
|--------------------|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `COSINE` (default) | Normalized embeddings (unit vectors). Most text embedding models<br/>(OpenAI, sentence-transformers) output normalized vectors. | Measures the angle between vectors. Ideal when magnitude is<br/>irrelevant and only direction matters.                                                 |
| `DOT_PRODUCT`      | Non-normalized embeddings, or when magnitude carries meaning<br/>(e.g., popularity-weighted vectors).                           | May be slightly faster than cosine because it avoids the<br/>normalization division. Requires careful handling if vectors have<br/>varying magnitudes. |
| `EUCLIDEAN`        | Spatial data, geographic coordinates, or when absolute distance<br/>matters.                                                    | Measures straight-line distance in vector space. Less common for<br/>text embeddings but useful for spatial applications.                              |

<a id="vs-index-tuning"></a>

### Tuning the Vector Index

The HNSW index has tunable parameters that affect the trade-off between
recall (search accuracy), build speed, and query latency:

| Parameter                                | Default   | Description                                                                                                                                |
|------------------------------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------|
| `maximum_node_connections` (m)           | `16`      | Maximum number of connections per node in the HNSW graph. Higher<br/>values improve recall but increase memory usage and index build time. |
| `construction_beam_width` (ef_construct) | `128`     | Size of the dynamic candidate list during index construction. Higher<br/>values yield a higher-quality graph at the cost of slower builds. |
| `search_beam_width` (ef_search)          | `128`     | Size of the dynamic candidate list during query time. Higher values<br/>improve recall at the cost of higher query latency.                |

Example with tuned parameters:

```cql
CREATE CUSTOM INDEX IF NOT EXISTS tuned_ann_idx
ON myapp.comments(comment_vector)
USING 'vector_index'
WITH OPTIONS = {
  'similarity_function': 'COSINE',
  'maximum_node_connections': '32',
  'construction_beam_width': '200',
  'search_beam_width': '200'
};
```

**General guidance:**

* Start with defaults. Increase `search_beam_width` if recall is too low.
* Increase `maximum_node_connections` for high-dimensional vectors (>512
  dimensions).
* Remember: you cannot alter index options after creation. Drop and recreate
  the index to change parameters.

<a id="vs-ann-queries"></a>

## ANN OF Queries

Approximate Nearest Neighbor (ANN) is a search technique used to find data
points in large, high-dimensional datasets that are most similar to a given
query vector. Rather than computing exact distances for all entries, ANN
algorithms trade off a small amount of accuracy for significant speed
improvements, returning results that are sufficiently similar. This makes ANN
especially effective for applications like semantic search, recommendations,
image and audio retrieval, and generative AI, where real-time response and
scalability are critical.

Once a vector index is created on a VECTOR-typed column, you can use
the `ANN OF` query to perform ANN searches. This query allows you to
efficiently retrieve the top-k rows with vectors most similar to a given input
vector, using the similarity function defined while creating the vector index.

Syntax:

```cql
SELECT column1, column2, ...
FROM keyspace.table
ORDER BY vector_column ANN OF [v1, v2, ..., vn]
LIMIT k;
```

* **vector_column**: The name of the indexed vector column used for similarity search.
* **[v1, …, vn]**: The input query vector. It must match the dimensionality of
  the indexed column.
* **k**: The number of the nearest neighbors to return (required).

The query returns up to k most similar vectors, ranked according to the similarity function
defined in the index (`COSINE`, `DOT_PRODUCT`, or `EUCLIDEAN`).

Example:

```cql
SELECT id, commenter, comment, created_at
FROM myapp.comments
ORDER BY comment_vector ANN OF [0.12, 0.34, 0.56, 0.78, 0.91]
LIMIT 5;
```

See
[Data Manipulation - SELECT - Vector Queries](https://docs.scylladb.com/manual/branch-2026.1/cql/dml/select.html#vector-queries-scylladb-cloud)
in the ScyllaDB documentation for details.

<a id="vs-write-to-query-latency"></a>

## Write-to-Query Latency

After inserting or updating a vector, there is a short delay before the new
data becomes available in similarity search results. ScyllaDB uses a dual
CDC (Change Data Capture) reader system to propagate changes to the vector
index:

* A **fine-grained reader** with sub-second intervals provides low-latency
  updates (typical p50 latency under 1 second).
* A **wide-framed reader** with a 30-second safety interval ensures
  consistency and catches any data missed by the fast reader.

For most workloads, newly inserted vectors are queryable within approximately
1 second.

<a id="vs-drivers"></a>

## Vector Search in ScyllaDB Drivers

If you use a ScyllaDB driver for application development and want to use
the Vector Search feature, note that:

* Your driver version must support the [vector data type](#work-with-vs-vector-type).
  See [ScyllaDB Drivers - Support for Vector Search](https://docs.scylladb.com/stable/drivers/cql-drivers.html#cql-drivers-vector-support)
  to check from which version the vector type is supported in each driver.
* Vector search requires the driver to be configured with a DC-aware load
  balancing policy.

<a id="vs-driver-python-example"></a>

### Driver Examples

The following examples demonstrate connecting to a ScyllaDB Cloud cluster,
inserting a vector, and running a similarity query in different programming
languages.

Python

Uses the [scylla-driver](https://python-driver.docs.scylladb.com/)
package.

```python
import ssl
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
from cassandra.policies import DCAwareRoundRobinPolicy

auth = PlainTextAuthProvider(username='scylla', password='YOUR_PASSWORD')
ssl_context = ssl.create_default_context()
cluster = Cluster(
    contact_points=['node-0.your-cluster.cloud.scylladb.com'],
    port=9042,
    auth_provider=auth,
    load_balancing_policy=DCAwareRoundRobinPolicy(local_dc='AWS_US_EAST_1'),
    ssl_context=ssl_context,
)
session = cluster.connect('myapp')

# Insert a vector
session.execute(
    """INSERT INTO comments (record_id, id, commenter, comment, comment_vector, created_at)
       VALUES (now(), uuid(), %s, %s, %s, toTimestamp(now()))""",
    ('Alice', 'I like vector search in ScyllaDB.', [0.12, 0.34, 0.56, 0.78, 0.91])
)

# Run a similarity search
rows = session.execute(
    """SELECT commenter, comment FROM comments
       ORDER BY comment_vector ANN OF %s LIMIT 3""",
    ([0.12, 0.34, 0.56, 0.78, 0.91],)
)
for row in rows:
    print(f"{row.commenter}: {row.comment}")
```

Node.js

Uses the
[cassandra-driver](https://github.com/datastax/nodejs-driver)
package (compatible with ScyllaDB).

```javascript
const cassandra = require('cassandra-driver');

const client = new cassandra.Client({
  contactPoints: ['node-0.your-cluster.cloud.scylladb.com'],
  localDataCenter: 'AWS_US_EAST_1',
  keyspace: 'myapp',
  authProvider: new cassandra.auth.PlainTextAuthProvider(
    'scylla', 'YOUR_PASSWORD'
  ),
  sslOptions: { rejectUnauthorized: true },
});

async function main() {
  await client.connect();

  // Insert a vector
  await client.execute(
    `INSERT INTO comments (record_id, id, commenter, comment, comment_vector, created_at)
     VALUES (now(), uuid(), ?, ?, ?, toTimestamp(now()))`,
    ['Alice', 'I like vector search in ScyllaDB.', [0.12, 0.34, 0.56, 0.78, 0.91]],
    { prepare: true }
  );

  // Run a similarity search
  const result = await client.execute(
    `SELECT commenter, comment FROM comments
     ORDER BY comment_vector ANN OF ? LIMIT 3`,
    [[0.12, 0.34, 0.56, 0.78, 0.91]],
    { prepare: true }
  );
  for (const row of result.rows) {
    console.log(`${row.commenter}: ${row.comment}`);
  }

  await client.shutdown();
}

main().catch(console.error);
```

Java

Uses the
[scylla-java-driver](https://java-driver.docs.scylladb.com/).

```java
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.data.CqlVector;
import java.net.InetSocketAddress;

public class VectorSearchExample {
    public static void main(String[] args) {
        try (CqlSession session = CqlSession.builder()
                .addContactPoint(new InetSocketAddress(
                    "node-0.your-cluster.cloud.scylladb.com", 9042))
                .withLocalDatacenter("AWS_US_EAST_1")
                .withKeyspace("myapp")
                .withAuthCredentials("scylla", "YOUR_PASSWORD")
                .build()) {

            CqlVector<Float> vector = CqlVector.newInstance(
                0.12f, 0.34f, 0.56f, 0.78f, 0.91f);

            // Insert a vector
            session.execute(session.prepare(
                "INSERT INTO comments (record_id, id, commenter, comment, "
                + "comment_vector, created_at) "
                + "VALUES (now(), uuid(), ?, ?, ?, toTimestamp(now()))")
                .bind("Alice", "I like vector search in ScyllaDB.", vector));

            // Run a similarity search
            ResultSet rs = session.execute(session.prepare(
                "SELECT commenter, comment FROM comments "
                + "ORDER BY comment_vector ANN OF ? LIMIT 3")
                .bind(vector));
            for (Row row : rs) {
                System.out.printf("%s: %s%n",
                    row.getString("commenter"), row.getString("comment"));
            }
        }
    }
}
```

Go

Uses [gocql](https://github.com/gocql/gocql).

```go
package main

import (
    "fmt"
    "github.com/gocql/gocql"
)

func main() {
    cluster := gocql.NewCluster("node-0.your-cluster.cloud.scylladb.com")
    cluster.Keyspace = "myapp"
    cluster.Authenticator = gocql.PasswordAuthenticator{
        Username: "scylla",
        Password: "YOUR_PASSWORD",
    }
    cluster.SslOpts = &gocql.SslOptions{
        EnableHostVerification: true,
    }
    cluster.PoolConfig.HostSelectionPolicy = gocql.DCAwareRoundRobinPolicy("AWS_US_EAST_1")

    session, err := cluster.CreateSession()
    if err != nil {
        panic(err)
    }
    defer session.Close()

    vector := []float32{0.12, 0.34, 0.56, 0.78, 0.91}

    // Insert a vector
    err = session.Query(
        `INSERT INTO comments (record_id, id, commenter, comment, comment_vector, created_at)
         VALUES (now(), uuid(), ?, ?, ?, toTimestamp(now()))`,
        "Alice", "I like vector search in ScyllaDB.", vector,
    ).Exec()
    if err != nil {
        panic(err)
    }

    // Run a similarity search
    iter := session.Query(
        `SELECT commenter, comment FROM comments
         ORDER BY comment_vector ANN OF ? LIMIT 3`, vector,
    ).Iter()

    var commenter, comment string
    for iter.Scan(&commenter, &comment) {
        fmt.Printf("%s: %s\n", commenter, comment)
    }
    if err := iter.Close(); err != nil {
        panic(err)
    }
}
```

<a id="vs-framework-integration"></a>

### Framework Integration

Beyond the native drivers, ScyllaDB Vector Search is compatible with AI
frameworks that integrate through the Cassandra connector (CassIO), such as
LangChain and LlamaIndex. ScyllaDB recognizes the Cassandra Storage Attached
Index (SAI) statements these libraries generate, so they can run against a
ScyllaDB cluster with little or no code changes. See
[LangChain and CassIO Compatibility](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-langchain.md)
for requirements, limitations, and a complete RAG example.

<a id="vs-alter-index"></a>

## Altering a Vector Index

ScyllaDB does not support `ALTER INDEX` for vector indexes — you cannot
change the similarity function, HNSW parameters, or any other index option
after creation.

To work around this constraint (for example, to change the similarity
function from `COSINE` to `DOT_PRODUCT`, or to introduce quantization),
use the following procedure to migrate the index with zero downtime.

<a id="vs-alter-index-procedure"></a>

### Procedure

ScyllaDB 2026.2+

Starting with ScyllaDB 2026.2, you can create multiple vector indexes
on the same column. This allows you to rebuild an index in place — the
old index continues serving queries while the new one is being built,
and once the new index is ready, queries are automatically routed to it.

1. **Verify that the Vector Search instances have enough memory** to hold
   both the existing index and the new one simultaneously. Use the
   [memory estimation formula](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-faq.md#vs-faq-memory) to calculate the
   required memory for both indexes. If the available memory is
   insufficient, [resize the Vector Search deployment](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-clusters.md#vs-resize)
   before proceeding.
2. **Create a new vector index on the same column** with the desired
   configuration. You must give the new index a different name:
   ```cql
   CREATE CUSTOM INDEX IF NOT EXISTS ann_idx_v2
   ON myapp.comments(comment_vector)
   USING 'vector_index'
   WITH OPTIONS = { 'similarity_function': 'DOT_PRODUCT' };
   ```

   While the new index is being built, ANN queries continue to be
   served by the old index.
3. **Wait for the new index to finish building.** Once the new index is
   ready, Vector Search automatically routes queries to the newest
   serving index. No application changes are required.
4. **Drop the old index** once you have confirmed that the new index is
   serving queries correctly:
   ```cql
   DROP INDEX IF EXISTS ann_idx;
   ```
5. **Resize the Vector Search instances down** if the removal of the old
   index freed enough memory. Check the current memory usage and
   [reduce the instance size](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-clusters.md#vs-resize) if there is sufficient
   free RAM.

ScyllaDB < 2026.2

On ScyllaDB versions prior to 2026.2, a single vector column can only
have **one** vector index at a time. To change the index configuration
you must create a temporary duplicate vector column and migrate the
index to it.

1. **Add a duplicate vector column** to the table. The new column must
   have the same vector type and dimensions as the original.
   ```cql
   ALTER TABLE myapp.comments
   ADD comment_vector_v2 vector<float, 5>;
   ```
2. **Update your application to write embeddings to both columns.**
   Every new `INSERT` or `UPDATE` must populate both the original
   (`comment_vector`) and the new (`comment_vector_v2`) column.
   ```cql
   INSERT INTO myapp.comments (
       record_id, id, commenter, comment,
       comment_vector, comment_vector_v2, created_at
   ) VALUES (
       now(), uuid(), 'Bob', 'Dual-write example.',
       [0.10, 0.20, 0.30, 0.40, 0.50],
       [0.10, 0.20, 0.30, 0.40, 0.50],
       toTimestamp(now())
   );
   ```
3. **Backfill the new column** for all existing rows that were written
   before the dual-write was enabled. Perform a full table scan and
   copy the vector values:
   ```cql
   -- For each row returned by the scan:
   SELECT id, created_at, comment_vector FROM myapp.comments;

   -- For each row, replace the <...> placeholders with actual column values:
   UPDATE myapp.comments
   SET comment_vector_v2 = <comment_vector>
   WHERE id = <row_id> AND created_at = <row_created_at>;
   ```

   #### NOTE
   For large tables, use your application or a script to iterate
   over all rows and copy vectors in batches. To scan efficiently,
   implement a tablet-aware full table scan as described in
   [Efficient Full Table Scans with ScyllaDB Tablets](https://www.scylladb.com/2025/05/13/efficient-full-table-scans-with-scylladb-tablets/).
   To avoid impacting production queries, run the backfill under a
   dedicated database role with `workload_type = 'batch'` and
   lower `SHARES` using ScyllaDB’s
   [Workload Prioritization](https://docs.scylladb.com/manual/stable/features/workload-prioritization.html)
   feature.
4. **Verify that the Vector Search instances have enough memory** to
   hold both the existing index and the new one simultaneously. Use the
   [memory estimation formula](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-faq.md#vs-faq-memory) to calculate the
   required memory for both indexes. If the available memory is
   insufficient, [resize the Vector Search deployment](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-clusters.md#vs-resize)
   before proceeding.
5. **Create the new index** on the duplicate column with the desired
   configuration:
   ```cql
   CREATE CUSTOM INDEX IF NOT EXISTS ann_idx_v2
   ON myapp.comments(comment_vector_v2)
   USING 'vector_index'
   WITH OPTIONS = { 'similarity_function': 'DOT_PRODUCT' };
   ```

   Wait for the index to finish building over the existing data.
6. **Switch your application to query the new column.** Update all
   `ANN OF` queries to use `comment_vector_v2` instead of
   `comment_vector`:
   ```cql
   SELECT id, commenter, comment
   FROM myapp.comments
   ORDER BY comment_vector_v2 ANN OF [0.12, 0.34, 0.56, 0.78, 0.91]
   LIMIT 5;
   ```
7. **Drop the old index and remove the original column** once you have
   confirmed that the new index is serving queries correctly:

   First, stop writing to the old column in your application code.
   Then drop the index and the column:
   ```cql
   DROP INDEX IF EXISTS ann_idx;
   ALTER TABLE myapp.comments DROP comment_vector;
   ```
8. **Resize the Vector Search instances down** if the removal of the
   old index freed enough memory. Check the current memory usage and
   [reduce the instance size](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-clusters.md#vs-resize) if there is sufficient
   free RAM.

<a id="vs-ttl"></a>

## Row Expiration with Per-Row TTL

#### NOTE
Per-row TTL support for vector-indexed tables is available starting with
**ScyllaDB 2026.2.0**.

Vector Search supports automatic row expiration through ScyllaDB’s *per-row
TTL* feature. Unlike classic cell-level TTL (`USING TTL` or
`default_time_to_live`), where every write carries its own relative
time-to-live, per-row TTL stores an **absolute expiration time in a dedicated
column**. When that time passes, ScyllaDB deletes the row and emits a CDC
event. The Vector Store consumes the event and removes the row from the HNSW
index, so the expired row stops appearing in `ANN OF` query results.

To use per-row TTL, designate one column as the expiration-time column with the
`TTL` keyword. The column value holds the absolute expiration time for that
row and must be one of the following types:

* `bigint` or `int` — seconds since the UNIX epoch.
* `timestamp` — millisecond precision (truncated to seconds).

The TTL column cannot be a primary key column or a static column, and a table
can have at most one TTL column. A row whose TTL column is left unset (`null`)
never expires.

**Create a table with a per-row TTL column and a vector index:**

```cql
CREATE TABLE IF NOT EXISTS myapp.comments (
  id uuid,
  commenter text,
  comment text,
  comment_vector vector<float, 5>,
  created_at timestamp,
  expire_at bigint TTL,   -- seconds since the UNIX epoch
  PRIMARY KEY (id, created_at)
);

CREATE CUSTOM INDEX IF NOT EXISTS ann_idx
ON myapp.comments(comment_vector)
USING 'vector_index';
```

**Insert rows with and without an expiration time:**

```cql
-- This row expires at the given UNIX epoch time (in seconds).
INSERT INTO myapp.comments (id, created_at, commenter, comment, comment_vector, expire_at)
VALUES (uuid(), toTimestamp(now()), 'Alice', 'Ephemeral note',
        [0.12, 0.34, 0.56, 0.78, 0.91], 1784311784);

-- Leaving the TTL column unset means the row never expires.
INSERT INTO myapp.comments (id, created_at, commenter, comment, comment_vector)
VALUES (uuid(), toTimestamp(now()), 'Bob', 'Permanent note',
        [0.20, 0.10, 0.05, 0.90, 0.30]);
```

Before `expire_at` passes, an `ANN OF` query returns both rows. After the
expiration time passes, the ephemeral row is removed from the table and from
the vector index, so the same query returns only the non-expiring row:

```cql
SELECT commenter, comment
FROM myapp.comments
ORDER BY comment_vector ANN OF [0.12, 0.34, 0.56, 0.78, 0.91]
LIMIT 10;
```

**Add or remove the TTL designation on an existing table** with `ALTER TABLE`:

```cql
-- Designate the expire_at column as the per-row TTL column.
ALTER TABLE myapp.comments TTL expire_at;

-- Stop interpreting the column values as expiration times.
ALTER TABLE myapp.comments TTL NULL;
```

#### NOTE
Expired rows are not removed at the exact expiration instant. A background
service scans and deletes expired rows periodically, and those deletions
then propagate to the vector index through CDC. Expect a short delay between
the expiration time and the row disappearing from `ANN OF` results.

<a id="vs-limitations"></a>

## CQL Features Not Supported with Vector Search

* `ANN OF` is only supported in `ORDER BY` clauses.
* The `DISTINCT` keyword in `ANN OF` queries is not supported.
* Filtering on columns **not** in the primary key is
  not supported. See [Filtering](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-filtering.md)
  for supported filtering options.
* The `TOKEN` function is not supported in vector queries.
* The `CONTAINS` operator is not supported in vector queries.
* The `ALTER INDEX` statement is not supported for vector indexes. You cannot
  modify index options after the index has been created. To change these
  settings, you must drop the existing index and recreate it with the updated
  configuration. See [Altering a Vector Index](#vs-alter-index) for a
  zero-downtime migration procedure.
* `TRUNCATE TABLE` is not supported. `TRUNCATE` does not generate CDC
  events, so the vector index is not updated — the table is emptied but the
  HNSW graph still contains all previous vectors, leading to stale or
  incorrect query results. Instead of truncating, drop and recreate both the
  table and the custom index.
* Partition-level and range deletes on tables with clustering keys are not
  propagated to the vector index. Specifically:
  > * `DELETE FROM t WHERE pk = ?` (partition delete with no clustering key
  >   specified) is **not** reflected in the vector index.
  > * `DELETE FROM t WHERE pk = ? AND ck > ?` (range delete using an
  >   inequality operator on the clustering key) is **not** reflected in the
  >   vector index.

  Only single-row deletes that fully specify the primary key (all partition
  key and clustering key columns) are propagated. Rows deleted with partition
  or range deletes are filtered out by ScyllaDB at query time, so they will
  not appear in ANN query results. However, because the vector index still
  contains entries for the deleted rows, they occupy candidate slots during
  the index search, which can cause ANN queries to return **fewer results
  than the requested LIMIT**. Additionally, the stale entries continue to
  consume memory on the vector search nodes.

  To avoid this, always delete rows from tables with vector indexes using a
  fully specified primary key.

## What’s Next

* [Filtering Vector Search Results](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-filtering.md) —
  combine similarity search with metadata constraints using global and local indexes.
* [Quantization and Rescoring](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-quantization.md) —
  reduce index memory usage while maintaining search quality.
* [Vector Search Concepts](https://cloud.docs.scylladb.com/stable/vector-search/vector-search-concepts.md) —
  architecture overview and data flow.
* [Reference](https://cloud.docs.scylladb.com/stable/vector-search/reference-vector-search.md) —
  CQL syntax reference and API endpoints.
