Revert "perf: parallelize getBusyTimes calls to improve performance (#21372)"
This reverts commit a06b2009ab.
This commit is contained in:
@@ -20,72 +20,6 @@ import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
||||
import { getDefinedBufferTimes } from "../features/eventtypes/lib/getDefinedBufferTimes";
|
||||
import { BookingRepository as BookingRepo } from "./server/repository/booking";
|
||||
|
||||
const processBookingsToBusyTimes = (
|
||||
bookings: (Pick<Booking, "id" | "uid" | "userId" | "startTime" | "endTime" | "title"> & {
|
||||
eventType: Pick<EventType, "id" | "beforeEventBuffer" | "afterEventBuffer" | "seatsPerTimeSlot"> | null;
|
||||
_count?: {
|
||||
seatsReferences: number;
|
||||
};
|
||||
})[],
|
||||
rescheduleUid: string | null | undefined,
|
||||
eventTypeId: number | undefined,
|
||||
beforeEventBuffer: number | undefined,
|
||||
afterEventBuffer: number | undefined
|
||||
) => {
|
||||
const bookingSeatCountMap: { [x: string]: number } = {};
|
||||
return {
|
||||
busyTimes: bookings.reduce((aggregate: EventBusyDetails[], booking) => {
|
||||
const { id, startTime, endTime, eventType, title, ...rest } = booking;
|
||||
|
||||
const minutesToBlockBeforeEvent = (eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0);
|
||||
const minutesToBlockAfterEvent = (eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0);
|
||||
|
||||
if (rest._count?.seatsReferences) {
|
||||
const bookedAt = `${dayjs(startTime).utc().format()}<>${dayjs(endTime).utc().format()}`;
|
||||
bookingSeatCountMap[bookedAt] = bookingSeatCountMap[bookedAt] || 0;
|
||||
bookingSeatCountMap[bookedAt]++;
|
||||
// Seat references on the current event are non-blocking until the event is fully booked.
|
||||
if (
|
||||
// there are still seats available.
|
||||
bookingSeatCountMap[bookedAt] < (eventType?.seatsPerTimeSlot || 1) &&
|
||||
// and this is the seated event, other event types should be blocked.
|
||||
eventTypeId === eventType?.id
|
||||
) {
|
||||
// then we ONLY add the before/after buffer times as busy times.
|
||||
if (minutesToBlockBeforeEvent) {
|
||||
aggregate.push({
|
||||
start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
|
||||
end: dayjs(startTime).toDate(), // The event starts after the buffer
|
||||
});
|
||||
}
|
||||
if (minutesToBlockAfterEvent) {
|
||||
aggregate.push({
|
||||
start: dayjs(endTime).toDate(), // The event ends before the buffer
|
||||
end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(),
|
||||
});
|
||||
}
|
||||
return aggregate;
|
||||
}
|
||||
// if it does get blocked at this point; we remove the bookingSeatCountMap entry
|
||||
// doing this allows using the map later to remove the ranges from calendar busy times.
|
||||
delete bookingSeatCountMap[bookedAt];
|
||||
}
|
||||
// rescheduling the same booking to the same time should be possible. Why?
|
||||
if (rest.uid === rescheduleUid) {
|
||||
return aggregate;
|
||||
}
|
||||
aggregate.push({
|
||||
start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
|
||||
end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(),
|
||||
title,
|
||||
source: `eventType-${eventType?.id}-booking-${id}`,
|
||||
});
|
||||
return aggregate;
|
||||
}, []),
|
||||
bookingSeatCountMap,
|
||||
};
|
||||
};
|
||||
|
||||
const _getBusyTimes = async (params: {
|
||||
credentials: CredentialForCalendarService[];
|
||||
userId: number;
|
||||
@@ -173,45 +107,65 @@ const _getBusyTimes = async (params: {
|
||||
// to avoid potential side effects.
|
||||
let bookings = params.currentBookings;
|
||||
|
||||
const promises = [];
|
||||
let bookingsPromise;
|
||||
|
||||
if (!bookings) {
|
||||
bookingsPromise = BookingRepo.findAllExistingBookingsForEventTypeBetween({
|
||||
bookings = await BookingRepo.findAllExistingBookingsForEventTypeBetween({
|
||||
userIdAndEmailMap: new Map([[userId, userEmail]]),
|
||||
eventTypeId,
|
||||
startDate: startTimeAdjustedWithMaxBuffer,
|
||||
endDate: endTimeAdjustedWithMaxBuffer,
|
||||
seatedEvent,
|
||||
});
|
||||
promises.push(bookingsPromise);
|
||||
}
|
||||
|
||||
let calendarBusyTimesPromise;
|
||||
if (credentials?.length > 0 && !bypassBusyCalendarTimes) {
|
||||
calendarBusyTimesPromise = getBusyCalendarTimes(
|
||||
credentials,
|
||||
startTime,
|
||||
endTime,
|
||||
selectedCalendars,
|
||||
shouldServeCache
|
||||
);
|
||||
promises.push(calendarBusyTimesPromise);
|
||||
}
|
||||
const bookingSeatCountMap: { [x: string]: number } = {};
|
||||
const busyTimes = bookings.reduce((aggregate: EventBusyDetails[], booking) => {
|
||||
const { id, startTime, endTime, eventType, title, ...rest } = booking;
|
||||
|
||||
await Promise.all(promises);
|
||||
const minutesToBlockBeforeEvent = (eventType?.beforeEventBuffer || 0) + (afterEventBuffer || 0);
|
||||
const minutesToBlockAfterEvent = (eventType?.afterEventBuffer || 0) + (beforeEventBuffer || 0);
|
||||
|
||||
if (bookingsPromise) {
|
||||
bookings = await bookingsPromise;
|
||||
}
|
||||
|
||||
const { busyTimes, bookingSeatCountMap } = processBookingsToBusyTimes(
|
||||
bookings || [],
|
||||
rescheduleUid,
|
||||
eventTypeId,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer
|
||||
);
|
||||
if (rest._count?.seatsReferences) {
|
||||
const bookedAt = `${dayjs(startTime).utc().format()}<>${dayjs(endTime).utc().format()}`;
|
||||
bookingSeatCountMap[bookedAt] = bookingSeatCountMap[bookedAt] || 0;
|
||||
bookingSeatCountMap[bookedAt]++;
|
||||
// Seat references on the current event are non-blocking until the event is fully booked.
|
||||
if (
|
||||
// there are still seats available.
|
||||
bookingSeatCountMap[bookedAt] < (eventType?.seatsPerTimeSlot || 1) &&
|
||||
// and this is the seated event, other event types should be blocked.
|
||||
eventTypeId === eventType?.id
|
||||
) {
|
||||
// then we ONLY add the before/after buffer times as busy times.
|
||||
if (minutesToBlockBeforeEvent) {
|
||||
aggregate.push({
|
||||
start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
|
||||
end: dayjs(startTime).toDate(), // The event starts after the buffer
|
||||
});
|
||||
}
|
||||
if (minutesToBlockAfterEvent) {
|
||||
aggregate.push({
|
||||
start: dayjs(endTime).toDate(), // The event ends before the buffer
|
||||
end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(),
|
||||
});
|
||||
}
|
||||
return aggregate;
|
||||
}
|
||||
// if it does get blocked at this point; we remove the bookingSeatCountMap entry
|
||||
// doing this allows using the map later to remove the ranges from calendar busy times.
|
||||
delete bookingSeatCountMap[bookedAt];
|
||||
}
|
||||
// rescheduling the same booking to the same time should be possible. Why?
|
||||
if (rest.uid === rescheduleUid) {
|
||||
return aggregate;
|
||||
}
|
||||
aggregate.push({
|
||||
start: dayjs(startTime).subtract(minutesToBlockBeforeEvent, "minute").toDate(),
|
||||
end: dayjs(endTime).add(minutesToBlockAfterEvent, "minute").toDate(),
|
||||
title,
|
||||
source: `eventType-${eventType?.id}-booking-${id}`,
|
||||
});
|
||||
return aggregate;
|
||||
}, []);
|
||||
|
||||
logger.debug(
|
||||
`Busy Time from Cal Bookings ${JSON.stringify({
|
||||
@@ -222,10 +176,15 @@ const _getBusyTimes = async (params: {
|
||||
);
|
||||
performance.mark("prismaBookingGetEnd");
|
||||
performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");
|
||||
|
||||
if (credentials?.length > 0 && !bypassBusyCalendarTimes && calendarBusyTimesPromise) {
|
||||
if (credentials?.length > 0 && !bypassBusyCalendarTimes) {
|
||||
const startConnectedCalendarsGet = performance.now();
|
||||
const calendarBusyTimes = await calendarBusyTimesPromise;
|
||||
const calendarBusyTimes = await getBusyCalendarTimes(
|
||||
credentials,
|
||||
startTime,
|
||||
endTime,
|
||||
selectedCalendars,
|
||||
shouldServeCache
|
||||
);
|
||||
const endConnectedCalendarsGet = performance.now();
|
||||
logger.debug(
|
||||
`Connected Calendars get took ${
|
||||
@@ -245,7 +204,7 @@ const _getBusyTimes = async (params: {
|
||||
});
|
||||
|
||||
if (rescheduleUid) {
|
||||
const originalRescheduleBooking = bookings?.find((booking) => booking.uid === rescheduleUid);
|
||||
const originalRescheduleBooking = bookings.find((booking) => booking.uid === rescheduleUid);
|
||||
// calendar busy time from original rescheduled booking should not be blocked
|
||||
if (originalRescheduleBooking) {
|
||||
openSeatsDateRanges.push({
|
||||
|
||||
@@ -349,15 +349,12 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
const durationLimits = parseDurationLimit(eventType?.durationLimits);
|
||||
|
||||
let busyTimesFromLimits: EventBusyDetails[] = [];
|
||||
let busyTimesFromTeamLimits: EventBusyDetails[] = [];
|
||||
|
||||
const busyTimesPromises = [];
|
||||
|
||||
if (initialData?.busyTimesFromLimits && initialData?.eventTypeForLimits) {
|
||||
busyTimesFromLimits = initialData.busyTimesFromLimits.get(user.id) || [];
|
||||
} else if (eventType && (bookingLimits || durationLimits)) {
|
||||
// Fall back to individual query if not available in initialData
|
||||
const busyTimesFromLimitsPromise = getBusyTimesFromLimits(
|
||||
busyTimesFromLimits = await getBusyTimesFromLimits(
|
||||
bookingLimits,
|
||||
durationLimits,
|
||||
dateFrom.tz(timeZone),
|
||||
@@ -367,10 +364,7 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
initialData?.busyTimesFromLimitsBookings ?? [],
|
||||
timeZone,
|
||||
initialData?.rescheduleUid ?? undefined
|
||||
).then((result) => {
|
||||
busyTimesFromLimits = result;
|
||||
});
|
||||
busyTimesPromises.push(busyTimesFromLimitsPromise);
|
||||
);
|
||||
}
|
||||
|
||||
const teamForBookingLimits =
|
||||
@@ -380,11 +374,13 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
|
||||
const teamBookingLimits = parseBookingLimit(teamForBookingLimits?.bookingLimits);
|
||||
|
||||
let busyTimesFromTeamLimits: EventBusyDetails[] = [];
|
||||
|
||||
if (initialData?.teamBookingLimits && teamForBookingLimits) {
|
||||
busyTimesFromTeamLimits = initialData.teamBookingLimits.get(user.id) || [];
|
||||
} else if (teamForBookingLimits && teamBookingLimits) {
|
||||
// Fall back to individual query if not available in initialData
|
||||
const busyTimesFromTeamLimitsPromise = getBusyTimesFromTeamLimits(
|
||||
busyTimesFromTeamLimits = await getBusyTimesFromTeamLimits(
|
||||
user,
|
||||
teamBookingLimits,
|
||||
dateFrom.tz(timeZone),
|
||||
@@ -393,14 +389,7 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
|
||||
teamForBookingLimits.includeManagedEventsInLimits,
|
||||
timeZone,
|
||||
initialData?.rescheduleUid ?? undefined
|
||||
).then((result) => {
|
||||
busyTimesFromTeamLimits = result;
|
||||
});
|
||||
busyTimesPromises.push(busyTimesFromTeamLimitsPromise);
|
||||
}
|
||||
|
||||
if (busyTimesPromises.length > 0) {
|
||||
await Promise.all(busyTimesPromises);
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: only query what we need after applying limits (shrink date range)
|
||||
|
||||
@@ -31,51 +31,41 @@ const _getBusyTimesFromLimits = async (
|
||||
// shared amongst limiters to prevent processing known busy periods
|
||||
const limitManager = new LimitManager();
|
||||
|
||||
const limitChecks = [];
|
||||
|
||||
// run this first, as counting bookings should always run faster..
|
||||
if (bookingLimits) {
|
||||
performance.mark("bookingLimitsStart");
|
||||
limitChecks.push(
|
||||
getBusyTimesFromBookingLimits({
|
||||
bookings,
|
||||
bookingLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
eventTypeId: eventType.id,
|
||||
limitManager,
|
||||
rescheduleUid,
|
||||
timeZone,
|
||||
}).then(() => {
|
||||
performance.mark("bookingLimitsEnd");
|
||||
performance.measure(`checking booking limits took $1'`, "bookingLimitsStart", "bookingLimitsEnd");
|
||||
})
|
||||
);
|
||||
await getBusyTimesFromBookingLimits({
|
||||
bookings,
|
||||
bookingLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
eventTypeId: eventType.id,
|
||||
limitManager,
|
||||
rescheduleUid,
|
||||
timeZone,
|
||||
});
|
||||
performance.mark("bookingLimitsEnd");
|
||||
performance.measure(`checking booking limits took $1'`, "bookingLimitsStart", "bookingLimitsEnd");
|
||||
}
|
||||
|
||||
// ..than adding up durations (especially for the whole year)
|
||||
if (durationLimits) {
|
||||
performance.mark("durationLimitsStart");
|
||||
limitChecks.push(
|
||||
getBusyTimesFromDurationLimits(
|
||||
bookings,
|
||||
durationLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
duration,
|
||||
eventType,
|
||||
limitManager,
|
||||
timeZone,
|
||||
rescheduleUid
|
||||
).then(() => {
|
||||
performance.mark("durationLimitsEnd");
|
||||
performance.measure(`checking duration limits took $1'`, "durationLimitsStart", "durationLimitsEnd");
|
||||
})
|
||||
await getBusyTimesFromDurationLimits(
|
||||
bookings,
|
||||
durationLimits,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
duration,
|
||||
eventType,
|
||||
limitManager,
|
||||
timeZone,
|
||||
rescheduleUid
|
||||
);
|
||||
performance.mark("durationLimitsEnd");
|
||||
performance.measure(`checking duration limits took $1'`, "durationLimitsStart", "durationLimitsEnd");
|
||||
}
|
||||
|
||||
await Promise.all(limitChecks);
|
||||
|
||||
performance.mark("limitsEnd");
|
||||
performance.measure(`checking all limits took $1'`, "limitsStart", "limitsEnd");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user