Redis & Caching
django_mcp uses Django’s default cache backend (django.core.cache.cache) for three independent purposes. All three work with any Django cache backend, but Redis is assumed in production because QueryBudget depends on pipeline operations.
Cache key summary
| Prefix | Scope | TTL | Purpose |
|---|---|---|---|
mcp:query:<hash> | Global | 5 minutes | Query result cache |
mcp:budget:{session_id}:* | Per session | 1 hour (sliding) | Resource budget tracking |
mcp_chat:{user_id}:{session_id} | Per user + session | 24 hours (sliding) | Chat conversation history |
1. Query cache (cache.py — QueryCache)
Caches tool query results to avoid hitting the database repeatedly when the LLM calls the same tool with identical arguments in one session.
Key format
mcp:query:<16-char-sha256-hex>The key is a SHA-256 hash of {sql, params, tenant_id} serialized as JSON, truncated to 16 hex characters.
Behavior
- TTL: 5 minutes (configurable per
QueryCache(ttl=N)instance). - Stampede prevention: uses
cache.add()instead ofcache.set()— only writes if the key does not already exist, so concurrent requests for the same query do not trigger multiple DB hits. - Invalidation:
cache.invalidate(sql, params, tenant_id)deletes a specific key.cache.clear_all()uses RedisKEYS mcp:query:*— avoid this in the production hot path.
Usage in tools
QueryCache is instantiated per-request inside tool implementations. It is not wired automatically — each tool that wants caching creates its own instance:
from django_cfg.modules.django_mcp.cache import QueryCache
cache = QueryCache(ttl=300)
result = cache.get(sql, params, tenant_id="default")
if result is None:
result = run_query(sql, params)
cache.set(sql, params, tenant_id="default", value=result)2. Query budget (budget.py — QueryBudget)
Tracks per-session resource consumption and enforces limits. Prevents a single agent session from hammering the database or burning excessive LLM tokens on tool calls.
Key format
mcp:budget:{session_id}:time # float — total execution seconds
mcp:budget:{session_id}:rows # int — total rows fetched
mcp:budget:{session_id}:cost # float — total EXPLAIN cost units
mcp:budget:{session_id}:queries # int — total number of queriesAll four keys share the same session scope and TTL (1 hour, sliding — reset on every record_usage() call via EXPIRE).
Default limits
| Metric | Default limit |
|---|---|
| Total execution time | 60 seconds |
| Total rows fetched | 10,000 rows |
| Total EXPLAIN cost | 500,000 units |
| Total queries | 200 queries |
Behavior
record_usage(execution_ms, rows, cost) runs a Redis pipeline:
INCRBYFLOATon time and cost.INCRon rows and queries.EXPIREall four keys (sliding TTL).- Reads results and compares against limits.
- Returns
True(within budget) orFalse(exceeded).
Fail-open: if Redis is unavailable during the pipeline, record_usage() returns True and lets the query proceed. This keeps the system operational when Redis is temporarily down, but means budget tracking is silently skipped. For stricter environments, you can override this to fail-closed.
Usage
from django_cfg.modules.django_mcp.budget import QueryBudget
budget = QueryBudget(session_id="agent-session-abc123")
ok = budget.record_usage(execution_ms=250, rows=150, cost=1200.5)
if not ok:
return "Error: session resource budget exceeded. Try a more specific query."3. Chat history (chat/store.py — RedisMCPChatStore)
Status: planned / in progress. The store class exists but is not yet wired into the streaming view. Currently all chat history is in-memory and resets on each request.
Persists the conversation history for the admin chat agent between page reloads.
Key format
mcp_chat:{user_id}:{session_id}Stored as a JSON list of {"role": ..., "content": ...} dicts.
Behavior
- Storage order: newest-first (prepend on write, trim to
max_messages=50). - Read order: reversed to chronological (oldest-first) before returning to the agent.
- TTL: 24 hours, sliding — reset on every read and write.
- Per-user isolation:
user_idis always part of the key, so different admin users have separate histories even with the samesession_id.
Usage
from django_cfg.modules.django_mcp.chat.store import RedisMCPChatStore
store = RedisMCPChatStore(user_id=request.user.pk, session_id="admin-chat")
# Load history before running the agent
history = store.get_history() # [{"role": "user", "content": "..."}, ...]
# Persist messages after stream completes
store.append_message("user", user_message)
store.append_message("assistant", assistant_reply)
# Clear the session (user requests a fresh start)
store.clear()Redis not configured?
All three features use django.core.cache.cache, so they fall back gracefully to whatever backend is configured:
| Backend | Effect |
|---|---|
LocMemCache (Django default in dev) | Works for single-process; resets on server restart. |
DummyCache | Everything is a no-op — queries never cached, budgets never tracked, history always lost. |
Redis (production default) | Full functionality. Required for QueryBudget pipelines to work correctly. |
The QueryBudget.record_usage() method catches all exceptions and returns True (fail-open) when Redis is unavailable, so the rest of the system continues working.