a cache for slack profile pictures and emojis

feat: move the touch to refresh to non blocking

dunkirk.sh 85f77fdc 5234f52f

verified
+39 -7
+39 -7
src/cache.ts
··· 348 348 private slackWrapper?: SlackUserProvider; // Will be injected after construction 349 349 private currentSessionId?: number; 350 350 351 + // Touch-refresh queue for async non-blocking updates 352 + private touchRefreshQueue: Array<{ 353 + newExpiration: number; 354 + normalizedId: string; 355 + }> = []; 356 + private isFlushingTouchRefresh = false; 357 + 351 358 // Prepared statements for hot paths 352 359 private stmtTraffic10min!: Statement; 353 360 private stmtTrafficHourly!: Statement; ··· 382 389 this.initPreparedStatements(); 383 390 this.setupPurgeSchedule(); 384 391 this.startQueueProcessor(); 392 + this.startTouchRefreshFlush(); 385 393 386 394 // Run migrations 387 395 this.runMigrations(); ··· 987 995 } 988 996 989 997 /** 998 + * Starts the touch-refresh flush timer (no-op, using microtasks instead) 999 + * @private 1000 + */ 1001 + private startTouchRefreshFlush() { 1002 + // No timer needed - using queueMicrotask for immediate async execution 1003 + } 1004 + 1005 + /** 1006 + * Flushes a single touch-refresh update asynchronously using microtask 1007 + * @private 1008 + */ 1009 + private flushTouchRefresh(newExpiration: number, normalizedId: string) { 1010 + queueMicrotask(() => { 1011 + try { 1012 + this.db.run("UPDATE users SET expiration = ? WHERE userId = ?", [ 1013 + newExpiration, 1014 + normalizedId, 1015 + ]); 1016 + } catch (error) { 1017 + console.error("Error in touch-refresh update:", error); 1018 + } 1019 + }); 1020 + } 1021 + 1022 + /** 990 1023 * Processes the user update queue with rate limiting 991 1024 * @private 992 1025 */ ··· 1188 1221 if (userAge < twentyFourHoursAgo) { 1189 1222 // Extend TTL by another 7 days from now 1190 1223 const newExpiration = now + 7 * 24 * 60 * 60 * 1000; 1191 - this.db.run("UPDATE users SET expiration = ? WHERE userId = ?", [ 1192 - newExpiration, 1193 - normalizedId, 1194 - ]); 1224 + 1225 + // Flush the UPDATE asynchronously using microtask (non-blocking) 1226 + this.flushTouchRefresh(newExpiration, normalizedId); 1195 1227 1196 1228 // Queue for background update to get fresh data 1197 1229 this.queueUserUpdate(normalizedId); ··· 1281 1313 const respTime = responseTime || 0; 1282 1314 const nowMs = Date.now(); 1283 1315 1284 - // Parse referer host outside transaction 1316 + // Parse referer host 1285 1317 let refererHost: string | null = null; 1286 1318 if (referer) { 1287 1319 try { ··· 1291 1323 } 1292 1324 } 1293 1325 1294 - // Use prepared statements without transaction wrapper for lower latency 1295 - // WAL mode allows concurrent writes, and prepared statements are pre-compiled 1326 + // Use prepared statements - these are FAST with WAL mode 1327 + // WAL allows concurrent reads during these writes 1296 1328 this.stmtTraffic10min.run(bucket10min, endpoint, statusCode, respTime); 1297 1329 this.stmtTrafficHourly.run(bucketHour, endpoint, statusCode, respTime); 1298 1330 this.stmtTrafficDaily.run(bucketDay, endpoint, statusCode, respTime);