Vector search
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 (semantic cache, agent
memory, the RAG pipeline) work with the same vector indexes.
Indexes and items
Section titled “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.
Create, add, search, delete
Section titled “Create, add, search, delete”VSIM.CREATEINDEX 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 3OK127.0.0.1:6379> VSIM.SET docs a 1 0 0(integer) 1127.0.0.1:6379> VSIM.SET docs b 0 1 0(integer) 1127.0.0.1:6379> VSIM.SET docs c 1 1 0(integer) 1127.0.0.1:6379> VSIM.SET docs c 1 1 0(integer) 0127.0.0.1:6379> VSIM.SEARCH docs 2 1 0 01) "a"2) "0"3) "c"4) "0.29289323"127.0.0.1:6379> VSIM.GET docs c1) "0.70710677"2) "0.70710677"3) "0"127.0.0.1:6379> VSIM.SET docs d 1 0(error) ERR expected 3 dimensions, got 2127.0.0.1:6379> VSIM.DEL docs b(integer) 1127.0.0.1:6379> VSIM.INFO1) "docs"127.0.0.1:6379> VSIM.DROPINDEX docsOK- VSIM.SET takes exactly as many numbers as the
index has dimensions. It replies
1for a new item and0when it replaced an existing item’s vector. - VSIM.SEARCH takes
k, then the query vector, and returns up tokitems, nearest first, each followed by its distance. - VSIM.GET 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
cshows above. - VSIM.DEL removes an item, and VSIM.DROPINDEX removes the whole index.
- VSIM.INFO 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
Section titled “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
Section titled “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
Section titled “Filtering by tag”VSIM.SEARCH ... FILTER tag [tag ...] restricts the search to items whose
name is a key carrying all of the given tags.
Put FILTER after the vector, and EF (if any) after the tags.
127.0.0.1:6379> VSIM.CREATEINDEX products 3OK127.0.0.1:6379> HSET product:1 name "Trail runner"(integer) 1127.0.0.1:6379> HSET product:2 name "Rain jacket"(integer) 1127.0.0.1:6379> TAG.SET product:1 shoes outdoor(integer) 2127.0.0.1:6379> TAG.SET product:2 jackets outdoor(integer) 2127.0.0.1:6379> VSIM.SET products product:1 1 1 0(integer) 1127.0.0.1:6379> VSIM.SET products product:2 1 0 0(integer) 1127.0.0.1:6379> VSIM.SEARCH products 5 1 0 01) "product:2"2) "0"3) "product:1"4) "0.29289323"127.0.0.1:6379> VSIM.SEARCH products 5 1 0 0 FILTER shoes1) "product:1"2) "0.29289323"127.0.0.1:6379> VSIM.SEARCH products 5 1 0 0 FILTER outdoor jackets1) "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
Section titled “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.
FLUSHALLremoves all indexes;FLUSHDBdoes not touch them.- In a cluster, 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.INFOwithout arguments lists only the node’s own indexes.
Vector snapshots
Section titled “Vector snapshots”vsim-persist licenseVSIM.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 for
licensing details.
Compatibility
Section titled “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
Section titled “Commands”| Command | What it does |
|---|---|
| VSIM.CREATEINDEX | Create an index |
| VSIM.DROPINDEX | Delete an index |
| VSIM.SET | Add or replace an item’s vector |
| VSIM.GET | Read an item’s vector |
| VSIM.DEL | Remove an item |
| VSIM.SEARCH | Nearest neighbors, optionally filtered by tags |
| VSIM.INFO | List indexes, or describe one |
| VSIM.SAVE / VSIM.LOAD | Store or fetch a named payload file |
| VSIM.LIST / VSIM.DROP | List or delete payload files |
From your application
Section titled “From your application”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()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();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())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);}Was this page helpful?
Thanks for the feedback.