Skip to content

Commit 83d0e87

Browse files
authored
Fix for Schedules list page slow loading (#1992)
* Fix for Schedules list page slow loading Getting BackgroundWorkerTask was very slow (Prisma was getting every single one…) * Same fix for the upserting of schedules in the dashboard
1 parent 280a6d0 commit 83d0e87

File tree

9 files changed

+81
-47
lines changed

9 files changed

+81
-47
lines changed

apps/webapp/app/components/runs/v3/ScheduleFilters.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ export const ScheduleListFilters = z.object({
2424
.string()
2525
.optional()
2626
.transform((value) => (value ? value.split(",") : undefined)),
27-
environments: z
28-
.string()
29-
.optional()
30-
.transform((value) => (value ? value.split(",") : undefined)),
3127
type: z.union([z.literal("declarative"), z.literal("imperative")]).optional(),
3228
search: z.string().optional(),
3329
});
@@ -44,7 +40,7 @@ export function ScheduleFilters({ possibleTasks }: ScheduleFiltersProps) {
4440
const navigate = useNavigate();
4541
const location = useOptimisticLocation();
4642
const searchParams = new URLSearchParams(location.search);
47-
const { environments, tasks, page, search, type } = ScheduleListFilters.parse(
43+
const { tasks, page, search, type } = ScheduleListFilters.parse(
4844
Object.fromEntries(searchParams.entries())
4945
);
5046

apps/webapp/app/presenters/v3/EditSchedulePresenter.server.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import { RuntimeEnvironmentType } from "@trigger.dev/database";
2-
import { PrismaClient, prisma } from "~/db.server";
3-
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
1+
import { type RuntimeEnvironmentType } from "@trigger.dev/database";
2+
import { type PrismaClient, prisma } from "~/db.server";
3+
import { displayableEnvironment, findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
44
import { logger } from "~/services/logger.server";
55
import { filterOrphanedEnvironments } from "~/utils/environmentSort";
66
import { getTimezones } from "~/utils/timezones.server";
7+
import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server";
8+
import { ServiceValidationError } from "~/v3/services/baseService.server";
79

810
type EditScheduleOptions = {
911
userId: string;
1012
projectSlug: string;
13+
environmentSlug: string;
1114
friendlyId?: string;
1215
};
1316

@@ -26,7 +29,7 @@ export class EditSchedulePresenter {
2629
this.#prismaClient = prismaClient;
2730
}
2831

29-
public async call({ userId, projectSlug, friendlyId }: EditScheduleOptions) {
32+
public async call({ userId, projectSlug, environmentSlug, friendlyId }: EditScheduleOptions) {
3033
// Find the project scoped to the organization
3134
const project = await this.#prismaClient.project.findFirstOrThrow({
3235
select: {
@@ -62,13 +65,25 @@ export class EditSchedulePresenter {
6265
},
6366
});
6467

65-
const possibleTasks = await this.#prismaClient.backgroundWorkerTask.findMany({
66-
distinct: ["slug"],
67-
where: {
68-
projectId: project.id,
69-
triggerSource: "SCHEDULED",
70-
},
71-
});
68+
const environment = await findEnvironmentBySlug(project.id, environmentSlug, userId);
69+
if (!environment) {
70+
throw new ServiceValidationError("No matching environment for project", 404);
71+
}
72+
73+
//get the latest BackgroundWorker
74+
const latestWorker = await findCurrentWorkerFromEnvironment(environment, this.#prismaClient);
75+
76+
//get all possible scheduled tasks
77+
const possibleTasks = latestWorker
78+
? await this.#prismaClient.backgroundWorkerTask.findMany({
79+
where: {
80+
workerId: latestWorker.id,
81+
projectId: project.id,
82+
runtimeEnvironmentId: environment.id,
83+
triggerSource: "SCHEDULED",
84+
},
85+
})
86+
: [];
7287

7388
const possibleEnvironments = filterOrphanedEnvironments(project.environments).map(
7489
(environment) => {
@@ -77,7 +92,7 @@ export class EditSchedulePresenter {
7792
);
7893

7994
return {
80-
possibleTasks: possibleTasks.map((task) => task.slug),
95+
possibleTasks: possibleTasks.map((task) => task.slug).sort(),
8196
possibleEnvironments,
8297
possibleTimezones: getTimezones(),
8398
schedule: await this.#getExistingSchedule(friendlyId, possibleEnvironments),

apps/webapp/app/presenters/v3/ScheduleListPresenter.server.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import { getLimit } from "~/services/platform.v3.server";
66
import { CheckScheduleService } from "~/v3/services/checkSchedule.server";
77
import { calculateNextScheduledTimestamp } from "~/v3/utils/calculateNextSchedule.server";
88
import { BasePresenter } from "./basePresenter.server";
9+
import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server";
10+
import { ServiceValidationError } from "~/v3/services/baseService.server";
911

1012
type ScheduleListOptions = {
1113
projectId: string;
14+
environmentId: string;
1215
userId?: string;
1316
pageSize?: number;
1417
} & ScheduleListFilters;
@@ -42,8 +45,8 @@ export class ScheduleListPresenter extends BasePresenter {
4245
public async call({
4346
userId,
4447
projectId,
48+
environmentId,
4549
tasks,
46-
environments,
4750
search,
4851
page,
4952
type,
@@ -84,16 +87,45 @@ export class ScheduleListPresenter extends BasePresenter {
8487
},
8588
});
8689

90+
const environment = project.environments.find((env) => env.id === environmentId);
91+
if (!environment) {
92+
throw new ServiceValidationError("No matching environment for project", 404);
93+
}
94+
8795
const schedulesCount = await CheckScheduleService.getUsedSchedulesCount({
8896
prisma: this._replica,
8997
environments: project.environments,
9098
});
9199

100+
const limit = await getLimit(project.organizationId, "schedules", 100_000_000);
101+
102+
//get the latest BackgroundWorker
103+
const latestWorker = await findCurrentWorkerFromEnvironment(environment, this._replica);
104+
if (!latestWorker) {
105+
return {
106+
currentPage: 1,
107+
totalPages: 1,
108+
totalCount: 0,
109+
schedules: [],
110+
possibleTasks: [],
111+
hasFilters,
112+
limits: {
113+
used: schedulesCount,
114+
limit,
115+
},
116+
filters: {
117+
tasks,
118+
search,
119+
},
120+
};
121+
}
122+
92123
//get all possible scheduled tasks
93124
const possibleTasks = await this._replica.backgroundWorkerTask.findMany({
94-
distinct: ["slug"],
95125
where: {
126+
workerId: latestWorker.id,
96127
projectId: project.id,
128+
runtimeEnvironmentId: environmentId,
97129
triggerSource: "SCHEDULED",
98130
},
99131
});
@@ -107,7 +139,7 @@ export class ScheduleListPresenter extends BasePresenter {
107139
taskIdentifier: tasks ? { in: tasks } : undefined,
108140
instances: {
109141
some: {
110-
environmentId: environments ? { in: environments } : undefined,
142+
environmentId,
111143
},
112144
},
113145
type: filterType,
@@ -168,13 +200,11 @@ export class ScheduleListPresenter extends BasePresenter {
168200
where: {
169201
projectId: project.id,
170202
taskIdentifier: tasks ? { in: tasks } : undefined,
171-
instances: environments
172-
? {
173-
some: {
174-
environmentId: environments ? { in: environments } : undefined,
175-
},
176-
}
177-
: undefined,
203+
instances: {
204+
some: {
205+
environmentId,
206+
},
207+
},
178208
type: filterType,
179209
AND: search
180210
? {
@@ -242,25 +272,19 @@ export class ScheduleListPresenter extends BasePresenter {
242272
};
243273
});
244274

245-
const limit = await getLimit(project.organizationId, "schedules", 100_000_000);
246-
247275
return {
248276
currentPage: page,
249277
totalPages: Math.ceil(totalCount / pageSize),
250278
totalCount: totalCount,
251279
schedules,
252-
possibleTasks: possibleTasks.map((task) => task.slug),
253-
possibleEnvironments: project.environments.map((environment) => {
254-
return displayableEnvironment(environment, userId);
255-
}),
280+
possibleTasks: possibleTasks.map((task) => task.slug).sort((a, b) => a.localeCompare(b)),
256281
hasFilters,
257282
limits: {
258283
used: schedulesCount,
259284
limit,
260285
},
261286
filters: {
262287
tasks,
263-
environments,
264288
search,
265289
},
266290
};

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.edit.$scheduleParam/route.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
1515
const result = await presenter.call({
1616
userId,
1717
projectSlug: projectParam,
18+
environmentSlug: envParam,
1819
friendlyId: scheduleParam,
1920
});
2021

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
22
import { typedjson, useTypedLoaderData } from "remix-typedjson";
33
import { EditSchedulePresenter } from "~/presenters/v3/EditSchedulePresenter.server";
44
import { requireUserId } from "~/services/session.server";
5-
import { ProjectParamSchema } from "~/utils/pathBuilder";
5+
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
66
import { humanToCronSupported } from "~/v3/humanToCron.server";
77
import { UpsertScheduleForm } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route";
88

99
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
1010
const userId = await requireUserId(request);
11-
const { projectParam, organizationSlug } = ProjectParamSchema.parse(params);
11+
const { projectParam, envParam, organizationSlug } = EnvironmentParamSchema.parse(params);
1212

1313
const presenter = new EditSchedulePresenter();
1414
const result = await presenter.call({
1515
userId,
1616
projectSlug: projectParam,
17+
environmentSlug: envParam,
1718
});
1819

1920
return typedjson({ ...result, showGenerateField: humanToCronSupported });

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules/route.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,28 +93,21 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
9393
const url = new URL(request.url);
9494
const s = Object.fromEntries(url.searchParams.entries());
9595
const filters = ScheduleListFilters.parse(s);
96-
filters.environments = [environment.id];
9796

9897
const presenter = new ScheduleListPresenter();
9998
const list = await presenter.call({
10099
userId,
101100
projectId: project.id,
101+
environmentId: environment.id,
102102
...filters,
103103
});
104104

105105
return typedjson(list);
106106
};
107107

108108
export default function Page() {
109-
const {
110-
schedules,
111-
possibleTasks,
112-
possibleEnvironments,
113-
hasFilters,
114-
limits,
115-
currentPage,
116-
totalPages,
117-
} = useTypedLoaderData<typeof loader>();
109+
const { schedules, possibleTasks, hasFilters, limits, currentPage, totalPages } =
110+
useTypedLoaderData<typeof loader>();
118111
const location = useLocation();
119112
const organization = useOrganization();
120113
const project = useProject();

apps/webapp/app/routes/api.v1.schedules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ export async function loader({ request }: LoaderFunctionArgs) {
100100

101101
const result = await presenter.call({
102102
projectId: authenticationResult.environment.projectId,
103+
environmentId: authenticationResult.environment.id,
103104
page: params.data.page ?? 1,
104105
pageSize: params.data.perPage,
105-
environments: [authenticationResult.environment.id],
106106
});
107107

108108
return {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- CreateIndex
2+
CREATE INDEX CONCURRENTLY IF NOT EXISTS "BackgroundWorker_runtimeEnvironmentId_createdAt_idx" ON "BackgroundWorker" ("runtimeEnvironmentId", "createdAt" DESC);

internal-packages/database/prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,8 @@ model BackgroundWorker {
16271627
16281628
@@unique([projectId, runtimeEnvironmentId, version])
16291629
@@index([runtimeEnvironmentId])
1630+
// Get the latest worker for a given environment
1631+
@@index([runtimeEnvironmentId, createdAt(sort: Desc)])
16301632
}
16311633

16321634
model BackgroundWorkerFile {

0 commit comments

Comments
 (0)