@@ -82,10 +82,12 @@ export type ZodWorkerDequeueOptions = {
8282} ;
8383
8484const CLEANUP_TASK_NAME = "__cleanupOldJobs" ;
85+ const REPORTER_TASK_NAME = "__reporter" ;
8586
8687export type ZodWorkerCleanupOptions = {
8788 frequencyExpression : string ; // cron expression
8889 ttl : number ;
90+ maxCount : number ;
8991 taskOptions ?: CronItemOptions ;
9092} ;
9193
@@ -97,6 +99,7 @@ export type ZodWorkerOptions<TMessageCatalog extends MessageCatalogSchema> = {
9799 tasks : ZodTasks < TMessageCatalog > ;
98100 recurringTasks ?: ZodRecurringTasks ;
99101 cleanup ?: ZodWorkerCleanupOptions ;
102+ reporter ?: ( subject : string , message : string ) => Promise < void > ;
100103} ;
101104
102105export class ZodWorker < TMessageCatalog extends MessageCatalogSchema > {
@@ -108,6 +111,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
108111 #recurringTasks?: ZodRecurringTasks ;
109112 #runner?: GraphileRunner ;
110113 #cleanup: ZodWorkerCleanupOptions | undefined ;
114+ #reporter?: ( subject : string , message : string ) => Promise < void > ;
111115
112116 constructor ( options : ZodWorkerOptions < TMessageCatalog > ) {
113117 this . #name = options . name ;
@@ -117,6 +121,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
117121 this . #tasks = options . tasks ;
118122 this . #recurringTasks = options . recurringTasks ;
119123 this . #cleanup = options . cleanup ;
124+ this . #reporter = options . reporter ;
120125 }
121126
122127 get graphileWorkerSchema ( ) {
@@ -356,6 +361,14 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
356361 taskList [ CLEANUP_TASK_NAME ] = task ;
357362 }
358363
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+
359372 return taskList ;
360373 }
361374
@@ -371,6 +384,14 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
371384 } ) ;
372385 }
373386
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+
374395 if ( ! this . #recurringTasks) {
375396 return cronItems ;
376397 }
@@ -493,8 +514,9 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
493514 } ) ;
494515
495516 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
498520 ) ;
499521
500522 const results = Array . isArray ( rawResults ) ? rawResults : [ ] ;
@@ -504,6 +526,65 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
504526 expirationDate,
505527 payload,
506528 } ) ;
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+ ) ;
507588 }
508589
509590 #logDebug( message : string , args ?: any ) {
0 commit comments