Skip to content

Commit 5567633

Browse files
1stvampTrigger.dev RepoOps
authored andcommitted
feat(webapp): route keyless deployments to containers at trigger
Runs whose deployment has no compute template now go to containers when they are triggered. The task metadata cache carries the deployment's template key, so the trigger reads it with no new database query. The key only keeps a run off compute; it never moves a run that is not migrated. Mono-RevId: cf013ae7cd32ef2e02711ac9278c11ad222487c1
1 parent 694312c commit 5567633

12 files changed

Lines changed: 666 additions & 23 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+
Runs whose deployment has no compute template now go to containers when they are triggered, instead of first passing through the compute worker queue.

‎apps/webapp/app/runEngine/concerns/computeMigration.server.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type ResolveInput = MigrationDecisionInput & {
5050
backing: { workerQueue: string; enableFastPath: boolean } | undefined;
5151
envType: string;
5252
environmentCompat: WorkerQueueEnvironmentOverride["compat"];
53+
/** Template key of the deployment from the task metadata. Missing means no key. */
54+
templateKey: string | null | undefined;
5355
};
5456

5557
/**
@@ -59,7 +61,10 @@ type ResolveInput = MigrationDecisionInput & {
5961
* either way. Same-geo swap (us-east-1 -> us-east-1-next): any explicit placement
6062
* is a geography preference, honored by staying in-region. Applied after region
6163
* resolution, mirroring the scheduled-split. An environment compat of compute or container
62-
* decides the swap ahead of the org's migration decision.
64+
* decides the swap ahead of the org's migration decision. A deployment without a template key
65+
* stays on the base queue. The key can only keep a migrated run off compute: templates can exist
66+
* for orgs that are not migrated, and the dequeue check still covers keyless runs that reach
67+
* compute.
6368
*/
6469
export function resolveComputeMigration({
6570
baseWorkerQueue,
@@ -68,6 +73,7 @@ export function resolveComputeMigration({
6873
backing,
6974
envType,
7075
environmentCompat,
76+
templateKey,
7177
...decision
7278
}: ResolveInput): {
7379
workerQueue: string | undefined;
@@ -85,5 +91,6 @@ export function resolveComputeMigration({
8591
? false
8692
: isOrgMigrated(decision);
8793
if (!migrated) return passthrough;
94+
if (!templateKey) return passthrough;
8895
return { workerQueue: backing.workerQueue, region, enableFastPath: backing.enableFastPath };
8996
}

‎apps/webapp/app/runEngine/concerns/queues.server.ts‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { sanitizeQueueName } from "@trigger.dev/core/v3/isomorphic";
22
import type { PrismaClientOrTransaction } from "@trigger.dev/database";
33
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
44
import { logger } from "~/services/logger.server";
5-
import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server";
5+
import {
6+
findCurrentWorkerDeployment,
7+
findCurrentWorkerFromEnvironment,
8+
} from "~/v3/models/workerDeployment.server";
69
import type {
710
LockedBackgroundWorker,
811
QueueManager,
@@ -101,6 +104,7 @@ export class DefaultQueueManager implements QueueManager {
101104
let taskTtl: string | null | undefined;
102105
let taskKind: string | undefined;
103106
let taskGates: TaskMetadataGate[] | null | undefined;
107+
let templateKey: string | null | undefined;
104108

105109
// Determine queue name based on lockToVersion and provided options
106110
if (lockedBackgroundWorker) {
@@ -153,6 +157,7 @@ export class DefaultQueueManager implements QueueManager {
153157
}
154158
taskKind = lockedMeta?.triggerSource;
155159
taskGates = lockedMeta?.gates;
160+
templateKey = lockedMeta?.templateKey;
156161
} else {
157162
// No queue override - resolve default queue + TTL + triggerSource via cache,
158163
// falling back to a single BackgroundWorkerTask lookup on miss.
@@ -192,6 +197,7 @@ export class DefaultQueueManager implements QueueManager {
192197
lockedQueueId = lockedMeta.queueId ?? undefined;
193198
taskKind = lockedMeta.triggerSource;
194199
taskGates = lockedMeta.gates;
200+
templateKey = lockedMeta.templateKey;
195201
}
196202
} else {
197203
// Task is not locked to a specific version, use regular logic
@@ -208,6 +214,7 @@ export class DefaultQueueManager implements QueueManager {
208214
taskTtl = taskInfo.taskTtl;
209215
taskKind = taskInfo.taskKind;
210216
taskGates = taskInfo.taskGates;
217+
templateKey = taskInfo.templateKey;
211218
}
212219

213220
// Sanitize the final determined queue name once
@@ -292,6 +299,7 @@ export class DefaultQueueManager implements QueueManager {
292299
taskTtl,
293300
taskKind,
294301
gates: gates && gates.length > 0 ? gates : undefined,
302+
templateKey,
295303
};
296304
}
297305

@@ -300,6 +308,7 @@ export class DefaultQueueManager implements QueueManager {
300308
taskTtl?: string | null;
301309
taskKind?: string | undefined;
302310
taskGates?: TaskMetadataGate[] | null;
311+
templateKey?: string | null;
303312
}> {
304313
const { taskId, environment, body } = request;
305314
const { queue } = body.options ?? {};
@@ -323,6 +332,7 @@ export class DefaultQueueManager implements QueueManager {
323332
taskTtl: meta?.ttl ?? undefined,
324333
taskKind: meta?.triggerSource,
325334
taskGates: meta?.gates,
335+
templateKey: meta?.templateKey,
326336
};
327337
}
328338

@@ -344,6 +354,7 @@ export class DefaultQueueManager implements QueueManager {
344354
taskTtl: meta.ttl,
345355
taskKind: meta.triggerSource,
346356
taskGates: meta.gates,
357+
templateKey: meta.templateKey,
347358
};
348359
}
349360

@@ -352,6 +363,7 @@ export class DefaultQueueManager implements QueueManager {
352363
taskTtl: meta.ttl,
353364
taskKind: meta.triggerSource,
354365
taskGates: meta.gates,
366+
templateKey: meta.templateKey,
355367
};
356368
}
357369

@@ -404,6 +416,7 @@ export class DefaultQueueManager implements QueueManager {
404416

405417
recordTaskMetaResolve("locked", source);
406418

419+
const templateKey = row.worker.deployment?.templateKey ?? null;
407420
const entry: TaskMetadataEntry = {
408421
slug,
409422
ttl: row.ttl,
@@ -415,9 +428,9 @@ export class DefaultQueueManager implements QueueManager {
415428

416429
// Fire-and-forget back-fill — `setByWorker` upserts the single field and
417430
// refreshes the hash TTL. Errors are logged inside the cache and swallowed.
418-
void this.taskMetaCache.setByWorker(workerId, entry);
431+
void this.taskMetaCache.setByWorker(workerId, entry, templateKey);
419432

420-
return entry;
433+
return { ...entry, templateKey };
421434
}
422435

423436
private findLockedTaskRow(
@@ -433,6 +446,7 @@ export class DefaultQueueManager implements QueueManager {
433446
triggerSource: true,
434447
gates: true,
435448
queue: { select: { id: true, name: true } },
449+
worker: { select: { deployment: { select: { templateKey: true } } } },
436450
},
437451
});
438452
}
@@ -459,7 +473,18 @@ export class DefaultQueueManager implements QueueManager {
459473
// the adjacent BackgroundWorkerTask lookup below uses `replicaPrisma` too
460474
// (replica lag for "just deployed" is bounded the same way for both
461475
// queries; reading from the writer here would only widen the window).
462-
const worker = await findCurrentWorkerFromEnvironment(environment, this.replicaPrisma);
476+
let worker: { id: string } | null;
477+
let templateKey: string | null = null;
478+
if (environment.type === "DEVELOPMENT") {
479+
worker = await findCurrentWorkerFromEnvironment(environment, this.replicaPrisma);
480+
} else {
481+
const deployment = await findCurrentWorkerDeployment({
482+
environmentId: environment.id,
483+
prismaClient: this.replicaPrisma,
484+
});
485+
worker = deployment?.worker ?? null;
486+
templateKey = deployment?.templateKey ?? null;
487+
}
463488
if (!worker) {
464489
recordTaskMetaResolve("current", "miss");
465490
return null;
@@ -495,9 +520,9 @@ export class DefaultQueueManager implements QueueManager {
495520
// keyspaces so a subsequent locked-or-not trigger hits the cache. The
496521
// env-keyspace TTL is preserved (promotion owns it); the by-worker TTL
497522
// is refreshed (sliding window keeps active workers warm).
498-
void this.taskMetaCache.setByCurrentWorker(environment.id, worker.id, entry);
523+
void this.taskMetaCache.setByCurrentWorker(environment.id, worker.id, entry, templateKey);
499524

500-
return entry;
525+
return { ...entry, templateKey };
501526
}
502527

503528
async validateQueueLimits(

‎apps/webapp/app/runEngine/concerns/workerQueueAssignment.server.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ describe("per-environment worker queue overrides", () => {
251251
flags: { computeMigrationEnabled: migrationEnabled, computeMigrationFreePercentage: 100 },
252252
envType: environmentType,
253253
environmentCompat: environmentOverride.compat,
254+
templateKey: "a".repeat(64),
254255
});
255256
const workerQueue = workerQueueForBirth({
256257
workerQueue: migrated.workerQueue!,
@@ -323,6 +324,7 @@ describe("per-environment worker queue overrides", () => {
323324
flags: undefined,
324325
envType: "STAGING",
325326
environmentCompat: environmentOverride.compat,
327+
templateKey: "a".repeat(64),
326328
});
327329
expect(migrated.workerQueue).toBe("us-east-1-next");
328330
const birth = (v2Enabled: boolean) =>
@@ -354,6 +356,7 @@ describe("per-environment worker queue overrides", () => {
354356
flags: undefined,
355357
envType: "STAGING",
356358
environmentCompat: environmentOverride.compat,
359+
templateKey: "a".repeat(64),
357360
});
358361
expect(
359362
workerQueueForBirth({

‎apps/webapp/app/runEngine/services/triggerTask.server.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export class RunEngineTriggerTaskService {
445445
const parkedOnExternalDeploymentId =
446446
externalDeploymentResolution?.outcome === "park" ? externalDeploymentId : undefined;
447447

448-
const { queueName, lockedQueueId, taskTtl, taskKind, gates } =
448+
const { queueName, lockedQueueId, taskTtl, taskKind, gates, templateKey } =
449449
await this.queueConcern.resolveQueueProperties(
450450
triggerRequest,
451451
lockedToBackgroundWorker ?? undefined
@@ -534,6 +534,7 @@ export class RunEngineTriggerTaskService {
534534
flags: globalFlagsRegistry.current(),
535535
envType: environment.type,
536536
environmentCompat: environmentOverride.compat,
537+
templateKey,
537538
});
538539

539540
const triggerSource = options.triggerSource ?? "api";

‎apps/webapp/app/runEngine/types.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export type QueueProperties = {
4848
taskKind?: string;
4949
/** Other queues the run must also hold a concurrency slot in while executing. */
5050
gates?: Array<{ queue: string; concurrencyKey?: string }>;
51+
/** Template key of the deployment the task metadata came from, if it has one. */
52+
templateKey?: string | null;
5153
};
5254

5355
export type LockedBackgroundWorker = Pick<

0 commit comments

Comments
 (0)