Skip to content

Commit 34ca766

Browse files
authored
Various perf improvements to prevent event loop lag (#1186)
* WIP * Handle tasks that have failed but are being auto yielded * Limit trace view to 25k event records, add a download run logs button Also added two new indexes to TaskEvent: ``` /// Used on eventRepository.getTraceSummary() @@index([traceId, startTime]) // Used for getting all logs for a run @@index([runId]) ``` * perf improvements on eventRepository.getSpan() * v2: Add a 5 minute timeout for run execution requests in dev * v3: Include presigned urls for downloading large payloads and outputs when using runs.retrieve * v3: better handle large task payloads and outputs * Change to 512KB * v2: paginate trigger schedules endpoint * v3: add 3MB limit on batch and single payloads * Update task payload and output limits
1 parent 3e327ac commit 34ca766

File tree

46 files changed

+211308
-491
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+211308
-491
lines changed

.changeset/thick-carrots-sneeze.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"@trigger.dev/sdk": patch
4+
---
5+
6+
v3: Include presigned urls for downloading large payloads and outputs when using runs.retrieve

apps/webapp/app/env.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ const EnvironmentSchema = z.object({
206206
USAGE_OPEN_METER_BASE_URL: z.string().optional(),
207207
EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"),
208208
MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000),
209+
MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000),
210+
TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB
211+
TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB
209212
});
210213

211214
export type Environment = z.infer<typeof EnvironmentSchema>;

apps/webapp/app/presenters/RunListPresenter.server.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { z } from "zod";
21
import {
32
Direction,
43
FilterableEnvironment,
54
FilterableStatus,
65
filterableStatuses,
76
} from "~/components/runs/RunStatuses";
8-
import { PrismaClient, prisma } from "~/db.server";
97
import { getUsername } from "~/utils/username";
108
import { BasePresenter } from "./v3/basePresenter.server";
119

@@ -29,8 +27,6 @@ const DEFAULT_PAGE_SIZE = 20;
2927
export type RunList = Awaited<ReturnType<RunListPresenter["call"]>>;
3028

3129
export class RunListPresenter extends BasePresenter {
32-
33-
3430
public async call({
3531
userId,
3632
eventId,

apps/webapp/app/presenters/ScheduledTriggersPresenter.server.ts

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,52 @@
1-
import { User } from "@trigger.dev/database";
21
import { ScheduleMetadataSchema } from "@trigger.dev/core";
3-
import { PrismaClient, prisma } from "~/db.server";
2+
import { User } from "@trigger.dev/database";
43
import { Organization } from "~/models/organization.server";
54
import { Project } from "~/models/project.server";
65
import { calculateNextScheduledEvent } from "~/services/schedules/nextScheduledEvent.server";
6+
import { BasePresenter } from "./v3/basePresenter.server";
77

8-
export class ScheduledTriggersPresenter {
9-
#prismaClient: PrismaClient;
10-
11-
constructor(prismaClient: PrismaClient = prisma) {
12-
this.#prismaClient = prismaClient;
13-
}
8+
const DEFAULT_PAGE_SIZE = 20;
149

10+
export class ScheduledTriggersPresenter extends BasePresenter {
1511
public async call({
1612
userId,
1713
projectSlug,
1814
organizationSlug,
15+
direction = "forward",
16+
pageSize = DEFAULT_PAGE_SIZE,
17+
cursor,
1918
}: {
2019
userId: User["id"];
2120
projectSlug: Project["slug"];
2221
organizationSlug: Organization["slug"];
22+
direction?: "forward" | "backward";
23+
pageSize?: number;
24+
cursor?: string;
2325
}) {
24-
const scheduled = await this.#prismaClient.scheduleSource.findMany({
26+
const organization = await this._replica.organization.findFirstOrThrow({
27+
select: {
28+
id: true,
29+
},
30+
where: {
31+
slug: organizationSlug,
32+
members: { some: { userId } },
33+
},
34+
});
35+
36+
// Find the project scoped to the organization
37+
const project = await this._replica.project.findFirstOrThrow({
38+
select: {
39+
id: true,
40+
},
41+
where: {
42+
slug: projectSlug,
43+
organizationId: organization.id,
44+
},
45+
});
46+
47+
const directionMultiplier = direction === "forward" ? 1 : -1;
48+
49+
const scheduled = await this._replica.scheduleSource.findMany({
2550
select: {
2651
id: true,
2752
key: true,
@@ -50,23 +75,50 @@ export class ScheduledTriggersPresenter {
5075
},
5176
},
5277
],
53-
organization: {
54-
slug: organizationSlug,
55-
members: {
56-
some: {
57-
userId,
58-
},
59-
},
60-
},
61-
project: {
62-
slug: projectSlug,
63-
},
78+
projectId: project.id,
6479
},
6580
},
81+
orderBy: [{ id: "desc" }],
82+
//take an extra record to tell if there are more
83+
take: directionMultiplier * (pageSize + 1),
84+
//skip the cursor if there is one
85+
skip: cursor ? 1 : 0,
86+
cursor: cursor
87+
? {
88+
id: cursor,
89+
}
90+
: undefined,
6691
});
6792

93+
const hasMore = scheduled.length > pageSize;
94+
95+
//get cursors for next and previous pages
96+
let next: string | undefined;
97+
let previous: string | undefined;
98+
switch (direction) {
99+
case "forward":
100+
previous = cursor ? scheduled.at(0)?.id : undefined;
101+
if (hasMore) {
102+
next = scheduled[pageSize - 1]?.id;
103+
}
104+
break;
105+
case "backward":
106+
if (hasMore) {
107+
previous = scheduled[1]?.id;
108+
next = scheduled[pageSize]?.id;
109+
} else {
110+
next = scheduled[pageSize - 1]?.id;
111+
}
112+
break;
113+
}
114+
115+
const scheduledToReturn =
116+
direction === "backward" && hasMore
117+
? scheduled.slice(1, pageSize + 1)
118+
: scheduled.slice(0, pageSize);
119+
68120
return {
69-
scheduled: scheduled.map((s) => {
121+
scheduled: scheduledToReturn.map((s) => {
70122
const schedule = ScheduleMetadataSchema.parse(s.schedule);
71123
const nextEventTimestamp = s.active
72124
? calculateNextScheduledEvent(schedule, s.lastEventTimestamp)
@@ -78,6 +130,10 @@ export class ScheduledTriggersPresenter {
78130
nextEventTimestamp,
79131
};
80132
}),
133+
pagination: {
134+
next,
135+
previous,
136+
},
81137
};
82138
}
83139
}

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
import { Prisma, TaskRunAttemptStatus, TaskRunStatus } from "@trigger.dev/database";
1313
import assertNever from "assert-never";
1414
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
15+
import { generatePresignedUrl } from "~/v3/r2.server";
1516
import { BasePresenter } from "./basePresenter.server";
1617

1718
export class ApiRetrieveRunPresenter extends BasePresenter {
@@ -44,15 +45,29 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
4445
}
4546

4647
let $payload: any;
48+
let $payloadPresignedUrl: string | undefined;
4749
let $output: any;
50+
let $outputPresignedUrl: string | undefined;
4851

4952
if (showSecretDetails) {
5053
const payloadPacket = await conditionallyImportPacket({
5154
data: taskRun.payload,
5255
dataType: taskRun.payloadType,
5356
});
5457

55-
$payload = await parsePacket(payloadPacket);
58+
if (
59+
payloadPacket.dataType === "application/store" &&
60+
typeof payloadPacket.data === "string"
61+
) {
62+
$payloadPresignedUrl = await generatePresignedUrl(
63+
env.project.externalRef,
64+
env.slug,
65+
payloadPacket.data,
66+
"GET"
67+
);
68+
} else {
69+
$payload = await parsePacket(payloadPacket);
70+
}
5671

5772
if (taskRun.status === "COMPLETED_SUCCESSFULLY") {
5873
const completedAttempt = taskRun.attempts.find(
@@ -65,7 +80,19 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
6580
dataType: completedAttempt.outputType,
6681
});
6782

68-
$output = await parsePacket(outputPacket);
83+
if (
84+
outputPacket.dataType === "application/store" &&
85+
typeof outputPacket.data === "string"
86+
) {
87+
$outputPresignedUrl = await generatePresignedUrl(
88+
env.project.externalRef,
89+
env.slug,
90+
outputPacket.data,
91+
"GET"
92+
);
93+
} else {
94+
$output = await parsePacket(outputPacket);
95+
}
6996
}
7097
}
7198
}
@@ -85,7 +112,9 @@ export class ApiRetrieveRunPresenter extends BasePresenter {
85112
? taskRun.updatedAt
86113
: undefined,
87114
payload: $payload,
115+
payloadPresignedUrl: $payloadPresignedUrl,
88116
output: $output,
117+
outputPresignedUrl: $outputPresignedUrl,
89118
isTest: taskRun.isTest,
90119
schedule: taskRun.schedule
91120
? {

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers.scheduled/route.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { NoSymbolIcon } from "@heroicons/react/20/solid";
22
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
33
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
44
import { typedjson, useTypedLoaderData } from "remix-typedjson";
5+
import { z } from "zod";
6+
import { ListPagination } from "~/components/ListPagination";
57
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
68
import { DateTime } from "~/components/primitives/DateTime";
79
import { LabelValueStack } from "~/components/primitives/LabelValueStack";
@@ -17,30 +19,38 @@ import {
1719
TableRow,
1820
} from "~/components/primitives/Table";
1921
import { TextLink } from "~/components/primitives/TextLink";
20-
import { useOrganization } from "~/hooks/useOrganizations";
21-
import { useProject } from "~/hooks/useProject";
22+
import { DirectionSchema } from "~/components/runs/RunStatuses";
2223
import { ScheduledTriggersPresenter } from "~/presenters/ScheduledTriggersPresenter.server";
2324
import { requireUserId } from "~/services/session.server";
2425
import { ProjectParamSchema, docsPath } from "~/utils/pathBuilder";
2526

27+
const SearchSchema = z.object({
28+
cursor: z.string().optional(),
29+
direction: DirectionSchema.optional(),
30+
});
31+
2632
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
2733
const userId = await requireUserId(request);
2834
const { organizationSlug, projectParam } = ProjectParamSchema.parse(params);
2935

36+
const url = new URL(request.url);
37+
const s = Object.fromEntries(url.searchParams.entries());
38+
const searchParams = SearchSchema.parse(s);
39+
3040
const presenter = new ScheduledTriggersPresenter();
3141
const data = await presenter.call({
3242
userId,
3343
organizationSlug,
3444
projectSlug: projectParam,
45+
direction: searchParams.direction,
46+
cursor: searchParams.cursor,
3547
});
3648

3749
return typedjson(data);
3850
};
3951

40-
export default function Integrations() {
41-
const { scheduled } = useTypedLoaderData<typeof loader>();
42-
const organization = useOrganization();
43-
const project = useProject();
52+
export default function Route() {
53+
const { scheduled, pagination } = useTypedLoaderData<typeof loader>();
4454

4555
return (
4656
<>
@@ -49,6 +59,10 @@ export default function Integrations() {
4959
expression or an interval.
5060
</Paragraph>
5161

62+
{scheduled.length > 0 && (
63+
<ListPagination list={{ pagination }} className="mt-2 justify-end" />
64+
)}
65+
5266
<Table containerClassName="mt-4">
5367
<TableHeader>
5468
<TableRow>

0 commit comments

Comments
 (0)