Skip to content

Commit 7562ad2

Browse files
authored
refactor: Fix N+1 queries and optimize database operations (calcom#25813)
* refactor: prisma N+1 Queries * addressed review
1 parent 5626bce commit 7562ad2

5 files changed

Lines changed: 114 additions & 61 deletions

File tree

packages/features/ee/workflows/api/scheduleEmailReminders.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,26 +47,34 @@ export async function handler(req: NextRequest) {
4747
if (isSendgridEnabled) {
4848
const remindersToDelete: { referenceId: string | null; id: number }[] = await getAllRemindersToDelete();
4949

50+
const reminderIds: number[] = [];
5051
const handlePastCancelledReminders = remindersToDelete.map(async (reminder) => {
52+
reminderIds.push(reminder.id);
5153
try {
5254
if (reminder.referenceId) {
5355
await deleteScheduledSend(reminder.referenceId);
5456
}
5557
} catch (err) {
5658
logger.error(`Error deleting scheduled send (ref: ${reminder.referenceId}): ${err}`);
5759
}
60+
});
61+
62+
await Promise.allSettled(handlePastCancelledReminders);
5863

64+
if (reminderIds.length > 0) {
5965
try {
60-
await prisma.workflowReminder.update({
61-
where: { id: reminder.id },
66+
await prisma.workflowReminder.updateMany({
67+
where: {
68+
id: {
69+
in: reminderIds,
70+
},
71+
},
6272
data: { referenceId: null },
6373
});
6474
} catch (err) {
65-
logger.error(`Error updating reminder (id: ${reminder.id}): ${err}`);
75+
logger.error(`Error updating reminders: ${err}`);
6676
}
67-
});
68-
69-
await Promise.allSettled(handlePastCancelledReminders);
77+
}
7078

7179
//cancel reminders for cancelled/rescheduled bookings that are scheduled within the next hour
7280
const remindersToCancel: { referenceId: string | null; id: number }[] = await getAllRemindersToCancel();
@@ -89,12 +97,11 @@ export async function handler(req: NextRequest) {
8997
cancelUpdatePromises.push(cancelPromise, updatePromise);
9098
}
9199

92-
Promise.allSettled(cancelUpdatePromises).then((results) => {
93-
results.forEach((result) => {
94-
if (result.status === "rejected") {
95-
logger.error(`Error cancelling scheduled_sends: ${result.reason}`);
96-
}
97-
});
100+
const results = await Promise.allSettled(cancelUpdatePromises);
101+
results.forEach((result) => {
102+
if (result.status === "rejected") {
103+
logger.error(`Error cancelling scheduled_sends: ${result.reason}`);
104+
}
98105
});
99106
}
100107

@@ -490,12 +497,11 @@ export async function handler(req: NextRequest) {
490497
}
491498
}
492499

493-
Promise.allSettled(sendEmailPromises).then((results) => {
494-
results.forEach((result) => {
495-
if (result.status === "rejected") {
496-
logger.error("Email sending failed", result.reason);
497-
}
498-
});
500+
const sendResults = await Promise.allSettled(sendEmailPromises);
501+
sendResults.forEach((result) => {
502+
if (result.status === "rejected") {
503+
logger.error("Email sending failed", result.reason);
504+
}
499505
});
500506

501507
return NextResponse.json({ message: `${unscheduledReminders.length} Emails to schedule` }, { status: 200 });

packages/features/ee/workflows/lib/repository/workflowReminder.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,31 @@ export class WorkflowReminderRepository {
4949
});
5050
}
5151

52+
static async findWorkflowRemindersByStepIds(workflowStepIds: number[]) {
53+
if (workflowStepIds.length === 0) {
54+
return [];
55+
}
56+
57+
return await prisma.workflowReminder.findMany({
58+
where: {
59+
workflowStepId: {
60+
in: workflowStepIds,
61+
},
62+
},
63+
select: {
64+
id: true,
65+
referenceId: true,
66+
method: true,
67+
workflowStepId: true,
68+
booking: {
69+
select: {
70+
eventTypeId: true,
71+
},
72+
},
73+
},
74+
});
75+
}
76+
5277
static async findWorkflowReminderForAIPhoneCallExecution(id: number) {
5378
const bookingSelect = {
5479
uid: true,

packages/features/eventtypes/lib/getEventTypesByViewer.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -118,31 +118,27 @@ export const getEventTypesByViewer = async (user: User, filters?: Filters, forRo
118118

119119
const mapEventType = async (eventType: UserEventTypes) => {
120120
const userRepo = new UserRepository(prisma);
121+
const eventTypeUsers = eventType?.hosts?.length
122+
? eventType.hosts.map((host) => host.user)
123+
: eventType.users;
124+
const enrichedUsers = await userRepo.enrichUsersWithTheirProfiles(eventTypeUsers);
125+
126+
const children = eventType.children || [];
127+
const allChildUsers = children.flatMap((c) => c.users);
128+
const enrichedAllChildUsers = await userRepo.enrichUsersWithTheirProfiles(allChildUsers);
129+
const enrichedUsersMap = new Map(enrichedAllChildUsers.map((user) => [user.id, user]));
130+
131+
const enrichedChildren = children.map((c) => ({
132+
...c,
133+
users: c.users.map((user) => enrichedUsersMap.get(user.id)).filter((user) => !!user),
134+
}));
135+
121136
return {
122137
...eventType,
123138
safeDescription: eventType?.description ? markdownToSafeHTML(eventType.description) : undefined,
124-
users: await Promise.all(
125-
(eventType?.hosts?.length ? eventType?.hosts.map((host) => host.user) : eventType.users).map(
126-
async (u) =>
127-
await userRepo.enrichUserWithItsProfile({
128-
user: u,
129-
})
130-
)
131-
),
139+
users: enrichedUsers,
132140
metadata: eventType.metadata ? eventTypeMetaDataSchemaWithUntypedApps.parse(eventType.metadata) : null,
133-
children: await Promise.all(
134-
(eventType.children || []).map(async (c) => ({
135-
...c,
136-
users: await Promise.all(
137-
c.users.map(
138-
async (u) =>
139-
await userRepo.enrichUserWithItsProfile({
140-
user: u,
141-
})
142-
)
143-
),
144-
}))
145-
),
141+
children: enrichedChildren,
146142
};
147143
};
148144

packages/trpc/server/routers/viewer/eventTypes/heavy/update.handler.ts

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -430,34 +430,44 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
430430
},
431431
});
432432

433-
await Promise.all(
434-
hostGroups.map(async (group) => {
435-
await ctx.prisma.hostGroup.upsert({
436-
where: { id: group.id },
437-
update: { name: group.name },
438-
create: {
433+
const existingGroupsMap = new Map(existingHostGroups.map((group) => [group.id, group]));
434+
const newGroupsMap = new Map(hostGroups.map((group) => [group.id, group]));
435+
436+
const groupsToCreate = hostGroups.filter((group) => !existingGroupsMap.has(group.id));
437+
const groupsToUpdate = hostGroups.filter((group) => existingGroupsMap.has(group.id));
438+
const groupsToDelete = existingHostGroups.filter((existingGroup) => !newGroupsMap.has(existingGroup.id));
439+
440+
await ctx.prisma.$transaction(async (tx) => {
441+
// Create new groups
442+
if (groupsToCreate.length > 0) {
443+
await tx.hostGroup.createMany({
444+
data: groupsToCreate.map((group) => ({
439445
id: group.id,
440446
name: group.name,
441447
eventTypeId: id,
442-
},
448+
})),
443449
});
444-
})
445-
);
446-
447-
const newGroupsMap = new Map(hostGroups.map((group) => [group.id, group]));
450+
}
448451

449-
// Delete groups that are no longer in the new list
450-
const groupsToDelete = existingHostGroups.filter((existingGroup) => !newGroupsMap.has(existingGroup.id));
452+
// Update existing groups
453+
for (const group of groupsToUpdate) {
454+
await tx.hostGroup.update({
455+
where: { id: group.id },
456+
data: { name: group.name },
457+
});
458+
}
451459

452-
if (groupsToDelete.length > 0) {
453-
await ctx.prisma.hostGroup.deleteMany({
454-
where: {
455-
id: {
456-
in: groupsToDelete.map((group) => group.id),
460+
// Delete groups that are no longer in the new list
461+
if (groupsToDelete.length > 0) {
462+
await tx.hostGroup.deleteMany({
463+
where: {
464+
id: {
465+
in: groupsToDelete.map((group) => group.id),
466+
},
457467
},
458-
},
459-
});
460-
}
468+
});
469+
}
470+
});
461471
}
462472

463473
if (teamId && hosts) {

packages/trpc/server/routers/viewer/workflows/update.handler.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,22 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
253253

254254
const agentRepo = new PrismaAgentRepository(prisma);
255255

256+
const stepIds = userWorkflow.steps.map((step) => step.id);
257+
const allReminders = await WorkflowReminderRepository.findWorkflowRemindersByStepIds(stepIds);
258+
259+
const remindersByStepId = new Map<number, (typeof allReminders)[number][]>();
260+
for (const reminder of allReminders) {
261+
const stepId = reminder.workflowStepId;
262+
if (stepId === null) continue;
263+
264+
let list = remindersByStepId.get(stepId);
265+
if (!list) {
266+
list = [];
267+
remindersByStepId.set(stepId, list);
268+
}
269+
list.push(reminder);
270+
}
271+
256272
// handle deleted and edited workflow steps
257273
await Promise.all(
258274
userWorkflow.steps.map(async (oldStep) => {
@@ -271,7 +287,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
271287
};
272288
}
273289

274-
const remindersFromStep = await WorkflowReminderRepository.findWorkflowRemindersByStepId(oldStep.id);
290+
const remindersFromStep = remindersByStepId.get(oldStep.id) || [];
275291
//step was deleted
276292
if (!newStep) {
277293
if (oldStep.action === WorkflowActions.CAL_AI_PHONE_CALL && !!oldStep.agentId) {

0 commit comments

Comments
 (0)