|
1 | 1 | import type { BatchTaskRunExecutionResult } from "@trigger.dev/core/v3"; |
| 2 | +import { ownerEngine } from "@trigger.dev/core/v3/isomorphic"; |
2 | 3 | import { |
3 | 4 | $replica, |
4 | 5 | type PrismaClientOrTransaction, |
|
8 | 9 | import type { TaskRunWithAttempts } from "~/models/taskRun.server"; |
9 | 10 | import { executionResultForTaskRun } from "~/models/taskRun.server"; |
10 | 11 | import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; |
11 | | -import { readThroughRun } from "~/v3/runOpsMigration/readThrough.server"; |
12 | 12 | import { runStore as defaultRunStore } from "~/v3/runStore.server"; |
13 | 13 | import { BasePresenter } from "./basePresenter.server"; |
14 | 14 |
|
@@ -43,11 +43,14 @@ const memberRunSelect = { |
43 | 43 | } as const; |
44 | 44 |
|
45 | 45 | /** |
46 | | - * Split on: the batch row + its item rows resolve new-run-ops first, then the LEGACY RUN-OPS |
47 | | - * READ REPLICA ONLY (never the legacy primary — there is no such handle); each member run is |
48 | | - * hydrated independently via readThroughRun keyed on the member runId, so a batch whose members |
49 | | - * span migrated + abandoned runs returns the complete reachable set (the batch-spanning-the-line |
50 | | - * read; the dangling-reference termination gate is a separate, adjacent unit). |
| 46 | + * Split on: the batch row + its members resolve new-run-ops first, then the LEGACY RUN-OPS READ |
| 47 | + * REPLICA ONLY (never the legacy primary — there is no such handle). Members hydrate via ONE |
| 48 | + * grouped read against `newClient` for the whole id set, then ONE grouped read against |
| 49 | + * `legacyReplica` for just the misses that could still be legacy-resident — the same |
| 50 | + * residency-partitioned shape as the old per-member read-through, but batched instead of fanned |
| 51 | + * out one query per member. A batch whose members span migrated + abandoned runs returns the |
| 52 | + * complete reachable set (the batch-spanning-the-line read; the dangling-reference termination |
| 53 | + * gate is a separate, adjacent unit). |
51 | 54 | * |
52 | 55 | * Split off (single-DB / self-host): one passthrough read for the batch row + a single store |
53 | 56 | * id-set hydrate for the members — no legacy read, no known-migrated probe, no second connection. |
@@ -133,7 +136,7 @@ export class ApiBatchResultsPresenter extends BasePresenter { |
133 | 136 | // Split: resolve the batch row new-first then off the legacy READ REPLICA only (a batch id may |
134 | 137 | // be cuid or run-ops id, and a cuid-shaped id can still have been backfilled onto NEW, so id-shape |
135 | 138 | // residency is not authoritative for the row — the new-first-then-legacy probe is), then |
136 | | - // hydrate every member run independently via the per-run read-through primitive. |
| 139 | + // hydrate every member run in ONE grouped new-then-legacy read. |
137 | 140 | async #callSplit( |
138 | 141 | friendlyId: string, |
139 | 142 | env: AuthenticatedEnvironment |
@@ -175,41 +178,35 @@ export class ApiBatchResultsPresenter extends BasePresenter { |
175 | 178 | }; |
176 | 179 | } |
177 | 180 |
|
178 | | - const readMemberRun = (client: PrismaClientOrTransaction, taskRunId: string) => |
179 | | - client.taskRun.findFirst({ |
180 | | - where: { id: taskRunId }, |
181 | | - select: memberRunSelect, |
182 | | - }) as Promise<TaskRunWithAttempts | null>; |
183 | | - |
184 | | - // Per-member fan-out: each member may live on a different DB, so a single nested include cannot |
185 | | - // cross the seam. Promise.all preserves batchRun.items order, unchanged from today. |
186 | | - const memberResults = await Promise.all( |
187 | | - batchRun.items.map(async (item) => { |
188 | | - const result = await readThroughRun<TaskRunWithAttempts>({ |
189 | | - runId: item.taskRunId, |
190 | | - environmentId: env.id, |
191 | | - readNew: (client) => readMemberRun(client, item.taskRunId), |
192 | | - readLegacy: (replica) => readMemberRun(replica, item.taskRunId), |
193 | | - deps: { |
194 | | - splitEnabled: true, |
195 | | - // Pass the SAME resolved handles the batch row used, so the batch row and its members |
196 | | - // never resolve against different DBs. (Letting these fall through to readThroughRun's |
197 | | - // own module-level defaults would diverge from the batch read's `?? this._replica`.) |
198 | | - newClient, |
199 | | - legacyReplica, |
200 | | - isPastRetention: this.readThrough?.isPastRetention, |
201 | | - }, |
202 | | - }); |
| 181 | + const taskRunIds = batchRun.items.map((item) => item.taskRunId); |
203 | 182 |
|
204 | | - // not-found / past-retention members are omitted (matches today's drop-undefined behavior); |
205 | | - // the dangling-reference termination gate (separate unit) governs whether that's permitted. |
206 | | - if (result.source === "not-found" || result.source === "past-retention") { |
207 | | - return undefined; |
208 | | - } |
| 183 | + const newRows = (await newClient.taskRun.findMany({ |
| 184 | + where: { id: { in: taskRunIds } }, |
| 185 | + select: memberRunSelect, |
| 186 | + })) as TaskRunWithAttempts[]; |
| 187 | + const runsById = new Map(newRows.map((run) => [run.id, run])); |
209 | 188 |
|
210 | | - return executionResultForTaskRun(result.value); |
211 | | - }) |
| 189 | + // A run-ops id can only live on NEW, so only misses that AREN'T run-ops-shaped are candidates |
| 190 | + // for the legacy probe — mirrors readThroughRun's per-id "NEW residency skips legacy" rule. |
| 191 | + const legacyCandidateIds = taskRunIds.filter( |
| 192 | + (id) => !runsById.has(id) && ownerEngine(id) !== "NEW" |
212 | 193 | ); |
| 194 | + if (legacyCandidateIds.length > 0) { |
| 195 | + const legacyRows = (await legacyReplica.taskRun.findMany({ |
| 196 | + where: { id: { in: legacyCandidateIds } }, |
| 197 | + select: memberRunSelect, |
| 198 | + })) as TaskRunWithAttempts[]; |
| 199 | + for (const run of legacyRows) { |
| 200 | + runsById.set(run.id, run); |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + // not-found members are omitted (matches today's drop-undefined behavior); the |
| 205 | + // dangling-reference termination gate (separate unit) governs whether that's permitted. |
| 206 | + const memberResults = batchRun.items.map((item) => { |
| 207 | + const run = runsById.get(item.taskRunId); |
| 208 | + return run ? executionResultForTaskRun(run) : undefined; |
| 209 | + }); |
213 | 210 |
|
214 | 211 | return { |
215 | 212 | id: batchRun.friendlyId, |
|
0 commit comments