[object Object]

← back to Wallco Ai

PERF-3: cache /api/marketplace/status (30s in-process TTL)

28303ddd97d7a9c4d097f81a307f9f41fae0ba8a · 2026-05-23 11:55:37 -0700 · Steve Abrams

src/marketplace/index.js:196 — the status endpoint runs 13 correlated
COUNT(*) subqueries, each scanning a different mp_* table. Logs showed
637ms–7642ms per call. With no cache, every dashboard refresh (and
every external monitor) hit the full block.

Fix: single-shot in-process cache. {ts, payload} singleton, 30s TTL,
no LRU needed because the SQL is a fixed string with no parameters.
Cached responses include `cached: true` and `age_ms` so clients can see
the freshness without a separate uncached endpoint.

ROI: at 1 hit/sec the cached path saves 13 COUNT(*) full-table scans
59 times per minute. At even modest traffic the table-scan budget
collapses by 99%+. For monitors/uptime checks that poll every 10s, the
DB hit rate drops to 2/min instead of 6/min.

Cache invalidation: TTL only. Acceptable because the dashboard tolerates
30s lag on count widgets, and pattern uploads / approvals don't need
real-time count reflection on this endpoint (the per-designer-dashboard
endpoint pulls live counts via a separate route).

Files touched

Diff

commit 28303ddd97d7a9c4d097f81a307f9f41fae0ba8a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 23 11:55:37 2026 -0700

    PERF-3: cache /api/marketplace/status (30s in-process TTL)
    
    src/marketplace/index.js:196 — the status endpoint runs 13 correlated
    COUNT(*) subqueries, each scanning a different mp_* table. Logs showed
    637ms–7642ms per call. With no cache, every dashboard refresh (and
    every external monitor) hit the full block.
    
    Fix: single-shot in-process cache. {ts, payload} singleton, 30s TTL,
    no LRU needed because the SQL is a fixed string with no parameters.
    Cached responses include `cached: true` and `age_ms` so clients can see
    the freshness without a separate uncached endpoint.
    
    ROI: at 1 hit/sec the cached path saves 13 COUNT(*) full-table scans
    59 times per minute. At even modest traffic the table-scan budget
    collapses by 99%+. For monitors/uptime checks that poll every 10s, the
    DB hit rate drops to 2/min instead of 6/min.
    
    Cache invalidation: TTL only. Acceptable because the dashboard tolerates
    30s lag on count widgets, and pattern uploads / approvals don't need
    real-time count reflection on this endpoint (the per-designer-dashboard
    endpoint pulls live counts via a separate route).
---
 src/marketplace/index.js | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/marketplace/index.js b/src/marketplace/index.js
index b05c624..52ee79e 100644
--- a/src/marketplace/index.js
+++ b/src/marketplace/index.js
@@ -193,7 +193,17 @@ function mount(app) {
   });
 
   // ── public status & health
+  // PERF-3 (2026-05-23): in-process status cache. The 13-subquery COUNT() block
+  // scanned every mp_* table on every hit; logs showed 637-7642ms per call.
+  // Now memoized for 30s. Cache key is the SQL itself (fixed string), so a
+  // single shared {ts, db, counts} singleton is sufficient.
+  let __statusCache = { ts: 0, payload: null };
+  const __STATUS_TTL_MS = 30_000;
   app.get('/api/marketplace/status', async (_req, res) => {
+    const now = Date.now();
+    if (__statusCache.payload && (now - __statusCache.ts) < __STATUS_TTL_MS) {
+      return res.json({ ...__statusCache.payload, cached: true, age_ms: now - __statusCache.ts });
+    }
     try {
       const live = await ping();
       const counts = await one(`
@@ -212,7 +222,9 @@ function mount(app) {
           (SELECT COUNT(*) FROM mp_licensing_inquiries WHERE status='new') AS open_inquiries,
           (SELECT COUNT(*) FROM mp_takedown_requests WHERE status='open') AS open_takedowns
       `);
-      res.json({ ok: true, db: live, ts: new Date().toISOString(), counts });
+      const payload = { ok: true, db: live, ts: new Date().toISOString(), counts };
+      __statusCache = { ts: now, payload };
+      res.json(payload);
     } catch (err) {
       res.status(500).json({ ok: false, error: err.message });
     }

← 8f28f8f PERF-2: parallelize designer detail follow-up queries (4-awa  ·  back to Wallco Ai  ·  REFACTOR-1a: extract lib/db.js (psqlQuery, pgEsc, psqlExecLo 3b6954e →