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.