---
story_id: story_5_8
sprint_id: sprint_5
phase: Phase 2 - Resources
status: not_started
priority: P1
story_points: 5
assignee: backend
estimated_days: 2
created_date: 2025-11-06
dependencies: [story_5_5]
---

# Story 5.8: Redis Caching Integration

**Phase**: Phase 2 - Resources (Week 3-4)
**Priority**: P1 HIGH
**Estimated Effort**: 5 Story Points (2 days)

## User Story

**As a** System Architect
**I want** Redis caching for frequently accessed MCP Resources
**So that** AI agents get fast responses and database load is reduced

## Business Value

Performance optimization delivers:
- 30-50% faster response times (from 200ms to 80ms)
- Reduced database load (80% cache hit rate target)
- Better scalability (support more concurrent AI agents)
- Improved user experience (faster AI responses)

## Acceptance Criteria

### AC1: Redis Integration
- [ ] Redis client configured (StackExchange.Redis)
- [ ] Connection pooling and retry logic
- [ ] Health checks for Redis availability

### AC2: Cache-Aside Pattern
- [ ] Check cache before database query
- [ ] Populate cache on cache miss
- [ ] Return cached data on cache hit

### AC3: TTL Configuration
- [ ] `projects.list`: 5 minutes TTL
- [ ] `users.list`: 5 minutes TTL
- [ ] `sprints.current`: 2 minutes TTL
- [ ] `issues.search`: 2 minutes TTL (cache by query params)

### AC4: Cache Invalidation
- [ ] Invalidate on data updates (domain events)
- [ ] Manual cache clear API endpoint
- [ ] Tenant-scoped cache keys

### AC5: Performance Metrics
- [ ] Track cache hit rate (target > 80%)
- [ ] Track cache miss rate
- [ ] Track response time improvement
- [ ] Export metrics for monitoring

### AC6: Testing
- [ ] Unit tests for caching logic
- [ ] Integration tests (cache hit/miss scenarios)
- [ ] Performance benchmarks (with/without cache)

## Technical Design

### Cache Service Interface

```csharp
public interface IMcpCacheService
{
    Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
    Task SetAsync<T>(string key, T value, TimeSpan ttl, CancellationToken cancellationToken = default);
    Task RemoveAsync(string key, CancellationToken cancellationToken = default);
    Task RemoveByPatternAsync(string pattern, CancellationToken cancellationToken = default);
}

public class RedisMcpCacheService : IMcpCacheService
{
    private readonly IConnectionMultiplexer _redis;
    private readonly ILogger<RedisMcpCacheService> _logger;

    public async Task<T?> GetAsync<T>(string key, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        var value = await db.StringGetAsync(key);

        if (!value.HasValue)
        {
            _logger.LogDebug("Cache miss: {Key}", key);
            return default;
        }

        _logger.LogDebug("Cache hit: {Key}", key);
        return JsonSerializer.Deserialize<T>(value!);
    }

    public async Task SetAsync<T>(string key, T value, TimeSpan ttl, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        var json = JsonSerializer.Serialize(value);
        await db.StringSetAsync(key, json, ttl);
        _logger.LogDebug("Cache set: {Key} (TTL: {TTL}s)", key, ttl.TotalSeconds);
    }

    public async Task RemoveAsync(string key, CancellationToken ct)
    {
        var db = _redis.GetDatabase();
        await db.KeyDeleteAsync(key);
        _logger.LogDebug("Cache removed: {Key}", key);
    }

    public async Task RemoveByPatternAsync(string pattern, CancellationToken ct)
    {
        var server = _redis.GetServer(_redis.GetEndPoints().First());
        var keys = server.Keys(pattern: pattern);

        var db = _redis.GetDatabase();
        foreach (var key in keys)
        {
            await db.KeyDeleteAsync(key);
        }

        _logger.LogDebug("Cache removed by pattern: {Pattern}", pattern);
    }
}
```

### Cached Resource Example

```csharp
public class ProjectsListResource : IMcpResource
{
    private readonly IProjectRepository _projectRepo;
    private readonly ITenantContext _tenantContext;
    private readonly IMcpCacheService _cache;

    public async Task<McpResourceContent> GetContentAsync(
        McpResourceRequest request,
        CancellationToken cancellationToken)
    {
        var tenantId = _tenantContext.CurrentTenantId;
        var cacheKey = $"mcp:{tenantId}:projects.list";

        // Try cache first
        var cached = await _cache.GetAsync<ProjectListDto[]>(cacheKey, cancellationToken);
        if (cached != null)
        {
            return CreateResponse(cached);
        }

        // Cache miss - query database
        var projects = await _projectRepo.GetAllAsync(tenantId, cancellationToken);
        var projectDtos = projects.Select(MapToDto).ToArray();

        // Populate cache
        await _cache.SetAsync(cacheKey, projectDtos, TimeSpan.FromMinutes(5), cancellationToken);

        return CreateResponse(projectDtos);
    }
}
```

### Cache Key Format

```
mcp:{tenantId}:{resourceUri}[:{params_hash}]

Examples:
- mcp:00000000-0000-0000-0000-000000000001:projects.list
- mcp:00000000-0000-0000-0000-000000000001:issues.search:abc123
- mcp:00000000-0000-0000-0000-000000000001:sprints.current
```

### Cache Invalidation (Domain Events)

```csharp
public class ProjectUpdatedEventHandler : INotificationHandler<ProjectUpdatedEvent>
{
    private readonly IMcpCacheService _cache;

    public async Task Handle(ProjectUpdatedEvent e, CancellationToken ct)
    {
        // Invalidate projects.list for this tenant
        await _cache.RemoveAsync($"mcp:{e.TenantId}:projects.list", ct);

        // Invalidate specific project
        await _cache.RemoveAsync($"mcp:{e.TenantId}:projects.get/{e.ProjectId}", ct);
    }
}
```

## Tasks

### Task 1: Redis Client Setup (2 hours)
- [ ] Add StackExchange.Redis NuGet package
- [ ] Configure connection string in `appsettings.json`
- [ ] Create `RedisMcpCacheService` implementation
- [ ] Add health checks

**Files to Create**:
- `ColaFlow.Modules.Mcp/Services/RedisMcpCacheService.cs`

### Task 2: Cache Service Interface (2 hours)
- [ ] Create `IMcpCacheService` interface
- [ ] Implement Get/Set/Remove methods
- [ ] Add logging and metrics

**Files to Create**:
- `ColaFlow.Modules.Mcp/Contracts/IMcpCacheService.cs`

### Task 3: Update Resources with Caching (6 hours)
- [ ] Update `ProjectsListResource` to use cache
- [ ] Update `UsersListResource` to use cache
- [ ] Update `SprintsCurrentResource` to use cache
- [ ] Update `IssuesSearchResource` to use cache (with query hash)

### Task 4: Cache Invalidation Event Handlers (3 hours)
- [ ] ProjectCreated/Updated/Deleted → invalidate projects cache
- [ ] EpicCreated/Updated/Deleted → invalidate issues cache
- [ ] StoryCreated/Updated/Deleted → invalidate issues cache
- [ ] TaskCreated/Updated/Deleted → invalidate issues cache

**Files to Create**:
- `ColaFlow.Modules.Mcp/EventHandlers/CacheInvalidationEventHandlers.cs`

### Task 5: Performance Metrics (2 hours)
- [ ] Track cache hit/miss rates
- [ ] Track response time improvement
- [ ] Log metrics to structured logs

### Task 6: Unit & Integration Tests (4 hours)
- [ ] Test cache hit scenario
- [ ] Test cache miss scenario
- [ ] Test cache invalidation
- [ ] Performance benchmarks (with/without cache)

**Files to Create**:
- `ColaFlow.Modules.Mcp.Tests/Services/RedisMcpCacheServiceTests.cs`
- `ColaFlow.Modules.Mcp.Tests/Integration/CachingIntegrationTests.cs`

## Testing Strategy

### Performance Benchmarks
```
Scenario: projects.list (100 projects)
- Without cache: 180ms (database query)
- With cache: 60ms (67% improvement)
- Cache hit rate: 85%
```

### Integration Tests
- Test cache hit after first request
- Test cache miss on first request
- Test cache invalidation on update
- Test TTL expiration

## Dependencies

**Prerequisites**:
- Story 5.5 (Core Resources) - Resources to cache
- Redis server running (Docker or cloud)

**Optional**: Not blocking for M2 MVP

## Risks & Mitigation

| Risk | Impact | Probability | Mitigation |
|------|--------|-------------|------------|
| Redis unavailable | Medium | Low | Fallback to database, circuit breaker |
| Cache inconsistency | Medium | Medium | Short TTL, event-driven invalidation |
| Memory usage | Low | Low | Set max memory limit in Redis |

## Definition of Done

- [ ] Redis integration working
- [ ] Cache hit rate > 80%
- [ ] Response time improved by 30%+
- [ ] Cache invalidation working
- [ ] All tests passing
- [ ] Performance benchmarks documented

## Notes

### Why This Story Matters
- **Performance**: 30-50% faster response times
- **Scalability**: Reduce database load by 80%
- **Cost**: Lower database resource usage
- **User Experience**: Faster AI responses

### Redis Configuration

```json
{
  "Redis": {
    "ConnectionString": "localhost:6379",
    "InstanceName": "colaflow:",
    "DefaultTTL": 300
  }
}
```
