a cache for slack profile pictures and emojis

perf: optimize cache strategy to eliminate latency spikes

- Replace expiration-based emoji updates with scheduled 3 AM refresh
- Implement lazy user loading with 7-day TTL instead of daily purge
- Move analytics cleanup to off-peak hours (2-6 AM)
- Remove aggressive midnight purgeAll() causing cache stampedes
- Add probabilistic user cleanup (10% chance during 3-5 AM)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

dunkirk.sh ef84672f 2e38ff21

verified
Changed files
+47 -32
src
+44 -15
src/cache.ts
···
}
/**
-
* Sets up hourly purge of expired items
+
* Sets up scheduled tasks for cache maintenance
* @private
*/
private setupPurgeSchedule() {
-
// Run purge every hour
+
// Run purge every hour at 45 minutes (only expired items, analytics cleanup)
schedule("45 * * * *", async () => {
await this.purgeExpiredItems();
+
await this.lazyUserCleanup();
+
});
+
+
// Schedule emoji updates daily at 3 AM to avoid peak hours
+
schedule("0 3 * * *", async () => {
+
console.log("Scheduled emoji update starting...");
+
if (this.onEmojiExpired) {
+
await this.onEmojiExpired();
+
console.log("Scheduled emoji update completed");
+
}
});
}
···
* @returns int indicating number of items purged
*/
async purgeExpiredItems(): Promise<number> {
-
const result = this.db.run("DELETE FROM users WHERE expiration < ?", [
-
Date.now(),
-
]);
+
// Only purge emojis - users will use lazy loading with longer TTL
const result2 = this.db.run("DELETE FROM emojis WHERE expiration < ?", [
Date.now(),
]);
-
// Clean up old analytics data (older than 30 days)
+
// Clean up old analytics data (older than 30 days) - moved to off-peak hours
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
-
this.db.run("DELETE FROM request_analytics WHERE timestamp < ?", [
-
thirtyDaysAgo,
-
]);
+
const currentHour = new Date().getHours();
+
// Only run analytics cleanup during off-peak hours (2-6 AM)
+
if (currentHour >= 2 && currentHour < 6) {
+
this.db.run("DELETE FROM request_analytics WHERE timestamp < ?", [
+
thirtyDaysAgo,
+
]);
+
console.log(`Analytics cleanup completed - removed records older than 30 days`);
+
}
+
+
// Emojis are now updated on schedule, not on expiration
+
return result2.changes;
+
}
-
if (this.onEmojiExpired) {
-
if (result2.changes > 0) {
-
this.onEmojiExpired();
+
/**
+
* Lazy cleanup of truly expired users (older than 7 days) during off-peak hours only
+
* This runs much less frequently than the old aggressive purging
+
* @private
+
*/
+
private async lazyUserCleanup(): Promise<void> {
+
const currentHour = new Date().getHours();
+
// Only run during off-peak hours (3-5 AM) and not every time
+
if (currentHour >= 3 && currentHour < 5 && Math.random() < 0.1) { // 10% chance
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
+
const result = this.db.run("DELETE FROM users WHERE expiration < ?", [
+
sevenDaysAgo,
+
]);
+
if (result.changes > 0) {
+
console.log(`Lazy user cleanup: removed ${result.changes} expired users`);
}
}
-
-
return result.changes + result2.changes;
}
/**
···
expirationHours?: number,
) {
const id = crypto.randomUUID();
+
// Users get longer TTL (7 days) for lazy loading, unless custom expiration specified
+
const userDefaultTTL = 7 * 24; // 7 days in hours
const expiration =
-
Date.now() + (expirationHours || this.defaultExpiration) * 3600000;
+
Date.now() + (expirationHours || userDefaultTTL) * 3600000;
try {
this.db.run(
+3 -17
src/index.ts
···
},
);
-
// Setup cron jobs
-
setupCronJobs();
+
// Cache maintenance is now handled automatically by cache.ts scheduled tasks
// Start the server
const server = serve({
···
return Response.json(userAgents);
}
-
// Setup cron jobs for cache maintenance
-
function setupCronJobs() {
-
// Daily purge of all expired items
-
const dailyPurge = setInterval(async () => {
-
const now = new Date();
-
if (now.getHours() === 0 && now.getMinutes() === 0) {
-
await cache.purgeAll();
-
}
-
}, 60 * 1000); // Check every minute
-
-
// Clean up on process exit
-
process.on("exit", () => {
-
clearInterval(dailyPurge);
-
});
-
}
+
// Cache maintenance is now handled by scheduled tasks in cache.ts
+
// No aggressive daily purge needed - users will lazy load with longer TTL