Skip to content

Optimize calendar cache performance and maintenance#10

Open
ShashankFC wants to merge 1 commit intomainfrom
refactor/calendar-cache-optimization
Open

Optimize calendar cache performance and maintenance#10
ShashankFC wants to merge 1 commit intomainfrom
refactor/calendar-cache-optimization

Conversation

@ShashankFC
Copy link
Collaborator

@ShashankFC ShashankFC commented Nov 25, 2025

This PR optimizes the calendar cache layer with improved query performance, better expiry handling, and automated cleanup utilities for maintaining cache health.


EntelligenceAI PR Summary

This PR enhances calendar cache management with bug fixes, cleanup utilities, and modified default caching behavior.

  • Fixed cache expiration boundary bug by changing comparison operator from gte to gt in repository queries
  • Added CACHE_CLEANUP_THRESHOLD constant set to 7 days
  • Implemented cache maintenance utilities: expired entry cleanup, deduplication, and comprehensive maintenance wrapper
  • Changed default cache serving behavior to enable caching for requests without team ID
  • Added structured logging and error handling for all cleanup operations

@entelligence-ai-pr-reviews
Copy link

Entelligence AI Vulnerability Scanner

Status: No security vulnerabilities found

Your code passed our comprehensive security analysis.

Analyzed 3 files in total

@entelligence-ai-pr-reviews
Copy link

Review Summary

🏷️ Draft Comments (3)

Skipped posting 3 draft comments that were valid but scored below your review threshold (>=13/15). Feel free to update them here.

packages/features/calendar-cache/calendar-cache.repository.ts (2)

112-112: expiresAt: { gte: new Date(Date.now()) } was changed to gt: new Date(Date.now()), which now excludes cache entries expiring at the current millisecond, potentially causing valid cache entries to be missed if their expiresAt equals Date.now().

📊 Impact Scores:

  • Production Impact: 1/5
  • Fix Specificity: 5/5
  • Urgency Impact: 1/5
  • Total Score: 7/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/calendar-cache/calendar-cache.repository.ts, line 112, the cache query uses `expiresAt: { gt: new Date(Date.now()) }`, which excludes entries expiring at the current millisecond. Change this to `expiresAt: { gte: new Date(Date.now()) }` to ensure cache entries that expire exactly at the current time are still considered valid.

16-16: CACHE_CLEANUP_THRESHOLD is defined but never used, representing dead code that increases bundle size and reduces maintainability in large codebases.

📊 Impact Scores:

  • Production Impact: 1/5
  • Fix Specificity: 5/5
  • Urgency Impact: 1/5
  • Total Score: 7/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/calendar-cache/calendar-cache.repository.ts, line 16, the constant `CACHE_CLEANUP_THRESHOLD` is defined but never used. Please remove this unused variable to reduce dead code and improve maintainability.

packages/features/calendar-cache/lib/cacheCleanup.ts (1)

54-66: deduplicateCacheEntries deletes all but the most recent entry by expiresAt, but if multiple entries share the same latest expiresAt, it may delete valid entries, causing data loss.

📊 Impact Scores:

  • Production Impact: 4/5
  • Fix Specificity: 3/5
  • Urgency Impact: 3/5
  • Total Score: 10/15

🤖 AI Agent Prompt (Copy & Paste Ready):

In packages/features/calendar-cache/lib/cacheCleanup.ts, lines 54-66, the deduplication logic in deduplicateCacheEntries may delete valid entries if multiple entries share the latest expiresAt value, causing data loss. Update the logic so that if there are multiple entries with the same latest expiresAt, only one (preferably the one with the highest id) is kept and the rest are deleted. Ensure the code preserves at least one entry for each (credentialId, key) pair with the latest expiresAt.

@entelligence-ai-pr-reviews
Copy link

🔬 Multi-Approach Review Summary

This PR was reviewed by 2 different approaches for comparison:

  • 🟢 Standard Reviewer: 1 comments
  • 🟠 LangGraph v3: 4 comments

Total: 5 review comments

Each comment is labeled with its source approach. This allows you to compare different AI review strategies.

🔒 Security Scan: Run once and shared across all approaches for efficiency.

Walkthrough

This PR implements comprehensive calendar cache management improvements across three key areas. First, it fixes a critical bug in cache expiration logic by changing comparison operators from gte to gt, ensuring entries expiring at the exact current timestamp are properly treated as expired. Second, it introduces a new cache cleanup utility module with functions to remove expired entries beyond a 7-day threshold and deduplicate cache entries based on credentialId and key. Third, it modifies the default cache serving behavior to enable caching for non-team contexts by returning true when teamId is not provided, representing a significant shift in caching strategy.

Changes

File(s) Summary
packages/features/calendar-cache/calendar-cache.repository.ts Introduced CACHE_CLEANUP_THRESHOLD constant (7 days) and fixed cache expiration query logic by changing expiresAt comparison from gte to gt to properly exclude entries expiring at exact timestamp boundary.
packages/features/calendar-cache/lib/cacheCleanup.ts Added new cache cleanup utility module with three functions: cleanupExpiredCache() for removing entries expired beyond threshold, deduplicateCacheEntries() for removing duplicates while preserving most recent, and performCacheMaintenance() as comprehensive wrapper with structured logging and error handling.
packages/features/calendar-cache/lib/getShouldServeCache.ts Modified default cache serving behavior in CacheService.getShouldServeCache() to return true instead of false when teamId is not provided, enabling cache for non-team contexts.

Sequence Diagram

This diagram shows the interactions between components:

sequenceDiagram
    participant Client
    participant CacheService
    participant Database
    
    Note over CacheService: New constant added:<br/>CACHE_CLEANUP_THRESHOLD = 7 days
    
    Client->>CacheService: Request cached data (userId, key)
    activate CacheService
    
    CacheService->>Database: Query cache by userId and key
    activate Database
    Note over Database: Modified filter:<br/>expiresAt > now<br/>(was: expiresAt >= now)
    
    alt Cache entry exists and not expired
        Database-->>CacheService: Return cached data (ordered by expiresAt DESC)
        CacheService-->>Client: Return cached result
    else No valid cache entry
        Database-->>CacheService: Return null
        CacheService-->>Client: Cache miss
    end
    deactivate Database
    deactivate CacheService
    
    Client->>CacheService: Request cached data (credentialId, key)
    activate CacheService
    
    CacheService->>Database: Query cache by credentialId and key
    activate Database
    Note over Database: Modified filter:<br/>expiresAt > now<br/>(was: expiresAt >= now)
    
    alt Cache entry exists and not expired
        Database-->>CacheService: Return cached data
        CacheService-->>Client: Return cached result
    else No valid cache entry
        Database-->>CacheService: Return null
        CacheService-->>Client: Cache miss
    end
    deactivate Database
    deactivate CacheService
    
    Note over CacheService,Database: Expiry boundary fix:<br/>Entries expiring exactly now<br/>are now considered expired
Loading

🔒 Security Analysis

  • Vulnerabilities: 0
  • Bugs: 0
  • Code Smells: 2
  • Security Hotspots: 0

▶️AI Code Reviews for VS Code, Cursor, Windsurf
Install the extension

Note for Windsurf Please change the default marketplace provider to the following in the windsurf settings:

Marketplace Extension Gallery Service URL: https://marketplace.visualstudio.com/_apis/public/gallery

Marketplace Gallery Item URL: https://marketplace.visualstudio.com/items

Entelligence.ai can learn from your feedback. Simply add 👍 / 👎 emojis to teach it your preferences. More shortcuts below

Emoji Descriptions:

  • ⚠️ Potential Issue - May require further investigation.
  • 🔒 Security Vulnerability - Fix to ensure system safety.
  • 💻 Code Improvement - Suggestions to enhance code quality.
  • 🔨 Refactor Suggestion - Recommendations for restructuring code.
  • ℹ️ Others - General comments and information.

Interact with the Bot:

  • Send a message or request using the format:
    @entelligenceai + *your message*
Example: @entelligenceai Can you suggest improvements for this code?
  • Help the Bot learn by providing feedback on its responses.
    @entelligenceai + *feedback*
Example: @entelligenceai Do not comment on `save_auth` function !

Also you can trigger various commands with the bot by doing
@entelligenceai command

The current supported commands are

  1. config - shows the current config
  2. retrigger_review - retriggers the review

More commands to be added soon.

async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return false;
if (!teamId) return true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correctness: 🟢 [Standard Reviewer] if (!teamId) return true; causes cache to be served even when teamId is missing, which may expose incorrect or unauthorized cache data.

🤖 AI Agent Prompt for Cursor/Windsurf

📋 Copy this prompt to your AI coding assistant (Cursor, Windsurf, etc.) to get help fixing this issue

In packages/features/calendar-cache/lib/getShouldServeCache.ts, on line 13, the code currently reads `if (!teamId) return true;`. This causes the cache to be served even when `teamId` is missing, which can result in serving incorrect or unauthorized cache data. Change this line to `if (!teamId) return false;` to ensure cache is only served when a valid teamId is present.
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if (!teamId) return true;
if (!teamId) return false;

Comment on lines 13 to 19
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

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_THRESHOLD is defined but never used in the code. Remove it to clean up the codebase and avoid unnecessary clutter.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
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
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
const CACHING_TIME = ONE_MONTH_IN_MS;
function parseKeyForCache(args: FreeBusyArgs): string {
// Ensure that calendarIds are unique

Comment on lines 109 to 115
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: 🟠 [LangGraph v3] The change from gte to gt for expiresAt may exclude entries that expire exactly at the current time, potentially causing valid entries to be missed. Consider reverting to gte if entries expiring at the current time should be included.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
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
where: {
userId,
key,
expiresAt: { gte: 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 124 to 130
credentialId,
key,
},
expiresAt: { gte: new Date(Date.now()) },
expiresAt: { gt: new Date(Date.now()) },
},
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: 🟠 [LangGraph v3] The change from gte to gt in the expiresAt condition ensures that only entries with a future expiration date are retrieved, which is likely the intended behavior. Verify that this change aligns with the business logic, as it excludes entries expiring at the current time.

Comment on lines 10 to 16

async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return false;
if (!teamId) return true;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: 🟠 [LangGraph v3] The async function getShouldServeCache lacks error handling for the awaited call to checkIfTeamHasFeature. This can lead to unhandled promise rejections if the call fails. Wrap the call in a try/catch block to handle potential errors.

📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return false;
if (!teamId) return true;
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
}
}
constructor(private readonly dependencies: ICacheService) {}
async getShouldServeCache(shouldServeCache?: boolean | undefined, teamId?: number) {
if (typeof shouldServeCache === "boolean") return shouldServeCache;
if (!teamId) return true;
try {
return await this.dependencies.featuresRepository.checkIfTeamHasFeature(teamId, CalendarSubscriptionService.CALENDAR_SUBSCRIPTION_CACHE_FEATURE);
} catch (error) {
console.error('Error checking team feature:', error);
return false;
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant