Skip to content

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:

curl http://localhost:8000/healthz

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:

# From API container
redis-cli -h $REDIS_HOST -p $REDIS_PORT ping
# Response: PONG

Cache Statistics

Get cache hit/miss stats:

redis-cli info 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:

  1. Deploy Redis:

    docker-compose up -d redis
    

  2. Update Environment:

    DISABLE_REDIS=false
    REDIS_HOST=redis
    

  3. Restart API:

    docker-compose restart api
    

  4. Verify:

    curl http://localhost:8000/healthz
    # Should show "backend": "redis"
    

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

References