Redis Caching Strategy and Cache Key Generation in Production Agentic RAG
The jamwithai/production-agentic-rag-course repository implements an exact-match, TTL-based Redis caching strategy that generates deterministic SHA-256 hash keys from normalized request payloads to deliver O(1) cache lookups for identical RAG queries.
The production-agentic-rag-course codebase leverages Redis as a high-performance caching layer to eliminate redundant computation in Retrieval-Augmented Generation (RAG) pipelines. This Redis caching strategy employs exact-match lookups with configurable time-to-live (TTL) expiration, ensuring that repeated identical queries return instantly while stale entries automatically purge themselves after a defined duration. Understanding how cache keys are generated from request parameters is essential for optimizing cache hit rates and maintaining deterministic behavior across distributed deployments.
Exact-Match Cache Strategy with TTL Expiration
The caching layer operates on an exact-match paradigm where every unique combination of query parameters maps to a single Redis entry. When an AskRequest arrives at the endpoint, the CacheClient first attempts an O(1) GET operation using a deterministically generated key. If the key exists, the cached response returns immediately, short-circuiting the expensive RAG retrieval and generation process.
Cache entries persist for a configurable duration defined by RedisSettings.ttl_hours (defaulting to 6 hours). The client applies this TTL using Redis's SET … EX <ttl> command during storage operations. The implementation also includes comprehensive logging for cache hits, misses, and serialization errors, enabling production observability without impacting performance.
Deterministic Cache Key Generation
Cache stability depends on generating identical keys for semantically equivalent requests, regardless of input ordering or formatting variations.
Normalizing Request Payloads
The private method _generate_cache_key in src/services/cache/client.py constructs a normalized dictionary containing the request's salient fields:
{
"query": request.query,
"model": request.model,
"top_k": request.top_k,
"use_hybrid": request.use_hybrid,
"categories": sorted(request.categories) if request.categories else [],
}
By sorting the categories list before hashing, the strategy guarantees that requests containing the same categories in different orders produce identical cache keys, preventing unnecessary cache fragmentation.
SHA-256 Hashing and Key Prefixing
The normalized payload undergoes JSON encoding with sorted keys, then feeds into hashlib.sha256 to produce a cryptographic hash. The algorithm retains only the first 16 hexadecimal characters (64 bits) of the digest to create compact keys, balancing collision resistance with key length efficiency.
The final Redis key follows this format:
exact_cache:<16-char-hash>
This prefixing convention isolates RAG cache entries from other potential Redis data while maintaining human-readable identification during debugging sessions.
Implementation Details in Source Code
The core caching logic resides in src/services/cache/client.py, which implements the CacheClient class. This class encapsulates key generation, GET retrieval, and SET storage operations:
def _generate_cache_key(self, request: AskRequest) -> str:
payload = {
"query": request.query,
"model": request.model,
"top_k": request.top_k,
"use_hybrid": request.use_hybrid,
"categories": sorted(request.categories) if request.categories else [],
}
json_str = json.dumps(payload, sort_keys=True)
hash_ = hashlib.sha256(json_str.encode()).hexdigest()[:16]
return f"exact_cache:{hash_}"
Factory Pattern and Client Initialization
The repository uses a factory pattern to decouple client instantiation from configuration. The make_cache_client function in src/services/cache/factory.py constructs a redis.Redis connection using parameters from RedisSettings (defined in src/config.py), then assembles the CacheClient with the appropriate TTL configuration:
from src.services.cache.factory import make_cache_client
from src.config import get_settings
settings = get_settings()
cache = make_cache_client(settings) # Returns CacheClient backed by Redis
Usage Patterns for Cache Operations
Typical endpoint handlers utilize two primary methods: find_cached_response for retrieval and store_response for persistence. These methods abstract the key generation logic, allowing business code to operate on domain objects rather than raw cache keys:
# Inside an async endpoint handler
cached = await cache.find_cached_response(ask_request)
if cached:
return cached # Cache hit → immediate return
# Cache miss path
response = await generate_answer(ask_request) # Expensive RAG operation
await cache.store_response(ask_request, response)
return response
Summary
- Exact-match strategy: Identical RAG requests map to single Redis entries via O(1)
GEToperations, eliminating redundant computation. - Deterministic keys: Cache keys derive from SHA-256 hashes of normalized JSON payloads, with category sorting ensuring stability regardless of input order.
- TTL configuration: Default 6-hour expiration controlled via
RedisSettings.ttl_hoursautomatically purges stale data. - Implementation locations: Core logic in
src/services/cache/client.py, factory insrc/services/cache/factory.py, configuration insrc/config.py.
Frequently Asked Questions
How does the cache handle variations in category ordering?
The _generate_cache_key method explicitly sorts the categories list before including it in the hash payload. This normalization ensures that requests containing the same categories in different orders produce identical cache keys, maximizing cache hit rates regardless of how frontend clients order the input.
What is the default TTL for cached responses and how is it configured?
The default TTL is 6 hours, defined by the ttl_hours field in RedisSettings (located in src/config.py). This value converts to a datetime.timedelta during CacheClient instantiation and applies to all SET operations as the Redis EX parameter, automatically expiring entries without manual invalidation logic.
How collision-resistant is the 16-character hexadecimal hash?
The 16-character prefix of a SHA-256 hash provides 64 bits of entropy, offering approximately 1.8 × 10¹⁹ possible unique combinations. For typical RAG query volumes, this strikes an optimal balance between key length efficiency and collision probability, while the full SHA-256 digest ensures cryptographic uniqueness before truncation.
Can the caching strategy distinguish between different LLM models?
Yes, the model field from AskRequest is included in the cache key payload. Queries identical in all parameters except the target model (e.g., GPT-4 vs. Claude) generate different SHA-256 hashes and therefore occupy separate cache entries, preventing model-specific responses from cross-contaminating each other.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →