avoid calendar cache on cold starts (#8311)
Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Keith Williams
Omar López
parent
ad958f10bd
commit
2bdd6df755
@@ -19,6 +19,7 @@ import type { CredentialPayload, CredentialWithAppName } from "@calcom/types/Cre
|
||||
import type { EventResult } from "@calcom/types/EventManager";
|
||||
|
||||
const log = logger.getChildLogger({ prefix: ["CalendarManager"] });
|
||||
let coldStart = true;
|
||||
|
||||
export const getCalendarCredentials = (credentials: Array<CredentialPayload>) => {
|
||||
const calendarCredentials = getApps(credentials)
|
||||
@@ -183,48 +184,71 @@ export const getCachedResults = async (
|
||||
*/
|
||||
const getNextCache = async (username: string, month: string): Promise<EventBusyDate[][]> => {
|
||||
let localCache: EventBusyDate[][] = [];
|
||||
const { NODE_ENV } = process.env;
|
||||
const cacheDir = `${NODE_ENV === "development" ? NODE_ENV : process.env.BUILD_ID}`;
|
||||
const baseUrl = `${WEBAPP_URL}/_next/data/${cacheDir}/en`;
|
||||
const url = `${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`;
|
||||
try {
|
||||
const { NODE_ENV } = process.env;
|
||||
const cacheDir = `${NODE_ENV === "development" ? NODE_ENV : process.env.BUILD_ID}`;
|
||||
const baseUrl = `${WEBAPP_URL}/_next/data/${cacheDir}/en`;
|
||||
console.log(`${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`);
|
||||
localCache = await fetch(
|
||||
`${baseUrl}/${username}/calendar-cache/${month}.json?user=${username}&month=${month}`
|
||||
)
|
||||
localCache = await fetch(url)
|
||||
.then((r) => r.json())
|
||||
.then((json) => json?.pageProps?.results);
|
||||
// No need to wait for this, the purpose is to force re-validation every second as indicated
|
||||
// in page getStaticProps.
|
||||
fetch(`${baseUrl}/${username}/calendar-cache/${month}`).catch(console.log);
|
||||
} catch (e) {
|
||||
log.warn(e);
|
||||
log.warn(url, e);
|
||||
}
|
||||
return localCache;
|
||||
};
|
||||
/**
|
||||
* Get months between given dates
|
||||
* @returns ["2023-04", "2024-05"]
|
||||
*/
|
||||
const getMonths = (dateFrom: string, dateTo: string): string[] => {
|
||||
const months: string[] = [dayjs(dateFrom).format("YYYY-MM")];
|
||||
for (
|
||||
let i = 1;
|
||||
dayjs(dateFrom).add(i, "month").isBefore(dateTo) ||
|
||||
dayjs(dateFrom).add(i, "month").isSame(dateTo, "month");
|
||||
i++
|
||||
) {
|
||||
months.push(dayjs(dateFrom).add(i, "month").format("YYYY-MM"));
|
||||
}
|
||||
return months;
|
||||
};
|
||||
|
||||
const createCalendarCachePage = (username: string, month: string): void => {
|
||||
// No need to wait for this, the purpose is to force re-validation every second as indicated
|
||||
// in page getStaticProps.
|
||||
fetch(`${WEBAPP_URL}/${username}/calendar-cache/${month}`).catch(console.log);
|
||||
};
|
||||
export const getBusyCalendarTimes = async (
|
||||
username: string,
|
||||
withCredentials: CredentialPayload[],
|
||||
dateFrom: string,
|
||||
dateTo: string
|
||||
dateTo: string,
|
||||
selectedCalendars: SelectedCalendar[]
|
||||
) => {
|
||||
let results: EventBusyDate[][] = [];
|
||||
if (dayjs(dateFrom).isSame(dayjs(dateTo), "month")) {
|
||||
results = await getNextCache(username, dayjs(dateFrom).format("YYYY-MM"));
|
||||
} else {
|
||||
// if dateFrom and dateTo is from different months get cache by each month
|
||||
const months: string[] = [dayjs(dateFrom).format("YYYY-MM")];
|
||||
for (
|
||||
let i = 1;
|
||||
dayjs(dateFrom).add(i, "month").isBefore(dateTo) ||
|
||||
dayjs(dateFrom).add(i, "month").isSame(dateTo, "month");
|
||||
i++
|
||||
) {
|
||||
months.push(dayjs(dateFrom).add(i, "month").format("YYYY-MM"));
|
||||
const months = getMonths(dateFrom, dateTo);
|
||||
try {
|
||||
if (coldStart) {
|
||||
results = await getCachedResults(withCredentials, dateFrom, dateTo, selectedCalendars);
|
||||
logger.info("Generating calendar cache in background");
|
||||
// on cold start the calendar cache page generated in the background
|
||||
Promise.all(months.map((month) => createCalendarCachePage(username, month)));
|
||||
} else {
|
||||
if (months.length === 1) {
|
||||
results = await getNextCache(username, dayjs(dateFrom).format("YYYY-MM"));
|
||||
} else {
|
||||
// if dateFrom and dateTo is from different months get cache by each month
|
||||
const data: EventBusyDate[][][] = await Promise.all(
|
||||
months.map((month) => getNextCache(username, month))
|
||||
);
|
||||
results = data.flat(1);
|
||||
}
|
||||
}
|
||||
const data: EventBusyDate[][][] = await Promise.all(months.map((month) => getNextCache(username, month)));
|
||||
results = data.flat(1);
|
||||
} catch (e) {
|
||||
logger.warn(e);
|
||||
}
|
||||
coldStart = false;
|
||||
return results.reduce((acc, availability) => acc.concat(availability), []);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import dayjs from "@calcom/dayjs";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { performance } from "@calcom/lib/server/perfObserver";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { SelectedCalendar } from "@calcom/prisma/client";
|
||||
import type { EventBusyDetails } from "@calcom/types/Calendar";
|
||||
|
||||
export async function getBusyTimes(params: {
|
||||
@@ -17,6 +18,7 @@ export async function getBusyTimes(params: {
|
||||
beforeEventBuffer?: number;
|
||||
afterEventBuffer?: number;
|
||||
endTime: string;
|
||||
selectedCalendars: SelectedCalendar[];
|
||||
}) {
|
||||
const {
|
||||
credentials,
|
||||
@@ -27,6 +29,7 @@ export async function getBusyTimes(params: {
|
||||
endTime,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
selectedCalendars,
|
||||
} = params;
|
||||
logger.silly(
|
||||
`Checking Busy time from Cal Bookings in range ${startTime} to ${endTime} for input ${JSON.stringify({
|
||||
@@ -119,7 +122,20 @@ export async function getBusyTimes(params: {
|
||||
performance.mark("prismaBookingGetEnd");
|
||||
performance.measure(`prisma booking get took $1'`, "prismaBookingGetStart", "prismaBookingGetEnd");
|
||||
if (credentials?.length > 0) {
|
||||
const calendarBusyTimes = await getBusyCalendarTimes(username, credentials, startTime, endTime);
|
||||
const startConnectedCalendarsGet = performance.now();
|
||||
const calendarBusyTimes = await getBusyCalendarTimes(
|
||||
username,
|
||||
credentials,
|
||||
startTime,
|
||||
endTime,
|
||||
selectedCalendars
|
||||
);
|
||||
const endConnectedCalendarsGet = performance.now();
|
||||
logger.debug(
|
||||
`Connected Calendars get took ${
|
||||
endConnectedCalendarsGet - startConnectedCalendarsGet
|
||||
} ms for user ${username}`
|
||||
);
|
||||
busyTimes.push(
|
||||
...calendarBusyTimes.map((value) => ({
|
||||
...value,
|
||||
|
||||
@@ -149,6 +149,7 @@ export async function getUserAvailability(
|
||||
username: `${user.username}`,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
selectedCalendars: user.selectedCalendars,
|
||||
});
|
||||
|
||||
let bufferedBusyTimes: EventBusyDetails[] = busyTimes.map((a) => ({
|
||||
|
||||
Reference in New Issue
Block a user