fix: optimize routing form mutation to avoid costly name update triggers (#26781)

* fix: optimize routing form mutation to avoid costly name update triggers

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* Add load test script for form responses

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Hariom BalharaandDevin AI authored and GitHub committed 2026-01-13 08:53:10 +00:00
1 parent 7c66f33de2
commit cee3195708
2 files changed
+97 -2

No files matched your search

@@ -37,7 +37,7 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
const existingForm = await prisma.app_RoutingForms_Form.findUnique({
where: { id },
select: { id: true },
select: { id: true, name: true },
});
if (existingForm) {
// Check PBAC permissions for updating routing forms only
@@ -144,6 +144,8 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
delete settings.sendUpdatesTo;
}
const oldFormName = existingForm?.name;
return await prisma.app_RoutingForms_Form.upsert({
where: {
id: id,
@@ -173,7 +175,9 @@ export const formMutationHandler = async ({ ctx, input }: FormMutationHandlerOpt
update: {
disabled: disabled,
fields,
name: name,
// Don't update name field if the name is the same as the old name to ensure that it doesn't change "name" update triggers which are very costly.
// This is an immediate quick workaround. Correct fix is to decouple trigger related work from the transaction.
name: oldFormName !== name ? name : undefined,
description,
settings: settings === null ? Prisma.JsonNull : settings,
routes: routes === null ? Prisma.JsonNull : routes,
+91
View File
@@ -0,0 +1,91 @@
/**
* Run this script quickly as `npx dotenv -e .env -- npx tsx scripts/seed-load-test-form-reponses.ts` from the root of the repo.
*/
import { prisma } from "@calcom/prisma";
import type { App_RoutingForms_Form } from "@calcom/prisma/client";
async function createTestForm(): Promise<App_RoutingForms_Form> {
const user = await prisma.user.findFirstOrThrow({
where: { email: "owner1-acme@example.com" },
});
if (!user) {
throw new Error("owner1-acme@example.com not found");
}
const testForm =
(await prisma.app_RoutingForms_Form.findFirst({
where: { name: "Load Test Form" },
})) ||
(await prisma.app_RoutingForms_Form.create({
data: {
name: "Load Test Form",
userId: user.id,
fields: [
{
id: "test-field-1",
type: "text",
label: "Test Field",
required: true,
},
],
routes: [
{
id: "test-route-1",
action: { type: "customPageMessage", value: "Thank you" },
isFallback: true,
queryValue: { id: "test-route-1", type: "group" },
},
],
},
}));
return testForm;
}
/**
* Creates a form in Acme's owner-1 account and creates 300,000 responses in it.
*/
async function createBulkResponsesAndExport(): Promise<void> {
const testForm = await createTestForm();
console.log(`Using form: ${testForm.id}`);
const TOTAL_RESPONSES = 300000;
const BATCH_SIZE = 1000;
const NUM_BATCHES = Math.ceil(TOTAL_RESPONSES / BATCH_SIZE);
for (let batch = 0; batch < NUM_BATCHES; batch++) {
const batchStart = batch * BATCH_SIZE;
const batchEnd = Math.min((batch + 1) * BATCH_SIZE, TOTAL_RESPONSES);
const batchSize = batchEnd - batchStart;
console.log(`Creating batch ${batch + 1}/${NUM_BATCHES} (responses ${batchStart + 1}-${batchEnd})...`);
// Create responses in parallel within each batch
const responsePromises = Array.from({ length: batchSize }, (_, i) => {
const responseIndex = batchStart + i;
return prisma.app_RoutingForms_FormResponse.create({
data: {
formFillerId: `load-test-filler-${responseIndex}`,
formId: testForm.id,
response: {
"test-field-1": {
label: "Test Field",
value: `Response ${responseIndex}`,
},
},
},
});
});
await Promise.all(responsePromises);
console.log(`✓ Batch ${batch + 1} completed (${batchEnd}/${TOTAL_RESPONSES} total)`);
await new Promise((resolve) => setTimeout(resolve, 100));
}
console.log(`\n✓ Successfully created ${TOTAL_RESPONSES} responses`);
}
createBulkResponsesAndExport()
.catch(console.error)
.finally(() => prisma.$disconnect());