# Redis Cache Implementation

High-performance Redis caching layer for Mawidi with 90%+ hit rate target.

## Overview

This implementation provides a comprehensive caching strategy for frequently accessed data:

- **User Profiles**: 5-minute TTL
- **Pricing Tiers**: 1-hour TTL
- **Settings**: 15-minute TTL
- **Automatic Invalidation**: On data updates
- **Hit Rate Tracking**: Real-time metrics

## Architecture

```
┌─────────────────┐
│   API Request   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Cache Layer    │ ◄─── redis-cache.ts
│  (Redis)        │
└────────┬────────┘
         │
         │ Cache Miss
         ▼
┌─────────────────┐
│   Database      │
│  (PostgreSQL)   │
└─────────────────┘
```

## Files

- `lib/cache/redis-cache.ts` - Main implementation
- `lib/cache/examples.ts` - Usage examples
- `app/api/cache/metrics/route.ts` - Performance monitoring endpoint
- `__tests__/lib/redis-cache.test.ts` - Comprehensive tests

## Usage Examples

### 1. User Profile Caching

```typescript
import { getUserProfile, invalidateUserProfile } from '@/lib/cache/redis-cache';

// Fetch user (cached for 5 minutes)
const user = await getUserProfile('user-123');

// After updating user
await prisma.users.update({ where: { id: 'user-123' }, data: { ... } });
await invalidateUserProfile('user-123', user.email);
```

### 2. Pricing Tiers Caching

```typescript
import {
  getPricingTiers,
  invalidatePricingTiers,
} from "@/lib/cache/redis-cache";

// Fetch all tiers (cached for 1 hour)
const tiers = await getPricingTiers();

// After updating Stripe products
await invalidatePricingTiers();
```

### 3. Settings Caching

```typescript
import { getSettings, invalidateSettings } from "@/lib/cache/redis-cache";

// Fetch settings (cached for 15 minutes)
const appSettings = await getSettings("app");
const maxUploadSize = await getSetting<number>("app", "maxUploadSize");

// After updating settings
await invalidateSettings("app");
```

## Cache Keys

All cache keys follow a namespace pattern:

- `user:profile:{userId}` - User by ID
- `user:profile:email:{email}` - User by email
- `pricing:tiers:all` - All pricing tiers
- `pricing:tiers:{tierId}` - Specific tier
- `settings:{namespace}` - Settings by namespace

## TTL Strategy

| Data Type     | TTL    | Reason                             |
| ------------- | ------ | ---------------------------------- |
| User Profiles | 5 min  | Balance freshness with performance |
| Pricing Tiers | 1 hour | Rarely changes, expensive to fetch |
| Settings      | 15 min | Moderate change frequency          |

## Invalidation Strategy

### Automatic Invalidation

Cache is invalidated automatically on write operations:

```typescript
// Example: User update endpoint
export async function PATCH(req: Request, { params }) {
  const data = await req.json();

  // Update database
  const user = await prisma.users.update({
    where: { id: params.id },
    data,
  });

  // Invalidate cache (automatic)
  await invalidateUserProfile(params.id, user.email);

  return Response.json(user);
}
```

### Manual Invalidation

For bulk operations or admin actions:

```typescript
import { clearAllCache } from "@/lib/cache/redis-cache";

// Clear all caches (use with caution!)
await clearAllCache();
```

## Performance Monitoring

### API Endpoint

```bash
GET /api/cache/metrics
```

**Response:**

```json
{
  "overall": {
    "total": 1000,
    "hits": 920,
    "misses": 80,
    "hitRate": "92.00%",
    "hitRatio": 0.92
  },
  "namespaces": {
    "userProfile": {
      "hits": 450,
      "misses": 30,
      "hitRate": "93.75%",
      "lastHitAt": "2024-01-15T10:30:00.000Z"
    },
    "pricingTiers": {
      "hits": 380,
      "misses": 20,
      "hitRate": "95.00%"
    },
    "settings": {
      "hits": 90,
      "misses": 30,
      "hitRate": "75.00%"
    }
  },
  "performance": {
    "meetsTarget": true,
    "target": "90%",
    "status": "good"
  }
}
```

### Programmatic Access

```typescript
import { getCacheHitRate } from "@/lib/cache/redis-cache";

const metrics = getCacheHitRate();
console.log(`Hit rate: ${(metrics.hitRatio * 100).toFixed(2)}%`);
```

## Graceful Degradation

The cache layer automatically falls back to direct database queries on Redis failures:

```typescript
// This works even if Redis is down
const user = await getUserProfile("user-123");
// ✓ Returns from cache if available
// ✓ Falls back to database on cache miss or error
```

## Testing

Run tests to verify >90% hit rate:

```bash
npm test __tests__/lib/redis-cache.test.ts
```

### Test Coverage

- ✅ User profile caching with 5min TTL
- ✅ Pricing tiers caching with 1hr TTL
- ✅ Settings caching with 15min TTL
- ✅ Cache invalidation on writes
- ✅ >90% hit rate achievement
- ✅ Graceful degradation on failures
- ✅ Email normalization (lowercase)
- ✅ Realistic workflow simulation

## Hit Rate Optimization

### Strategies

1. **Cache Warming**: Pre-populate frequently accessed data

   ```typescript
   // On application startup
   await Promise.all([
     getUserProfile("common-user-1"),
     getUserProfile("common-user-2"),
     getPricingTiers(),
     getSettings("app"),
   ]);
   ```

2. **Long TTL for Static Data**: Pricing tiers change rarely (1hr TTL)

3. **Intelligent Invalidation**: Only invalidate affected keys

   ```typescript
   // Only invalidate specific user, not all users
   await invalidateUserProfile(userId, email);
   ```

4. **Namespace Isolation**: Separate keys prevent cascade invalidation

### Expected Hit Rates

| Namespace     | Target   | Typical    |
| ------------- | -------- | ---------- |
| User Profiles | >90%     | 93-95%     |
| Pricing Tiers | >95%     | 97-99%     |
| Settings      | >85%     | 88-92%     |
| **Overall**   | **>90%** | **92-95%** |

## Redis Configuration

### Development (Docker)

```env
REDIS_URL=redis://localhost:6379
```

### Production (Upstash)

```env
UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token
```

## Best Practices

1. **Always invalidate on writes**

   ```typescript
   await prisma.users.update({ ... });
   await invalidateUserProfile(userId, email); // Don't forget!
   ```

2. **Use appropriate TTLs**
   - Frequently changing data: Short TTL (5-15 min)
   - Rarely changing data: Long TTL (1-24 hours)

3. **Monitor hit rates**

   ```typescript
   const metrics = getCacheHitRate();
   if (metrics.hitRatio < 0.9) {
     logger.warn("Cache hit rate below target", { metrics });
   }
   ```

4. **Handle cache failures gracefully**
   - The library does this automatically
   - No special error handling needed

5. **Test invalidation logic**
   ```typescript
   // Always test that cache is invalidated after updates
   await updateUser(userId);
   const cachedUser = await getUserProfile(userId); // Should be fresh
   ```

## Migration Guide

### Before (Direct Database)

```typescript
const user = await prisma.users.findUnique({
  where: { id: userId },
});
```

### After (With Cache)

```typescript
import { getUserProfile } from "@/lib/cache/redis-cache";

const user = await getUserProfile(userId);
```

### Update Operations

```typescript
// Update user
const updatedUser = await prisma.users.update({
  where: { id: userId },
  data: { name: "New Name" },
});

// Invalidate cache
await invalidateUserProfile(userId, updatedUser.email);
```

## Troubleshooting

### Low Hit Rate (<90%)

1. **Check TTL settings**: May be too short
2. **Verify invalidation**: Excessive invalidation reduces hit rate
3. **Monitor access patterns**: Highly varied access reduces effectiveness

### Cache Not Working

1. **Verify Redis connection**:

   ```bash
   # Check Redis is running
   docker ps | grep redis

   # Test connection
   redis-cli ping
   ```

2. **Check environment variables**:

   ```bash
   echo $REDIS_URL
   echo $UPSTASH_REDIS_REST_URL
   ```

3. **Review logs**:
   ```typescript
   // Enable debug logging
   logger.debug("[Redis Cache] User profile cache miss", { userId });
   ```

## Performance Impact

### Before Caching

- Database query: 50-200ms per request
- Pricing fetch: 500-1000ms (Stripe API)

### After Caching (90%+ hit rate)

- Cache hit: 5-15ms
- Cache miss: 50-200ms (first request only)
- **Average latency reduction: 85-95%**

### Load Reduction

With 90% hit rate on 10,000 requests/hour:

- Database queries: 10,000 → 1,000 (90% reduction)
- Stripe API calls: Reduced by ~95%

## Future Enhancements

1. **Cache warming on deployment**
2. **Predictive cache refreshing** (refresh before TTL expires)
3. **Cache size monitoring** (memory usage tracking)
4. **Multi-tier caching** (L1: Memory, L2: Redis)
5. **Distributed cache invalidation** (pub/sub)

## Support

For issues or questions:

1. Check logs: `lib/logger.ts`
2. Review metrics: `GET /api/cache/metrics`
3. Run tests: `npm test __tests__/lib/redis-cache.test.ts`
