Skip to content

Commit 1739114

Browse files
perf: separate task creation from processing to eliminate compilation overhead (calcom#23485)
* refactor: separate task creation from processing to eliminate compilation overhead - Create TaskProcessor class to handle task processing logic - Move processQueue and cleanup methods to TaskProcessor - Remove task handler imports from InternalTasker creation path - Eliminate 2.5-3s compilation overhead by only loading task handlers during processing - Maintain existing tasker.create() API for all callers - Use composition pattern for clean separation of concerns Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> refactor: completely separate TaskProcessor from InternalTasker - Remove processQueue() and cleanup() methods from Tasker interface - Eliminate TaskProcessor dependency from InternalTasker class - Update API endpoints (cron.ts, cleanup.ts) to use TaskProcessor directly - Update README documentation to show correct TaskProcessor usage - Complete separation ensures task creation no longer imports task handlers - Eliminates 2.5-3s compilation overhead while preserving all functionality Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * Moved the cleanup method back to internal tasker --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 9724bc0 commit 1739114

5 files changed

Lines changed: 52 additions & 39 deletions

File tree

packages/features/tasker/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ For simplicity sake will explain how the `InternalTasker` works:
4545

4646
```ts
4747
// /app/api/tasks/cron/route.ts
48-
import tasker from "@calcom/features/tasker";
48+
import { TaskProcessor } from "@calcom/features/tasker/task-processor";
4949

5050
export async function GET() {
5151
// authenticate the call...
52-
await tasker.processQueue();
52+
const processor = new TaskProcessor();
53+
await processor.processQueue();
5354
return Response.json({ success: true });
5455
}
5556
```
@@ -62,11 +63,12 @@ For simplicity sake will explain how the `InternalTasker` works:
6263

6364
```ts
6465
// /app/api/tasks/cleanup/route.ts
65-
import tasker from "@calcom/features/tasker";
66+
import { TaskProcessor } from "@calcom/features/tasker/task-processor";
6667

6768
export async function GET() {
6869
// authenticate the call...
69-
await tasker.cleanup();
70+
const processor = new TaskProcessor();
71+
await processor.cleanup();
7072
return Response.json({ success: true });
7173
}
7274
```

packages/features/tasker/api/cron.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import type { NextRequest } from "next/server";
22
import { NextResponse } from "next/server";
33

4-
import tasker from "..";
4+
import { TaskProcessor } from "../task-processor";
55

66
async function handler(request: NextRequest) {
77
const authHeader = request.headers.get("authorization");
88
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
99
return new Response("Unauthorized", { status: 401 });
1010
}
11-
await tasker.processQueue();
11+
const processor = new TaskProcessor();
12+
await processor.processQueue();
1213
return NextResponse.json({ success: true });
1314
}
1415

packages/features/tasker/internal-tasker.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Task } from "./repository";
22
import type { TaskTypes } from "./tasker";
33
import { type TaskerCreate, type Tasker } from "./tasker";
4-
import tasksMap, { tasksConfig } from "./tasks";
54

65
/**
76
* This is the default internal Tasker that uses the Task repository to create tasks.
@@ -14,42 +13,12 @@ export class InternalTasker implements Tasker {
1413
const payloadString = typeof payload === "string" ? payload : JSON.stringify(payload);
1514
return Task.create(type, payloadString, options);
1615
};
17-
async processQueue(): Promise<void> {
18-
const tasks = await Task.getNextBatch();
19-
console.info(`Processing ${tasks.length} tasks`, tasks);
2016

21-
const tasksPromises = tasks.map(async (task) => {
22-
console.info(
23-
`Processing task ${task.id}, attempt:${task.attempts} maxAttempts:${task.maxAttempts} lastFailedAttempt:${task.lastFailedAttemptAt}`,
24-
task
25-
);
26-
const taskHandlerGetter = tasksMap[task.type as keyof typeof tasksMap];
27-
if (!taskHandlerGetter) throw new Error(`Task handler not found for type ${task.type}`);
28-
const taskConfig = tasksConfig[task.type as keyof typeof tasksConfig];
29-
const taskHandler = await taskHandlerGetter();
30-
return taskHandler(task.payload)
31-
.then(async () => {
32-
await Task.succeed(task.id);
33-
})
34-
.catch(async (error) => {
35-
console.info(`Retrying task ${task.id}: ${error}`);
36-
await Task.retry({
37-
taskId: task.id,
38-
lastError: error instanceof Error ? error.message : "Unknown error",
39-
minRetryIntervalMins:
40-
taskConfig && "minRetryIntervalMins" in taskConfig ? taskConfig.minRetryIntervalMins : null,
41-
});
42-
});
43-
});
44-
const settled = await Promise.allSettled(tasksPromises);
45-
const failed = settled.filter((result) => result.status === "rejected");
46-
const succeded = settled.filter((result) => result.status === "fulfilled");
47-
console.info({ failed, succeded });
48-
}
4917
async cleanup(): Promise<void> {
5018
const count = await Task.cleanup();
5119
console.info(`Cleaned up ${count} tasks`);
5220
}
21+
5322
async cancel(id: string): Promise<string> {
5423
const task = await Task.cancel(id);
5524
return task.id;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Task } from "./repository";
2+
import tasksMap, { tasksConfig } from "./tasks";
3+
4+
/**
5+
* TaskProcessor handles the processing of tasks from the queue.
6+
* This is separated from task creation to avoid importing all task handlers
7+
* when only creating tasks, which eliminates compilation overhead.
8+
*/
9+
export class TaskProcessor {
10+
async processQueue(): Promise<void> {
11+
const tasks = await Task.getNextBatch();
12+
console.info(`Processing ${tasks.length} tasks`, tasks);
13+
14+
const tasksPromises = tasks.map(async (task) => {
15+
console.info(
16+
`Processing task ${task.id}, attempt:${task.attempts} maxAttempts:${task.maxAttempts} lastFailedAttempt:${task.lastFailedAttemptAt}`,
17+
task
18+
);
19+
const taskHandlerGetter = tasksMap[task.type as keyof typeof tasksMap];
20+
if (!taskHandlerGetter) throw new Error(`Task handler not found for type ${task.type}`);
21+
const taskConfig = tasksConfig[task.type as keyof typeof tasksConfig];
22+
const taskHandler = await taskHandlerGetter();
23+
return taskHandler(task.payload)
24+
.then(async () => {
25+
await Task.succeed(task.id);
26+
})
27+
.catch(async (error) => {
28+
console.info(`Retrying task ${task.id}: ${error}`);
29+
await Task.retry({
30+
taskId: task.id,
31+
lastError: error instanceof Error ? error.message : "Unknown error",
32+
minRetryIntervalMins:
33+
taskConfig && "minRetryIntervalMins" in taskConfig ? taskConfig.minRetryIntervalMins : null,
34+
});
35+
});
36+
});
37+
const settled = await Promise.allSettled(tasksPromises);
38+
const failed = settled.filter((result) => result.status === "rejected");
39+
const succeded = settled.filter((result) => result.status === "fulfilled");
40+
console.info({ failed, succeded });
41+
}
42+
}

packages/features/tasker/tasker.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export type TaskerCreate = <TaskKey extends keyof TaskPayloads>(
4242
export interface Tasker {
4343
/** Create a new task with the given type and payload. */
4444
create: TaskerCreate;
45-
processQueue(): Promise<void>;
4645
cleanup(): Promise<void>;
4746
cancel(id: string): Promise<string>;
4847
cancelWithReference(referenceUid: string, type: TaskTypes): Promise<string | null>;

0 commit comments

Comments
 (0)