[object Object]

← back to AsSeenInMovies

asseeninmovies: home page — 60s in-memory cache with inflight dedup + startup warmup (count(*) on 290k movies stays slow cold but every user request hits cached data; concurrent home hits no longer stack up in pg_stat_activity)

d6451f313d2a97780e561098d43fa2c8cea5bf92 · 2026-05-12 17:25:15 -0700 · SteveStudio2

Files touched

Diff

commit d6451f313d2a97780e561098d43fa2c8cea5bf92
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 17:25:15 2026 -0700

    asseeninmovies: home page — 60s in-memory cache with inflight dedup + startup warmup (count(*) on 290k movies stays slow cold but every user request hits cached data; concurrent home hits no longer stack up in pg_stat_activity)
---
 server.js | 54 +++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 39 insertions(+), 15 deletions(-)

diff --git a/server.js b/server.js
index affe258..8362f38 100644
--- a/server.js
+++ b/server.js
@@ -1223,17 +1223,17 @@ app.get('/people', async (req, res) => {
 });
 
 // Home — placeholder until tick 1 ships TMDB ingest + real UI.
-app.get('/', async (_req, res) => {
-  // Live-stats home with featured rails of recent movies + people. Read-only.
-  let stats = { movies_total: 0, people_total: 0, movies_poster: 0, people_headshot: 0, spotted_total: 0 };
-  let featuredMovies = [], featuredPeople = [], featuredSpotted = [];
-  try {
-    // Parallel via Promise.all so the home page wall time = max of the four
-    // queries instead of sum. count(*) on asim_movies/asim_people is the
-    // long-pole at ~3s cold; running the featured-rail picks alongside (not
-    // after) cuts total home-page time roughly in half. Kept ORDER BY
-    // random() since TABLESAMPLE SYSTEM had 3s startup AND produced empty
-    // samples when paired with a restrictive WHERE.
+// 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;
+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 () => {
     const [s, fm, fp, fs] = await Promise.all([
       pool.query(`
         SELECT
@@ -1251,10 +1251,29 @@ app.get('/', async (_req, res) => {
          WHERE s.verified_at IS NOT NULL AND (verified_by IS NULL OR verified_by NOT LIKE 'rejected%')
          ORDER BY s.verified_at DESC LIMIT 6`)
     ]);
-    stats = s.rows[0];
-    featuredMovies = fm.rows;
-    featuredPeople = fp.rows;
-    featuredSpotted = fs.rows;
+    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 };
+    return data;
+  } catch (e) {
+    _homeCache = { at: now, inflight: null, data: _homeCache?.data || null };
+    throw e;
+  }
+}
+
+app.get('/', async (_req, res) => {
+  // Live-stats home with featured rails of recent movies + people. Read-only.
+  let stats = { movies_total: 0, people_total: 0, movies_poster: 0, people_headshot: 0, spotted_total: 0 };
+  let featuredMovies = [], featuredPeople = [], featuredSpotted = [];
+  try {
+    const cached = await getHomeCache();
+    stats = cached.stats;
+    featuredMovies = cached.featuredMovies;
+    featuredPeople = cached.featuredPeople;
+    featuredSpotted = cached.featuredSpotted;
   } catch (e) {
     // home page should never 500; fall back to static.
   }
@@ -1392,4 +1411,9 @@ app.get('/', async (_req, res) => {
 app.listen(PORT, '127.0.0.1', async () => {
   await ensureSchema();
   console.log(`asseeninmovies → http://127.0.0.1:${PORT}`);
+  // Prime the home-page cache so the first real request doesn't wait on
+  // cold count(*) queries (2-3s each on 290k+61k rows).
+  getHomeCache()
+    .then(d => console.log(`[home-cache] warm — movies=${d.stats.movies_total} people=${d.stats.people_total} spotted=${d.stats.spotted_total}`))
+    .catch(e => console.warn(`[home-cache] warmup failed: ${e.message}`));
 });

← 25508fb asseeninmovies: home page perf — parallelize 4 queries via P  ·  back to AsSeenInMovies  ·  asseeninmovies: /movies + /people — parallelize page query a dbd4763 →