Skip to content

Commit e41b15a

Browse files
committed
Add regex & memoize caches; optimize queries
Prevent memory leaks and reduce work by introducing a RegExp compile cache and clearing memoization caches on clearCache. Key changes: - Override clearCache to clear regexpCache and any decorator-based memoize caches stored on the instance. - Add a private regexpCache Map and reuse compiled RegExp objects in getMatchedPattern to avoid repeated compilation. - Annotate getProjectPatterns with @memoize({ max: 100, ttl: MEMOIZATION_TTL, strategy: 'hash' }). - Tighten DB queries used with cache.get to only project _id and pass a 300s TTL to the cache.get calls to reduce payload and caching duration. - Improve log messages to include groupHash/totalCount or indicate none, for clearer diagnostics. - Change getEventCacheKey format to `event:<projectId>:<groupHash>` for clearer keys. These changes reduce memory usage, avoid repeated RegExp compilation, make logs more actionable, and minimize DB transfer sizes.
1 parent 81b0d27 commit e41b15a

1 file changed

Lines changed: 63 additions & 17 deletions

File tree

workers/grouper/src/index.ts

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)