# Vector search

> Store embedding vectors in named indexes and find the nearest neighbors, optionally filtered by key tags.

URL: https://docs.hotkv.com/data-types/vector-search/


**Availability:** All HotKV Cloud plans. On-Premise: no license needed.

Vector search finds the stored vectors closest to a query vector. Put the
embeddings of your documents, products, or images into an index, then ask
for the `k` nearest neighbors of a query embedding: that is the retrieval
step of semantic search, recommendations ("more like this"), duplicate
detection, and retrieval-augmented generation (RAG).

HotKV's `VSIM.*` commands do this with HNSW-indexed approximate nearest
neighbor search, using cosine, Euclidean (L2) or inner-product distance.
They are part of every HotKV server and every HotKV Cloud plan, with no
license needed. The [AI features](https://docs.hotkv.com/ai-features/index.md) (semantic cache, agent
memory, the RAG pipeline) work with the same vector indexes.

## Indexes and items

An **index** has a name, a fixed number of dimensions, and a distance
metric. Each **item** in it is a name (the "key" in the command syntax)
with one vector. Indexes and their items live next to the keyspace, not in
it: an index is not a key, `DEL` and `EXPIRE` do not apply to it, and an
item name does not have to be an existing key. Using your object's key as
the item name (for example `product:1`) is still a good habit, because it
lets you fetch the object after a search and filter by
[tags](https://docs.hotkv.com/data-types/tags/index.md).

## Create, add, search, delete

[VSIM.CREATEINDEX](https://docs.hotkv.com/commands/vsim-createindex/index.md) creates an index with its
dimensions (1 to 65536). The metric is cosine unless you pass
`METRIC L2` or `METRIC IP`.

```
127.0.0.1:6379> VSIM.CREATEINDEX docs 3
OK
127.0.0.1:6379> VSIM.SET docs a 1 0 0
(integer) 1
127.0.0.1:6379> VSIM.SET docs b 0 1 0
(integer) 1
127.0.0.1:6379> VSIM.SET docs c 1 1 0
(integer) 1
127.0.0.1:6379> VSIM.SET docs c 1 1 0
(integer) 0
127.0.0.1:6379> VSIM.SEARCH docs 2 1 0 0
1) "a"
2) "0"
3) "c"
4) "0.29289323"
127.0.0.1:6379> VSIM.GET docs c
1) "0.70710677"
2) "0.70710677"
3) "0"
127.0.0.1:6379> VSIM.SET docs d 1 0
(error) ERR expected 3 dimensions, got 2
127.0.0.1:6379> VSIM.DEL docs b
(integer) 1
127.0.0.1:6379> VSIM.INFO
1) "docs"
127.0.0.1:6379> VSIM.DROPINDEX docs
OK
```

- [VSIM.SET](https://docs.hotkv.com/commands/vsim-set/index.md) takes exactly as many numbers as the
  index has dimensions. It replies `1` for a new item and `0` when it
  replaced an existing item's vector.
- [VSIM.SEARCH](https://docs.hotkv.com/commands/vsim-search/index.md) takes `k`, then the query vector,
  and returns up to `k` items, nearest first, each followed by its
  distance.
- [VSIM.GET](https://docs.hotkv.com/commands/vsim-get/index.md) returns an item's stored vector (nil if
  there is none). A cosine index stores vectors scaled to unit length, so
  you get back the normalized vector, as `c` shows above.
- [VSIM.DEL](https://docs.hotkv.com/commands/vsim-del/index.md) removes an item, and
  [VSIM.DROPINDEX](https://docs.hotkv.com/commands/vsim-dropindex/index.md) removes the whole index.
- [VSIM.INFO](https://docs.hotkv.com/commands/vsim-info/index.md) without arguments lists the indexes;
  with an index name it reports its dimensions, metric, number of vectors,
  index parameters and memory use.

Vector values are stored as 32-bit floats and must be finite numbers.

### Distances

Lower is always closer.

| Metric | Distance returned | Range |
| --- | --- | --- |
| `COSINE` (default) | 1 minus the cosine similarity | 0 (same direction) to 2 (opposite) |
| `L2` | Squared Euclidean distance | 0 and up |
| `IP` | The negative dot product | Any number; more negative is closer |

Use the metric your embedding model was trained for. Most text embedding
models are meant for cosine; if your vectors are already unit length,
cosine and inner product rank results the same way.

### Accuracy and speed

Search is approximate: on large indexes it may occasionally miss a true
nearest neighbor in exchange for answering quickly. `VSIM.CREATEINDEX`
accepts `M`, `EF_CONSTRUCTION` and `EF_SEARCH` to tune the index, and
`VSIM.SEARCH` takes `EF <n>` to trade speed for recall on a single query:
a higher `EF` examines more candidates, finding the true neighbors more
reliably but taking longer. `VSIM.INFO <index>` shows the values an index
uses. The defaults suit most workloads.

## Filtering by tag

`VSIM.SEARCH ... FILTER tag [tag ...]` restricts the search to items whose
name is a key carrying **all** of the given [tags](https://docs.hotkv.com/data-types/tags/index.md).
Put `FILTER` after the vector, and `EF` (if any) after the tags.

```
127.0.0.1:6379> VSIM.CREATEINDEX products 3
OK
127.0.0.1:6379> HSET product:1 name "Trail runner"
(integer) 1
127.0.0.1:6379> HSET product:2 name "Rain jacket"
(integer) 1
127.0.0.1:6379> TAG.SET product:1 shoes outdoor
(integer) 2
127.0.0.1:6379> TAG.SET product:2 jackets outdoor
(integer) 2
127.0.0.1:6379> VSIM.SET products product:1 1 1 0
(integer) 1
127.0.0.1:6379> VSIM.SET products product:2 1 0 0
(integer) 1
127.0.0.1:6379> VSIM.SEARCH products 5 1 0 0
1) "product:2"
2) "0"
3) "product:1"
4) "0.29289323"
127.0.0.1:6379> VSIM.SEARCH products 5 1 0 0 FILTER shoes
1) "product:1"
2) "0.29289323"
127.0.0.1:6379> VSIM.SEARCH products 5 1 0 0 FILTER outdoor jackets
1) "product:2"
2) "0"
```

The jacket is the closer match, but with `FILTER shoes` only the shoe is
considered. Tags can only be set on keys that exist, which is why each
product has a hash here.

## Persistence, replication, and clusters

- Indexes and their vectors are saved in snapshots and the AOF and come
  back on restart; replicas hold the same indexes as their primary.
- `FLUSHALL` removes all indexes; `FLUSHDB` does not touch them.
- In a [cluster](https://docs.hotkv.com/clustering/index.md), an index lives on the node that owns its
  name's hash slot, and commands for it are redirected like key commands.
  A filtered search sees only the tags held on that node, so keep tagged
  keys and their index on the same node (with a common hash tag) when you
  filter in a cluster. `VSIM.INFO` without arguments lists only the node's
  own indexes.

## Vector snapshots

**Availability:** HotKV Cloud: Performance and Scale plans. On-Premise: requires the `vsim-persist` license feature.

`VSIM.SAVE <name> <payload>` stores a payload you supply (for example an
export of your vectors in your own format) as a named file on the server,
and replies with the number of bytes written. `VSIM.LOAD <name>` returns
it (nil if there is none), `VSIM.LIST` names the saved payloads, and
`VSIM.DROP <name>` deletes one (`1` or `0`). These commands do not save or
restore an index by themselves: indexes are already persisted as described
above.

They need the `vsim-persist` license feature and `enabled = true` under
`[vsim_persist]` in the configuration; files go to the directory set by
`dir` in that section (`vsim_snapshots` by default). In a cluster, each
node keeps its own files. See the [AI features guide](https://docs.hotkv.com/ai-features/index.md) for
licensing details.

## Compatibility

`VSIM.*` is HotKV's own command family. HotKV does not implement the
vector set commands of Redis 8 (`VADD`, `VSIM`, `VREM`, `VEMB` and the
rest), and Redis's single `VSIM` command is unrelated to HotKV's
`VSIM.SEARCH`. Applications that use Redis vector sets or the RediSearch
vector fields need to switch to these commands.

## Commands

| Command | What it does |
| --- | --- |
| [VSIM.CREATEINDEX](https://docs.hotkv.com/commands/vsim-createindex/index.md) | Create an index |
| [VSIM.DROPINDEX](https://docs.hotkv.com/commands/vsim-dropindex/index.md) | Delete an index |
| [VSIM.SET](https://docs.hotkv.com/commands/vsim-set/index.md) | Add or replace an item's vector |
| [VSIM.GET](https://docs.hotkv.com/commands/vsim-get/index.md) | Read an item's vector |
| [VSIM.DEL](https://docs.hotkv.com/commands/vsim-del/index.md) | Remove an item |
| [VSIM.SEARCH](https://docs.hotkv.com/commands/vsim-search/index.md) | Nearest neighbors, optionally filtered by tags |
| [VSIM.INFO](https://docs.hotkv.com/commands/vsim-info/index.md) | List indexes, or describe one |
| [VSIM.SAVE](https://docs.hotkv.com/commands/vsim-save/index.md) / [VSIM.LOAD](https://docs.hotkv.com/commands/vsim-load/index.md) | Store or fetch a named payload file |
| [VSIM.LIST](https://docs.hotkv.com/commands/vsim-list/index.md) / [VSIM.DROP](https://docs.hotkv.com/commands/vsim-drop/index.md) | List or delete payload files |

## From your application

**Python**

```python
import hotkv

client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)

client.vsim_createindex("docs", 3, metric="cosine")
client.vsim_set("docs", "a", [1.0, 0.0, 0.0])
client.vsim_set("docs", "c", [1.0, 1.0, 0.0])

for name, distance in client.vsim_search("docs", 2, [1.0, 0.0, 0.0]):
    print(name, distance)

# Only among keys tagged "shoes":
client.vsim_search("products", 5, [1.0, 0.0, 0.0], filter_tags=["shoes"])
client.close()
```

**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

await client.vsimCreateIndex("docs", 3, { metric: "cosine" });
await client.vsimSet("docs", "a", [1, 0, 0]);
await client.vsimSet("docs", "c", [1, 1, 0]);
console.log(await client.vsimSearch("docs", 2, [1, 0, 0]));

// Only among keys tagged "shoes":
await client.vsimSearch("products", 5, [1, 0, 0], { filter: ["shoes"] });
await client.close();
```

**Go**

```go
client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})
if err != nil {
    log.Fatal(err)
}
defer client.Close()
ctx := context.Background()

client.VSIMCreateIndex(ctx, "docs", 3, &hotkv.VSIMIndexOptions{Metric: "cosine"})
if err := client.VSIMSet(ctx, "docs", "a", []float32{1, 0, 0}).Err(); err != nil {
    log.Fatal(err)
}
client.VSIMSet(ctx, "docs", "c", []float32{1, 1, 0})

// Note the argument order: vector, then k.
res := client.VSIMSearch(ctx, "docs", []float32{1, 0, 0}, 2, nil)
fmt.Println(res.Val(), res.Err())

filtered := client.VSIMSearch(ctx, "products", []float32{1, 0, 0}, 5,
    &hotkv.VSIMSearchOptions{Filter: []string{"shoes"}})
fmt.Println(filtered.Val())
```

**Java**

```java
import com.hotkv.HotKV;
import java.util.List;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    var vsim = client.vsim();
    vsim.createIndex("docs", 3);
    vsim.set("docs", "a", new double[] {1, 0, 0});
    vsim.set("docs", "c", new double[] {1, 1, 0});
    System.out.println(vsim.search("docs", 2, new double[] {1, 0, 0}, null, null));

    // Only among keys tagged "shoes":
    vsim.search("products", 5, new double[] {1, 0, 0}, List.of("shoes"), null);
}
```
