perf(webapp): scope declarative schedule sync to the current environment (#4577)

## Summary

Background worker registration runs on every deploy and every `trigger
dev` file save. Its declarative-schedule reconcile loaded every
declarative schedule for the whole project across all environments, then
re-fetched the deletion candidates it already had in memory. For
projects with many scheduled tasks or many environments, that meant
reading tens of thousands of rows on each registration. This scopes the
load to the environment being registered, drops the redundant re-fetch,
and selects only the columns the reconcile needs.

It also fixes the schedule-limit count (`getUsedSchedulesCount`), which
joined `TaskSchedule` and `RuntimeEnvironment` without a project
constraint and could scan those tables in full. Pushing `projectId` onto
both joins gives it a project-scoped index path with the same result.

Follow-up to
[#4522](https://github.com/triggerdotdev/trigger.dev/pull/4522), which
batched the delete side of the same reconcile.
This commit is contained in:
Eric Allam
2026-08-12 08:16:55 +01:00
committed by GitHub
parent 26cdedda1c
commit 326e9950f4
3 changed files with 25 additions and 12 deletions
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---
Make background worker registration cheaper for projects with many scheduled tasks by scoping declarative schedule reconciliation to the current environment and dropping redundant schedule lookups.
@@ -131,12 +131,14 @@ export class CheckScheduleService extends BaseService {
projectId,
active: true,
environment: {
projectId,
type: {
not: "DEVELOPMENT",
},
archivedAt: null,
},
taskSchedule: {
projectId,
active: true,
},
},
@@ -655,9 +655,21 @@ export async function syncDeclarativeSchedules(
where: {
type: "DECLARATIVE",
projectId: environment.projectId,
instances: {
some: {
environmentId: environment.id,
},
},
},
include: {
instances: true,
select: {
id: true,
friendlyId: true,
taskIdentifier: true,
instances: {
select: {
environmentId: true,
},
},
},
});
@@ -764,16 +776,9 @@ export async function syncDeclarativeSchedules(
//Delete instances for this environment
//Delete schedules that have no instances left
const potentiallyDeletableSchedules = await prisma.taskSchedule.findMany({
where: {
id: {
in: boundedIn(Array.from(missingSchedules)),
},
},
include: {
instances: true,
},
});
const potentiallyDeletableSchedules = existingDeclarativeSchedules.filter((schedule) =>
missingSchedules.has(schedule.id)
);
const scheduleIdsToDelete: string[] = [];
const scheduleIdsToDetachFromEnvironment: string[] = [];