[object Object]

← back to AsSeenInMovies

asseeninmovies: home cache — true stale-while-revalidate. Stale hits now return cached data immediately + trigger background refresh, instead of blocking on the next 2-3s count(*). Empty-cache first hit still awaits (warmup covers it on startup).

f4b860b261fcbdea0f9d525f2426eb332feef067 · 2026-05-12 18:32:32 -0700 · SteveStudio2

Files touched

Diff

commit f4b860b261fcbdea0f9d525f2426eb332feef067
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 18:32:32 2026 -0700

    asseeninmovies: home cache — true stale-while-revalidate. Stale hits now return cached data immediately + trigger background refresh, instead of blocking on the next 2-3s count(*). Empty-cache first hit still awaits (warmup covers it on startup).
---
 server.js | 45 ++++++++++++++++++++++++++++-----------------
 1 file changed, 28 insertions(+), 17 deletions(-)

diff --git a/server.js b/server.js
index 4875aa6..3d41d67 100644
--- a/server.js
+++ b/server.js
@@ -1368,17 +1368,17 @@ app.get('/people', async (req, res) => {
 });
 
 // Home — placeholder until tick 1 ships TMDB ingest + real UI.
-// In-memory home-page cache. count(*) on 290k asim_movies + 61k asim_people
-// is 2-3s cold each, and concurrent home hits stack up in pg_stat_activity.
-// TTL 60s — stats don't change second-to-second, and the spotted/featured
-// rails benefit from light memoization too.
-let _homeCache = null;
+// In-memory home-page cache with stale-while-revalidate semantics. count(*)
+// on 290k asim_movies + 61k asim_people is 2-3s cold each, so we never want
+// a user request to wait on that. Behavior:
+//   - cache fresh (< TTL):   return immediately
+//   - cache stale + has data: return stale data, kick off background refresh
+//   - cache empty (first hit): await the inflight refresh (warmup covers this)
+let _homeCache = { at: 0, data: null, inflight: null };
 const HOME_CACHE_TTL_MS = 60_000;
-async function getHomeCache() {
-  const now = Date.now();
-  if (_homeCache && (now - _homeCache.at) < HOME_CACHE_TTL_MS) return _homeCache.data;
-  if (_homeCache && _homeCache.inflight) return _homeCache.inflight;
-  const inflight = (async () => {
+function startHomeRefresh() {
+  if (_homeCache.inflight) return _homeCache.inflight;
+  const p = (async () => {
     const [s, fm, fp, fs] = await Promise.all([
       pool.query(`
         SELECT
@@ -1397,16 +1397,27 @@ async function getHomeCache() {
          ORDER BY s.verified_at DESC LIMIT 6`)
     ]);
     return { stats: s.rows[0], featuredMovies: fm.rows, featuredPeople: fp.rows, featuredSpotted: fs.rows };
-  })();
-  _homeCache = { at: now, inflight, data: _homeCache?.data || null };
-  try {
-    const data = await inflight;
-    _homeCache = { at: Date.now(), inflight: null, data };
+  })().then(data => {
+    _homeCache = { at: Date.now(), data, inflight: null };
     return data;
-  } catch (e) {
-    _homeCache = { at: now, inflight: null, data: _homeCache?.data || null };
+  }).catch(e => {
+    _homeCache = { ..._homeCache, inflight: null };
     throw e;
+  });
+  _homeCache.inflight = p;
+  return p;
+}
+async function getHomeCache() {
+  const now = Date.now();
+  const fresh = (now - _homeCache.at) < HOME_CACHE_TTL_MS;
+  if (fresh && _homeCache.data) return _homeCache.data;
+  if (_homeCache.data) {
+    // stale-while-revalidate: serve immediately, refresh in background
+    if (!_homeCache.inflight) startHomeRefresh().catch(() => {});
+    return _homeCache.data;
   }
+  // empty cache — first hit ever. Must await.
+  return await startHomeRefresh();
 }
 
 app.get('/', async (_req, res) => {

← 2261023 asseeninmovies: GET /favicon.ico fallback — same inline SVG  ·  back to AsSeenInMovies  ·  asseeninmovies: canonical URLs + WebSite/Organization JSON-L f89066d →