From f291c47ce49e3062e530709700cc01aaf2b557f0 Mon Sep 17 00:00:00 2001 From: Saty <5752427+SatyPardus@users.noreply.github.com> Date: Sun, 18 Oct 2026 19:40:59 +0200 Subject: [PATCH] Shared caches for the numbers every page polls; refresh only visible pages /api/progress and /api/crew ran heavy queries for every open tab every 30 s (about 3 requests per second) and saturated the web process; they are now computed at most every 20 s and shared. The symbol table is built at most once a minute. Pages refresh every 60 s and only while visible. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW --- src/server/app.js | 27 ++++++++++++++++++++------- src/server/web/app.js | 8 ++++++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/server/app.js b/src/server/app.js index cc0f673..6b58f4e 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -183,9 +183,12 @@ export function createApp(ctx) { router.on('GET', '/api/symbols', ({ req, res, query }) => { const v = query.get('version') ? store.get('SELECT * FROM versions WHERE id = ?', query.get('version')) : store.currentVersion(); if (!v) throw new HttpError(404, 'unknown version'); - const rows = [...store.symbolMap(v.id).values()].map((s) => [s.id, s.kind, s.addr, s.size, s.name, s.decorated, s.library ? 1 : 0, s.unit]); - const body = JSON.stringify({ version: v.id, columns: ['id', 'kind', 'addr', 'size', 'name', 'decorated', 'library', 'unit'], symbols: rows }); - const etag = `"${crypto.createHash('sha1').update(body).digest('hex')}"`; + // Building it takes a while; a minute-old table is fine for agents. + const { body, etag } = cached(`symbols:${v.id}`, 60_000, () => { + const rows = [...store.symbolMap(v.id).values()].map((s) => [s.id, s.kind, s.addr, s.size, s.name, s.decorated, s.library ? 1 : 0, s.unit]); + const json = JSON.stringify({ version: v.id, columns: ['id', 'kind', 'addr', 'size', 'name', 'decorated', 'library', 'unit'], symbols: rows }); + return { body: json, etag: `"${crypto.createHash('sha1').update(json).digest('hex')}"` }; + }); const headers = { etag, 'cache-control': 'no-cache', 'x-content-type-options': 'nosniff' }; if (req.headers['if-none-match'] === etag) { res.writeHead(304, headers); @@ -198,7 +201,17 @@ export function createApp(ctx) { // Public: who is on the project and what their agents are doing right now. No token labels or // other account details; only names, roles and work. const ACTIVE_MS = 15 * 60_000; - router.on('GET', '/api/crew', () => { + // Public numbers every open page polls: computed at most once per `ttl` and shared by everyone. + const shared = new Map(); + const cached = (key, ttl, compute) => { + const hit = shared.get(key); + if (hit && Date.now() - hit.at < ttl) return hit.value; + const value = compute(); + shared.set(key, { at: Date.now(), value }); + return value; + }; + + router.on('GET', '/api/crew', () => cached('crew', 20_000, () => { const since = Date.now() - ACTIVE_MS; const users = store.all( `SELECT u.name, u.role, u.approved_at AS joined, @@ -226,9 +239,9 @@ export function createApp(ctx) { return { name: a.name, agents: a.agents, lastSeen: a.last_seen, units: units.map((u) => ({ unit: u.unit ?? '(no unit)', functions: u.functions })), reviewing, verifying: waiting, submittedToday: today }; }); return { activeMinutes: ACTIVE_MS / 60_000, users, active }; - }); + })); - router.on('GET', '/api/progress', () => { + router.on('GET', '/api/progress', () => cached('progress', 20_000, () => { const total = store.get(`SELECT COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM symbols s JOIN symbol_addresses a ON a.symbol_id = s.id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 WHERE s.kind = 'function' AND s.library = 0`); const byStatus = store.all(`SELECT fs.status, COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM func_status fs JOIN symbol_addresses a ON a.symbol_id = fs.symbol_id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 GROUP BY fs.status`); const byTaskState = store.all('SELECT state, COUNT(*) AS n FROM tasks GROUP BY state'); @@ -236,7 +249,7 @@ export function createApp(ctx) { const confirmed = store.get(`SELECT COUNT(*) AS n, COALESCE(SUM(a.size), 0) AS bytes FROM func_status fs JOIN symbol_addresses a ON a.symbol_id = fs.symbol_id JOIN versions v ON v.id = a.version_id AND v.is_current = 1 WHERE fs.confirmed = 1 AND fs.status NOT IN ('exact', 'match_unresolved')`); const peerQueue = store.get("SELECT COUNT(*) AS n FROM attempts WHERE state = 'peer_review'").n; return { functions: total.n, bytes: total.bytes, byStatus, confirmed, peerQueue, byTaskState, contributors }; - }); + })); // --- accounts diff --git a/src/server/web/app.js b/src/server/web/app.js index 3d2aea5..3806f27 100644 --- a/src/server/web/app.js +++ b/src/server/web/app.js @@ -732,8 +732,12 @@ api('/api/info') loadProgress().catch((e) => ($('progress-body').textContent = e.message)); loadCrew().catch((e) => ($('active-body').textContent = e.message)); // Live numbers: progress and the crew views refresh on their own. -setInterval(() => { +// Live numbers, only while the page is visible (background tabs would otherwise keep polling). +const refreshLive = () => { + if (document.visibilityState !== 'visible') return; loadProgress().catch(() => {}); loadCrew().catch(() => {}); -}, 30_000); +}; +setInterval(refreshLive, 60_000); +document.addEventListener('visibilitychange', refreshLive); refresh();