@@ -131,6 +131,35 @@ export default class GrouperWorker extends Worker {
131131 await this . redis . close ( ) ;
132132 }
133133
134+ /**
135+ * Override clearCache to also clear memoization caches and RegExp cache
136+ * This prevents memory leaks from decorator-based LRU caches
137+ */
138+ public clearCache ( ) : void {
139+ super . clearCache ( ) ;
140+
141+ /**
142+ * Clear RegExp cache
143+ */
144+ this . regexpCache . clear ( ) ;
145+
146+ /**
147+ * Clear memoization caches from decorators
148+ * These are stored as properties on the instance
149+ */
150+ const memoizeCachePrefix = 'memoizeCache:' ;
151+
152+ for ( const key in this ) {
153+ if ( key . startsWith ( memoizeCachePrefix ) ) {
154+ const cache = this [ key ] as any ;
155+
156+ if ( cache && typeof cache . reset === 'function' ) {
157+ cache . reset ( ) ;
158+ }
159+ }
160+ }
161+ }
162+
134163 /**
135164 * Task handling function
136165 *
@@ -156,7 +185,7 @@ export default class GrouperWorker extends Worker {
156185 const similarEvent = await this . findSimilarEvent ( task . projectId , task . payload . title ) ;
157186
158187 if ( similarEvent ) {
159- this . logger . info ( `similar event: ${ JSON . stringify ( similarEvent ) } ` ) ;
188+ this . logger . info ( `similar event found: groupHash= ${ similarEvent . groupHash } , totalCount= ${ similarEvent . totalCount } ` ) ;
160189
161190 /**
162191 * Override group hash with found event's group hash
@@ -386,7 +415,7 @@ export default class GrouperWorker extends Worker {
386415 try {
387416 const originalEvent = await this . findFirstEventByPattern ( matchingPattern . pattern , projectId ) ;
388417
389- this . logger . info ( `original event for pattern: ${ JSON . stringify ( originalEvent ) } ` ) ;
418+ this . logger . info ( `original event for pattern found: groupHash= ${ originalEvent ?. groupHash || 'none' } ` ) ;
390419
391420 if ( originalEvent ) {
392421 return originalEvent ;
@@ -400,6 +429,11 @@ export default class GrouperWorker extends Worker {
400429 return undefined ;
401430 }
402431
432+ /**
433+ * Cache for compiled RegExp patterns to avoid repeated compilation
434+ */
435+ private regexpCache = new Map < string , RegExp > ( ) ;
436+
403437 /**
404438 * Method that returns matched pattern for event, if event do not match any of patterns return null
405439 *
@@ -416,7 +450,12 @@ export default class GrouperWorker extends Worker {
416450 }
417451
418452 return patterns . filter ( pattern => {
419- const patternRegExp = new RegExp ( pattern . pattern ) ;
453+ let patternRegExp = this . regexpCache . get ( pattern . pattern ) ;
454+
455+ if ( ! patternRegExp ) {
456+ patternRegExp = new RegExp ( pattern . pattern ) ;
457+ this . regexpCache . set ( pattern . pattern , patternRegExp ) ;
458+ }
420459
421460 return title . match ( patternRegExp ) ;
422461 } ) . pop ( ) || null ;
@@ -428,6 +467,7 @@ export default class GrouperWorker extends Worker {
428467 * @param projectId - id of the project to find related event patterns
429468 * @returns {ProjectEventGroupingPatternsDBScheme[] } EventPatterns object with projectId and list of patterns
430469 */
470+ @memoize ( { max : 100 , ttl : MEMOIZATION_TTL , strategy : 'hash' } )
431471 private async getProjectPatterns ( projectId : string ) : Promise < ProjectEventGroupingPatternsDBScheme [ ] > {
432472 const project = await this . accountsDb . getConnection ( )
433473 . collection ( 'projects' )
@@ -478,11 +518,14 @@ export default class GrouperWorker extends Worker {
478518 const repetitionCacheKey = `repetitions:${ task . projectId } :${ existedEvent . groupHash } :${ eventUser . id } ` ;
479519 const repetition = await this . cache . get ( repetitionCacheKey , async ( ) => {
480520 return this . eventsDb . getConnection ( ) . collection ( `repetitions:${ task . projectId } ` )
481- . findOne ( {
482- groupHash : existedEvent . groupHash ,
483- 'payload.user.id' : eventUser . id ,
484- } ) ;
485- } ) ;
521+ . findOne (
522+ {
523+ groupHash : existedEvent . groupHash ,
524+ 'payload.user.id' : eventUser . id ,
525+ } ,
526+ { projection : { _id : 1 } }
527+ ) ;
528+ } , 300 ) ;
486529
487530 if ( repetition ) {
488531 shouldIncrementRepetitionAffectedUsers = false ;
@@ -512,15 +555,18 @@ export default class GrouperWorker extends Worker {
512555 const repetitionDailyCacheKey = `repetitions:${ task . projectId } :${ existedEvent . groupHash } :${ eventUser . id } :${ eventMidnight } ` ;
513556 const repetitionDaily = await this . cache . get ( repetitionDailyCacheKey , async ( ) => {
514557 return this . eventsDb . getConnection ( ) . collection ( `repetitions:${ task . projectId } ` )
515- . findOne ( {
516- groupHash : existedEvent . groupHash ,
517- 'payload.user.id' : eventUser . id ,
518- timestamp : {
519- $gte : eventMidnight ,
520- $lt : eventNextMidnight ,
558+ . findOne (
559+ {
560+ groupHash : existedEvent . groupHash ,
561+ 'payload.user.id' : eventUser . id ,
562+ timestamp : {
563+ $gte : eventMidnight ,
564+ $lt : eventNextMidnight ,
565+ } ,
521566 } ,
522- } ) ;
523- } ) ;
567+ { projection : { _id : 1 } }
568+ ) ;
569+ } , 300 ) ;
524570
525571 /**
526572 * If daily repetition exists, don't increment daily affected users
@@ -575,7 +621,7 @@ export default class GrouperWorker extends Worker {
575621 * @returns {string } cache key for event
576622 */
577623 private async getEventCacheKey ( projectId : string , groupHash : string ) : Promise < string > {
578- return `${ projectId } :${ JSON . stringify ( { groupHash } ) } ` ;
624+ return `event: ${ projectId } :${ groupHash } ` ;
579625 }
580626
581627 /**
0 commit comments