-
Notifications
You must be signed in to change notification settings - Fork 0
Optimize calendar cache performance and maintenance #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,7 @@ const log = logger.getSubLogger({ prefix: ["CalendarCacheRepository"] }); | |||||||||||||||||||||||||||||||
| const MS_PER_DAY = 24 * 60 * 60 * 1000; | ||||||||||||||||||||||||||||||||
| const ONE_MONTH_IN_MS = 30 * MS_PER_DAY; | ||||||||||||||||||||||||||||||||
| const CACHING_TIME = ONE_MONTH_IN_MS; | ||||||||||||||||||||||||||||||||
| const CACHE_CLEANUP_THRESHOLD = 7 * MS_PER_DAY; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function parseKeyForCache(args: FreeBusyArgs): string { | ||||||||||||||||||||||||||||||||
| // Ensure that calendarIds are unique | ||||||||||||||||||||||||||||||||
|
|
@@ -108,7 +109,7 @@ export class CalendarCacheRepository implements ICalendarCacheRepository { | |||||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||||||
| userId, | ||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||
| expiresAt: { gte: new Date(Date.now()) }, | ||||||||||||||||||||||||||||||||
| expiresAt: { gt: new Date(Date.now()) }, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| orderBy: { | ||||||||||||||||||||||||||||||||
| // In case of multiple entries for same key and userId, we prefer the one with highest expiry, which will be the most updated one | ||||||||||||||||||||||||||||||||
|
Comment on lines
109
to
115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: 🟠 [LangGraph v3] The change from 📝 Committable Code Suggestion
Suggested change
|
||||||||||||||||||||||||||||||||
|
|
@@ -123,7 +124,7 @@ export class CalendarCacheRepository implements ICalendarCacheRepository { | |||||||||||||||||||||||||||||||
| credentialId, | ||||||||||||||||||||||||||||||||
| key, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| expiresAt: { gte: new Date(Date.now()) }, | ||||||||||||||||||||||||||||||||
| expiresAt: { gt: new Date(Date.now()) }, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
124
to
130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: 🟠 [LangGraph v3] The change from |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import logger from "@calcom/lib/logger"; | ||
| import prisma from "@calcom/prisma"; | ||
|
|
||
| const log = logger.getSubLogger({ prefix: ["CacheCleanup"] }); | ||
|
|
||
| const MS_PER_DAY = 24 * 60 * 60 * 1000; | ||
| const CLEANUP_THRESHOLD_DAYS = 7; | ||
|
|
||
| /** | ||
| * Removes expired cache entries older than the threshold | ||
| * @param thresholdDays - Number of days after expiry to keep entries | ||
| * @returns Number of deleted entries | ||
| */ | ||
| export async function cleanupExpiredCache(thresholdDays: number = CLEANUP_THRESHOLD_DAYS): Promise<number> { | ||
| const thresholdDate = new Date(Date.now() - thresholdDays * MS_PER_DAY); | ||
|
|
||
| try { | ||
| const result = await prisma.calendarCache.deleteMany({ | ||
| where: { | ||
| expiresAt: { | ||
| lt: thresholdDate, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| log.info(`Cleaned up ${result.count} expired cache entries`); | ||
| return result.count; | ||
| } catch (error) { | ||
| log.error("Failed to cleanup expired cache", error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Removes duplicate cache entries for the same credential and key | ||
| * Keeps only the most recent entry | ||
| */ | ||
| export async function deduplicateCacheEntries(): Promise<number> { | ||
| try { | ||
| const duplicates = await prisma.calendarCache.groupBy({ | ||
| by: ["credentialId", "key"], | ||
| having: { | ||
| credentialId: { | ||
| _count: { | ||
| gt: 1, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| let deletedCount = 0; | ||
|
|
||
| for (const duplicate of duplicates) { | ||
| const entries = await prisma.calendarCache.findMany({ | ||
| where: { | ||
| credentialId: duplicate.credentialId, | ||
| key: duplicate.key, | ||
| }, | ||
| orderBy: { | ||
| expiresAt: "desc", | ||
| }, | ||
| }); | ||
|
|
||
| if (entries.length > 1) { | ||
| const idsToDelete = entries.slice(1).map((entry) => entry.id); | ||
|
|
||
| const result = await prisma.calendarCache.deleteMany({ | ||
| where: { | ||
| id: { | ||
| in: idsToDelete, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| deletedCount += result.count; | ||
| } | ||
| } | ||
|
|
||
| log.info(`Deduplicated ${deletedCount} cache entries`); | ||
| return deletedCount; | ||
| } catch (error) { | ||
| log.error("Failed to deduplicate cache entries", error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Performs comprehensive cache maintenance | ||
| */ | ||
| export async function performCacheMaintenance(): Promise<{ cleaned: number; deduplicated: number }> { | ||
| log.info("Starting cache maintenance"); | ||
|
|
||
| const cleaned = await cleanupExpiredCache(); | ||
| const deduplicated = await deduplicateCacheEntries(); | ||
|
|
||
| log.info("Cache maintenance completed", { cleaned, deduplicated }); | ||
|
|
||
| return { cleaned, deduplicated }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,7 @@ export class CacheService { | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) { | ||||||||||||||||||||||||||||||||||||||||||
| if (typeof shouldServeCache === "boolean") return shouldServeCache; | ||||||||||||||||||||||||||||||||||||||||||
| if (!teamId) return false; | ||||||||||||||||||||||||||||||||||||||||||
| if (!teamId) return true; | ||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. correctness: 🟢 [Standard Reviewer] 🤖 AI Agent Prompt for Cursor/Windsurf
📝 Committable Code Suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
| return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
10
to
16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correctness: 🟠 [LangGraph v3] The async function 📝 Committable Code Suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correctness: 🟠 [LangGraph v3] The constant
CACHE_CLEANUP_THRESHOLDis defined but never used in the code. Remove it to clean up the codebase and avoid unnecessary clutter.📝 Committable Code Suggestion