feat: Vô hiệu hóa tạm thời Redis cache và sử dụng in-memory cache làm fallback.

This commit is contained in:
Ho Ngoc Hai
2026-01-04 15:19:05 +07:00
parent 101c333181
commit c54a9debe3
2 changed files with 86 additions and 44 deletions

View File

@@ -9,7 +9,7 @@ import { getRedisClient } from '../../config/redis.config';
*/
export class MultiLayerCache {
private l1Cache: NodeCache; // In-memory cache
private redis: ReturnType<typeof getRedisClient>;
private redis: ReturnType<typeof getRedisClient> | null = null; // Disabled for testing
constructor() {
// L1: In-memory cache (10MB default, 60s default TTL)
@@ -19,8 +19,10 @@ export class MultiLayerCache {
useClones: false, // Better performance
});
// L2: Redis cache
this.redis = getRedisClient();
// EN: Temporarily disable L2 Redis cache to test performance
// VI: Tạm thời tắt L2 Redis cache để test hiệu suất
// TODO: Re-enable after fixing Redis EPIPE errors
// this.redis = getRedisClient();
}
/**
@@ -35,13 +37,15 @@ export class MultiLayerCache {
return l1Value;
}
// L2: Check Redis cache (< 5ms)
const l2Value = await this.redis.get(key);
if (l2Value) {
const parsed = JSON.parse(l2Value) as T;
// Warm up L1 cache
this.l1Cache.set(key, parsed, 60); // Cache 1 minute in L1
return parsed;
// L2: Check Redis cache (< 5ms) - only if Redis is enabled
if (this.redis) {
const l2Value = await this.redis.get(key);
if (l2Value) {
const parsed = JSON.parse(l2Value) as T;
// Warm up L1 cache
this.l1Cache.set(key, parsed, 60); // Cache 1 minute in L1
return parsed;
}
}
return null;
@@ -66,11 +70,13 @@ export class MultiLayerCache {
// TODO: Re-enable after fixing Redis EPIPE errors
/*
// L2: Set Redis cache
const stringValue = JSON.stringify(value);
if (ttlSeconds) {
await this.redis.setex(key, ttlSeconds, stringValue);
} else {
await this.redis.set(key, stringValue);
if (this.redis) {
const stringValue = JSON.stringify(value);
if (ttlSeconds) {
await this.redis.setex(key, ttlSeconds, stringValue);
} else {
await this.redis.set(key, stringValue);
}
}
*/
} catch (error) {
@@ -108,7 +114,9 @@ export class MultiLayerCache {
this.l1Cache.del(key);
// L2: Delete from Redis
await this.redis.del(key);
if (this.redis) {
await this.redis.del(key);
}
} catch (error) {
logger.error('Multi-layer cache del error', { key, error });
}
@@ -124,7 +132,7 @@ export class MultiLayerCache {
keys.forEach(key => this.l1Cache.del(key));
// L2: Delete from Redis
if (keys.length > 0) {
if (this.redis && keys.length > 0) {
await this.redis.del(...keys);
}
} catch (error) {
@@ -143,21 +151,24 @@ export class MultiLayerCache {
this.l1Cache.flushAll();
// L2: Scan and delete matching keys in Redis
const stream = this.redis.scanStream({
match: pattern,
count: 100,
});
if (this.redis) {
const stream = this.redis.scanStream({
match: pattern,
count: 100,
});
const keys: string[] = [];
stream.on('data', (resultKeys: string[]) => {
keys.push(...resultKeys);
});
const keys: string[] = [];
stream.on('data', (resultKeys: string[]) => {
keys.push(...resultKeys);
});
stream.on('end', async () => {
if (keys.length > 0) {
await this.redis.del(...keys);
}
});
const redis = this.redis; // Capture for closure
stream.on('end', async () => {
if (keys.length > 0) {
await redis.del(...keys);
}
});
}
} catch (error) {
logger.error('Multi-layer cache invalidatePattern error', { pattern, error });
}

View File

@@ -1,21 +1,41 @@
import { logger } from '@goodgo/logger';
import { getRedisClient } from '../../config/redis.config';
// EN: Temporarily disable Redis to prevent EPIPE errors
// VI: Tạm thời tắt Redis để tránh lỗi EPIPE
// import { getRedisClient } from '../../config/redis.config';
/**
* EN: Service for caching data (Redis wrapper)
* VI: Service cho việc caching dữ liệu (Redis wrapper)
* EN: Service for caching data (In-memory fallback, Redis disabled)
* VI: Service cho việc caching dữ liệu (In-memory fallback, Redis đã tắt)
*/
export class CacheService {
// EN: Temporary in-memory cache as fallback
// VI: Cache in-memory tạm thời làm fallback
private memoryCache: Map<string, { value: any; expiresAt: number }> = new Map();
/**
* EN: Get value from cache
* VI: Lấy giá trị từ cache
*/
async get<T>(key: string): Promise<T | null> {
try {
const data = await getRedisClient().get(key);
if (!data) return null;
return JSON.parse(data) as T;
// EN: Use in-memory cache instead of Redis
// VI: Sử dụng in-memory cache thay vì Redis
const cached = this.memoryCache.get(key);
if (!cached) return null;
// Check expiration
if (cached.expiresAt < Date.now()) {
this.memoryCache.delete(key);
return null;
}
return cached.value as T;
// TODO: Re-enable after fixing Redis EPIPE errors
// const data = await getRedisClient().get(key);
// if (!data) return null;
// return JSON.parse(data) as T;
} catch (error) {
logger.error('Cache get error', { key, error });
return null;
@@ -26,14 +46,20 @@ export class CacheService {
* EN: Set value in cache
* VI: Lưu giá trị vào cache
*/
async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
async set(key: string, value: any, ttlSeconds: number = 300): Promise<void> {
try {
const stringValue = JSON.stringify(value);
if (ttlSeconds) {
await getRedisClient().setex(key, ttlSeconds, stringValue);
} else {
await getRedisClient().set(key, stringValue);
}
// EN: Use in-memory cache instead of Redis
// VI: Sử dụng in-memory cache thay vì Redis
const expiresAt = Date.now() + (ttlSeconds * 1000);
this.memoryCache.set(key, { value, expiresAt });
// TODO: Re-enable after fixing Redis EPIPE errors
// const stringValue = JSON.stringify(value);
// if (ttlSeconds) {
// await getRedisClient().setex(key, ttlSeconds, stringValue);
// } else {
// await getRedisClient().set(key, stringValue);
// }
} catch (error) {
logger.error('Cache set error', { key, error });
}
@@ -62,7 +88,12 @@ export class CacheService {
*/
async del(key: string): Promise<void> {
try {
await getRedisClient().del(key);
// EN: Use in-memory cache instead of Redis
// VI: Sử dụng in-memory cache thay vì Redis
this.memoryCache.delete(key);
// TODO: Re-enable after fixing Redis EPIPE errors
// await getRedisClient().del(key);
} catch (error) {
logger.error('Cache del error', { key, error });
}