RDS
TCP
RE
Redis Reconnect
failover • readonly • retry

NestJS mất kết nối Redis: cấu hình ioredis reconnect

Cách cấu hình ioredis trong NestJS để reconnect an toàn khi Redis disconnect, gặp TCP idle timeout hoặc ElastiCache failover trả về READONLY.

6 phút đọc08/06/2026

Khi nào bài toán này thực sự xảy ra

Trong production, lỗi thường không hiện ra dưới dạng "Redis chết hẳn" mà là app treo lệnh, queue chậm bất thường hoặc log lặp lại reconnect sau một đợt network chập chờn hay ElastiCache failover. Đây là lúc cấu hình mặc định của ioredis thường chưa đủ an toàn.

Các nguyên nhân phổ biến:

  • Network blip tạm thời
  • Redis server restart (maintenance/update)
  • Cloud provider TCP timeout
  • Load balancer connection draining

NestJS với ioredis mặc định có retry logic nhưng không tối ưu cho production.

Retry Strategy

import Redis from 'ioredis';
import { Logger } from '@nestjs/common';

const logger = new Logger('Redis');

export function createRedisConnection(): Redis {
  return new Redis({
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    password: process.env.REDIS_PASSWORD,

    // Reconnect với exponential backoff
    retryStrategy: (times: number) => {
      if (times > 10) {
        logger.error('Redis: max retries reached, giving up');
        return null; // stop retrying
      }
      const delay = Math.min(times * 100, 3000);
      logger.warn(`Redis: retry #${times} in ${delay}ms`);
      return delay;
    },

    // Reconnect khi gặp READONLY error (ElastiCache failover)
    reconnectOnError: (err: Error) => {
      logger.error('Redis connection error:', err.message);
      return err.message.includes('READONLY');
    },

    // Connection options
    keepAlive: 30000,
    connectTimeout: 10000,
    commandTimeout: 5000,
    enableOfflineQueue: true,
    maxRetriesPerRequest: 3,
    lazyConnect: false,
  });
}

Health Check

import { Injectable } from '@nestjs/common';
import {
  HealthIndicator,
  HealthIndicatorResult,
} from '@nestjs/terminus';
import Redis from 'ioredis';

@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
  constructor(private readonly redis: Redis) {
    super();
  }

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    try {
      const ping = await this.redis.ping();
      return this.getStatus(key, ping === 'PONG');
    } catch (err) {
      return this.getStatus(key, false, { error: err.message });
    }
  }
}

Monitoring và Alerting

Listen to ioredis events để log và alert:

const redis = createRedisConnection();

redis.on('connect', () => logger.log('Redis connected'));
redis.on('ready', () => logger.log('Redis ready'));
redis.on('error', (err) => logger.error('Redis error', err));
redis.on('close', () => logger.warn('Redis connection closed'));
redis.on('reconnecting', (ms: number) =>
  logger.warn(`Redis reconnecting in ${ms}ms`)
);
redis.on('end', () => logger.error('Redis connection ended'));

Với cấu hình này, application sẽ tự động recover sau Redis outage mà không cần restart.