Vector Sets vs the Query Engine: Picking the Right Primitive
Redis has two ways to search vectors and the docs never put them side by side. One is a data type, the other a search engine, and cluster mode settles it more often than latency does.
Redis has two ways to do vector similarity search. The documentation explains each of them well and never puts them next to each other, which leaves anyone choosing between them with an awkward opening question: which of these am I supposed to be using?
The one-line version is that vector sets are a data type and the query engine is a search engine. That distinction decides more than any benchmark will. But a couple of the deciding factors are easy to miss until you are a week into a proof of concept and committed, so the longer version is worth having.
Two different things wearing the same label
Vector sets arrived in Redis 8.0 as a native data type, in the same family as sorted sets. One key holds one HNSW graph. You add elements with VADD, you query with VSIM, and that is most of the API:
VADD points VALUES 3 0.1 0.9 0.2 item:1 SETATTR '{"year": 2024, "tier": "gold"}'
VSIM points VALUES 3 0.1 0.9 0.2 COUNT 10 WITHSCORES FILTER '.year > 2020'
The query engine is the secondary-index layer, FT.CREATE and friends. A vector is one field type among many, inside an index defined over your keyspace by prefix:
FT.CREATE idx ON HASH PREFIX 1 doc: SCHEMA
title TEXT
year NUMERIC
embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 768 DISTANCE_METRIC COSINE
FT.SEARCH idx "(@title:redis @year:[2020 2026])=>[KNN 10 @embedding $v AS score]"
PARAMS 2 v "$BLOB" SORTBY score DIALECT 2
Both run approximate nearest-neighbour search over HNSW. Almost everything else about them differs.
| Vector sets | Query engine | |
|---|---|---|
| Create | implicit on first VADD | FT.CREATE with a schema |
| Insert | VADD | HSET or JSON.SET, indexed automatically |
| Query | VSIM | FT.SEARCH, FT.AGGREGATE, FT.HYBRID |
| Delete | VREM | delete the key |
| Introspect | VINFO, VDIM, VCARD, VEMB, VLINKS | FT.INFO |
| Attributes | VSETATTR and VGETATTR, JSON per element | any indexed field |
| Ground truth | VSIM … TRUTH, an exact scan | HYBRID_POLICY ADHOC_BF |
The TRUTH option on VSIM is worth calling out on its own. It bypasses the graph and does an exact linear scan, which means you can measure your real recall instead of trusting a default, without leaving Redis to do it.
Index tuning
Vector sets put HNSW's knobs directly on the commands. M defaults to 16, with layer zero getting M * 2. Build-time EF defaults to 200, and search-time EF has a useful range the docs put at 50 to 1000. Cosine is the distance metric and you do not get to change it.
The query engine offers three index types instead. FLAT does exact search and suits smaller sets, under a million vectors by the docs' reckoning, with no runtime parameters at all. HNSW gives you M, EF_CONSTRUCTION at 200, EF_RUNTIME at a default of 10, which is low enough that you should raise it, and EPSILON. Since 8.2 there is also SVS-VAMANA, Intel's graph index. On top of that you choose a distance metric from L2, IP or COSINE, and one of six vector types including INT8 and UINT8 for pre-quantised embeddings.
There is a catch on SVS-VAMANA that matters if you are not on Redis Enterprise. Intel's LVQ and LeanVec compression are not available in Redis Open Source, so on OSS or on non-Intel platforms a request for COMPRESSION quietly falls back to plain 8-bit scalar quantisation. The option is accepted either way, which is the problem. You do not get an error telling you that you are not getting what you asked for.
Memory and quantisation
Vector sets keep this simple. Three modes, fixed at the first VADD for a given key and immutable afterwards:
| Mode | Size vs FP32 | Trade-off |
|---|---|---|
Q8 (default) | 4× smaller | High recall, high speed |
BIN | 32× smaller | Lower recall, fastest search |
NOQUANT | full size | Highest precision, slowest |
A 300-dimension vector is 1200 bytes at FP32 against 300 bytes at Q8, plus graph overhead per node that scales with M. The docs work an example at roughly 1193 bytes per node with M 64, which is a useful reminder that the graph itself is not free.
REDUCE is also available, applying random-projection dimensionality reduction on the way in. It has one sharp edge: the projection matrix is not replicated. A replica will produce different projected vectors for the same input, which changes what VEMB gives back. Similarity search still works correctly, but do not build anything that compares embeddings across a replication boundary.
Filtering
Vector sets filter on a JSON attribute blob attached to each element, using an expression language that looks like JavaScript:
VSIM movies VALUES 3 0.5 0.8 0.2 FILTER '(.year - 2000) ** 2 < 100 and .rating / 2 > 4'
You get arithmetic, comparisons, logical operators, parentheses, and in as of 8.2. Two limits are worth committing to memory. Only top-level fields work, so .genre is fine and .movie.genre is not. And a missing or invalid field causes the element to be skipped rather than raising an error, which is a genuinely nasty debugging experience: your filter returns nothing, your vectors look wrong, and the actual problem is a typo in an attribute name. Check the names first.
The parameter to know is FILTER-EF, the number of candidate nodes inspected while looking for enough filtered results. It defaults to COUNT * 100. When you filter down to a narrow slice of a large set and get back fewer results than you asked for, that is the number to raise.
The query engine filters on indexed fields instead, text, tag, numeric or geo, as a prefilter ahead of the KNN stage. It can run that in batches or as an ad-hoc brute-force scan via HYBRID_POLICY, and the ad-hoc path is exact even though the index is approximate, which is occasionally just what you need.
"Hybrid" means two different things
This causes real confusion, and it is worth being careful about because both usages appear in Redis documentation.
The vector-set docs describe hybrid search, and there hybrid means vector similarity combined with an attribute filter. There is no full-text operator anywhere in VSIM.
If you mean hybrid in the retrieval sense, BM25 text relevance fused with vector similarity, that is the query engine, and since 8.4 there is a command built for it:
FT.HYBRID idx
SEARCH "redis vector search"
VSIM @embedding $vec KNN 4 K 10
COMBINE RRF 2 WINDOW 20
PARAMS 2 vec "$BLOB"
FT.HYBRID fuses the two result sets with reciprocal rank fusion by default, or a linear combination if you prefer, and it supports GROUPBY, APPLY and the rest of the aggregation vocabulary. Nothing on the vector-set side is comparable, and no amount of attribute filtering substitutes for text relevance.
Cluster mode, which usually settles it
This is the factor I would weigh most heavily, and it is the one that catches people out.
A vector set is a single key. One key means one HNSW graph on one shard. Scaling past a single node means partitioning across keys yourself and then, the part that matters, sending VSIM to every shard and merging the results in your client. The documentation states it directly: write operations scale linearly, read operations do not.
A query engine index is defined over the keyspace by prefix, and the cluster handles distribution. One FT.SEARCH goes out and merged results come back.
Which reframes the question. It is not about which is faster, it is about whether you want to write and maintain a scatter-gather layer in your application. If your dataset sits comfortably on one shard and will keep doing so, vector sets save you an index definition. If it will not, the query engine saves you a distributed system.
High availability
Short and binary: vector sets are not supported on Active-Active databases. The compatibility table on every vector-set command page says so, while FT.HYBRID lists Active-Active as supported.
Two regions taking writes for the same data therefore decides this before anyone runs a benchmark.
Data model
A vector set element is the vector plus an optional JSON attribute blob. If your document has fifteen other fields, they live somewhere else and you fetch them separately.
The query engine indexes hashes or JSON documents that you were storing anyway, so the vector sits with the rest of the record and one query returns all of it. Since 2.6.1 a single JSON path can hold multiple vectors per document.
The numbers Redis actually publishes
These are specific, and specific numbers are rare enough to quote.
VSIM is multi-threaded, up to 32 threads, and the docs cite roughly 50,000 queries per second on a 3M-element, 300-dimension int8 set. Insertion, by contrast, is single-threaded by default at "a few thousand insertions per second on a single node." That asymmetry is the thing to plan around, because it makes the initial bulk load a much bigger job than steady-state writes suggest. A 3M by 300-dimension set loads from disk in about 15 seconds. And DEL on a large vector set can cause a latency spike, which is worth knowing before you wire one into a cleanup job.
Gaps in the documentation
Four things I could not resolve while checking all of the above, which you should verify rather than take from me.
The status of vector sets is unclear. The Redis 8.0 release notes call them beta, with APIs and behaviours that may change. Nothing since has said otherwise, and the current data-type pages carry no beta banner. I would read that silence as unresolved rather than as a quiet promotion, and confirm with Redis before betting a roadmap on the API being frozen.
No maximum dimension is documented for vector sets. If you are working with large embeddings, test rather than assume.
SHARD_K_RATIO contradicts itself across pages. The vectors page shows a working FT.HYBRID example using it and the command reference documents it, but the 8.4 release notes list it under known limitations as not yet available on FT.HYBRID. Verify against your version before designing around it.
Search during slot migration carries a caveat. The 8.4 notes say FT.SEARCH, FT.AGGREGATE, FT.CURSOR and FT.HYBRID may return partial results or duplicates during atomic slot migration. That is fine for recommendations and needs more thought if the result set drives something transactional.
Testing it properly
When the choice is close, the tie-breakers are cheap to measure.
Start with recall against ground truth rather than against defaults, using VSIM … TRUTH on one side and HYBRID_POLICY ADHOC_BF on the other. Report p5 recall next to the mean, because the average hides exactly the queries a user will complain about.
Measure memory at your real dimension count with the quantisation you would actually ship. Q8 against NOQUANT is a factor of four, which changes the cluster size and therefore the price.
Run your narrowest realistic filter. On vector sets, watch whether you have to raise FILTER-EF to get a full result set. On the query engine, compare batched prefiltering against ad-hoc brute force.
Then time the ingest for the full dataset. Single-threaded insertion on vector sets is the constraint people discover late, usually the week before go-live.
Where I would land
The query engine is the reasonable default. Automatic sharding, real hybrid search, aggregations, multiple vector fields per index, Active-Active support, and a data model where the vector lives with the record it belongs to.
Vector sets are the better choice when the workload really is only vector similarity with simple top-level attribute filters, the dataset fits on one shard, and you would rather have a five-command API than a schema definition. That is a real category. Recommendation lookups, dedupe and semantic caching keyed by tenant all sit in it, and for those, vector sets are less machinery for the same outcome.
Latency almost never decides this. Three questions do: whether you need full-text relevance, whether you need Active-Active, and whether you are willing to own scatter-gather code. Answer those and the choice is usually already made.