Home / AI & GenAI / Vector Databases

AI & GenAICheat sheetIntermediate

Redis vector search cheat sheet: FT.CREATE vector indexes, KNN and range queries, filters, aliases and index health

The redis-cli and Python commands for Redis vector indexes - create HNSW or FLAT indexes, run filtered KNN queries, check indexing failures and swap indexes with aliases.

Deepak Bhardwaj7 min read

Applies toRedis 8.x (Query Engine built in), Redis Stack 7.2+, Redis Software/Cloud and Azure Managed Redis with the RediSearch module

When to use this

Redis is often already in the stack for caching, so teams use it for semantic caches and small-to-medium RAG indexes. This sheet covers the day-to-day commands: create an index, load vectors, query, check that documents are actually indexed, and change an index without downtime.

Vector search needs the Redis Query Engine (RediSearch). It's built into Redis 8, included in Redis Stack, and available as a module on Redis Software, Redis Cloud and Azure Managed Redis (where it must be enabled when the cache is created). Check MODULE LIST or INFO modules on older builds. Managed Valkey and MemoryDB services have their own vector search implementations with a similar FT.* syntax but different options; check that service's documentation.

redis-cli -h $REDIS_HOST -p 6379 --tls -a "$REDIS_PASSWORD" INFO modules
redis-cli FT._LIST          # list indexes

Create an index

HNSW on hashes

FT.CREATE idx:docs ON HASH PREFIX 1 doc: SCHEMA
  title TEXT
  tenant TAG
  category TAG
  updated NUMERIC SORTABLE
  embedding VECTOR HNSW 10
    TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE M 16 EF_CONSTRUCTION 200

The number after the algorithm (10) is the count of attribute tokens that follow: five name/value pairs here. Getting it wrong is the most common FT.CREATE syntax error.

FLAT (brute force) for small sets

FT.CREATE idx:cache ON HASH PREFIX 1 cache: SCHEMA
  model TAG
  embedding VECTOR FLAT 6 TYPE FLOAT32 DIM 768 DISTANCE_METRIC COSINE

FLAT gives exact results and is fine up to tens of thousands of vectors. Use HNSW above that.

On JSON documents

FT.CREATE idx:kb ON JSON PREFIX 1 kb: SCHEMA
  $.tenant AS tenant TAG
  $.text AS text TEXT
  $.embedding AS embedding VECTOR HNSW 6 TYPE FLOAT32 DIM 1536 DISTANCE_METRIC COSINE

With JSON, the vector is stored as an array of numbers. With hashes, it's a raw binary blob.

Vector field options

Option Values Notes
Algorithm FLAT, HNSW Newer releases add more (for example SVS-VAMANA in Redis 8.2+); check your version
TYPE FLOAT32, FLOAT64 (more types on newer builds) Must match the bytes you write
DIM Embedding dimension Must match the model exactly
DISTANCE_METRIC COSINE, IP, L2 Fixed at creation
M HNSW links per node Higher = better recall, more memory
EF_CONSTRUCTION HNSW build breadth Higher = slower build, better graph
EF_RUNTIME Default query breadth Can be overridden per query

Warning

DIM, TYPE and DISTANCE_METRIC can't be changed with FT.ALTER. Changing the embedding model means a new index. Use an alias (below) so applications don't notice the switch.

Load vectors

Hash vectors must be little-endian float32 bytes. In Python with redis-py and NumPy:

import numpy as np, redis

r = redis.Redis(host="localhost", port=6379, decode_responses=False)

def put(doc_id, title, tenant, vec):
    r.hset(f"doc:{doc_id}", mapping={
        "title": title,
        "tenant": tenant,
        "category": "runbook",
        "updated": 1762732800,
        "embedding": np.asarray(vec, dtype=np.float32).tobytes(),
    })

pipe = r.pipeline(transaction=False)
# for many docs, queue hset calls on pipe and pipe.execute() every 500-1000 docs

A vector with the wrong byte length doesn't raise an error on HSET. The document is written but not indexed and shows up only in hash_indexing_failures (see index health).

Query

All vector queries need query dialect 2 or higher. Pass DIALECT 2 on every command unless you've set the default dialect for the server (the config name varies by version; check your build's documentation).

Plain KNN

FT.SEARCH idx:docs "*=>[KNN 5 @embedding $vec AS score]"
  PARAMS 2 vec "<binary blob>"
  SORTBY score
  RETURN 3 title tenant score
  DIALECT 2

score is a distance: lower is closer. For COSINE it's 1 - cosine similarity.

KNN with filters (hybrid)

The part before => filters first, then KNN runs over the matching documents:

FT.SEARCH idx:docs "(@tenant:{acme} @category:{runbook|kb} @updated:[1735689600 +inf])=>[KNN 10 @embedding $vec AS score]"
  PARAMS 2 vec "<blob>" SORTBY score RETURN 2 title score DIALECT 2

TAG values with spaces, dashes or dots need escaping: @tenant:{acme\-eu}.

Per-query HNSW breadth

FT.SEARCH idx:docs "*=>[KNN 10 @embedding $vec EF_RUNTIME $ef AS score]"
  PARAMS 4 vec "<blob>" ef 200 SORTBY score DIALECT 2

Raise EF_RUNTIME when recall is poor; it costs latency.

Range query (semantic cache lookups)

Return everything within a distance threshold rather than a fixed count:

FT.SEARCH idx:cache "@embedding:[VECTOR_RANGE $radius $vec]=>{$YIELD_DISTANCE_AS: dist}"
  PARAMS 4 radius 0.1 vec "<blob>" SORTBY dist LIMIT 0 1 DIALECT 2

The same from Python

q = np.asarray(query_vec, dtype=np.float32).tobytes()
res = r.execute_command(
    "FT.SEARCH", "idx:docs",
    "(@tenant:{acme})=>[KNN 5 @embedding $vec AS score]",
    "PARAMS", 2, "vec", q,
    "SORTBY", "score", "RETURN", 2, "title", "score",
    "DIALECT", 2,
)
print(res[0], res[1:])   # total, then key/fields pairs

execute_command avoids differences between redis-py versions in the redis.commands.search helper modules.

Index health

FT.INFO idx:docs

Fields to read:

Field What it tells you
num_docs Documents in the index. Compare with your source count
hash_indexing_failures Documents that matched the prefix but failed to index (wrong vector size, bad numeric value)
indexing / percent_indexed 1 / below 1 while a background scan of existing keys is running
vector_index_sz_mb or per-field memory stats Vector index memory; field names vary by version

Quick checks:

redis-cli FT.INFO idx:docs | grep -A1 -E "num_docs|hash_indexing_failures|percent_indexed"
redis-cli --scan --pattern 'doc:*' | wc -l          # keys with the prefix, compare with num_docs
redis-cli HSTRLEN doc:123 embedding                 # expect DIM * 4 bytes for FLOAT32 (1536 -> 6144)
redis-cli FT.EXPLAIN idx:docs "(@tenant:{acme})=>[KNN 5 @embedding \$vec]" DIALECT 2

FT.PROFILE idx:docs SEARCH QUERY "..." PARAMS ... DIALECT 2 shows where query time goes.

Change an index without downtime

Applications should query an alias, never the index name:

FT.ALIASADD docs idx:docs_v1                         # once
FT.CREATE idx:docs_v2 ON HASH PREFIX 1 docv2: SCHEMA ... DIM 3072 ...
# backfill docv2:* keys, wait until percent_indexed is 1, run recall checks
FT.ALIASUPDATE docs idx:docs_v2                      # atomic switch
FT.ALIASUPDATE docs idx:docs_v1                      # rollback if needed

Non-vector fields can be added in place:

FT.ALTER idx:docs SCHEMA ADD source TAG

Existing documents are re-scanned in the background for the new field.

Drop an index

FT.DROPINDEX idx:docs_v1          # removes the index, keeps the keys
FT.DROPINDEX idx:docs_v1 DD       # removes the index AND deletes every indexed key

Caution

DD deletes the documents themselves. If two indexes share a prefix, DD on one deletes data the other still uses. Check FT.INFO for prefixes before dropping, and confirm you have an RDB/AOF backup.

Common errors

Error or symptom Cause Fix
Syntax error at offset ... on a KNN query Dialect 1 in use Add DIALECT 2
Unknown index name Wrong name, alias missing, or wrong database/shard endpoint FT._LIST, FT.INFO <alias>
num_docs lower than key count Vector length mismatch or wrong prefix Check hash_indexing_failures and HSTRLEN
Query returns 0 results with a filter TAG value not escaped, or case differs Escape punctuation; TAGs are case-insensitive unless CASESENSITIVE is set
Good matches missing EF_RUNTIME too low, or filter very selective Raise EF_RUNTIME; for small filtered sets consider FLAT
OOM command not allowed during load Vector index counts against maxmemory Size memory for vectors plus HNSW graph; don't use allkeys-* eviction on a vector database

Prevent it next time

  • Put index definitions in code or migration scripts, not only in someone's shell history.
  • Alert when hash_indexing_failures is above zero or num_docs drifts from the source count.
  • Query through aliases so model changes become a create, backfill and FT.ALIASUPDATE.
  • Keep vector indexes on a Redis instance with noeviction and its own memory budget, separate from the general cache.