Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .server-changes/collapse-trigger-queued-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

Triggering a task now reaches the queue marginally faster.
33 changes: 26 additions & 7 deletions internal-packages/run-engine/src/engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@trigger.dev/core/v3";
import type { TaskRunError } from "@trigger.dev/core/v3/schemas";
import {
generateInternalId,
parseNaturalLanguageDurationInMs,
RunId,
WaitpointId,
Expand Down Expand Up @@ -947,6 +948,7 @@ export class RunEngine {

let taskRun: TaskRun & { associatedWaitpoint: Waitpoint | null };
const taskRunId = RunId.fromFriendlyId(friendlyId);
const initialSnapshotId = generateInternalId();

// App-level replacement for the dropped TaskRun env/project Cascade FKs.
await this.controlPlaneResolver.assertEnvExists(environment.id);
Expand Down Expand Up @@ -1032,9 +1034,10 @@ export class RunEngine {
annotations,
},
snapshot: {
id: initialSnapshotId,
engine: "V2",
executionStatus: delayUntil ? "DELAYED" : "RUN_CREATED",
description: delayUntil ? "Run is delayed" : "Run was created",
executionStatus: delayUntil ? "DELAYED" : "QUEUED",
description: delayUntil ? "Run is delayed" : "Run was QUEUED",
runStatus: status,
environmentId: environment.id,
environmentType: environment.type,
Expand Down Expand Up @@ -1161,13 +1164,29 @@ export class RunEngine {
await this.ttlSystem.scheduleExpireRun({ runId: taskRun.id, ttl: taskRun.ttl });
}

await this.enqueueSystem.enqueueRun({
this.eventBus.emit("executionSnapshotCreated", {
time: new Date(),
run: {
id: taskRun.id,
},
snapshot: {
id: initialSnapshotId,
executionStatus: "QUEUED",
description: "Run was QUEUED",
runStatus: taskRun.status,
attemptNumber: taskRun.attemptNumber ?? null,
checkpointId: null,
workerId: workerId ?? null,
runnerId: runnerId ?? null,
isValid: true,
error: null,
completedWaitpointIds: [],
},
});

await this.enqueueSystem.publishRun({
run: taskRun,
env: environment,
workerId,
runnerId,
tx: prisma,
skipRunLock: true,
includeTtl: true,
anchorEligibilityAtQueuePosition: true,
enableFastPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ function createEngineOptions(redisOptions: any, prisma: any, store?: PostgresRun
}

/**
* A real PostgresRunStore subclass that counts the snapshot create method that enqueueRun's
* snapshot write routes through (via executionSnapshotSystem.createExecutionSnapshot). super.*
* runs the genuine store implementation, so the routing is observed over real containers without
* ever mocking prisma or the store.
* A real PostgresRunStore subclass that counts the store methods a run's QUEUED snapshot can be
* written through: nested in `createRun` on the trigger path, or standalone via
* `createExecutionSnapshot` on every re-enqueue. super.* runs the genuine store implementation, so
* the routing is observed over real containers without ever mocking prisma or the store.
*/
class CountingPostgresRunStore extends PostgresRunStore {
public snapshotCreates = 0;
Expand All @@ -59,12 +59,19 @@ class CountingPostgresRunStore extends PostgresRunStore {
this.snapshotCreates++;
return super.createExecutionSnapshot(input, tx);
}

override async createRun(
params: Parameters<PostgresRunStore["createRun"]>[0],
tx?: any
): ReturnType<PostgresRunStore["createRun"]> {
this.snapshotCreates++;
return super.createRun(params, tx);
}
}

describe("RunEngine enqueueRun store routing", () => {
// The QUEUED snapshot written while enqueuing a run routes through the injected store.
containerTest(
"enqueueRun snapshot routes through the store",
"the QUEUED snapshot routes through the store",
async ({ prisma, redisOptions }) => {
const countingStore = new CountingPostgresRunStore({ prisma, readOnlyPrisma: prisma });
const engine = new RunEngine(createEngineOptions(redisOptions, prisma, countingStore));
Expand Down
94 changes: 63 additions & 31 deletions internal-packages/run-engine/src/engine/systems/enqueueSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,42 +113,74 @@ export class EnqueueSystem {
store
);

// Force development runs to use the environment id as the worker queue.
const workerQueue = env.type === "DEVELOPMENT" ? env.id : run.workerQueue;

const queuePositionMs = (run.queueTimestamp ?? run.createdAt).getTime();
const timestamp = queuePositionMs - run.priorityMs;
const eligibleAtMs = anchorEligibilityAtQueuePosition ? queuePositionMs : Date.now();

let ttlExpiresAt: number | undefined;
if (includeTtl && run.ttl) {
const expireAt = parseNaturalLanguageDuration(run.ttl);
if (expireAt) {
ttlExpiresAt = expireAt.getTime();
}
}

await this.$.runQueue.enqueueMessage({
await this.publishRun({
run,
env,
workerQueue,
includeTtl,
anchorEligibilityAtQueuePosition,
enableFastPath,
message: {
runId: run.id,
taskIdentifier: run.taskIdentifier,
orgId: env.organization.id,
projectId: env.project.id,
environmentId: env.id,
environmentType: env.type,
queue: run.queue,
concurrencyKey: run.concurrencyKey ?? undefined,
timestamp,
eligibleAtMs,
attempt: 0,
ttlExpiresAt,
},
});

return newSnapshot;
});
}

/**
* Publishes the run to the RunQueue without writing an execution snapshot. Callers that already
* hold a `QUEUED` snapshot (the trigger path writes one inside the run-create transaction) use
* this so the run does not pay for a second snapshot write.
*/
public async publishRun({
run,
env,
includeTtl = false,
anchorEligibilityAtQueuePosition = false,
enableFastPath = false,
}: {
run: TaskRun;
env: MinimalAuthenticatedEnvironment;
/** See `enqueueRun`. */
includeTtl?: boolean;
/** See `enqueueRun`. */
anchorEligibilityAtQueuePosition?: boolean;
/** When true, allow the queue to push directly to worker queue if concurrency is available. */
enableFastPath?: boolean;
}) {
// Force development runs to use the environment id as the worker queue.
const workerQueue = env.type === "DEVELOPMENT" ? env.id : run.workerQueue;

const queuePositionMs = (run.queueTimestamp ?? run.createdAt).getTime();
const timestamp = queuePositionMs - run.priorityMs;
const eligibleAtMs = anchorEligibilityAtQueuePosition ? queuePositionMs : Date.now();

// Include TTL only when explicitly requested (first enqueue from trigger).
// Re-enqueues (waitpoint, checkpoint, delayed, pending version) must not add TTL.
let ttlExpiresAt: number | undefined;
if (includeTtl && run.ttl) {
const expireAt = parseNaturalLanguageDuration(run.ttl);
if (expireAt) {
ttlExpiresAt = expireAt.getTime();
}
}

await this.$.runQueue.enqueueMessage({
env,
workerQueue,
enableFastPath,
message: {
runId: run.id,
taskIdentifier: run.taskIdentifier,
orgId: env.organization.id,
projectId: env.project.id,
environmentId: env.id,
environmentType: env.type,
queue: run.queue,
concurrencyKey: run.concurrencyKey ?? undefined,
timestamp,
eligibleAtMs,
attempt: 0,
ttlExpiresAt,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class CountingPostgresRunStore extends PostgresRunStore {
return super.createExecutionSnapshot(input, tx);
}

override async createRun(
params: Parameters<PostgresRunStore["createRun"]>[0],
tx?: any
): ReturnType<PostgresRunStore["createRun"]> {
this.creates++;
return super.createRun(params, tx);
}

override async findLatestExecutionSnapshot(
runId: string,
client?: any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { PrismaClient } from "@trigger.dev/database";
import { RunEngine } from "../index.js";
import { getExecutionSnapshotsSince } from "../systems/executionSnapshotSystem.js";
import { copySnapshotsToReplica, createTestMetricsMeter } from "./helpers/replicaTestHelpers.js";
import { setupTestScenario } from "./helpers/snapshotTestHelpers.js";
import { createTestSnapshot, setupTestScenario } from "./helpers/snapshotTestHelpers.js";
import { setupAuthenticatedEnvironment, setupBackgroundWorker } from "./setup.js";

vi.setConfig({ testTimeout: 120_000 });
Expand Down Expand Up @@ -1154,6 +1154,15 @@ describe("RunEngine getSnapshotsSince", () => {
workerQueue: "main",
});

await createTestSnapshot(prisma, {
runId: run.id,
status: "EXECUTING",
environmentId: authenticatedEnvironment.id,
environmentType: authenticatedEnvironment.type,
projectId: authenticatedEnvironment.project.id,
organizationId: authenticatedEnvironment.organization.id,
});

const allSnapshots = await prisma.taskRunExecutionSnapshot.findMany({
where: { runId: run.id, isValid: true },
orderBy: { createdAt: "asc" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,6 @@ const cancelledSnapshot = (friendlyId: string, environment: any) => ({
});

describe("RunEngine trigger/create routing", () => {
// trigger create routes through runStore.createRun with the structured
// DTO, and the persisted run + its nested first RUN_CREATED snapshot land via
// the single create call.
containerTest(
"trigger routes createRun and lands run + first snapshot",
async ({ prisma, redisOptions }) => {
Expand Down Expand Up @@ -169,7 +166,7 @@ describe("RunEngine trigger/create routing", () => {
orderBy: { createdAt: "asc" },
});
expect(snapshot).not.toBeNull();
expect(snapshot!.executionStatus).toBe("RUN_CREATED");
expect(snapshot!.executionStatus).toBe("QUEUED");
} finally {
await engine.quit();
}
Expand Down
Loading