Skip to content

Commit

Permalink
Cleanup finished graphile_worker.jobs last run more than 7 days ago
Browse files Browse the repository at this point in the history
  • Loading branch information
ericallam committed Oct 10, 2023
1 parent 476e2f0 commit 773765f
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 5 deletions.
74 changes: 73 additions & 1 deletion apps/webapp/app/platform/zodWorker.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { run as graphileRun, parseCronItems } from "graphile-worker";
import omit from "lodash.omit";
import { z } from "zod";
import { PrismaClient, PrismaClientOrTransaction } from "~/db.server";
import { logger } from "~/services/logger.server";
import { workerLogger as logger } from "~/services/logger.server";

export interface MessageCatalogSchema {
[key: string]: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
Expand Down Expand Up @@ -81,13 +81,22 @@ export type ZodWorkerDequeueOptions = {
tx?: PrismaClientOrTransaction;
};

const CLEANUP_TASK_NAME = "__cleanupOldJobs";

export type ZodWorkerCleanupOptions = {
frequencyExpression: string; // cron expression
ttl: number;
taskOptions?: CronItemOptions;
};

export type ZodWorkerOptions<TMessageCatalog extends MessageCatalogSchema> = {
name: string;
runnerOptions: RunnerOptions;
prisma: PrismaClient;
schema: TMessageCatalog;
tasks: ZodTasks<TMessageCatalog>;
recurringTasks?: ZodRecurringTasks;
cleanup?: ZodWorkerCleanupOptions;
};

export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
Expand All @@ -98,6 +107,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
#tasks: ZodTasks<TMessageCatalog>;
#recurringTasks?: ZodRecurringTasks;
#runner?: GraphileRunner;
#cleanup: ZodWorkerCleanupOptions | undefined;

constructor(options: ZodWorkerOptions<TMessageCatalog>) {
this.#name = options.name;
Expand All @@ -106,6 +116,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
this.#runnerOptions = options.runnerOptions;
this.#tasks = options.tasks;
this.#recurringTasks = options.recurringTasks;
this.#cleanup = options.cleanup;
}

get graphileWorkerSchema() {
Expand Down Expand Up @@ -337,12 +348,29 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
taskList[key] = task;
}

if (this.#cleanup) {
const task: Task = (payload, helpers) => {
return this.#handleCleanup(payload, helpers);
};

taskList[CLEANUP_TASK_NAME] = task;
}

return taskList;
}

#createCronItemsFromRecurringTasks() {
const cronItems: CronItem[] = [];

if (this.#cleanup) {
cronItems.push({
pattern: this.#cleanup.frequencyExpression,
identifier: CLEANUP_TASK_NAME,
task: CLEANUP_TASK_NAME,
options: this.#cleanup.taskOptions,
});
}

if (!this.#recurringTasks) {
return cronItems;
}
Expand Down Expand Up @@ -434,6 +462,50 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
}
}

async #handleCleanup(rawPayload: unknown, helpers: JobHelpers): Promise<void> {
if (!this.#cleanup) {
return;
}

const job = helpers.job;

logger.debug("Received cleanup task", {
payload: rawPayload,
job,
});

const parsedPayload = RawCronPayloadSchema.safeParse(rawPayload);

if (!parsedPayload.success) {
throw new Error(
`Failed to parse cleanup task payload: ${JSON.stringify(parsedPayload.error)}`
);
}

const payload = parsedPayload.data;

// Add the this.#cleanup.ttl to the payload._cron.ts
const expirationDate = new Date(payload._cron.ts.getTime() - this.#cleanup.ttl);

logger.debug("Cleaning up old jobs", {
expirationDate,
payload,
});

const rawResults = await this.#prisma.$queryRawUnsafe(
`DELETE FROM ${this.graphileWorkerSchema}.jobs WHERE run_at < $1 AND locked_at IS NULL AND max_attempts = attempts RETURNING id`,
expirationDate
);

const results = Array.isArray(rawResults) ? rawResults : [];

logger.debug("Cleaned up old jobs", {
count: results.length,
expirationDate,
payload,
});
}

#logDebug(message: string, args?: any) {
logger.debug(`[worker][${this.#name}] ${message}`, args);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/routes/api.v1.runs.$runId.tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export class RunTaskService {
{
id: task.id,
},
{ tx, runAt: task.delayUntil ?? undefined }
{ tx, runAt: task.delayUntil ?? undefined, jobKey: `operation:${task.id}` }
);
} else if (task.status === "WAITING" && callbackUrl && taskBody.callback) {
if (taskBody.callback.timeoutInSeconds > 0) {
Expand Down
7 changes: 7 additions & 0 deletions apps/webapp/app/services/logger.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ export const logger = new Logger(
["examples", "output", "connectionString", "payload"],
sensitiveDataReplacer
);

export const workerLogger = new Logger(
"worker",
(process.env.APP_LOG_LEVEL ?? "debug") as LogLevel,
["examples", "output", "connectionString"],
sensitiveDataReplacer
);
10 changes: 7 additions & 3 deletions apps/webapp/app/services/worker.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ function getWorkerQueue() {
return new ZodWorker({
name: "workerQueue",
prisma,
cleanup: {
// cleanup once per hour
frequencyExpression: "0 * * * *",
// delete jobs that have been completed for more than 7 days
ttl: 7 * 24 * 60 * 60 * 1000,
},
runnerOptions: {
connectionString: env.DATABASE_URL,
concurrency: env.WORKER_CONCURRENCY,
Expand Down Expand Up @@ -229,6 +235,7 @@ function getWorkerQueue() {
deliverHttpSourceRequest: {
priority: 1, // smaller number = higher priority
maxAttempts: 14,
queueName: (payload) => `sources:${payload.id}`,
handler: async (payload, job) => {
const service = new DeliverHttpSourceRequestService();

Expand All @@ -255,7 +262,6 @@ function getWorkerQueue() {
},
performTaskOperation: {
priority: 0, // smaller number = higher priority
queueName: (payload) => `tasks:${payload.id}`,
maxAttempts: 3,
handler: async (payload, job) => {
const service = new PerformTaskOperationService();
Expand All @@ -264,7 +270,6 @@ function getWorkerQueue() {
},
},
scheduleEmail: {
queueName: "internal-queue",
priority: 100,
maxAttempts: 3,
handler: async (payload, job) => {
Expand All @@ -291,7 +296,6 @@ function getWorkerQueue() {
},
refreshOAuthToken: {
priority: 8, // smaller number = higher priority
queueName: "internal-queue",
maxAttempts: 7,
handler: async (payload, job) => {
await integrationAuthRepository.refreshConnection({
Expand Down

0 comments on commit 773765f

Please sign in to comment.