@@ -82,10 +82,12 @@ export type ZodWorkerDequeueOptions = {
82
82
} ;
83
83
84
84
const CLEANUP_TASK_NAME = "__cleanupOldJobs" ;
85
+ const REPORTER_TASK_NAME = "__reporter" ;
85
86
86
87
export type ZodWorkerCleanupOptions = {
87
88
frequencyExpression : string ; // cron expression
88
89
ttl : number ;
90
+ maxCount : number ;
89
91
taskOptions ?: CronItemOptions ;
90
92
} ;
91
93
@@ -97,6 +99,7 @@ export type ZodWorkerOptions<TMessageCatalog extends MessageCatalogSchema> = {
97
99
tasks : ZodTasks < TMessageCatalog > ;
98
100
recurringTasks ?: ZodRecurringTasks ;
99
101
cleanup ?: ZodWorkerCleanupOptions ;
102
+ reporter ?: ( subject : string , message : string ) => Promise < void > ;
100
103
} ;
101
104
102
105
export class ZodWorker < TMessageCatalog extends MessageCatalogSchema > {
@@ -108,6 +111,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
108
111
#recurringTasks?: ZodRecurringTasks ;
109
112
#runner?: GraphileRunner ;
110
113
#cleanup: ZodWorkerCleanupOptions | undefined ;
114
+ #reporter?: ( subject : string , message : string ) => Promise < void > ;
111
115
112
116
constructor ( options : ZodWorkerOptions < TMessageCatalog > ) {
113
117
this . #name = options . name ;
@@ -117,6 +121,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
117
121
this . #tasks = options . tasks ;
118
122
this . #recurringTasks = options . recurringTasks ;
119
123
this . #cleanup = options . cleanup ;
124
+ this . #reporter = options . reporter ;
120
125
}
121
126
122
127
get graphileWorkerSchema ( ) {
@@ -356,6 +361,14 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
356
361
taskList [ CLEANUP_TASK_NAME ] = task ;
357
362
}
358
363
364
+ if ( this . #reporter) {
365
+ const task : Task = ( payload , helpers ) => {
366
+ return this . #handleReporter( payload , helpers ) ;
367
+ } ;
368
+
369
+ taskList [ REPORTER_TASK_NAME ] = task ;
370
+ }
371
+
359
372
return taskList ;
360
373
}
361
374
@@ -371,6 +384,14 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
371
384
} ) ;
372
385
}
373
386
387
+ if ( this . #reporter) {
388
+ cronItems . push ( {
389
+ pattern : "50 * * * *" , // Every hour at 50 minutes past the hour
390
+ identifier : REPORTER_TASK_NAME ,
391
+ task : REPORTER_TASK_NAME ,
392
+ } ) ;
393
+ }
394
+
374
395
if ( ! this . #recurringTasks) {
375
396
return cronItems ;
376
397
}
@@ -493,8 +514,9 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
493
514
} ) ;
494
515
495
516
const rawResults = await this . #prisma. $queryRawUnsafe (
496
- `DELETE FROM ${ this . graphileWorkerSchema } .jobs WHERE run_at < $1 AND locked_at IS NULL AND max_attempts = attempts RETURNING id` ,
497
- expirationDate
517
+ `WITH rows AS (SELECT id FROM ${ this . graphileWorkerSchema } .jobs WHERE run_at < $1 AND locked_at IS NULL AND max_attempts = attempts ORDER BY run_at ASC LIMIT $2) DELETE FROM ${ this . graphileWorkerSchema } .jobs WHERE id IN (SELECT id FROM rows) RETURNING id` ,
518
+ expirationDate ,
519
+ this . #cleanup. maxCount
498
520
) ;
499
521
500
522
const results = Array . isArray ( rawResults ) ? rawResults : [ ] ;
@@ -504,6 +526,65 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
504
526
expirationDate,
505
527
payload,
506
528
} ) ;
529
+
530
+ if ( this . #reporter) {
531
+ await this . #reporter(
532
+ "Worker Queue Cleanup" ,
533
+ `Cleaned up ${ results . length } jobs older than ${ expirationDate . toISOString ( ) } `
534
+ ) ;
535
+ }
536
+ }
537
+
538
+ async #handleReporter( rawPayload : unknown , helpers : JobHelpers ) : Promise < void > {
539
+ if ( ! this . #reporter) {
540
+ return ;
541
+ }
542
+
543
+ logger . debug ( "Received reporter task" , {
544
+ payload : rawPayload ,
545
+ } ) ;
546
+
547
+ const parsedPayload = RawCronPayloadSchema . safeParse ( rawPayload ) ;
548
+
549
+ if ( ! parsedPayload . success ) {
550
+ throw new Error (
551
+ `Failed to parse cleanup task payload: ${ JSON . stringify ( parsedPayload . error ) } `
552
+ ) ;
553
+ }
554
+
555
+ const payload = parsedPayload . data ;
556
+
557
+ // Subtract an hour from the payload._cron.ts
558
+ const startAt = new Date ( payload . _cron . ts . getTime ( ) - 1000 * 60 * 60 ) ;
559
+
560
+ const schema = z . array ( z . object ( { count : z . coerce . number ( ) } ) ) ;
561
+
562
+ // Count the number of jobs that have been added since the startAt date and before the payload._cron.ts date
563
+ const rawAddedResults = await this . #prisma. $queryRawUnsafe (
564
+ `SELECT COUNT(*) FROM ${ this . graphileWorkerSchema } .jobs WHERE created_at > $1 AND created_at < $2` ,
565
+ startAt ,
566
+ payload . _cron . ts
567
+ ) ;
568
+
569
+ const addedCountResults = schema . parse ( rawAddedResults ) [ 0 ] ;
570
+
571
+ // Count the total number of jobs in the jobs table
572
+ const rawTotalResults = await this . #prisma. $queryRawUnsafe (
573
+ `SELECT COUNT(*) FROM ${ this . graphileWorkerSchema } .jobs`
574
+ ) ;
575
+
576
+ const totalCountResults = schema . parse ( rawTotalResults ) [ 0 ] ;
577
+
578
+ logger . debug ( "Calculated metrics about the jobs table" , {
579
+ rawAddedResults,
580
+ rawTotalResults,
581
+ payload,
582
+ } ) ;
583
+
584
+ await this . #reporter(
585
+ "Worker Queue Metrics" ,
586
+ `Added ${ addedCountResults . count } jobs in the last hour, total jobs: ${ totalCountResults . count } `
587
+ ) ;
507
588
}
508
589
509
590
#logDebug( message : string , args ?: any ) {
0 commit comments