Skip to content

Commit dd4ca1b

Browse files
committed
Handle unique constraint error on BatchTaskRunItem creation and allow different limits for batchTrigger and batchTriggerAndWait
1 parent ad4334c commit dd4ca1b

File tree

4 files changed

+89
-22
lines changed

4 files changed

+89
-22
lines changed

apps/webapp/app/env.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ const EnvironmentSchema = z.object({
362362
MAXIMUM_DEV_QUEUE_SIZE: z.coerce.number().int().optional(),
363363
MAXIMUM_DEPLOYED_QUEUE_SIZE: z.coerce.number().int().optional(),
364364
MAX_BATCH_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500),
365+
MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500),
365366

366367
REALTIME_STREAM_VERSION: z.enum(["v1", "v2"]).default("v1"),
367368
BATCH_METADATA_OPERATIONS_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000),

apps/webapp/app/routes/api.v1.tasks.batch.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,24 @@ const { action, loader } = createActionApiRoute(
4040
}
4141

4242
// Check the there are fewer than MAX_BATCH_V2_TRIGGER_ITEMS items
43-
if (body.items.length > env.MAX_BATCH_V2_TRIGGER_ITEMS) {
44-
return json(
45-
{
46-
error: `Batch size of ${body.items.length} is too large. Maximum allowed batch size is ${env.MAX_BATCH_V2_TRIGGER_ITEMS}.`,
47-
},
48-
{ status: 400 }
49-
);
43+
if (body.dependentAttempt) {
44+
if (body.items.length > env.MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS) {
45+
return json(
46+
{
47+
error: `Batch size of ${body.items.length} is too large. Maximum allowed batch size is ${env.MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS} when batchTriggerAndWait.`,
48+
},
49+
{ status: 400 }
50+
);
51+
}
52+
} else {
53+
if (body.items.length > env.MAX_BATCH_V2_TRIGGER_ITEMS) {
54+
return json(
55+
{
56+
error: `Batch size of ${body.items.length} is too large. Maximum allowed batch size is ${env.MAX_BATCH_V2_TRIGGER_ITEMS}.`,
57+
},
58+
{ status: 400 }
59+
);
60+
}
5061
}
5162

5263
const {

apps/webapp/app/v3/services/batchTriggerV3.server.ts

+39-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import {
55
packetRequiresOffloading,
66
parsePacket,
77
} from "@trigger.dev/core/v3";
8-
import { BatchTaskRun, Prisma, TaskRunAttempt } from "@trigger.dev/database";
8+
import {
9+
BatchTaskRun,
10+
isUniqueConstraintError,
11+
Prisma,
12+
TaskRunAttempt,
13+
} from "@trigger.dev/database";
14+
import { z } from "zod";
915
import { $transaction, prisma, PrismaClientOrTransaction } from "~/db.server";
1016
import { env } from "~/env.server";
1117
import { batchTaskRunItemStatusForRunStatus } from "~/models/taskRun.server";
@@ -20,9 +26,8 @@ import { downloadPacketFromObjectStore, uploadPacketToObjectStore } from "../r2.
2026
import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus";
2127
import { startActiveSpan } from "../tracer.server";
2228
import { BaseService, ServiceValidationError } from "./baseService.server";
23-
import { OutOfEntitlementError, TriggerTaskService } from "./triggerTask.server";
24-
import { z } from "zod";
2529
import { ResumeBatchRunService } from "./resumeBatchRun.server";
30+
import { OutOfEntitlementError, TriggerTaskService } from "./triggerTask.server";
2631

2732
const PROCESSING_BATCH_SIZE = 50;
2833
const ASYNC_BATCH_PROCESS_SIZE_THRESHOLD = 20;
@@ -819,20 +824,39 @@ export class BatchTriggerV3Service extends BaseService {
819824
}
820825

821826
if (!result.isCached) {
822-
await $transaction(this._prisma, async (tx) => {
823-
await tx.batchTaskRunItem.create({
824-
data: {
825-
batchTaskRunId: batch.id,
826-
taskRunId: result.run.id,
827-
status: batchTaskRunItemStatusForRunStatus(result.run.status),
828-
},
829-
});
827+
try {
828+
await $transaction(this._prisma, async (tx) => {
829+
// [batchTaskRunId, taskRunId] is a unique index
830+
await tx.batchTaskRunItem.create({
831+
data: {
832+
batchTaskRunId: batch.id,
833+
taskRunId: result.run.id,
834+
status: batchTaskRunItemStatusForRunStatus(result.run.status),
835+
},
836+
});
830837

831-
await tx.batchTaskRun.update({
832-
where: { id: batch.id },
833-
data: { expectedCount: { increment: 1 } },
838+
await tx.batchTaskRun.update({
839+
where: { id: batch.id },
840+
data: { expectedCount: { increment: 1 } },
841+
});
834842
});
835-
});
843+
} catch (error) {
844+
if (isUniqueConstraintError(error, ["batchTaskRunId", "taskRunId"])) {
845+
// This means there is already a batchTaskRunItem for this batch and taskRun
846+
logger.debug(
847+
"[BatchTriggerV2][processBatchTaskRunItem] BatchTaskRunItem already exists",
848+
{
849+
batchId: batch.friendlyId,
850+
runId: task.runId,
851+
currentIndex,
852+
}
853+
);
854+
855+
return;
856+
}
857+
858+
throw error;
859+
}
836860
}
837861
}
838862

internal-packages/database/src/transaction.ts

+31
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,34 @@ export async function $transaction<R>(
5656
throw error;
5757
}
5858
}
59+
60+
export function isUniqueConstraintError<T extends readonly string[]>(
61+
error: unknown,
62+
columns: T
63+
): boolean {
64+
if (!isPrismaKnownError(error)) {
65+
return false;
66+
}
67+
68+
if (error.code !== "P2002") {
69+
return false;
70+
}
71+
72+
const target = error.meta?.target;
73+
74+
if (!Array.isArray(target)) {
75+
return false;
76+
}
77+
78+
if (target.length !== columns.length) {
79+
return false;
80+
}
81+
82+
for (let i = 0; i < columns.length; i++) {
83+
if (target[i] !== columns[i]) {
84+
return false;
85+
}
86+
}
87+
88+
return true;
89+
}

0 commit comments

Comments
 (0)