Production Deployment and Best Practices

Learning Objectives: By the end of this lesson, you will be able to:

  • Deploy Redis in production environments with high availability and security
  • Configure Redis for optimal performance and reliability
  • Implement monitoring, alerting, and maintenance procedures
  • Design Redis architectures that scale to millions of operations per second
  • Handle Redis upgrades, backups, and disaster recovery scenarios

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

You’ve learned to build amazing applications with Redis, but now comes the real challenge: running them in production where millions of users depend on your system being fast, secure, and always available. Today we’ll learn how companies like Netflix, Twitter, and GitHub deploy Redis at massive scale!

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Production Architecture - High availability, clustering, and security design
  2. Configuration Tuning - Memory management, persistence, and performance optimization
  3. Monitoring & Alerting - Key metrics, dashboards, and automated responses
  4. Backup & Recovery - Data protection and disaster recovery strategies
  5. Security Hardening - Authentication, encryption, and network security
  6. Scaling Strategies - Horizontal scaling with Redis Cluster and Sentinel

3. Foundation Knowledge (15-20 minutes)

Production Requirements

The Five Pillars of Production Redis

  1. Availability - System uptime and failover capabilities
  2. Performance - Consistent low latency under load
  3. Security - Data protection and access control
  4. Scalability - Ability to handle growth
  5. Observability - Monitoring and troubleshooting capabilities

Production Architecture Patterns

1. Master-Replica with Sentinel (High Availability)

[App Servers] --> [Redis Sentinel] --> [Redis Master]
                        |                    |
                        |               [Redis Replica 1]
                        |               [Redis Replica 2]
                   [Health Checks]   [Automatic Failover]

2. Redis Cluster (Horizontal Scaling)

[App Servers] --> [Redis Cluster]
                        |
         [Master 1]  [Master 2]  [Master 3]
             |           |           |
        [Replica 1] [Replica 2] [Replica 3]

4. Production Configuration (25-30 minutes)

Redis Configuration File (redis.conf)

# === PRODUCTION REDIS CONFIGURATION ===

# Basic settings
bind 127.0.0.1 10.0.1.100  # Bind to specific interfaces
port 6379
protected-mode yes
requirepass your_secure_password_here

# Memory management
maxmemory 8gb
maxmemory-policy allkeys-lru
maxmemory-samples 10

# Persistence configuration
save 900 1      # Save if at least 1 key changed in 900 seconds
save 300 10     # Save if at least 10 keys changed in 300 seconds  
save 60 10000   # Save if at least 10000 keys changed in 60 seconds

# AOF configuration for durability
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# Replication settings
replica-read-only yes
replica-serve-stale-data yes
repl-diskless-sync no
repl-diskless-sync-delay 5

# Security settings
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command DEBUG ""
rename-command SHUTDOWN SHUTDOWN_8f2a9d3e

# Network and connection settings
tcp-keepalive 300
timeout 0
tcp-backlog 511
maxclients 10000

# Logging
loglevel notice
logfile /var/log/redis/redis-server.log
syslog-enabled yes
syslog-ident redis

# Slow log
slowlog-log-slower-than 10000  # 10ms
slowlog-max-len 128

# Latency monitoring
latency-monitor-threshold 100  # 100ms

Go Production Client Configuration

package main

import (
    "context"
    "crypto/tls"
    "time"

    "github.com/redis/go-redis/v9"
)

type ProductionRedisConfig struct {
    Addresses    []string
    Password     string
    DB           int
    PoolSize     int
    MinIdleConns int
    MaxRetries   int
    EnableTLS    bool
}

func NewProductionRedisClient(config ProductionRedisConfig) *redis.Client {
    options := &redis.Options{
        Addr:         config.Addresses[0], // Primary address
        Password:     config.Password,
        DB:           config.DB,
        PoolSize:     config.PoolSize,
        MinIdleConns: config.MinIdleConns,
        MaxRetries:   config.MaxRetries,
        
        // Timeouts
        DialTimeout:  5 * time.Second,
        ReadTimeout:  3 * time.Second,
        WriteTimeout: 3 * time.Second,
        IdleTimeout:  5 * time.Minute,
        
        // Connection pool settings
        PoolTimeout:      4 * time.Second,
        IdleCheckFrequency: time.Minute,
        MaxConnAge:       30 * time.Minute,
    }

    // TLS configuration for production
    if config.EnableTLS {
        options.TLSConfig = &tls.Config{
            MinVersion: tls.VersionTLS12,
            ServerName: "redis.yourdomain.com",
        }
    }

    return redis.NewClient(options)
}

// Redis Cluster client for horizontal scaling
func NewProductionRedisCluster(config ProductionRedisConfig) *redis.ClusterClient {
    return redis.NewClusterClient(&redis.ClusterOptions{
        Addrs:        config.Addresses,
        Password:     config.Password,
        PoolSize:     config.PoolSize,
        MinIdleConns: config.MinIdleConns,
        MaxRetries:   config.MaxRetries,
        
        // Cluster-specific settings
        MaxRedirects:   8,
        ReadOnly:       false,
        RouteByLatency: true,
        RouteRandomly:  false,
        
        // Timeouts
        DialTimeout:  5 * time.Second,
        ReadTimeout:  3 * time.Second,
        WriteTimeout: 3 * time.Second,
        PoolTimeout:  4 * time.Second,
        IdleTimeout:  5 * time.Minute,
    })
}

// Connection health checking
type HealthChecker struct {
    client *redis.Client
}

func (hc *HealthChecker) CheckHealth(ctx context.Context) error {
    // Basic ping test
    if err := hc.client.Ping(ctx).Err(); err != nil {
        return fmt.Errorf("ping failed: %w", err)
    }

    // Memory usage check
    info, err := hc.client.Info(ctx, "memory").Result()
    if err != nil {
        return fmt.Errorf("info command failed: %w", err)
    }

    // Parse memory usage (simplified)
    if strings.Contains(info, "maxmemory_policy:noeviction") {
        memoryUsage, _ := hc.client.Info(ctx, "memory").Result()
        if strings.Contains(memoryUsage, "used_memory_rss:") {
            // Add memory usage validation logic
        }
    }

    return nil
}

func main() {
    config := ProductionRedisConfig{
        Addresses:    []string{"redis-master:6379", "redis-replica1:6379", "redis-replica2:6379"},
        Password:     "your_production_password",
        DB:           0,
        PoolSize:     20,
        MinIdleConns: 10,
        MaxRetries:   3,
        EnableTLS:    true,
    }

    client := NewProductionRedisClient(config)
    defer client.Close()

    // Health checking
    healthChecker := &HealthChecker{client: client}
    
    ctx := context.Background()
    if err := healthChecker.CheckHealth(ctx); err != nil {
        log.Fatalf("Redis health check failed: %v", err)
    }

    log.Println("Redis client configured and healthy")
}

5. Monitoring and Alerting (20-25 minutes)

Essential Redis Metrics

package monitoring

import (
    "context"
    "log"
    "strconv"
    "strings"
    "time"

    "github.com/redis/go-redis/v9"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

// Prometheus metrics
var (
    redisConnectedClients = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "redis_connected_clients",
        Help: "Number of connected clients",
    })

    redisUsedMemory = promauto.NewGauge(prometheus.GaugeOpts{
        Name: "redis_used_memory_bytes",
        Help: "Used memory in bytes",
    })

    redisOperations = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "redis_operations_total",
        Help: "Total number of Redis operations",
    }, []string{"operation"})

    redisLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
        Name:    "redis_operation_duration_seconds",
        Help:    "Redis operation latency",
        Buckets: prometheus.DefBuckets,
    }, []string{"operation"})

    redisErrors = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "redis_errors_total",
        Help: "Total number of Redis errors",
    }, []string{"type"})
)

type RedisMonitor struct {
    client   *redis.Client
    ctx      context.Context
    interval time.Duration
}

func NewRedisMonitor(client *redis.Client) *RedisMonitor {
    return &RedisMonitor{
        client:   client,
        ctx:      context.Background(),
        interval: 30 * time.Second,
    }
}

func (rm *RedisMonitor) Start() {
    ticker := time.NewTicker(rm.interval)
    go func() {
        for range ticker.C {
            rm.collectMetrics()
        }
    }()
}

func (rm *RedisMonitor) collectMetrics() {
    info, err := rm.client.Info(rm.ctx).Result()
    if err != nil {
        redisErrors.WithLabelValues("info_command").Inc()
        log.Printf("Failed to get Redis info: %v", err)
        return
    }

    rm.parseAndSetMetrics(info)
}

func (rm *RedisMonitor) parseAndSetMetrics(info string) {
    lines := strings.Split(info, "\r\n")
    
    for _, line := range lines {
        if strings.Contains(line, ":") {
            parts := strings.Split(line, ":")
            if len(parts) != 2 {
                continue
            }
            
            key, value := parts[0], parts[1]
            
            switch key {
            case "connected_clients":
                if val, err := strconv.ParseFloat(value, 64); err == nil {
                    redisConnectedClients.Set(val)
                }
            case "used_memory":
                if val, err := strconv.ParseFloat(value, 64); err == nil {
                    redisUsedMemory.Set(val)
                }
            case "total_commands_processed":
                if val, err := strconv.ParseFloat(value, 64); err == nil {
                    redisOperations.WithLabelValues("total").Add(val)
                }
            }
        }
    }
}

// Custom Redis client with metrics
type MonitoredRedisClient struct {
    *redis.Client
}

func (mrc *MonitoredRedisClient) Get(ctx context.Context, key string) *redis.StringCmd {
    start := time.Now()
    result := mrc.Client.Get(ctx, key)
    
    duration := time.Since(start).Seconds()
    redisLatency.WithLabelValues("get").Observe(duration)
    
    if result.Err() != nil {
        redisErrors.WithLabelValues("get").Inc()
    } else {
        redisOperations.WithLabelValues("get").Inc()
    }
    
    return result
}

func (mrc *MonitoredRedisClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd {
    start := time.Now()
    result := mrc.Client.Set(ctx, key, value, expiration)
    
    duration := time.Since(start).Seconds()
    redisLatency.WithLabelValues("set").Observe(duration)
    
    if result.Err() != nil {
        redisErrors.WithLabelValues("set").Inc()
    } else {
        redisOperations.WithLabelValues("set").Inc()
    }
    
    return result
}

Alerting Rules (Prometheus/AlertManager)

# redis-alerts.yml
groups:
- name: redis
  rules:
  - alert: RedisDown
    expr: up{job="redis"} == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Redis instance is down"
      description: "Redis instance {{ $labels.instance }} has been down for more than 1 minute"

  - alert: RedisHighMemoryUsage
    expr: redis_used_memory_bytes / redis_maxmemory_bytes > 0.9
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Redis memory usage is high"
      description: "Redis instance {{ $labels.instance }} memory usage is above 90%"

  - alert: RedisHighLatency
    expr: rate(redis_operation_duration_seconds_sum[5m]) / rate(redis_operation_duration_seconds_count[5m]) > 0.1
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "Redis high latency detected"
      description: "Redis instance {{ $labels.instance }} average latency is above 100ms"

  - alert: RedisConnectionLimitReached
    expr: redis_connected_clients / redis_maxclients > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Redis connection limit nearly reached"
      description: "Redis instance {{ $labels.instance }} is using more than 80% of available connections"

  - alert: RedisSlowlogGrowing
    expr: increase(redis_slowlog_length[5m]) > 10
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "Redis slowlog is growing"
      description: "Redis instance {{ $labels.instance }} slowlog has grown by more than 10 entries in 5 minutes"

6. Backup and Recovery (15 minutes)

Automated Backup Strategy

#!/bin/bash
# redis-backup.sh - Production Redis backup script

set -euo pipefail

# Configuration
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD="your_password"
BACKUP_DIR="/backups/redis"
RETENTION_DAYS=30
S3_BUCKET="your-backup-bucket"

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Generate backup filename with timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="redis_backup_${TIMESTAMP}.rdb"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}"

# Function to log with timestamp
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# Function to cleanup old backups
cleanup_old_backups() {
    find "$BACKUP_DIR" -name "redis_backup_*.rdb" -mtime +$RETENTION_DAYS -delete
    log "Cleaned up backups older than $RETENTION_DAYS days"
}

# Function to create backup
create_backup() {
    log "Starting Redis backup..."
    
    # Use redis-cli to create backup
    redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" \
        --rdb "$BACKUP_PATH" --no-auth-warning
    
    if [ $? -eq 0 ]; then
        log "Backup created successfully: $BACKUP_PATH"
        
        # Compress backup
        gzip "$BACKUP_PATH"
        BACKUP_PATH="${BACKUP_PATH}.gz"
        log "Backup compressed: $BACKUP_PATH"
        
        # Upload to S3
        if command -v aws &> /dev/null; then
            aws s3 cp "$BACKUP_PATH" "s3://${S3_BUCKET}/redis-backups/"
            log "Backup uploaded to S3: s3://${S3_BUCKET}/redis-backups/$(basename $BACKUP_PATH)"
        fi
        
        # Verify backup integrity
        verify_backup "$BACKUP_PATH"
        
    else
        log "ERROR: Backup failed"
        exit 1
    fi
}

# Function to verify backup
verify_backup() {
    local backup_file="$1"
    local file_size=$(stat -f%z "$backup_file" 2>/dev/null || stat -c%s "$backup_file" 2>/dev/null)
    
    if [ "$file_size" -gt 0 ]; then
        log "Backup verification passed - Size: ${file_size} bytes"
    else
        log "ERROR: Backup verification failed - File is empty or missing"
        exit 1
    fi
}

# Main execution
main() {
    log "Starting Redis backup process"
    
    # Check Redis connectivity
    if ! redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -a "$REDIS_PASSWORD" ping --no-auth-warning > /dev/null; then
        log "ERROR: Cannot connect to Redis server"
        exit 1
    fi
    
    create_backup
    cleanup_old_backups
    
    log "Redis backup process completed successfully"
}

# Execute main function
main "$@"

Recovery Procedures

// Redis recovery utilities
package recovery

import (
    "bufio"
    "fmt"
    "os"
    "os/exec"
    "path/filepath"
    "time"
)

type RecoveryManager struct {
    RedisDataDir string
    BackupDir    string
    RedisConfig  string
}

func NewRecoveryManager(dataDir, backupDir, configFile string) *RecoveryManager {
    return &RecoveryManager{
        RedisDataDir: dataDir,
        BackupDir:    backupDir,
        RedisConfig:  configFile,
    }
}

func (rm *RecoveryManager) ListBackups() ([]string, error) {
    pattern := filepath.Join(rm.BackupDir, "redis_backup_*.rdb*")
    matches, err := filepath.Glob(pattern)
    if err != nil {
        return nil, fmt.Errorf("failed to list backups: %w", err)
    }
    return matches, nil
}

func (rm *RecoveryManager) RestoreFromBackup(backupFile string) error {
    fmt.Printf("WARNING: This will replace current Redis data with backup from %s\n", backupFile)
    fmt.Print("Do you want to continue? (yes/no): ")
    
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Scan()
    if scanner.Text() != "yes" {
        return fmt.Errorf("restore cancelled by user")
    }

    // Stop Redis service
    if err := rm.stopRedisService(); err != nil {
        return fmt.Errorf("failed to stop Redis service: %w", err)
    }

    // Backup current data
    if err := rm.backupCurrentData(); err != nil {
        return fmt.Errorf("failed to backup current data: %w", err)
    }

    // Restore from backup
    if err := rm.restoreData(backupFile); err != nil {
        return fmt.Errorf("failed to restore data: %w", err)
    }

    // Start Redis service
    if err := rm.startRedisService(); err != nil {
        return fmt.Errorf("failed to start Redis service: %w", err)
    }

    fmt.Println("Recovery completed successfully")
    return nil
}

func (rm *RecoveryManager) stopRedisService() error {
    cmd := exec.Command("systemctl", "stop", "redis-server")
    return cmd.Run()
}

func (rm *RecoveryManager) startRedisService() error {
    cmd := exec.Command("systemctl", "start", "redis-server")
    return cmd.Run()
}

func (rm *RecoveryManager) backupCurrentData() error {
    timestamp := time.Now().Format("20060102_150405")
    backupName := fmt.Sprintf("pre_restore_backup_%s", timestamp)
    
    currentRDB := filepath.Join(rm.RedisDataDir, "dump.rdb")
    backupPath := filepath.Join(rm.BackupDir, backupName+".rdb")
    
    if _, err := os.Stat(currentRDB); err == nil {
        cmd := exec.Command("cp", currentRDB, backupPath)
        return cmd.Run()
    }
    
    return nil // No current data to backup
}

func (rm *RecoveryManager) restoreData(backupFile string) error {
    targetFile := filepath.Join(rm.RedisDataDir, "dump.rdb")
    
    // Handle compressed backups
    if filepath.Ext(backupFile) == ".gz" {
        cmd := exec.Command("gunzip", "-c", backupFile)
        output, err := cmd.Output()
        if err != nil {
            return err
        }
        
        return os.WriteFile(targetFile, output, 0644)
    }
    
    // Copy uncompressed backup
    cmd := exec.Command("cp", backupFile, targetFile)
    return cmd.Run()
}

// Point-in-time recovery for AOF
func (rm *RecoveryManager) RecoverToTimestamp(targetTime time.Time) error {
    aofFile := filepath.Join(rm.RedisDataDir, "appendonly.aof")
    
    // This is a simplified example - production implementation would be more complex
    fmt.Printf("Recovering to timestamp: %s\n", targetTime.Format(time.RFC3339))
    fmt.Printf("AOF file: %s\n", aofFile)
    
    // In practice, you would:
    // 1. Parse AOF file
    // 2. Find commands up to target timestamp
    // 3. Create new AOF with commands up to that point
    // 4. Restart Redis with the truncated AOF
    
    return fmt.Errorf("point-in-time recovery not implemented in this example")
}

7. Security Hardening (15 minutes)

Security Checklist

# Security configuration checklist

# 1. Network Security
# - Bind to specific interfaces only
bind 127.0.0.1 10.0.1.100

# - Use firewalls to restrict access
# iptables -A INPUT -p tcp --dport 6379 -s 10.0.1.0/24 -j ACCEPT
# iptables -A INPUT -p tcp --dport 6379 -j DROP

# 2. Authentication
requirepass your_very_secure_password_here
# Use ACL for fine-grained access control (Redis 6+)

# 3. Command Security
# Rename dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command DEBUG ""
rename-command CONFIG "CONFIG_a8b7c6d5e4f3"

# 4. TLS Encryption
port 0                    # Disable non-TLS port
tls-port 6380            # Enable TLS port
tls-cert-file /path/to/redis.crt
tls-key-file /path/to/redis.key
tls-ca-cert-file /path/to/ca.crt
tls-protocols "TLSv1.2 TLSv1.3"

Go TLS Client Configuration

package security

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"

    "github.com/redis/go-redis/v9"
)

func NewSecureRedisClient(addr, password, certFile, keyFile, caFile string) (*redis.Client, error) {
    // Load client certificate
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return nil, fmt.Errorf("failed to load client certificate: %w", err)
    }

    // Load CA certificate
    caCert, err := ioutil.ReadFile(caFile)
    if err != nil {
        return nil, fmt.Errorf("failed to load CA certificate: %w", err)
    }

    caCertPool := x509.NewCertPool()
    if !caCertPool.AppendCertsFromPEM(caCert) {
        return nil, fmt.Errorf("failed to parse CA certificate")
    }

    // Configure TLS
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
        MinVersion:   tls.VersionTLS12,
        ServerName:   "redis.yourdomain.com",
    }

    // Create Redis client with TLS
    client := redis.NewClient(&redis.Options{
        Addr:      addr,
        Password:  password,
        TLSConfig: tlsConfig,
    })

    return client, nil
}

// ACL management (Redis 6+)
type ACLManager struct {
    client *redis.Client
}

func NewACLManager(client *redis.Client) *ACLManager {
    return &ACLManager{client: client}
}

func (am *ACLManager) CreateUser(username, password string, commands []string, keys []string) error {
    // Build ACL command
    args := []interface{}{"ACL", "SETUSER", username, "on", ">" + password}
    
    // Add command permissions
    for _, cmd := range commands {
        args = append(args, "+"+cmd)
    }
    
    // Add key permissions
    for _, key := range keys {
        args = append(args, "~"+key)
    }

    return am.client.Do(context.Background(), args...).Err()
}

func (am *ACLManager) ListUsers() ([]string, error) {
    result, err := am.client.Do(context.Background(), "ACL", "LIST").Result()
    if err != nil {
        return nil, err
    }
    
    // Parse result (simplified)
    users := make([]string, 0)
    if userList, ok := result.([]interface{}); ok {
        for _, user := range userList {
            if userStr, ok := user.(string); ok {
                users = append(users, userStr)
            }
        }
    }
    
    return users, nil
}

func (am *ACLManager) DeleteUser(username string) error {
    return am.client.Do(context.Background(), "ACL", "DELUSER", username).Err()
}

8. Production Deployment Scripts (10 minutes)

Docker Compose for Production

# docker-compose.prod.yml
version: '3.8'

services:
  redis-master:
    image: redis:7-alpine
    container_name: redis-master
    restart: unless-stopped
    command: redis-server /etc/redis/redis.conf
    volumes:
      - ./redis-master.conf:/etc/redis/redis.conf:ro
      - redis-master-data:/data
      - ./logs:/var/log/redis
    ports:
      - "6379:6379"
    networks:
      - redis-network
    environment:
      - REDIS_REPLICATION_MODE=master
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  redis-replica1:
    image: redis:7-alpine
    container_name: redis-replica1
    restart: unless-stopped
    command: redis-server /etc/redis/redis.conf --replicaof redis-master 6379
    volumes:
      - ./redis-replica.conf:/etc/redis/redis.conf:ro
      - redis-replica1-data:/data
      - ./logs:/var/log/redis
    networks:
      - redis-network
    depends_on:
      - redis-master
    environment:
      - REDIS_REPLICATION_MODE=slave

  redis-replica2:
    image: redis:7-alpine
    container_name: redis-replica2
    restart: unless-stopped
    command: redis-server /etc/redis/redis.conf --replicaof redis-master 6379
    volumes:
      - ./redis-replica.conf:/etc/redis/redis.conf:ro
      - redis-replica2-data:/data
      - ./logs:/var/log/redis
    networks:
      - redis-network
    depends_on:
      - redis-master
    environment:
      - REDIS_REPLICATION_MODE=slave

  redis-sentinel1:
    image: redis:7-alpine
    container_name: redis-sentinel1
    restart: unless-stopped
    command: redis-sentinel /etc/redis/sentinel.conf
    volumes:
      - ./sentinel.conf:/etc/redis/sentinel.conf:ro
      - ./logs:/var/log/redis
    networks:
      - redis-network
    ports:
      - "26379:26379"
    depends_on:
      - redis-master

  redis-exporter:
    image: oliver006/redis_exporter:latest
    container_name: redis-exporter
    restart: unless-stopped
    environment:
      - REDIS_ADDR=redis://redis-master:6379
      - REDIS_PASSWORD=your_password
    ports:
      - "9121:9121"
    networks:
      - redis-network
    depends_on:
      - redis-master

volumes:
  redis-master-data:
    driver: local
  redis-replica1-data:
    driver: local
  redis-replica2-data:
    driver: local

networks:
  redis-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

Kubernetes Deployment

# redis-cluster.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: production
spec:
  serviceName: redis-cluster
  replicas: 6
  selector:
    matchLabels:
      app: redis-cluster
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
          name: client
        - containerPort: 16379
          name: gossip
        command:
        - redis-server
        args:
        - /etc/redis/redis.conf
        - --cluster-enabled
        - yes
        - --cluster-config-file
        - /data/nodes.conf
        - --cluster-node-timeout
        - "5000"
        - --appendonly
        - yes
        - --protected-mode
        - no
        volumeMounts:
        - name: redis-data
          mountPath: /data
        - name: redis-config
          mountPath: /etc/redis
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        livenessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: redis-config
        configMap:
          name: redis-config
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi
      storageClassName: fast-ssd

---
apiVersion: v1
kind: Service
metadata:
  name: redis-cluster
  namespace: production
spec:
  clusterIP: None
  ports:
  - port: 6379
    targetPort: 6379
    name: client
  - port: 16379
    targetPort: 16379
    name: gossip
  selector:
    app: redis-cluster

9. Summary and Review (5 minutes)

Production Readiness Checklist

  • ✅ High Availability: Master-replica setup with Sentinel or Redis Cluster
  • ✅ Security: Authentication, TLS encryption, command renaming, network restrictions
  • ✅ Monitoring: Comprehensive metrics, alerting, and dashboards
  • ✅ Backups: Automated daily backups with off-site storage and recovery procedures
  • ✅ Performance: Optimized configuration, connection pooling, memory management
  • ✅ Scalability: Horizontal scaling strategy with Redis Cluster
  • ✅ Compliance: Data encryption, access controls, audit logging
  • ✅ Documentation: Runbooks, recovery procedures, configuration management

Key Production Metrics to Monitor

  1. Performance Metrics

    • Operations per second
    • Average latency (p50, p95, p99)
    • Memory usage and fragmentation
    • Connection count
  2. Availability Metrics

    • Uptime percentage
    • Failover frequency
    • Replication lag
    • Network partitions
  3. Security Metrics

    • Failed authentication attempts
    • Suspicious command patterns
    • Network connection sources
    • Configuration changes

Final Architecture Example

Internet --> [Load Balancer] --> [App Servers] --> [Redis Sentinel] --> [Redis Cluster]
                    |                  |                 |                     |
                [SSL Term]         [Monitoring]     [Health Checks]      [Replication]
                    |                  |                 |                     |
                [WAF]              [Metrics]         [Auto Failover]     [Persistence]

Congratulations! You now have the knowledge to deploy and operate Redis at production scale. Remember: start simple, monitor everything, and scale incrementally based on actual needs.


For Instructors

Teaching Tips

  • Use real production scenarios and war stories
  • Demonstrate actual monitoring dashboards and alerts
  • Show the impact of different configuration options
  • Practice disaster recovery scenarios
  • Emphasize the importance of gradual rollouts

Assessment Ideas

  • Design a complete production Redis architecture
  • Create monitoring and alerting configurations
  • Develop backup and recovery procedures
  • Security assessment of Redis deployment
  • Performance tuning exercise with load testing

This completes our comprehensive Redis course - you’re now ready to master Redis in production! 🚀