Health Monitoring
Monitor Redis connections and handle failures gracefully.
Connection Status
typescript
enum ConnectionStatus {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
ERROR = 'error',
}Health Checks
Single Client Health Check
typescript
import { Injectable, Inject } from '@nestjs/common';
import { CLIENT_MANAGER, RedisClientManager, IHealthStatus } from '@nestjs-redisx/core';
@Injectable()
export class HealthService {
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
async checkRedisHealth(): Promise<IHealthStatus> {
return this.clientManager.healthCheck('default') as Promise<IHealthStatus>;
}
}All Clients Health Check
typescript
async checkAllClients(): Promise<IHealthStatus[]> {
return this.clientManager.healthCheck() as Promise<IHealthStatus[]>;
}Health Status Response
typescript
interface IHealthStatus {
name: string;
healthy: boolean;
status: ConnectionStatus;
latency: number | null; // Ping latency in ms
lastError: string | null;
lastCheckAt: Date;
metadata: {
driverType: string;
connectionType: string;
reconnectAttempts: number;
uptime: number; // Milliseconds
};
}NestJS Terminus Integration
Health Indicator
typescript
import { Injectable, Inject } from '@nestjs/common';
import {
HealthIndicator,
HealthIndicatorResult,
HealthCheckError,
} from '@nestjs/terminus';
import { CLIENT_MANAGER, RedisClientManager } from '@nestjs-redisx/core';
@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {
super();
}
async isHealthy(key: string): Promise<HealthIndicatorResult> {
const healthStatuses = await this.clientManager.healthCheck();
const statuses = Array.isArray(healthStatuses)
? healthStatuses
: [healthStatuses];
const isHealthy = statuses.every((s) => s.healthy);
const result = this.getStatus(key, isHealthy, {
clients: statuses.map((s) => ({
name: s.name,
status: s.status,
latency: s.latency,
})),
});
if (isHealthy) {
return result;
}
throw new HealthCheckError('Redis health check failed', result);
}
}Health Controller
typescript
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService } from '@nestjs/terminus';
import { RedisHealthIndicator } from './terminus-indicator.usage';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private redisHealth: RedisHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.redisHealth.isHealthy('redis'),
]);
}
}Connection Statistics
Get Stats
typescript
const stats = this.clientManager.getStats();Stats Response
typescript
interface IConnectionStats {
totalClients: number;
connectedClients: number;
disconnectedClients: number;
errorClients: number;
clients: Record<string, IClientStats>;
collectedAt: Date;
}
interface IClientStats {
name: string;
status: ConnectionStatus;
commandsExecuted: number;
errors: number;
reconnections: number;
averageLatency: number;
peakLatency: number;
lastActivityAt: Date | null;
connectedAt: Date | null;
uptime: number;
}Stats Endpoint
typescript
import { Controller, Get, Inject } from '@nestjs/common';
import { CLIENT_MANAGER, RedisClientManager, IConnectionStats } from '@nestjs-redisx/core';
@Controller('admin')
export class AdminController {
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
@Get('redis/stats')
getRedisStats(): IConnectionStats {
return this.clientManager.getStats();
}
}Auto-Reconnection
Default Behavior
Automatic reconnection with exponential backoff:
- Initial delay: 1000ms
- Maximum delay: 30000ms
- Backoff multiplier: 2x
- Jitter: enabled (prevents thundering herd)
- Max attempts: unlimited
Custom Reconnection Options
typescript
await this.clientManager.createClient('custom', config, {
reconnection: {
maxAttempts: 10,
initialDelay: 500,
maxDelay: 10000,
backoffMultiplier: 1.5,
enableJitter: true,
},
driverType: 'ioredis',
metadata: {
name: 'custom',
config,
status: ConnectionStatus.DISCONNECTED,
reconnectAttempts: 0,
},
});typescript
await this.clientManager.createClient('custom', config, {
reconnection: {
maxAttempts: 10,
initialDelay: 500,
maxDelay: 10000,
backoffMultiplier: 1.5,
enableJitter: true,
},
driverType: 'node-redis',
metadata: {
name: 'custom',
config,
status: ConnectionStatus.DISCONNECTED,
reconnectAttempts: 0,
},
});Reconnection Timeline
Attempt 1: 500ms (initial delay)
Attempt 2: 750ms (500 * 1.5)
Attempt 3: 1125ms (750 * 1.5)
Attempt 4: 1687ms (1125 * 1.5)
...
Attempt N: capped at 10000ms (max delay)Event Monitoring
Manager Events
typescript
import { ManagerEvent } from '@nestjs-redisx/core';
enum ManagerEvent {
CONNECTED = 'manager:connected',
DISCONNECTED = 'manager:disconnected',
RECONNECTING = 'manager:reconnecting',
ERROR = 'manager:error',
CREATED = 'manager:created',
REMOVED = 'manager:removed',
}Event Listener
typescript
import { Injectable, OnModuleInit, Inject, Logger } from '@nestjs/common';
import {
CLIENT_MANAGER,
RedisClientManager,
ManagerEvent,
} from '@nestjs-redisx/core';
@Injectable()
export class RedisMonitor implements OnModuleInit {
private readonly logger = new Logger(RedisMonitor.name);
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
onModuleInit(): void {
this.clientManager.on(ManagerEvent.CONNECTED, (data) => {
this.logger.log(`Redis client '${data.name}' connected`);
});
this.clientManager.on(ManagerEvent.DISCONNECTED, (data) => {
this.logger.warn(`Redis client '${data.name}' disconnected`);
});
this.clientManager.on(ManagerEvent.RECONNECTING, (data) => {
this.logger.log(
`Redis client '${data.name}' reconnecting (attempt ${data.metadata?.attempt})`,
);
});
this.clientManager.on(ManagerEvent.ERROR, (data) => {
this.logger.error(
`Redis client '${data.name}' error: ${data.error?.message}`,
);
});
}
}Prometheus Metrics
typescript
import { Injectable, OnModuleInit, Inject } from '@nestjs/common';
import { Counter, Gauge, Histogram } from 'prom-client';
import { CLIENT_MANAGER, RedisClientManager, ManagerEvent } from '@nestjs-redisx/core';
@Injectable()
export class RedisMetricsCollector implements OnModuleInit {
private readonly connectionGauge = new Gauge({
name: 'redis_connection_status',
help: 'Redis connection status (1=connected, 0=disconnected)',
labelNames: ['client'],
});
private readonly reconnectCounter = new Counter({
name: 'redis_reconnect_total',
help: 'Total Redis reconnection attempts',
labelNames: ['client'],
});
private readonly errorCounter = new Counter({
name: 'redis_error_total',
help: 'Total Redis errors',
labelNames: ['client'],
});
private readonly latencyHistogram = new Histogram({
name: 'redis_latency_seconds',
help: 'Redis command latency',
labelNames: ['client'],
buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1],
});
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
onModuleInit(): void {
this.clientManager.on(ManagerEvent.CONNECTED, (data) => {
this.connectionGauge.labels(data.name).set(1);
});
this.clientManager.on(ManagerEvent.DISCONNECTED, (data) => {
this.connectionGauge.labels(data.name).set(0);
});
this.clientManager.on(ManagerEvent.RECONNECTING, (data) => {
this.reconnectCounter.labels(data.name).inc();
});
this.clientManager.on(ManagerEvent.ERROR, (data) => {
this.errorCounter.labels(data.name).inc();
});
}
recordLatency(client: string, latency: number): void {
this.latencyHistogram.labels(client).observe(latency / 1000);
}
}Client Metadata
Get Metadata
typescript
const metadata = this.clientManager.getMetadata('default');Metadata Structure
typescript
interface IClientMetadata {
name: string;
config: ConnectionConfig;
status: ConnectionStatus;
connectedAt?: Date;
lastError?: Error;
reconnectAttempts: number;
}Update Metadata
typescript
this.clientManager.updateMetadata('default', {
// Custom metadata fields
});Graceful Shutdown
Automatic Cleanup
RedisModule handles cleanup automatically via NestJS lifecycle hooks:
typescript
// In RedisService and RedisClientManager
async onModuleDestroy(): Promise<void> {
await this.clientManager.closeAll();
}Manual Cleanup
typescript
// Close specific client
await this.clientManager.closeClient('cache');
// Close all clients
await this.clientManager.closeAll();Shutdown Timeout
Configure graceful shutdown timeout:
typescript
RedisModule.forRoot({
clients: { ... },
global: {
gracefulShutdown: true,
gracefulShutdownTimeout: 10000, // 10 seconds
},
})Best Practices
Health Check Interval
typescript
import { Injectable, OnModuleInit, OnModuleDestroy, Inject } from '@nestjs/common';
import { CLIENT_MANAGER, RedisClientManager } from '@nestjs-redisx/core';
// Periodic health check
@Injectable()
export class HealthMonitor implements OnModuleInit, OnModuleDestroy {
private interval: NodeJS.Timeout;
constructor(
@Inject(CLIENT_MANAGER)
private readonly clientManager: RedisClientManager,
) {}
onModuleInit(): void {
this.interval = setInterval(async () => {
const health = await this.clientManager.healthCheck();
// Process health results
}, 30000); // Every 30 seconds
}
onModuleDestroy(): void {
clearInterval(this.interval);
}
}Alert on Errors
typescript
this.clientManager.on(ManagerEvent.ERROR, async (data) => {
// Send alert
await this.alertService.send({
level: 'error',
message: `Redis client '${data.name}' error`,
error: data.error?.message,
});
});Log Connection Changes
typescript
this.clientManager.on(ManagerEvent.CONNECTED, (data) => {
this.logger.log(`Connected: ${data.name}`);
});
this.clientManager.on(ManagerEvent.DISCONNECTED, (data) => {
this.logger.warn(`Disconnected: ${data.name}`);
});Next Steps
- Decorators — @InjectRedis usage
- Driver Abstraction — ioredis vs node-redis
- Troubleshooting — Common issues