Redis Integration
Redis Integration Guide¶
This guide explains how Redis caching is integrated into the Graffold for distributed session management and query result caching.
Overview¶
Redis provides distributed caching capabilities essential for multi-instance deployments of the FastAPI backend serving the React frontend. It enables:
- Distributed Session Management: Share session state across multiple API instances
- Query Result Caching: Cache expensive query results to reduce redundant LLM calls
- Feedback Persistence: Store user feedback across all instances
- Performance: Reduce database load and improve response times
Architecture¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ React Frontend │────▶│ Load Balancer │────▶│ FastAPI (1) │
└─────────────────┘ └─────────────────┘ │ RedisSessionMgr│
│ └────────┬────────┘
│ │
│ ▼
│ ┌─────────────────┐
│ │ Redis Cache │
│ │ - Sessions │
│ │ - Query Cache │
│ │ - Feedback │
│ └────────▲────────┘
│ │
└────────────────▶┌────────┴────────┐
│ FastAPI (2) │
│ RedisSessionMgr│
└─────────────────┘
Configuration¶
Environment Variables¶
Add these to your .env file:
# Redis Configuration
REDIS_HOST=localhost # Redis server hostname
REDIS_PORT=6379 # Redis server port
REDIS_PASSWORD= # Optional Redis password
DISABLE_REDIS=false # Set to 'true' to disable Redis
SESSION_TTL_MINUTES=60 # Session expiration time in minutes
Docker Compose¶
For local development, add Redis to your docker-compose.yml:
services:
redis:
image: redis:7-alpine
container_name: graffold-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
api:
build: .
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
redis:
condition: service_healthy
volumes:
redis-data:
Usage¶
Session Management¶
The RedisSessionManager automatically handles session persistence:
# Create a session (stored in Redis)
session = session_manager.create_session(config)
# Execute queries (results cached in Redis)
result = session_manager.execute_query(session.session_id, "What proteins interact with TP53?")
# Session is automatically shared across all API instances
Query Result Caching¶
Query results are automatically cached based on session and question:
# First call - executes query and caches result
result1 = session_manager.execute_query(session_id, question)
# Subsequent calls - returns cached result (fast!)
result2 = session_manager.execute_query(session_id, question)
Cache behavior:
- Cache Key: Hash of session_id + question + config
- TTL: 1 hour by default (configurable)
- Invalidation: Automatic expiration or manual cache clear
Fallback Behavior¶
If Redis is unavailable, the system automatically falls back to in-memory storage:
# RedisSessionManager handles fallback automatically
session_manager = RedisSessionManager(
query_service=query_service,
fallback_to_memory=True # Default: True
)
Fallback triggers:
- Redis connection fails on startup
- Redis becomes unavailable during operation
- DISABLE_REDIS=true environment variable
API Endpoints¶
Health Check¶
The /healthz endpoint includes Redis status:
Response:
{
"status": "ok",
"sessions": {
"backend": "redis",
"redis_available": true,
"session_count": 5
},
"version": "0.1.0"
}
Status values:
- ok: All systems operational
- degraded: Redis unavailable, using in-memory fallback
Redis Data Structure¶
Session Storage¶
Sessions are stored as JSON hashes:
Key: session:{session_id}
Type: String (JSON)
TTL: 60 minutes (configurable)
Value:
{
"session_id": "abc123",
"created_at": "2024-01-27T10:30:00",
"config": {
"llm_service": "ollama",
"database_name": "olink_ppi",
"agent_type": "Standard"
},
"history": [
{
"question": "What proteins interact with TP53?",
"answer": "...",
"sources": [...],
"created_at": "2024-01-27T10:31:00"
}
]
}
Query Cache¶
Query results are cached separately:
Key: query:{session_id}:{question_hash}
Type: String (JSON)
TTL: 1 hour
Value:
{
"answer": "...",
"sources": [...],
"detailed_results": [...],
"graph_analytics": {...}
}
Performance Optimization¶
Connection Pooling¶
Redis client uses connection pooling by default: - Keep-alive: Enabled - Health checks: Every 30 seconds - Connect timeout: 5 seconds
Serialization¶
The cache supports multiple serialization formats: - JSON: Default for text data (fast, human-readable) - Pickle: For complex Python objects (efficient, not portable)
Memory Management¶
Redis uses LRU (Least Recently Used) eviction when memory is full:
# Configure Redis memory limit (in redis.conf or docker-compose)
maxmemory 2gb
maxmemory-policy allkeys-lru
Production Deployment¶
AWS ElastiCache¶
For production on AWS, use ElastiCache for Redis:
# Environment variables for ElastiCache
REDIS_HOST=graffold-redis.abc123.0001.use1.cache.amazonaws.com
REDIS_PORT=6379
REDIS_PASSWORD= # ElastiCache in VPC doesn't need password
High Availability¶
For critical deployments, use Redis Sentinel or Cluster:
# Configure Redis Sentinel (future enhancement)
from redis.sentinel import Sentinel
sentinel = Sentinel([
('sentinel1', 26379),
('sentinel2', 26379)
])
master = sentinel.master_for('mymaster')
Security¶
Best practices for production:
1. VPC: Run Redis in private subnet
2. Authentication: Use REDIS_PASSWORD for auth
3. Encryption: Enable TLS for data in transit
4. Firewall: Restrict access to API instances only
Monitoring¶
Health Checks¶
Check Redis connectivity:
Cache Statistics¶
Get cache hit/miss stats:
Key metrics:
- keyspace_hits: Number of cache hits
- keyspace_misses: Number of cache misses
- used_memory_human: Memory usage
- connected_clients: Active connections
Logging¶
Redis operations are logged with structured logging:
INFO: RedisSessionManager initialized with Redis backend
INFO: Created query session (session_id=abc123, database=olink_ppi, backend=redis)
INFO: Query result cache hit (session_id=abc123, cache_key=query:abc123:456789)
Troubleshooting¶
Redis Connection Failed¶
Symptom: API logs show "Redis unavailable - falling back to in-memory"
Solutions:
1. Check Redis is running: docker ps | grep redis
2. Verify connection: redis-cli -h localhost -p 6379 ping
3. Check firewall rules and network connectivity
4. Verify REDIS_HOST and REDIS_PORT env variables
Session Not Found¶
Symptom: 404 error "Session 'xyz' not found"
Causes: - Session expired (exceeded TTL) - Redis restarted without persistence - Session created on different Redis instance
Solutions:
1. Increase SESSION_TTL_MINUTES if needed
2. Enable Redis persistence (AOF or RDB)
3. Ensure all API instances point to same Redis
Poor Cache Performance¶
Symptom: High latency or low cache hit rate
Solutions:
1. Check Redis memory: redis-cli info memory
2. Increase maxmemory if needed
3. Monitor eviction rate: redis-cli info stats | grep evicted
4. Review cache key patterns and TTLs
Migration from In-Memory¶
To migrate from in-memory session management:
-
Deploy Redis:
-
Update Environment:
-
Restart API:
-
Verify:
No code changes required - the system automatically uses Redis when available.
Testing¶
Unit Tests¶
Test Redis integration:
# tests/test_redis_session_manager.py
def test_session_persistence():
manager = RedisSessionManager(query_service)
session = manager.create_session(config)
# Verify session in Redis
retrieved = manager.get_session(session.session_id)
assert retrieved.session_id == session.session_id
Integration Tests¶
Test with real Redis:
# Start Redis for testing
docker run -d -p 6379:6379 redis:7-alpine
# Run tests
uv run pytest tests/test_redis_session_manager.py -v
Load Testing¶
Test cache performance under load:
# Using Apache Bench
ab -n 1000 -c 10 -p query.json -T application/json \
http://localhost:8000/v1/sessions/abc123/query
Monitor cache hit rate during load test.
Future Enhancements¶
Planned improvements: - [ ] Redis Cluster support for horizontal scaling - [ ] Cache warming for common queries - [ ] Analytics dashboard for cache metrics - [ ] Feedback persistence to Redis - [ ] Embedding vector caching - [ ] Rate limiting with Redis counters - [ ] Session replay from Redis history