Skip to content

Commit 592c9bc

Browse files
MariefayTrigger.dev RepoOps
authored andcommitted
perf(webapp): bound finished-run trace reads by completion time
Reads of a finished run's trace events on the run page, in span details, the run events API and the dashboard agent's run trace are now bounded to events stored up to 7 days after the run completed, so opening an old run no longer scans every day of stored events since it started. Running runs stay unbounded and the logs download is unchanged. Mono-RevId: daaf4338a262842b760a4015cfe208649787451a
1 parent 46f0253 commit 592c9bc

15 files changed

Lines changed: 485 additions & 55 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Older runs now load their trace, span details and run events faster. A span that finishes more than a week after its run shows as still in progress.

‎apps/webapp/app/presenters/v3/RunPresenter.server.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { buildTraceView } from "~/v3/eventRepository/traceViewBuilder";
1212
import { runStore } from "~/v3/runStore.server";
1313
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
1414
import { runTriggeredAt } from "~/v3/runTimestamps";
15-
import { getRunFinishedAt } from "~/v3/eventRepository/traceInsertedAtBound";
15+
import { getRunFinishedAt, getTraceInsertedAtEnd } from "~/v3/eventRepository/traceInsertedAtBound";
1616

1717
export class RunEnvironmentMismatchError extends Error {
1818
constructor(message: string) {
@@ -193,14 +193,15 @@ export class RunPresenter {
193193
);
194194

195195
const traceScope = runTraceReadScope(run, environment.id);
196+
const insertedAtEnd = getTraceInsertedAtEnd(run);
196197
const queuedDuration = runQueuedDuration(run);
197198

198199
const firstChunkPromise = canLiveTail(
199200
run.taskEventStore,
200201
env.TRACE_VIEW_EMERGENCY_SPAN_CAP,
201202
env.EVENT_REPOSITORY_V1_REROUTE_ENABLED
202203
)
203-
? readFirstTraceChunk({ repository, scope: traceScope, showDebug })
204+
? readFirstTraceChunk({ repository, scope: traceScope, showDebug, insertedAtEnd })
204205
: Promise.resolve(undefined);
205206

206207
const [user, firstRead] = await Promise.all([
@@ -220,6 +221,7 @@ export class RunPresenter {
220221
firstRead,
221222
anchorSpanId: run.spanId,
222223
selectedSpanId,
224+
insertedAtEnd,
223225
buildOptions,
224226
startedAt: run.startedAt,
225227
queuedDuration,
@@ -259,7 +261,7 @@ export class RunPresenter {
259261
run.traceId,
260262
traceScope.startCreatedAt,
261263
traceScope.endCreatedAt,
262-
{ includeDebugLogs: showDebug, anchorSpanId: run.spanId }
264+
{ includeDebugLogs: showDebug, anchorSpanId: run.spanId, insertedAtEnd }
263265
);
264266

265267
let isTruncated = traceSummary?.isTruncated ?? false;
@@ -273,7 +275,7 @@ export class RunPresenter {
273275
traceScope.startCreatedAt,
274276
traceScope.endCreatedAt,
275277
// The anchor is the viewed run: a root has no ancestors, a child doesn't need them.
276-
{ includeDebugLogs: showDebug }
278+
{ includeDebugLogs: showDebug, insertedAtEnd }
277279
);
278280

279281
if (subtreeSummary) {

‎apps/webapp/app/presenters/v3/SpanPresenter.server.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ import {
5252
type ResolvedRunLockedWorker,
5353
} from "~/v3/runOpsMigration/controlPlaneResolver.server";
5454
import type { AuthenticatedEnvironment } from "@trigger.dev/core/v3/auth/environment";
55-
import { getTraceStartTimeEnd } from "~/v3/eventRepository/traceInsertedAtBound";
55+
import {
56+
getTraceInsertedAtEnd,
57+
getTraceStartTimeEnd,
58+
} from "~/v3/eventRepository/traceInsertedAtBound";
5659
import {
5760
findStartedSession,
5861
startedSessionExternalId,
@@ -238,6 +241,7 @@ export class SpanPresenter extends BasePresenter {
238241
projectId: parentRun.projectId,
239242
createdAt: parentRun.createdAt,
240243
endCreatedAt: getTraceStartTimeEnd(parentRun),
244+
insertedAtEnd: getTraceInsertedAtEnd(parentRun),
241245
eventRepository: repository,
242246
showDebug,
243247
rootRunId: traceRootRunId(parentRun),
@@ -706,6 +710,7 @@ export class SpanPresenter extends BasePresenter {
706710
projectId,
707711
createdAt,
708712
endCreatedAt,
713+
insertedAtEnd,
709714
showDebug,
710715
rootRunId,
711716
userId,
@@ -718,6 +723,7 @@ export class SpanPresenter extends BasePresenter {
718723
eventStore: TaskEventStoreTable;
719724
createdAt: Date;
720725
endCreatedAt: Date | undefined;
726+
insertedAtEnd: Date | undefined;
721727
showDebug: boolean;
722728
rootRunId: string;
723729
userId: string;
@@ -729,7 +735,7 @@ export class SpanPresenter extends BasePresenter {
729735
traceId,
730736
createdAt,
731737
endCreatedAt,
732-
{ includeDebugLogs: showDebug }
738+
{ includeDebugLogs: showDebug, insertedAtEnd }
733739
);
734740

735741
if (!span) {

‎apps/webapp/app/presenters/v3/TraceChunkPresenter.server.ts‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ const startRunSelect = {
6262
annotations: true,
6363
} satisfies Prisma.TaskRunSelect;
6464

65-
type ChunkRun = Prisma.TaskRunGetPayload<{ select: typeof chunkRunSelect }>;
66-
6765
type TraceChunkPresenterDeps = {
6866
prismaClient: PrismaClient;
6967
runStore: Pick<RunStore, "findRun">;
@@ -137,7 +135,7 @@ export class TraceChunkPresenter {
137135
);
138136

139137
const scope = runTraceReadScope(run, environment.id);
140-
const insertedAtEnd = traceInsertedAtEndFor(run, context);
138+
const insertedAtEnd = getTraceInsertedAtEnd(run);
141139
recordReadAttributes(context, insertedAtEnd);
142140

143141
if (filter === "errors") {
@@ -232,7 +230,7 @@ export class TraceChunkPresenter {
232230
);
233231

234232
const scope = runTraceReadScope(run, environment.id);
235-
const insertedAtEnd = traceInsertedAtEndFor(run, context);
233+
const insertedAtEnd = getTraceInsertedAtEnd(run);
236234
recordReadAttributes(context, insertedAtEnd);
237235

238236
const firstRead = await readFirstTraceChunk({ repository, scope, showDebug, insertedAtEnd });
@@ -295,11 +293,6 @@ export class TraceChunkPresenter {
295293
}
296294
}
297295

298-
// The run page reads unbounded: a child finishing long after the run would lose its final row.
299-
function traceInsertedAtEndFor(run: ChunkRun, context: TraceReadContext | undefined) {
300-
return context === "session" ? getTraceInsertedAtEnd(run) : undefined;
301-
}
302-
303296
function recordReadAttributes(
304297
context: TraceReadContext | undefined,
305298
insertedAtEnd: Date | undefined

‎apps/webapp/app/routes/api.v1.runs.$runId.events.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
44
import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
55
import { ApiRetrieveRunPresenter } from "~/presenters/v3/ApiRetrieveRunPresenter.server";
66
import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server";
7-
import { getTraceStartTimeEnd } from "~/v3/eventRepository/traceInsertedAtBound";
7+
import {
8+
getTraceInsertedAtEnd,
9+
getTraceStartTimeEnd,
10+
} from "~/v3/eventRepository/traceInsertedAtBound";
811

912
const ParamsSchema = z.object({
1013
runId: z.string(), // This is the run friendly ID
@@ -57,7 +60,8 @@ export const loader = createLoaderApiRoute(
5760
run.traceId,
5861
run.friendlyId,
5962
run.createdAt,
60-
getTraceStartTimeEnd(run)
63+
getTraceStartTimeEnd(run),
64+
{ insertedAtEnd: getTraceInsertedAtEnd(run) }
6165
);
6266

6367
// TODO: return only relevant fields, avoid returning the whole events

‎apps/webapp/app/routes/api.v1.runs.$runId.spans.$spanId.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { $replica } from "~/db.server";
55
import { extractAISpanData } from "~/components/runs/v3/ai";
66
import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
77
import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server";
8-
import { getTraceStartTimeEnd } from "~/v3/eventRepository/traceInsertedAtBound";
8+
import {
9+
getTraceInsertedAtEnd,
10+
getTraceStartTimeEnd,
11+
} from "~/v3/eventRepository/traceInsertedAtBound";
912
import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
1013
import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server";
1114
import { buildSyntheticSpanDetailBody } from "~/v3/mollifier/syntheticApiResponses.server";
@@ -107,7 +110,8 @@ export const loader = createLoaderApiRoute(
107110
params.spanId,
108111
run.traceId,
109112
run.createdAt,
110-
getTraceStartTimeEnd(run)
113+
getTraceStartTimeEnd(run),
114+
{ insertedAtEnd: getTraceInsertedAtEnd(run) }
111115
);
112116

113117
if (!span) {

‎apps/webapp/app/services/dashboardAgentRunTrace.server.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { logger } from "~/services/logger.server";
77
import { getAdminOnlyForEvent } from "~/utils/timelineSpanEvents";
88
import { type SpanSummary, type TraceSummary } from "~/v3/eventRepository/eventRepository.types";
99
import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server";
10-
import { getTraceStartTimeEnd } from "~/v3/eventRepository/traceInsertedAtBound";
10+
import {
11+
getTraceInsertedAtEnd,
12+
getTraceStartTimeEnd,
13+
} from "~/v3/eventRepository/traceInsertedAtBound";
1114
import { runStore } from "~/v3/runStore.server";
1215
import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
1316

@@ -66,14 +69,15 @@ export async function getDashboardAgentRunTrace({
6669
const repository = await getEventRepositoryForStore(run.taskEventStore, organizationId);
6770
const storeTable = getTaskEventStoreTableForRun(run);
6871
const endCreatedAt = getTraceStartTimeEnd(run);
72+
const insertedAtEnd = getTraceInsertedAtEnd(run);
6973

7074
let summary: TraceSummary | undefined = await repository.getTraceSummary(
7175
storeTable,
7276
environmentId,
7377
run.traceId,
7478
run.createdAt,
7579
endCreatedAt,
76-
{ includeDebugLogs: false, anchorSpanId: run.spanId }
80+
{ includeDebugLogs: false, anchorSpanId: run.spanId, insertedAtEnd }
7781
);
7882

7983
// No summary means the anchor span isn't in the summary rows, e.g. it fell past the
@@ -87,7 +91,7 @@ export async function getDashboardAgentRunTrace({
8791
run.createdAt,
8892
endCreatedAt,
8993
// The tree is built from the anchor down, so ancestors would be read for nothing.
90-
{ includeDebugLogs: false }
94+
{ includeDebugLogs: false, insertedAtEnd }
9195
);
9296

9397
if (subtree) {

‎apps/webapp/app/v3/eventRepository/clickhouseEventRepository.server.ts‎

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,7 @@ export class ClickhouseEventRepository implements IEventRepository {
16961696
traceId: string,
16971697
startCreatedAt: Date,
16981698
endCreatedAt?: Date,
1699-
options?: { includeDebugLogs?: boolean; anchorSpanId?: string }
1699+
options?: { includeDebugLogs?: boolean; anchorSpanId?: string; insertedAtEnd?: Date }
17001700
): Promise<TraceSummary | undefined> {
17011701
const limit = this._config.maximumTraceSummaryViewCount;
17021702
const records = await this.#fetchTraceSummaryRecords({
@@ -1741,7 +1741,7 @@ export class ClickhouseEventRepository implements IEventRepository {
17411741
anchorSpanId: string,
17421742
startCreatedAt: Date,
17431743
endCreatedAt?: Date,
1744-
options?: { includeDebugLogs?: boolean }
1744+
options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date }
17451745
): Promise<TraceSummary | undefined> {
17461746
const { records, isTruncated, missingAnchor } = await this.#fetchTraceSubtreeRecords({
17471747
environmentId,
@@ -1927,6 +1927,17 @@ export class ClickhouseEventRepository implements IEventRepository {
19271927
queryBuilder.where(sortKey.clause, sortKey.params);
19281928
}
19291929

1930+
#whereInsertedAtEnd(
1931+
queryBuilder: { where: (clause: string, params?: any) => unknown },
1932+
end: Date | undefined
1933+
) {
1934+
if (end) {
1935+
queryBuilder.where("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
1936+
insertedAtEnd: convertDateToClickhouseDateTime(end),
1937+
});
1938+
}
1939+
}
1940+
19301941
#applyTraceScopeWhere(
19311942
queryBuilder: { where: (clause: string, params?: any) => unknown },
19321943
{
@@ -1968,11 +1979,7 @@ export class ClickhouseEventRepository implements IEventRepository {
19681979
});
19691980
}
19701981

1971-
if (options?.insertedAtEnd) {
1972-
queryBuilder.where("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
1973-
insertedAtEnd: convertDateToClickhouseDateTime(options.insertedAtEnd),
1974-
});
1975-
}
1982+
this.#whereInsertedAtEnd(queryBuilder, options?.insertedAtEnd);
19761983
}
19771984

19781985
if (options?.includeDebugLogs !== true) {
@@ -2172,7 +2179,7 @@ export class ClickhouseEventRepository implements IEventRepository {
21722179
anchorSpanId: string;
21732180
startCreatedAt: Date;
21742181
endCreatedAt?: Date;
2175-
options?: { includeDebugLogs?: boolean };
2182+
options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date };
21762183
limit?: number;
21772184
}): Promise<{
21782185
records: TaskEventSummaryV1Result[];
@@ -2419,7 +2426,7 @@ export class ClickhouseEventRepository implements IEventRepository {
24192426
traceId: string;
24202427
startCreatedAt?: Date;
24212428
endCreatedAt?: Date;
2422-
options?: { includeDebugLogs?: boolean };
2429+
options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date };
24232430
spanIds?: string[];
24242431
parentSpanIds?: string[];
24252432
limit?: number;
@@ -2446,6 +2453,8 @@ export class ClickhouseEventRepository implements IEventRepository {
24462453
queryBuilder.where("inserted_at >= {insertedAtStart: DateTime64(3)}", {
24472454
insertedAtStart: convertDateToClickhouseDateTime(startCreatedAtWithBuffer),
24482455
});
2456+
2457+
this.#whereInsertedAtEnd(queryBuilder, options?.insertedAtEnd);
24492458
}
24502459
}
24512460

@@ -2542,7 +2551,7 @@ export class ClickhouseEventRepository implements IEventRepository {
25422551
traceId: string,
25432552
startCreatedAt: Date,
25442553
endCreatedAt?: Date,
2545-
options?: { includeDebugLogs?: boolean }
2554+
options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date }
25462555
): Promise<SpanDetail | undefined> {
25472556
const startCreatedAtWithBuffer = new Date(startCreatedAt.getTime() - 60_000);
25482557

@@ -2561,6 +2570,8 @@ export class ClickhouseEventRepository implements IEventRepository {
25612570
queryBuilder.where("inserted_at >= {insertedAtStart: DateTime64(3)}", {
25622571
insertedAtStart: convertDateToClickhouseDateTime(startCreatedAtWithBuffer),
25632572
});
2573+
2574+
this.#whereInsertedAtEnd(queryBuilder, options?.insertedAtEnd);
25642575
}
25652576

25662577
if (options?.includeDebugLogs !== true) {
@@ -3058,11 +3069,7 @@ export class ClickhouseEventRepository implements IEventRepository {
30583069
insertedAtStart: convertDateToClickhouseDateTime(startCreatedAtWithBuffer),
30593070
});
30603071

3061-
if (options?.insertedAtEnd) {
3062-
queryBuilder.where("inserted_at <= {insertedAtEnd: DateTime64(3)}", {
3063-
insertedAtEnd: convertDateToClickhouseDateTime(options.insertedAtEnd),
3064-
});
3065-
}
3072+
this.#whereInsertedAtEnd(queryBuilder, options?.insertedAtEnd);
30663073
}
30673074
} else if (insertedAtStart && this._version === "v2") {
30683075
queryBuilder.where("inserted_at >= {insertedAtStart: DateTime64(3)}", {
@@ -3484,7 +3491,8 @@ export class ClickhouseEventRepository implements IEventRepository {
34843491
traceId: string,
34853492
runId: string,
34863493
startCreatedAt: Date,
3487-
endCreatedAt?: Date
3494+
endCreatedAt?: Date,
3495+
options?: { insertedAtEnd?: Date }
34883496
): Promise<RunPreparedEvent[]> {
34893497
const startCreatedAtWithBuffer = new Date(startCreatedAt.getTime() - 1000);
34903498

@@ -3503,6 +3511,8 @@ export class ClickhouseEventRepository implements IEventRepository {
35033511
queryBuilder.where("inserted_at >= {insertedAtStart: DateTime64(3)}", {
35043512
insertedAtStart: convertDateToClickhouseDateTime(startCreatedAtWithBuffer),
35053513
});
3514+
3515+
this.#whereInsertedAtEnd(queryBuilder, options?.insertedAtEnd);
35063516
}
35073517

35083518
queryBuilder.where("kind != {kind: String}", { kind: "DEBUG_EVENT" });

‎apps/webapp/app/v3/eventRepository/eventRepository.server.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ export class EventRepository implements IEventRepository {
453453
traceId: string,
454454
startCreatedAt: Date,
455455
endCreatedAt?: Date,
456-
options?: { includeDebugLogs?: boolean; anchorSpanId?: string }
456+
options?: { includeDebugLogs?: boolean; anchorSpanId?: string; insertedAtEnd?: Date }
457457
): Promise<TraceSummary | undefined> {
458458
return await startActiveSpan("getTraceSummary", async (span) => {
459459
const events = await this.taskEventStore.findTraceEvents(
@@ -485,7 +485,7 @@ export class EventRepository implements IEventRepository {
485485
_anchorSpanId: string,
486486
_startCreatedAt: Date,
487487
_endCreatedAt?: Date,
488-
_options?: { includeDebugLogs?: boolean }
488+
_options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date }
489489
): Promise<TraceSummary | undefined> {
490490
// Subtree traversal is ClickHouse-only. Dashboard falls back to the full
491491
// summary when this returns undefined.
@@ -625,7 +625,8 @@ export class EventRepository implements IEventRepository {
625625
traceId: string,
626626
runId: string,
627627
startCreatedAt: Date,
628-
endCreatedAt?: Date
628+
endCreatedAt?: Date,
629+
_options?: { insertedAtEnd?: Date }
629630
): Promise<RunPreparedEvent[]> {
630631
return await startActiveSpan("getRunEvents", async (span) => {
631632
const events = await this.taskEventStore.findMany(
@@ -680,7 +681,7 @@ export class EventRepository implements IEventRepository {
680681
traceId: string,
681682
startCreatedAt: Date,
682683
endCreatedAt?: Date,
683-
options?: { includeDebugLogs?: boolean }
684+
options?: { includeDebugLogs?: boolean; insertedAtEnd?: Date }
684685
): Promise<SpanDetail | undefined> {
685686
return await startActiveSpan("getSpan", async (s) => {
686687
const spanEvent = await this.#getSpanEvent({

0 commit comments

Comments
 (0)