Skip to main content

Redis Migration Guide

Overview

This guide provides step-by-step instructions for migrating existing services to use the new Redis service implementation. The migration process is designed to be incremental and safe, allowing for gradual adoption of the new service while maintaining system stability.

Migration Steps

  1. Update Dependencies
    pnpm add ioredis
    
  2. Configure Environment Variables
    # .env
    REDIS_URL=redis://localhost:6379
    REDIS_KEY_PREFIX="gradiant:"
    REDIS_MAX_RETRIES=3
    REDIS_RETRY_DELAY=1000
    REDIS_CONNECT_TIMEOUT=5000
    REDIS_MAX_CONNECTIONS=10
    REDIS_MIN_CONNECTIONS=2
    REDIS_HEALTH_CHECK_INTERVAL=30000
    
  3. Initialize the Redis Service
    import { RedisService } from '@/lib/services/redis'
    
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: process.env.REDIS_KEY_PREFIX,
    })
    
    await redis.connect()
    

Service-Specific Migration Guides

Cache Invalidation Service

1
  • Update Import Statements
    // Before
    import { createClient } from 'redis'
    
  • 2
    // After import from ’@/lib/services/redis’
    3
    
    2. Update Service Constructor
    ```typescript
    // Before
    export class CacheInvalidationService {
      private readonly redis: ReturnType<typeof createClient>
    
      constructor() {
        this.redis = createClient()
      }
    }
    
    // After
    export class CacheInvalidationService {
      constructor(private readonly redis: RedisService) {}
    }
    
    4
  • Update Cache Operations
    // Before
    async invalidateKey(key: string): Promise<void> {
      await this.redis.del(key)
    }
    
    // After
    async invalidateKey(key: string): Promise<void> {
      try {
        await this.redis.del(key)
      } catch (error) {
        if (error instanceof RedisServiceError) {
          logger.error('Cache invalidation failed:', error)
          throw error
        }
      }
    }
    
  • Update Service Registration
    // Before
    const cacheInvalidation = new CacheInvalidationService()
    
    // After
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: 'cache:',
    })
    const cacheInvalidation = new CacheInvalidationService(redis)
    
  • Analytics Service

    1
  • Update Import Statements
    // Before
    import Redis from 'ioredis'
    
  • 2
    // After import from ’@/lib/services/redis’
    3
    
    2. Update Service Constructor
    ```typescript
    // Before
    export class AnalyticsService {
      private readonly redis: Redis
    
      constructor() {
        this.redis = new Redis()
      }
    }
    
    // After
    export class AnalyticsService {
      constructor(private readonly redis: RedisService) {}
    }
    
    4
  • Update Event Tracking
    // Before
    async trackEvent(event: AnalyticsEvent): Promise<void> {
      await this.redis.lpush('events', JSON.stringify(event))
    }
    
    // After
    async trackEvent(event: AnalyticsEvent): Promise<void> {
      try {
        const key = `events:${event.type}:${Date.now()}`
        await this.redis.set(key, JSON.stringify(event), 86400000) // 24h TTL
      } catch (error) {
        if (error instanceof RedisServiceError) {
          logger.error('Event tracking failed:', error)
          throw error
        }
      }
    }
    
  • Update Service Registration
    // Before
    const analytics = new AnalyticsService()
    
    // After
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: 'analytics:',
    })
    const analytics = new AnalyticsService(redis)
    
  • Pattern Recognition Service

    1
  • Update Import Statements
    // Before
    import { RedisClient } from 'redis'
    
  • 2
    // After import from ’@/lib/services/redis’
    3
    
    2. Update Service Constructor
    ```typescript
    // Before
    export class PatternRecognitionService {
      private readonly redis: RedisClient
    
      constructor() {
        this.redis = new RedisClient()
      }
    }
    
    // After
    export class PatternRecognitionService {
      constructor(private readonly redis: RedisService) {}
    }
    
    4
  • Update Pattern Storage
    // Before
    async storePattern(pattern: Pattern): Promise<void> {
      await this.redis.set(
        `pattern:${pattern.id}`,
        JSON.stringify(pattern)
      )
    }
    
    // After
    async storePattern(pattern: Pattern): Promise<void> {
      try {
        await this.redis.set(
          `pattern:${pattern.id}`,
          JSON.stringify(pattern),
          3600000 // 1h TTL
        )
      } catch (error) {
        if (error instanceof RedisServiceError) {
          logger.error('Pattern storage failed:', error)
          throw error
        }
      }
    }
    
  • Update Service Registration
    // Before
    const patternRecognition = new PatternRecognitionService()
    
    // After
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: 'patterns:',
    })
    const patternRecognition = new PatternRecognitionService(redis)
    
  • Notification Service

    1
  • Update Import Statements
    // Before
    import Redis from 'ioredis'
    
  • 2
    // After import from ’@/lib/services/redis’
    3
    
    2. Update Service Constructor
    ```typescript
    // Before
    export class NotificationService {
      private readonly redis: Redis
    
      constructor() {
        this.redis = new Redis()
      }
    }
    
    // After
    export class NotificationService {
      constructor(private readonly redis: RedisService) {}
    }
    
    4
  • Update Notification Storage
    // Before
    async storeNotification(notification: Notification): Promise<void> {
      await this.redis.set(
        `notification:${notification.id}`,
        JSON.stringify(notification)
      )
    }
    
    // After
    async storeNotification(notification: Notification): Promise<void> {
      try {
        await this.redis.set(
          `notification:${notification.id}`,
          JSON.stringify(notification),
          3600000 // 1h TTL
        )
      } catch (error) {
        if (error instanceof RedisServiceError) {
          logger.error('Notification storage failed:', error)
          throw error
        }
      }
    }
    
  • Update Service Registration
    // Before
    const notification = new NotificationService()
    
    // After
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: 'notifications:',
    })
    const notification = new NotificationService(redis)
    
  • Advanced Migration Patterns

    1
  • Update Import Statements
    // Before
    import Redis from 'ioredis'
    
  • 2
    // After import from ’@/lib/services/redis’
    3
    
    2. Update Server Constructor
    ```typescript
    // Before
    export class WebSocketServer {
      private readonly redis: Redis
    
      constructor() {
        this.redis = new Redis()
      }
    }
    
    // After
    export class WebSocketServer {
      constructor(private readonly redis: RedisService) {}
    }
    
    4
  • Update Session Management
    // Before
    async storeSession(sessionId: string, data: SessionData): Promise<void> {
      await this.redis.set(
        `session:${sessionId}`,
        JSON.stringify(data)
      )
    }
    
    // After
    async storeSession(sessionId: string, data: SessionData): Promise<void> {
      try {
        await this.redis.set(
          `session:${sessionId}`,
          JSON.stringify(data),
          1800000 // 30m TTL
        )
      } catch (error) {
        if (error instanceof RedisServiceError) {
          logger.error('Session storage failed:', error)
          throw error
        }
      }
    }
    
  • Update Server Initialization
    // Before
    const wsServer = new WebSocketServer()
    
    // After
    const redis = new RedisService({
      url: process.env.REDIS_URL,
      keyPrefix: 'ws:',
    })
    const wsServer = new WebSocketServer(redis)
    
  • Testing the Migration

    Unit Tests

    describe('Service Migration', () => {
      let redis: RedisService
    
      beforeEach(() => {
        redis = new RedisService({
          url: 'redis://localhost:6379',
          keyPrefix: 'test:',
        })
      })
    
      afterEach(async () => {
        await redis.disconnect()
      })
    
      describe('CacheInvalidationService', () => {
        it('should work with new Redis service', async () => {
          const service = new CacheInvalidationService(redis)
          await service.invalidateKey('test-key')
          expect(await redis.exists('test-key')).toBe(false)
        })
      })
    
      describe('AnalyticsService', () => {
        it('should track events with new Redis service', async () => {
          const service = new AnalyticsService(redis)
          await service.trackEvent({ type: 'test', data: {} })
          // Verify event storage
        })
      })
    })
    

    Integration Tests

    describe('Service Integration', () => {
      let redis: RedisService
      let analytics: AnalyticsService
      let patternRecognition: PatternRecognitionService
    
      beforeAll(async () => {
        redis = new RedisService({
          url: process.env.REDIS_URL,
          keyPrefix: 'integration:',
        })
        await redis.connect()
    
        analytics = new AnalyticsService(redis)
        patternRecognition = new PatternRecognitionService(redis)
      })
    
      afterAll(async () => {
        await redis.disconnect()
      })
    
      it('should work together with shared Redis service', async () => {
        // Test interaction between services
        await analytics.trackEvent({ type: 'pattern_detected', data: {} })
        await patternRecognition.processLatestEvents()
        // Verify results
      })
    })
    

    Rollback Procedures

    If issues are encountered during migration:
    1. Keep Old Implementation
      class CacheInvalidationService {
        private readonly redis: RedisService | ReturnType<typeof createClient>
      
        constructor(redis?: RedisService) {
          this.redis = redis || createClient()
        }
      
        async invalidateKey(key: string): Promise<void> {
          if (this.redis instanceof RedisService) {
            // New implementation
            await this.redis.del(key)
          } else {
            // Old implementation
            await this.redis.del(key)
          }
        }
      }
      
    2. Feature Flags
      const USE_NEW_REDIS = process.env.USE_NEW_REDIS === 'true'
      
      const redis = USE_NEW_REDIS ? new RedisService(config) : createClient()
      
    3. Gradual Rollout
      const shouldUseNewRedis = (userId: string): boolean => {
        const percentage = process.env.NEW_REDIS_ROLLOUT_PERCENTAGE || '0'
        const hash = createHash('md5').update(userId).digest('hex')
        const userPercentile = (parseInt(hash.substring(0, 2), 16) / 255) * 100
        return userPercentile <= parseInt(percentage, 10)
      }
      

    Best Practices

    1. Dependency Injection
      • Pass Redis service instance to other services
      • Use interface for better testing
      • Configure service per use case
    2. Error Handling
      • Use RedisServiceError for type safety
      • Implement proper logging
      • Add retry logic where appropriate
    3. Performance
      • Use appropriate TTLs
      • Implement caching strategies
      • Monitor connection pool
    4. Testing
      • Write comprehensive tests
      • Use test containers
      • Implement proper cleanup

    Monitoring Migration

    1. Error Tracking
      const errorMetrics = new RedisErrorMetrics(redis)
      
    2. Performance Monitoring
      const stats = await redis.getPoolStats()
      
    3. Usage Metrics
      logger.info('Redis operation completed', {
        operation: 'set',
        duration: Date.now() - start,
        service: 'CacheInvalidation',
      })