Add multi-layer caching: 24h Redis TTL, stale-while-revalidate, frontend LRU cache

- Increase Redis cache TTL from 30 minutes to 24 hours
- Add stale-while-revalidate: serve stale cache (>4h) immediately while
  repopulating in background with SETNX lock to prevent concurrent rebuilds
- Add in-memory frontend LRU cache (5 entries) so repeat filter visits
  are instant without network requests
- Invalidate frontend cache on listing refresh and task completion
- Add unit tests for get_cache_age, is_cache_stale, acquire_repopulation_lock
This commit is contained in:
Viktor Barzin 2026-02-23 20:09:36 +00:00
parent 04bda8c127
commit 1ae00b7cbf
No known key found for this signature in database
GPG key ID: 0EB088298288D958
5 changed files with 270 additions and 1 deletions

View file

@ -0,0 +1,45 @@
/**
* In-memory LRU cache for streaming listing results.
*
* Keyed by a deterministic hash of query parameters so that repeat visits
* to the same filter combination are instant (no network request).
*/
import type { PropertyFeature } from '@/types';
import type { ParameterValues } from '@/components/FilterPanel';
interface CacheEntry {
features: PropertyFeature[];
timestamp: number;
}
const cache = new Map<string, CacheEntry>();
const MAX_ENTRIES = 5;
export function makeCacheKey(params: ParameterValues): string {
const sorted = Object.entries(params)
.filter(([, v]) => v !== undefined && v !== null && v !== '')
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}=${v instanceof Date ? v.toISOString() : v}`);
return sorted.join('&');
}
export function getCached(params: ParameterValues): PropertyFeature[] | null {
const key = makeCacheKey(params);
const entry = cache.get(key);
if (!entry) return null;
return entry.features;
}
export function setCached(params: ParameterValues, features: PropertyFeature[]): void {
const key = makeCacheKey(params);
if (cache.size >= MAX_ENTRIES && !cache.has(key)) {
// Evict oldest entry (first inserted)
const oldest = cache.keys().next().value;
if (oldest) cache.delete(oldest);
}
cache.set(key, { features, timestamp: Date.now() });
}
export function invalidateAll(): void {
cache.clear();
}