From e425c4379cc0fb9c7ca7d4a6680c2efda5c45ae3 Mon Sep 17 00:00:00 2001 From: Alfredo Del Fabro Neto Date: Wed, 24 Jun 2026 17:43:28 -0300 Subject: [PATCH] fix: remove in-memory cache to fix multi-instance status inconsistency The TimeOffCache was an in-memory singleton Map per Node process. In a multi-instance Rocket.Chat deployment each instance held its own isolated cache with a 1-hour TTL, while MongoDB was the only shared store. After a user ended their time off on one instance, other instances kept serving the stale ON_TIME_OFF entry until their cache expired, wrongly notifying senders that the user was still OOO and forcing repeated slash-command retries. Remove the cache entirely and read/write straight through the persistence repository. Lookups use the existing indexed association query, so the app stays performant while every instance always sees the current status. Co-Authored-By: Claude Opus 4.8 (1M context) --- TimeOffApp.ts | 2 -- TimeOffCache.ts | 43 -------------------------------------- services/TimeOffService.ts | 24 ++------------------- 3 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 TimeOffCache.ts diff --git a/TimeOffApp.ts b/TimeOffApp.ts index 3bd87b4..3c6a90d 100644 --- a/TimeOffApp.ts +++ b/TimeOffApp.ts @@ -20,7 +20,6 @@ import { TimeOffService } from './services/TimeOffService'; import { AppNotifier } from './notifiers/AppNotifier'; import { PostMessageSentHandler } from './handlers/PostMessageSentHandler'; import { TimeOffRepository } from './repositories/TimeOffRepository'; -import { TimeOffCache } from './TimeOffCache'; import { UserService } from './services/UserService'; import { APP_SETTINGS, DEFAULT_TIME_OFF_REPLY_COOLDOWN_HOURS } from './helpers/AppSettings'; @@ -49,7 +48,6 @@ export class TimeOffApp extends App implements IPostMessageSent { _environmentRead: IEnvironmentRead, _configurationModify: IConfigurationModify, ): Promise { - TimeOffCache.getInstance().invalidateCache(); return Promise.resolve(true); } diff --git a/TimeOffCache.ts b/TimeOffCache.ts deleted file mode 100644 index 2b549bc..0000000 --- a/TimeOffCache.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ITimeOff } from './interfaces/ITimeOff'; - -export class TimeOffCache { - private static instance: TimeOffCache; - private cache: Map; - private TTL = 60 * 60 * 1000; // 1 hour cache - - private constructor() { - this.cache = new Map(); - } - - public static getInstance(): TimeOffCache { - if (!TimeOffCache.instance) { - TimeOffCache.instance = new TimeOffCache(); - } - return TimeOffCache.instance; - } - - public set(userId: string, timeOffData: ITimeOff): void { - const expiresAt = Date.now() + this.TTL; - this.cache.set(userId, { data: timeOffData, expiresAt }); - } - - public get(userId: string): ITimeOff | undefined { - const cached = this.cache.get(userId); - if (!cached) return undefined; - - if (Date.now() > cached.expiresAt) { - this.cache.delete(userId); - return undefined; - } - - return cached.data; - } - - public invalidateUser(userId: string): void { - this.cache.delete(userId); - } - - public invalidateCache(): void { - this.cache.clear(); - } -} diff --git a/services/TimeOffService.ts b/services/TimeOffService.ts index bc7a5dd..6eda7d1 100644 --- a/services/TimeOffService.ts +++ b/services/TimeOffService.ts @@ -1,35 +1,15 @@ import { ITimeOff } from '../interfaces/ITimeOff'; import { ITimeOffRepository } from '../repositories/ITimeOffRepository'; -import { TimeOffCache } from '../TimeOffCache'; import { ITimeOffService } from './ITimeOffService'; export class TimeOffService implements ITimeOffService { constructor(private readonly repository: ITimeOffRepository) {} public async saveTimeOff(timeOff: ITimeOff): Promise { - const success = await this.repository.save(timeOff); - - if (success) { - const cache = TimeOffCache.getInstance(); - cache.invalidateUser(timeOff.coreUserId); - cache.set(timeOff.coreUserId, timeOff); - } - - return success; + return this.repository.save(timeOff); } public async getTimeOffByUserId(coreUserId: string): Promise { - const cache: TimeOffCache = TimeOffCache.getInstance(); - let timeOffData: ITimeOff | undefined = cache.get(coreUserId); - - if (!timeOffData) { - timeOffData = await this.repository.findByUserId(coreUserId); - - if (timeOffData) { - cache.set(coreUserId, timeOffData); - } - } - - return timeOffData; + return this.repository.findByUserId(coreUserId); } }