redis-best-practices
AGENTS.md1 starsChanged 10 months agoMITView on GitHub
Version 1.0.0 Redis, Inc. January 2026 This document is optimized for AI agents and LLMs. Rules are prioritized by performance impact. Best practices for Redis including data structures, memory management, Redis Query Engine (RQE), vector search with RedisVL, semantic caching with LangCache, and performance optimization. Optimized for AI agents and LLMs. Impact: HIGH Impact: HIGH (Optimal memory usage and operation performance) Selecting the appropriate Redis data type for your use case is fundamental to performance and memory efficiency.
- Reads credentials
- Sends data out
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- redis-best-practicesSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
62 rules. About 1 is common, 51 are distinctive, and 10 are very specific to this project.
≈1
Common · estimate
Many files like this have a rule of this kind.
≈51
Distinctive · estimate
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 98
Recommended: Use Hash for objects with fields.
- Distinctive: line 118
Recommended: Use colons as separators with a consistent hierarchy.
- Distinctive: line 139
Keep keys short but readable—they consume memory
- Distinctive: line 140
Use TTL on cache keys to prevent unbounded growth
- Very specific: line 141
Avoid KEYS * in production—use SCAN instead
- Distinctive: line 142
Consider key prefixes for multi-tenant applications
- Very specific: line 158
Always configure maxmemory and an eviction policy to prevent Redis from consuming all available memory.
- Distinctive: line 182
Use Hashes for small objects (more memory-efficient than separate keys)
- Very specific: line 183
Use OBJECT ENCODING key to check how Redis stores your data
- Very specific: line 184
Use MEMORY USAGE key to check individual key memory consumption
- Distinctive: line 195
Always set expiration times on cache keys to prevent unbounded memory growth.
- Distinctive: line 244
Recommended: Use SCAN for iteration.
- Distinctive: line 272
Always configure timeouts to prevent hanging connections.
- Distinctive: line 301
Recommended: Use a connection pool.
- Distinctive: line 330
Recommended: Use pipeline for multiple commands.
- Distinctive: line 372
Recommended: Use JSON for nested structures.
- Distinctive: line 385
Recommended: Use Hash for flat objects.
- Distinctive: line 404
Use JSON path syntax to update specific fields without fetching the entire document.
- Distinctive: line 406
Recommended: Use JSON paths for targeted updates.
- Distinctive: line 469
Recommended: Use TAG for exact matching.
- Distinctive: line 497
Create indexes with only the fields you need to search, filter, or sort on.
- Distinctive: line 530
Start with the minimum required fields
- Distinctive: line 531
Add fields as query patterns emerge
- Very specific: line 532
Use FT.INFO to monitor index size
- Distinctive: line 542
Use aliases to swap indexes without application changes.
- Distinctive: line 544
Recommended: Use aliases for production indexes.
- Very specific: line 586
Always specify DIALECT 2 (or higher) for modern query syntax and consistent behavior.
- Distinctive: line 622
Recommended: Use specific filters and limit results.
- Very specific: line 647
Add SORTABLE to fields used in SORTBY
- Very specific: line 648
Use TAG SORTABLE UNF for best performance on tag fields
- Very specific: line 649
Use NOSTEM if you don't need stemming
- Distinctive: line 677
Recommended: Use HNSW for production workloads.
- Distinctive: line 697
Recommended: Use FLAT for small datasets or when exact results required.
- Distinctive: line 714
Start with HNSW defaults, tune M and EF based on benchmarks
- Distinctive: line 803
Use filters to reduce the search space before vector comparison
- Distinctive: line 804
Consider chunking long documents for better retrieval
- Distinctive: line 846
Use TAG fields for category filters
- Distinctive: line 847
Use NUMERIC fields for date/price ranges
- Distinctive: line 892
Recommended: Use separate caches for different use cases.
- Distinctive: line 918
Start with threshold 0.9, adjust based on your use case
- Distinctive: line 919
Use custom attributes to filter results within a single cache
- Distinctive: line 921
Use separate cache IDs for fundamentally different LLM tasks
- Distinctive: line 938
If found (cache hit), returns the cached response instantly
- Distinctive: line 939
If not found (cache miss), your app calls the LLM and stores the response
- Distinctive: line 941
Recommended: Use the LangCache Python SDK.
- Distinctive: line 1027
Recommended: Use Streams when message durability matters.
- Distinctive: line 1065
Recommended: Use hash tags for related keys.
- Very specific: line 1095
Only the part between { and } is hashed
- Distinctive: line 1096
Use meaningful identifiers like user ID or tenant ID
- Distinctive: line 1097
Don't overuse - too many keys on one slot creates hotspots
- Distinctive: line 1109
Recommended: Read from replicas with Sentinel.
- Distinctive: line 1141
Don't read from replicas for data that was just written
- Distinctive: line 1142
Use for read-heavy, slightly-stale-OK workloads
- Distinctive: line 1158
Never run Redis without authentication in production environments.
- Common: line 1160
Recommended: Use password and TLS.
- Distinctive: line 1207
Recommended: Use firewall rules.
- Distinctive: line 1224
Use TLS for connections
- Distinctive: line 1226
Use firewall rules to restrict access
- Distinctive: line 1244
Create users with only the permissions they need (principle of least privilege).
- Distinctive: line 1246
Recommended: Create specific users with limited permissions.
- Distinctive: line 1310
Use Redis Insight for visual monitoring, query profiling, and debugging.
- Distinctive: line 1351
Recommended: Check SLOWLOG regularly.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Redis Best Practices
Version 1.0.0 Redis, Inc. January 2026
This document is optimized for AI agents and LLMs. Rules are prioritized by performance impact.
Abstract
Best practices for Redis including data structures, memory management, Redis Query Engine (RQE), vector search with RedisVL, semantic caching with LangCache, and performance optimization. Optimized for AI agents and LLMs.
Table of Contents
- Data Structures & Keys — HIGH
- 1.1 Choose the Right Data Structure
- 1.2 Use Consistent Key Naming Conventions
- Memory & Expiration — HIGH
- 2.1 Configure Memory Limits and Eviction Policies
- 2.2 Set TTL on Cache Keys
- Connection & Performance — HIGH
- 3.1 Avoid Blocking Commands in Production
- 3.2 Configure Connection Timeouts
- 3.3 Use Connection Pooling
- 3.4 Use Pipelining for Bulk Operations
- JSON Documents — MEDIUM
- 4.1 Choose JSON vs Hash Appropriately
- 4.2 Use JSON Paths for Partial Updates
- Redis Query Engine — HIGH
- 5.1 Choose the Correct Field Type
- 5.2 Index Only Fields You Query
- 5.3 Manage Indexes for Zero-Downtime Updates
- 5.4 Use DIALECT 2 for Modern Query Syntax
- 5.5 Write Efficient Queries
- Vector Search & RedisVL — HIGH
- 6.1 Choose HNSW vs FLAT Based on Dataset Size
- 6.2 Configure Vector Indexes Properly
- 6.3 Implement RAG Pattern Correctly
- 6.4 Use Hybrid Search for Better Results
- Semantic Caching — MEDIUM
- 7.1 Configure Semantic Cache Properly
- 7.2 Use LangCache for LLM Response Caching
- Streams & Pub/Sub — MEDIUM
- 8.1 Choose Streams vs Pub/Sub Appropriately
- Clustering & Replication — MEDIUM
- 9.1 Use Hash Tags for Related Keys
- 9.2 Use Read Replicas for Read-Heavy Workloads
- Security — HIGH
- 10.1 Always Use Authentication in Production
- 10.2 Secure Network Access
- 10.3 Use ACLs for Fine-Grained Access Control
- Observability — MEDIUM
- 11.1 Monitor Key Redis Metrics
- 11.2 Use Observability Commands for Debugging
1. Data Structures & Keys
Impact: HIGH
1.1 Choose the Right Data Structure
Impact: HIGH (Optimal memory usage and operation performance)
Choose the Right Data Structure
Selecting the appropriate Redis data type for your use case is fundamental to performance and memory efficiency.
| Use Case | Recommended Type | Why |
| Simple values, counters | String | Fast, atomic operations |
| Object with fields | Hash | Memory efficient, partial updates |
| Queue, recent items | List | O(1) push/pop at ends |
| Unique items, membership | Set | O(1) add/remove/check |
| Rankings, ranges | Sorted Set | Score-based ordering |
| Documents for querying | JSON | Nested structures, RQE indexing |
| Event logs, messaging | Stream | Persistent, consumer groups |
| Similarity search | Vector (in Hash/JSON) | Use Redis Query Engine with HNSW/FLAT indexes |
Avoid: Using strings for everything.
# Storing object as JSON string loses atomic field updates
redis.set("user:1001", json.dumps({"name": "Alice", "email": "alice@example.com"}))
# To update email, must fetch, parse, modify, and rewrite entire object
user = json.loads(redis.get("user:1001"))
user["email"] = "new@example.com"
redis.set("user:1001", json.dumps(user))Recommended: Use Hash for objects with fields.
# Hash allows atomic field updates
redis.hset("user:1001", mapping={"name": "Alice", "email": "alice@example.com"})
# Update single field without touching others
redis.hset("user:1001", "email", "new@example.com")Reference: Redis Data Types
1.2 Use Consistent Key Naming Conventions
Impact: MEDIUM (Improved maintainability and debugging)
Use Consistent Key Naming Conventions
Well-structured key names improve code maintainability, debugging, and enable efficient key scanning.
Recommended: Use colons as separators with a consistent hierarchy.
# Pattern: service:entity:id:attribute
user:1001:profile
user:1001:settings
order:2024:items
cache:api:users:list
session:abc123Avoid: Inconsistent naming, spaces, or very long keys.
# These cause confusion and waste memory
User_1001_Profile
my key with spaces
com.mycompany.myapp.production.users.profile.data.1001Key tips:
- Keep keys short but readable—they consume memory
- Use TTL on cache keys to prevent unbounded growth
- Avoid
KEYS *in production—useSCANinstead - Consider key prefixes for multi-tenant applications
Reference: Redis Keys
2. Memory & Expiration
Impact: HIGH
2.1 Configure Memory Limits and Eviction Policies
Impact: HIGH (Prevents out-of-memory crashes and unpredictable behavior)
Configure Memory Limits and Eviction Policies
Always configure maxmemory and an eviction policy to prevent Redis from consuming all available memory.
Recommended: Set explicit memory limits.
maxmemory 2gb
maxmemory-policy allkeys-lru| Policy | Use Case |
volatile-lru | Evict keys with TTL, least recently used first |
allkeys-lru | Evict any key, least recently used first |
volatile-ttl | Evict keys closest to expiration |
noeviction | Return errors when memory is full (use for critical data) |
Avoid: Running Redis without memory limits.
# No maxmemory set - Redis will use all available RAM
# Can cause OOM killer to terminate Redis or other processesMemory optimization tips:
- Use Hashes for small objects (more memory-efficient than separate keys)
- Use
OBJECT ENCODING keyto check how Redis stores your data - Use
MEMORY USAGE keyto check individual key memory consumption - Enable compression in your client for large values
Reference: Redis Memory Optimization
2.2 Set TTL on Cache Keys
Impact: HIGH (Prevents unbounded memory growth)
Set TTL on Cache Keys
Always set expiration times on cache keys to prevent unbounded memory growth.
Recommended: Set TTL at write time.
# Good: TTL set atomically with the value
redis.setex("cache:user:1001", 3600, user_json)
# Good: For hashes, set TTL after
redis.hset("session:abc", mapping=session_data)
redis.expire("session:abc", 1800)Avoid: Forgetting TTL on cache keys.
# Risk: This key may live forever
redis.set("cache:user:1001", user_json)TTL strategies:
- Cache data: 1-24 hours depending on freshness requirements
- Sessions: 30 minutes to 24 hours
- Rate limiting: Seconds to minutes
- Temporary locks: Seconds with automatic release
Reference: Redis EXPIRE
3. Connection & Performance
Impact: HIGH
3.1 Avoid Blocking Commands in Production
Impact: HIGH (Prevents Redis from becoming unresponsive)
Avoid Blocking Commands in Production
Some Redis commands block the server while executing. Use non-blocking alternatives.
| Avoid | Use Instead |
KEYS * | SCAN with cursor |
SMEMBERS on large sets | SSCAN |
HGETALL on large hashes | HSCAN |
LRANGE 0 -1 on large lists | Paginate with LRANGE 0 100 |
Recommended: Use SCAN for iteration.
# Good: Non-blocking iteration
cursor = 0
while True:
cursor, keys = redis.scan(cursor, match="user:*", count=100)
for key in keys:
process(key)
if cursor == 0:
breakAvoid: Using KEYS in production.
# Bad: Blocks Redis while scanning all keys
keys = redis.keys("user:*")Reference: Redis SCAN
3.2 Configure Connection Timeouts
Impact: MEDIUM (Prevents hanging connections and improves reliability)
Configure Connection Timeouts
Always configure timeouts to prevent hanging connections.
Recommended: Set both socket and connect timeouts.
r = redis.Redis(
host='localhost',
socket_timeout=5.0, # Read/write timeout
socket_connect_timeout=2.0 # Connection timeout
)Avoid: Using default (no) timeouts.
# Bad: No timeouts - can hang indefinitely
r = redis.Redis(host='localhost')Reference: Redis Client Configuration
3.3 Use Connection Pooling
Impact: HIGH (Reduces connection overhead by 10x or more)
Use Connection Pooling
Reuse connections via a pool instead of creating new connections per request.
Recommended: Use a connection pool.
import redis
# Good: Connection pool
pool = redis.ConnectionPool(host='localhost', port=6379, max_connections=50)
r = redis.Redis(connection_pool=pool)Avoid: Creating new connections per request.
# Bad: New connection every time
def get_user(user_id):
r = redis.Redis(host='localhost', port=6379) # Don't do this
return r.get(f"user:{user_id}")Reference: Redis Connection Pooling
3.4 Use Pipelining for Bulk Operations
Impact: HIGH (Reduces round trips, 5-10x faster for batch operations)
Use Pipelining for Bulk Operations
Batch multiple commands into a single round trip to reduce network latency.
Recommended: Use pipeline for multiple commands.
# Good: Single round trip for multiple commands
pipe = redis.pipeline()
for user_id in user_ids:
pipe.get(f"user:{user_id}")
results = pipe.execute()Avoid: Sequential commands in a loop.
# Bad: N round trips
results = []
for user_id in user_ids:
results.append(redis.get(f"user:{user_id}"))Reference: Redis Pipelining
4. JSON Documents
Impact: MEDIUM
4.1 Choose JSON vs Hash Appropriately
Impact: MEDIUM (Optimal data model for your use case)
Choose JSON vs Hash Appropriately
Redis JSON and Hash serve different purposes. Choose based on your data structure and query needs.
| JSON | Hash |
| Nested structures | Flat key-value |
| Need to query with RQE | Simple field access |
| Partial updates at any depth | Top-level field updates |
| Complex documents | Simple objects |
Recommended: Use JSON for nested structures.
# JSON supports nested structures and deep updates
redis.json().set("user:1001", "$", {
"name": "Alice",
"preferences": {"theme": "dark", "notifications": True}
})
# Update nested field directly
redis.json().set("user:1001", "$.preferences.theme", "light")Recommended: Use Hash for flat objects.
# Hash is simpler and more memory-efficient for flat data
redis.hset("session:abc", mapping={
"user_id": "1001",
"created_at": "2024-01-01",
"ip": "192.168.1.1"
})Reference: Redis JSON
4.2 Use JSON Paths for Partial Updates
Impact: MEDIUM (Avoids fetching and rewriting entire documents)
Use JSON Paths for Partial Updates
Use JSON path syntax to update specific fields without fetching the entire document.
Recommended: Use JSON paths for targeted updates.
# Store JSON document
redis.json().set("user:1001", "$", {
"name": "Alice",
"email": "alice@example.com",
"preferences": {"theme": "dark", "notifications": True}
})
# Update nested field without fetching entire document
redis.json().set("user:1001", "$.preferences.theme", "light")
# Get specific field
theme = redis.json().get("user:1001", "$.preferences.theme")Avoid: Storing JSON as a string and parsing client-side.
# Bad: Loses queryability and atomic updates
redis.set("user:1001", json.dumps(user_data))
# Must fetch, parse, modify, serialize, and rewrite
data = json.loads(redis.get("user:1001"))
data["preferences"]["theme"] = "light"
redis.set("user:1001", json.dumps(data))Indexing JSON for search:
FT.CREATE idx:users ON JSON PREFIX 1 user:
SCHEMA
$.name AS name TEXT
$.email AS email TAG
$.preferences.theme AS theme TAGReference: Redis JSON Path
5. Redis Query Engine
Impact: HIGH
5.1 Choose the Correct Field Type
Impact: HIGH (Use TAG instead of TEXT for filtering to improve query speed 10x)
Choose the Correct Field Type
Each field type has different capabilities and performance characteristics.
| Field Type | Use When | Notes |
| TEXT | Full-text search needed | Tokenized, stemmed |
| TAG | Exact match, filtering | Faster than TEXT for filtering |
| NUMERIC | Range queries, sorting | Use for prices, counts, timestamps |
| GEO | Location queries | Lat/long coordinates |
| VECTOR | Similarity search | HNSW or FLAT algorithm |
Recommended: Use TAG for exact matching.
# Good: TAG for exact category matching
FT.CREATE idx:products ON HASH PREFIX 1 product:
SCHEMA
category TAG SORTABLE
status TAGAvoid: Using TEXT when you don't need full-text features.
# Overkill: TEXT for category adds unnecessary tokenization
FT.CREATE idx:products ON HASH PREFIX 1 product:
SCHEMA
category TEXT
status TEXTReference: Redis Search Field Types
5.2 Index Only Fields You Query
Impact: HIGH (Reduces index size and improves write performance)
Index Only Fields You Query
Create indexes with only the fields you need to search, filter, or sort on.
Recommended: Index specific fields.
FT.CREATE idx:products ON HASH PREFIX 1 product:
SCHEMA
name TEXT WEIGHT 2.0
description TEXT
category TAG SORTABLE
price NUMERIC SORTABLE
location GEOAvoid: Over-indexing or indexing unused fields.
# Bad: Indexing every field "just in case"
FT.CREATE idx:products ON HASH PREFIX 1 product:
SCHEMA
name TEXT
description TEXT
category TEXT
subcategory TEXT
brand TEXT
sku TEXT
price NUMERIC
cost NUMERIC
margin NUMERIC
...Tips:
- Start with the minimum required fields
- Add fields as query patterns emerge
- Use
FT.INFOto monitor index size
Reference: Redis Search Indexing
5.3 Manage Indexes for Zero-Downtime Updates
Impact: MEDIUM (Use aliases for seamless index updates)
Manage Indexes for Zero-Downtime Updates
Use aliases to swap indexes without application changes.
Recommended: Use aliases for production indexes.
# Create versioned index
FT.CREATE idx:products_v2 ON HASH PREFIX 1 product:
SCHEMA
name TEXT
category TAG SORTABLE
price NUMERIC SORTABLE
# Point alias to new index
FT.ALIASADD products idx:products_v2
# Application queries use alias
FT.SEARCH products "@category:{electronics}"
# Later, swap to new version
FT.ALIASUPDATE products idx:products_v3Useful management commands:
# Check index info
FT.INFO idx:products
# Drop and recreate (non-blocking)
FT.DROPINDEX idx:products
FT.CREATE idx:products ...
# List all indexes
FT._LISTReference: Redis Search Index Management
5.4 Use DIALECT 2 for Modern Query Syntax
Impact: MEDIUM (Enables modern query features and consistent behavior)
Use DIALECT 2 for Modern Query Syntax
Always specify DIALECT 2 (or higher) for modern query syntax and consistent behavior.
Recommended: Specify DIALECT 2.
FT.SEARCH idx:products "@name:laptop" DIALECT 2
FT.AGGREGATE idx:products "@category:{electronics}"
GROUPBY 1 @category
REDUCE COUNT 0 AS count
DIALECT 2Avoid: Relying on default dialect which may vary.
# Behavior may vary depending on server default
FT.SEARCH idx:products "@name:laptop"Why DIALECT 2:
- Consistent handling of special characters
- Better NULL value handling
- More predictable query parsing
- Required for some newer features
Reference: Redis Search Dialects
5.5 Write Efficient Queries
Impact: HIGH (Proper filtering reduces query time by orders of magnitude)
Write Efficient Queries
Be specific and use filters to reduce the result set early.
Recommended: Use specific filters and limit results.
# Good: Specific query with filters
FT.SEARCH idx:products "@category:{electronics} @price:[100 500]"
LIMIT 0 20
RETURN 3 name price category
# Good: Use SORTBY and LIMIT
FT.SEARCH idx:products "@name:laptop"
SORTBY price ASC
LIMIT 0 10Avoid: Broad queries returning large result sets.
# Bad: Wildcard prefix scans entire index
FT.SEARCH idx:products "*" LIMIT 0 10000
# Bad: Loading all fields from source document
FT.AGGREGATE idx:products "*" LOAD *Performance tips:
- Add
SORTABLEto fields used inSORTBY - Use
TAG SORTABLE UNFfor best performance on tag fields - Use
NOSTEMif you don't need stemming - Profile queries with
FT.PROFILE
FT.PROFILE idx:products SEARCH QUERY "@category:{electronics}"Reference: Redis Search Query Syntax
6. Vector Search & RedisVL
Impact: HIGH
6.1 Choose HNSW vs FLAT Based on Dataset Size
Impact: HIGH (HNSW is 100x faster for large datasets, FLAT is exact for small ones)
Choose HNSW vs FLAT Based on Dataset Size
Select the right algorithm based on your dataset size and accuracy requirements.
| Algorithm | Use Case | Trade-off |
| HNSW | Large datasets (>10k vectors) | Fast search, uses more memory |
| FLAT | Small datasets, exact results | Slower search, less memory |
Recommended: Use HNSW for production workloads.
from redisvl.schema import IndexSchema
# HNSW for large datasets - fast approximate search
schema = IndexSchema.from_dict({
"index": {"name": "idx:docs", "prefix": "doc:"},
"fields": [
{"name": "embedding", "type": "vector", "attrs": {
"dims": 1536,
"algorithm": "HNSW",
"distance_metric": "COSINE",
"M": 16, # Connections per node (higher = more accurate, more memory)
"EF_CONSTRUCTION": 200 # Build-time accuracy (higher = better index, slower build)
}}
]
})Recommended: Use FLAT for small datasets or when exact results required.
# FLAT for small datasets - exact brute-force search
schema = IndexSchema.from_dict({
"index": {"name": "idx:small", "prefix": "small:"},
"fields": [
{"name": "embedding", "type": "vector", "attrs": {
"dims": 1536,
"algorithm": "FLAT",
"distance_metric": "COSINE"
}}
]
})Tips:
- Start with HNSW defaults, tune M and EF based on benchmarks
- Higher M = more memory but better recall
- Set EF_RUNTIME at query time for accuracy/speed trade-off
Reference: Redis Vector Search
6.2 Configure Vector Indexes Properly
Impact: HIGH (Correct configuration is essential for vector search accuracy)
Configure Vector Indexes Properly
Set the correct dimensions, algorithm, and distance metric for your embeddings.
Recommended: Match index config to your embedding model.
from redisvl.index import SearchIndex
from redisvl.schema import IndexSchema
schema = IndexSchema.from_dict({
"index": {"name": "idx:docs", "prefix": "doc:"},
"fields": [
{"name": "content", "type": "text"},
{"name": "embedding", "type": "vector", "attrs": {
"dims": 1536, # Must match your embedding model
"algorithm": "HNSW",
"distance_metric": "COSINE"
}}
]
})
index = SearchIndex(schema)
index.create(overwrite=True)Avoid: Mismatched dimensions or wrong distance metric.
# Bad: Wrong dimensions for your model
{"dims": 768} # But using OpenAI which outputs 1536
# Bad: Wrong metric for normalized embeddings
{"distance_metric": "L2"} # When embeddings are normalized for COSINEReference: RedisVL Documentation
6.3 Implement RAG Pattern Correctly
Impact: HIGH (Proper RAG implementation improves LLM response quality)
Implement RAG Pattern Correctly
Store documents with embeddings, retrieve relevant context, and pass to LLM.
Recommended: Full RAG pipeline with RedisVL.
from redisvl.index import SearchIndex
from redisvl.query import VectorQuery
# 1. Store documents with embeddings
for doc in documents:
embedding = embed_model.encode(doc["content"])
index.load([{
"content": doc["content"],
"embedding": embedding.tolist(),
"source": doc["source"]
}])
# 2. Query with vector similarity
query_embedding = embed_model.encode(user_question)
results = index.search(VectorQuery(
vector=query_embedding,
vector_field_name="embedding",
return_fields=["content", "source"],
num_results=5
))
# 3. Pass context to LLM
context = "\n".join([r["content"] for r in results])
response = llm.generate(f"Context: {context}\n\nQuestion: {user_question}")Best practices:
- Normalize vectors if using COSINE distance
- Batch inserts using
index.load()with lists - Set appropriate M and EF_CONSTRUCTION for HNSW based on dataset size
- Use filters to reduce the search space before vector comparison
- Consider chunking long documents for better retrieval
Reference: Redis RAG Quickstart
6.4 Use Hybrid Search for Better Results
Impact: MEDIUM (Combining vector + filters improves relevance and reduces search space)
Use Hybrid Search for Better Results
Combine vector similarity with attribute filtering for more relevant results.
Recommended: Apply filters to reduce search space.
from redisvl.query import VectorQuery
query = VectorQuery(
vector=query_embedding,
vector_field_name="embedding",
return_fields=["content", "category", "date"],
num_results=10,
filter_expression="@category:{technology} @date:[2024 2025]"
)
results = index.search(query)Avoid: Searching entire vector space when filters apply.
# Bad: No filter - searches all vectors then filters client-side
results = index.search(VectorQuery(
vector=query_embedding,
vector_field_name="embedding",
num_results=1000
))
# Client-side filtering - wasteful
filtered = [r for r in results if r["category"] == "technology"]Tips:
- Use TAG fields for category filters
- Use NUMERIC fields for date/price ranges
- Filters are applied before vector search, reducing computation
Reference: Redis Hybrid Queries
7. Semantic Caching
Impact: MEDIUM
7.1 Configure Semantic Cache Properly
Impact: MEDIUM (Correct threshold tuning balances hit rate vs accuracy)
Configure Semantic Cache Properly
Note: LangCache is currently in preview on Redis Cloud. Features and behavior may change.
Tune similarity threshold and cache separation for optimal LangCache results.
Recommended: Tune similarity threshold for your use case.
from langcache import LangCache
lang_cache = LangCache(
server_url=f"https://{os.getenv('HOST')}",
cache_id=os.getenv("CACHE_ID"),
api_key=os.getenv("API_KEY")
)
# Stricter matching - fewer false positives (0.95 = very similar)
result = lang_cache.search(
prompt="What is Redis?",
similarity_threshold=0.95
)
# Looser matching - higher hit rate (0.8 = somewhat similar)
result = lang_cache.search(
prompt="What is Redis?",
similarity_threshold=0.8
)Recommended: Use separate caches for different use cases.
# Create different cache IDs in Redis Cloud for different LLM tasks
support_cache = LangCache(
server_url=server_url,
cache_id="support-cache-id",
api_key=api_key
)
code_cache = LangCache(
server_url=server_url,
cache_id="code-cache-id",
api_key=api_key
)Avoid: Using a single cache for all LLM tasks.
# All tasks share one cache - responses may not be relevant
result = lang_cache.search(prompt="How do I reset my password?")
# Could return a code snippet if someone asked a similar coding questionBest practices:
- Start with threshold 0.9, adjust based on your use case
- Use custom attributes to filter results within a single cache
- Monitor cache hit rates to evaluate effectiveness
- Use separate cache IDs for fundamentally different LLM tasks
Reference: LangCache Best Practices
7.2 Use LangCache for LLM Response Caching
Impact: HIGH (Reduces LLM API costs by 50-90% for similar queries)
Use LangCache for LLM Response Caching
Note: LangCache is currently in preview on Redis Cloud. Features and behavior may change.
LangCache is a fully-managed semantic caching service on Redis Cloud that reduces LLM costs and latency.
How it works:
- Your app sends a prompt to LangCache via
POST /v1/caches/{cacheId}/entries/search - LangCache generates an embedding and searches for similar cached responses
- If found (cache hit), returns the cached response instantly
- If not found (cache miss), your app calls the LLM and stores the response
Recommended: Use the LangCache Python SDK.
from langcache import LangCache
import os
lang_cache = LangCache(
server_url=f"https://{os.getenv('HOST')}",
cache_id=os.getenv("CACHE_ID"),
api_key=os.getenv("API_KEY")
)
# Search for cached response
result = lang_cache.search(
prompt="What is Redis?",
similarity_threshold=0.9
)
if result:
response = result[0]["response"]
else:
response = llm.generate("What is Redis?")
# Store for future queries
lang_cache.set(
prompt="What is Redis?",
response=response
)LangCache REST API:
# Search cache
curl -X POST "https://$HOST/v1/caches/$CACHE_ID/entries/search" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "What is Redis?"}'
# Store a response
curl -X POST "https://$HOST/v1/caches/$CACHE_ID/entries" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "What is Redis?", "response": "Redis is an in-memory database..."}'With custom attributes for filtering:
# Store with attributes
lang_cache.set(
prompt="What is Redis?",
response="Redis is an in-memory database...",
attributes={"category": "database", "version": "v1"}
)
# Search with attribute filter
result = lang_cache.search(
prompt="Tell me about Redis",
attributes={"category": "database"},
similarity_threshold=0.9
)Reference: LangCache Documentation
8. Streams & Pub/Sub
Impact: MEDIUM
8.1 Choose Streams vs Pub/Sub Appropriately
Impact: MEDIUM (Wrong choice leads to lost messages or unnecessary complexity)
Choose Streams vs Pub/Sub Appropriately
Redis supports two messaging approaches for different use cases.
Avoid: Using Pub/Sub when messages must not be lost.
# Pub/Sub - messages lost if no subscribers connected
r.publish("orders", json.dumps(order)) # Fire and forget!Recommended: Use Streams when message durability matters.
# Streams - messages persist and can be replayed
r.xadd("orders:stream", {"order": json.dumps(order)})
# Consumer group for reliable processing
r.xreadgroup("workers", "worker-1", {"orders:stream": ">"}, count=10)
r.xack("orders:stream", "workers", message_id)When to Use Each
| Requirement | Use |
| Real-time notifications, OK to miss messages | Pub/Sub |
| Messages must not be lost | Streams |
| Need to replay/reprocess messages | Streams |
| Multiple workers processing same queue | Streams (consumer groups) |
| Simple broadcast to connected clients | Pub/Sub |
| Event sourcing or audit trail | Streams |
Reference: Redis Streams
9. Clustering & Replication
Impact: MEDIUM
9.1 Use Hash Tags for Related Keys
Impact: HIGH (Enables multi-key operations in Redis Cluster)
Use Hash Tags for Related Keys
In Redis Cluster, keys are distributed across slots. Use hash tags to ensure related keys are on the same slot.
Recommended: Use hash tags for related keys.
# These keys go to the same slot because {1001} is the hash tag
redis.set("user:{1001}:profile", "...")
redis.set("user:{1001}:settings", "...")
redis.set("user:{1001}:cart", "...")
# Now you can use MULTI/pipeline on them
pipe = redis.pipeline()
pipe.get("user:{1001}:profile")
pipe.get("user:{1001}:settings")
pipe.execute()Avoid: Related keys without hash tags.
# Bad: These may be on different slots
redis.set("user:1001:profile", "...") # No hash tag
redis.set("user:1001:settings", "...")
# This will fail in cluster mode
pipe = redis.pipeline()
pipe.get("user:1001:profile")
pipe.get("user:1001:settings")
pipe.execute() # CROSSSLOT errorHash tag rules:
- Only the part between
{and}is hashed - Use meaningful identifiers like user ID or tenant ID
- Don't overuse - too many keys on one slot creates hotspots
Reference: Redis Cluster Key Distribution
9.2 Use Read Replicas for Read-Heavy Workloads
Impact: MEDIUM (Scales read throughput without adding primary nodes)
Use Read Replicas for Read-Heavy Workloads
For read-heavy workloads, distribute reads across replicas to reduce load on primaries.
Recommended: Read from replicas with Sentinel.
from redis import Redis
from redis.sentinel import Sentinel
# With Sentinel
sentinel = Sentinel([('sentinel1', 26379)], socket_timeout=0.1)
master = sentinel.master_for('mymaster')
replica = sentinel.slave_for('mymaster')
# Write to master
master.set("key", "value")
# Read from replica (eventually consistent)
value = replica.get("key")Recommended: Configure replica reads in Redis Cluster.
from redis.cluster import RedisCluster
rc = RedisCluster(
host='localhost',
port=6379,
read_from_replicas=True # Distribute reads to replicas
)Considerations:
- Replica reads are eventually consistent
- Don't read from replicas for data that was just written
- Use for read-heavy, slightly-stale-OK workloads
Reference: Redis Replication
10. Security
Impact: HIGH
10.1 Always Use Authentication in Production
Impact: HIGH (Prevents unauthorized access to your data)
Always Use Authentication in Production
Never run Redis without authentication in production environments.
Recommended: Use password and TLS.
r = redis.Redis(
host='localhost',
port=6379,
password='your-strong-password',
ssl=True,
ssl_cert_reqs='required'
)Avoid: Connecting without authentication.
# Bad: No authentication
r = redis.Redis(host='localhost', port=6379)Configuration:
# redis.conf
requirepass your-strong-password
tls-port 6380
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.keyReference: Redis Security
10.2 Secure Network Access
Impact: HIGH (Reduces attack surface and prevents unauthorized access)
Secure Network Access
Restrict network access to Redis to only trusted sources.
Recommended: Bind to specific interfaces.
# redis.conf
bind 127.0.0.1 192.168.1.100
protected-mode yesRecommended: Use firewall rules.
# Allow only application servers
iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROPAvoid: Exposing Redis to the internet.
# Bad: Binds to all interfaces
bind 0.0.0.0
protected-mode noSecurity checklist:
- Use TLS for connections
- Bind to specific interfaces, not
0.0.0.0 - Use firewall rules to restrict access
- Disable dangerous commands in production
# Disable dangerous commands
rename-command FLUSHALL ""
rename-command DEBUG ""
rename-command CONFIG ""Reference: Redis Security
10.3 Use ACLs for Fine-Grained Access Control
Impact: HIGH (Limits blast radius if credentials are compromised)
Use ACLs for Fine-Grained Access Control
Create users with only the permissions they need (principle of least privilege).
Recommended: Create specific users with limited permissions.
# Read-only user for cache access
ACL SETUSER app_readonly on >password ~cache:* +get +mget +scan
# Writer that can't run dangerous commands
ACL SETUSER app_writer on >password ~* +@all -@dangerous
# Admin user (use sparingly)
ACL SETUSER admin on >strong-password ~* +@allAvoid: Using the default user for everything.
# Bad: Single password for all access
requirepass shared-passwordACL categories:
@read- Read commands@write- Write commands@dangerous- Commands like FLUSHALL, DEBUG@admin- Administrative commands
Reference: Redis ACL
11. Observability
Impact: MEDIUM
11.1 Monitor Key Redis Metrics
Impact: MEDIUM (Early detection of performance and capacity issues)
Monitor Key Redis Metrics
Track these metrics to catch issues before they impact users.
| Metric | What It Tells You | Alert When |
used_memory | Current memory usage | > 80% of maxmemory |
connected_clients | Number of connections | Sudden spikes or drops |
blocked_clients | Clients waiting on blocking ops | > 0 sustained |
instantaneous_ops_per_sec | Current throughput | Significant drops |
keyspace_hits/misses | Cache hit ratio | Hit ratio < 80% |
rejected_connections | Connection limit issues | > 0 |
rdb_last_save_time | Last persistence snapshot | Too old |
Recommended: Export metrics to your monitoring system.
# Get key metrics
info = redis.info()
print(f"Memory: {info['used_memory_human']}")
print(f"Connections: {info['connected_clients']}")
print(f"Ops/sec: {info['instantaneous_ops_per_sec']}")
print(f"Hit ratio: {info['keyspace_hits'] / (info['keyspace_hits'] + info['keyspace_misses']) * 100:.1f}%")Redis Insight: Use Redis Insight for visual monitoring, query profiling, and debugging. It includes Redis Copilot for natural language queries.
Reference: Redis Insight
11.2 Use Observability Commands for Debugging
Impact: MEDIUM (Enables quick diagnosis of performance issues)
Use Observability Commands for Debugging
Redis provides built-in commands for monitoring and debugging.
Key commands:
# Slow query log - find slow commands
SLOWLOG GET 10
SLOWLOG LEN
SLOWLOG RESET
# Server info - comprehensive stats
INFO all
INFO memory
INFO stats
INFO replication
INFO clients
# Memory analysis
MEMORY DOCTOR
MEMORY STATS
MEMORY USAGE mykey
# Client connections
CLIENT LIST
CLIENT INFO
# Index info (RQE)
FT.INFO idx:products
FT.PROFILE idx:products SEARCH QUERY "@name:laptop"Recommended: Check SLOWLOG regularly.
# Get recent slow queries
slow_queries = redis.slowlog_get(10)
for query in slow_queries:
print(f"Duration: {query['duration']}μs, Command: {query['command']}")Reference: Redis Monitoring
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

