Full-Text Search with RedisSearch
June 9, 2026
7 min read
SQL LIKE '%query%' scans every row. Elasticsearch is a separate cluster to maintain. RedisSearch (part of Redis Stack) adds a true inverted index directly inside Redis, enabling sub-millisecond full-text search over your existing Redis data with no extra infrastructure.
How RedisSearch Works
RedisSearch builds an inverted index on top of Redis Hashes or JSON documents. You define which fields to index — the module maintains the index automatically as documents are added or updated. Queries hit the index directly, not the underlying data.
Creating an Index
# Create an index on the "products" prefix
FT.CREATE idx:products
ON HASH
PREFIX 1 product:
SCHEMA
name TEXT WEIGHT 2.0
description TEXT
category TAG
price NUMERIC SORTABLE
rating NUMERIC SORTABLE
# Fields:
# TEXT — full-text indexed, supports stemming and fuzzy
# TAG — exact-match categorical field (filterable)
# NUMERIC — range queries and sorting
Indexing Documents
# Add documents via HSET (RedisSearch auto-indexes anything matching the prefix)
HSET product:1 name "Wireless Headphones" description "Noise cancelling bluetooth headphones" category "electronics" price 79.99 rating 4.5
HSET product:2 name "Coffee Maker" description "12-cup programmable drip coffee maker" category "kitchen" price 49.99 rating 4.2
HSET product:3 name "Running Shoes" description "Lightweight mesh running shoes for trail" category "sports" price 120.00 rating 4.8
Searching
# Simple full-text search
FT.SEARCH idx:products "headphones"
# Phrase search
FT.SEARCH idx:products '"noise cancelling"'
# Fuzzy search (tolerates typos, 1 edit distance)
FT.SEARCH idx:products "%headphonez%"
# Filter by tag + text
FT.SEARCH idx:products "@category:{electronics} wireless"
# Numeric range filter
FT.SEARCH idx:products "@price:[0 100]" # under $100
# Combined: electronics, wireless, under $100, sorted by rating
FT.SEARCH idx:products "@category:{electronics} wireless @price:[0 100]"
SORTBY rating DESC
LIMIT 0 10
Python Integration
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def search_products(query, category=None, max_price=None, page=1, per_page=20):
filters = [query]
if category:
filters.append(f"@category:{{{category}}}")
if max_price:
filters.append(f"@price:[0 {max_price}]")
full_query = " ".join(filters)
offset = (page - 1) * per_page
result = r.execute_command(
'FT.SEARCH', 'idx:products', full_query,
'SORTBY', 'rating', 'DESC',
'LIMIT', offset, per_page
)
total = result[0]
docs = []
for i in range(1, len(result), 2):
doc_id = result[i]
fields = result[i + 1]
doc = dict(zip(fields[::2], fields[1::2]))
doc['id'] = doc_id
docs.append(doc)
return {"total": total, "results": docs}
results = search_products("wireless", category="electronics", max_price=100)
for product in results["results"]:
print(f"{product['name']} — ${product['price']} ★{product['rating']}")
Aggregations
# Count products per category
FT.AGGREGATE idx:products "*"
GROUPBY 1 @category
REDUCE COUNT 0 AS product_count
SORTBY 2 @product_count DESC
# Average price per category
FT.AGGREGATE idx:products "*"
GROUPBY 1 @category
REDUCE AVG 1 @price AS avg_price
Key Takeaways
- FT.CREATE defines an index schema — TEXT, TAG, NUMERIC field types with different query capabilities
- FT.SEARCH queries the inverted index instantly — no table scans
- Fuzzy search with
%term%tolerates typos and spelling variations - TAG fields enable exact-match filters (category, status, tags)
- NUMERIC fields support range queries and sorting
- Documents are indexed automatically when added via HSET with the matching prefix — no separate indexing step