@@ -19,11 +19,16 @@ import type { RepetitionDBScheme } from '../types/repetition';
1919import { DatabaseReadWriteError , DiffCalculationError , ValidationError } from '../../../lib/workerErrors' ;
2020import { decodeUnsafeFields , encodeUnsafeFields } from '../../../lib/utils/unsafeFields' ;
2121import { MS_IN_SEC } from '../../../lib/utils/consts' ;
22+ import TimeMs from '../../../lib/utils/time' ;
2223import DataFilter from './data-filter' ;
2324import RedisHelper from './redisHelper' ;
2425import { computeDelta } from './utils/repetitionDiff' ;
2526import { rightTrim } from '../../../lib/utils/string' ;
2627import { hasValue } from '../../../lib/utils/hasValue' ;
28+
29+ /**
30+ * eslint does not count decorators as a variable usage
31+ */
2732/* eslint-disable-next-line no-unused-vars */
2833import { memoize } from '../../../lib/memoize' ;
2934import { register , client } from '../../../lib/metrics' ;
@@ -32,7 +37,12 @@ import { register, client } from '../../../lib/metrics';
3237 * eslint does not count decorators as a variable usage
3338 */
3439/* eslint-disable-next-line no-unused-vars */
35- const MEMOIZATION_TTL = Number ( process . env . MEMOIZATION_TTL ?? 0 ) ;
40+ const MEMOIZATION_TTL = 600_000 ;
41+
42+ /**
43+ * Cache cleanup interval in minutes
44+ */
45+ const CACHE_CLEANUP_INTERVAL_MINUTES = 5 ;
3646
3747/**
3848 * Error code of MongoDB key duplication error
@@ -122,6 +132,11 @@ export default class GrouperWorker extends Worker {
122132 registers : [ register ] ,
123133 } ) ;
124134
135+ /**
136+ * Interval for periodic cache cleanup to prevent memory leaks from unbounded cache growth
137+ */
138+ private cacheCleanupInterval : NodeJS . Timeout | null = null ;
139+
125140 /**
126141 * Start consuming messages
127142 */
@@ -135,13 +150,30 @@ export default class GrouperWorker extends Worker {
135150
136151 await this . redis . initialize ( ) ;
137152 console . log ( 'redis initialized' ) ;
153+
154+ /**
155+ * Start periodic cache cleanup to prevent memory leaks from unbounded cache growth
156+ * Runs every 5 minutes to clear old cache entries
157+ */
158+ this . cacheCleanupInterval = setInterval ( ( ) => {
159+ this . clearCache ( ) ;
160+ } , CACHE_CLEANUP_INTERVAL_MINUTES * TimeMs . MINUTE ) ;
161+
138162 await super . start ( ) ;
139163 }
140164
141165 /**
142166 * Finish everything
143167 */
144168 public async finish ( ) : Promise < void > {
169+ /**
170+ * Clear cache cleanup interval to prevent resource leaks
171+ */
172+ if ( this . cacheCleanupInterval ) {
173+ clearInterval ( this . cacheCleanupInterval ) ;
174+ this . cacheCleanupInterval = null ;
175+ }
176+
145177 await super . finish ( ) ;
146178 this . prepareCache ( ) ;
147179 await this . eventsDb . close ( ) ;
@@ -198,7 +230,7 @@ export default class GrouperWorker extends Worker {
198230 const similarEvent = await this . findSimilarEvent ( task . projectId , task . payload . title ) ;
199231
200232 if ( similarEvent ) {
201- this . logger . info ( `similar event: ${ JSON . stringify ( similarEvent ) } ` ) ;
233+ this . logger . info ( `[handle] similar event found, groupHash= ${ similarEvent . groupHash } totalCount= ${ similarEvent . totalCount } ` ) ;
202234
203235 /**
204236 * Override group hash with found event's group hash
@@ -331,6 +363,12 @@ export default class GrouperWorker extends Worker {
331363 } as RepetitionDBScheme ;
332364
333365 repetitionId = await this . saveRepetition ( task . projectId , newRepetition ) ;
366+
367+ /**
368+ * Clear the large event payload references to allow garbage collection
369+ * This prevents memory leaks from retaining full event objects after delta is computed
370+ */
371+ delta = undefined ;
334372 }
335373
336374 /**
@@ -432,7 +470,7 @@ export default class GrouperWorker extends Worker {
432470 * @param projectId - where to find
433471 * @param title - title of the event to find similar one
434472 */
435- @memoize ( { max : 200 , ttl : MEMOIZATION_TTL , strategy : 'hash' , skipCache : [ undefined ] } )
473+ @memoize ( { max : 50 , ttl : MEMOIZATION_TTL , strategy : 'hash' , skipCache : [ undefined ] } )
436474 private async findSimilarEvent ( projectId : string , title : string ) : Promise < GroupedEventDBScheme | undefined > {
437475 /**
438476 * If no match by Levenshtein, try matching by patterns
@@ -446,9 +484,7 @@ export default class GrouperWorker extends Worker {
446484 try {
447485 const originalEvent = await this . findFirstEventByPattern ( matchingPattern . pattern , projectId ) ;
448486
449- const originalEventSize = Buffer . byteLength ( JSON . stringify ( originalEvent ) ) ;
450-
451- this . logger . info ( `[findSimilarEvent] found by pattern, originalEventSize=${ originalEventSize } b` ) ;
487+ this . logger . info ( `[findSimilarEvent] found by pattern, groupHash=${ originalEvent ?. groupHash } title="${ originalEvent ?. payload ?. title } "` ) ;
452488
453489 if ( originalEvent ) {
454490 return originalEvent ;
0 commit comments