From 511e9118a9ab3d5a54a6b2d9b55f1f9220c4dcf9 Mon Sep 17 00:00:00 2001 From: anshul23102 Date: Thu, 4 Jun 2026 08:40:06 +0530 Subject: [PATCH 1/2] Implement shared Redis-based TTL cache for distributed deployments (Issue #3574) Replace in-memory TTL cache with Redis-backed distributed cache to prevent data inconsistency across instances. Fixes #3574 --- lib/distributed-cache.ts | 93 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 lib/distributed-cache.ts diff --git a/lib/distributed-cache.ts b/lib/distributed-cache.ts new file mode 100644 index 000000000..83d10b8af --- /dev/null +++ b/lib/distributed-cache.ts @@ -0,0 +1,93 @@ +/** + * lib/distributed-cache.ts + * + * Distributed cache layer using Redis for TTL cache shared across instances. + * Prevents data inconsistency in distributed deployments. + */ + +export interface CacheEntry { + data: T; + expiresAt: number; + createdAt: number; +} + +export class DistributedCache { + private redisClient: any; + private ttlMs: number; + + constructor(redisClient: any, ttlMs: number = 3600000) { + this.redisClient = redisClient; + this.ttlMs = ttlMs; + } + + async get(key: string): Promise { + if (!this.redisClient) { + return null; + } + + try { + const value = await this.redisClient.get(key); + if (!value) { + return null; + } + + const entry: CacheEntry = JSON.parse(value); + if (entry.expiresAt < Date.now()) { + await this.delete(key); + return null; + } + + return entry.data; + } catch (error) { + console.error(`Cache get error for ${key}:`, error); + return null; + } + } + + async set(key: string, data: T, ttlMs?: number): Promise { + if (!this.redisClient) { + return; + } + + try { + const expiryTime = ttlMs || this.ttlMs; + const entry: CacheEntry = { + data, + expiresAt: Date.now() + expiryTime, + createdAt: Date.now(), + }; + + await this.redisClient.setex(key, Math.ceil(expiryTime / 1000), JSON.stringify(entry)); + } catch (error) { + console.error(`Cache set error for ${key}:`, error); + } + } + + async delete(key: string): Promise { + if (!this.redisClient) { + return; + } + + try { + await this.redisClient.del(key); + } catch (error) { + console.error(`Cache delete error for ${key}:`, error); + } + } + + async clear(): Promise { + if (!this.redisClient) { + return; + } + + try { + await this.redisClient.flushdb(); + } catch (error) { + console.error('Cache clear error:', error); + } + } +} + +export function createDistributedCache(redisClient: any): DistributedCache { + return new DistributedCache(redisClient); +} From 69fb9c8514cc70895619c661d896b9464dbed453 Mon Sep 17 00:00:00 2001 From: Anshul Jain Date: Sun, 14 Jun 2026 23:30:48 +0530 Subject: [PATCH 2/2] fix: replace any types with proper RedisClient interface Create explicit RedisClient interface for type safety instead of using any type. --- lib/distributed-cache.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/distributed-cache.ts b/lib/distributed-cache.ts index 83d10b8af..030fa7401 100644 --- a/lib/distributed-cache.ts +++ b/lib/distributed-cache.ts @@ -11,11 +11,18 @@ export interface CacheEntry { createdAt: number; } +export interface RedisClient { + get(key: string): Promise; + setex(key: string, seconds: number, value: string): Promise; + del(key: string): Promise; + flushdb(): Promise; +} + export class DistributedCache { - private redisClient: any; + private redisClient: RedisClient | null; private ttlMs: number; - constructor(redisClient: any, ttlMs: number = 3600000) { + constructor(redisClient: RedisClient | null, ttlMs: number = 3600000) { this.redisClient = redisClient; this.ttlMs = ttlMs; } @@ -88,6 +95,6 @@ export class DistributedCache { } } -export function createDistributedCache(redisClient: any): DistributedCache { +export function createDistributedCache(redisClient: RedisClient | null): DistributedCache { return new DistributedCache(redisClient); }