diff --git a/package.json b/package.json index 02cce3a..71d529b 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "node": ">=20" }, "scripts": { - "start": "node --env-file-if-exists=.env server.js", - "cron": "node --env-file-if-exists=.env cron/runAll.js", + "start": "node --max-old-space-size=4096 --env-file-if-exists=.env server.js", + "cron": "node --max-old-space-size=4096 --env-file-if-exists=.env cron/runAll.js", "cron:imdb": "node --env-file-if-exists=.env cron/imdbRatings.js", "cron:tmdb": "node --env-file-if-exists=.env cron/tmdbSync.js", "cron:justwatch": "node --env-file-if-exists=.env cron/justwatchSync.js", diff --git a/routes/api.js b/routes/api.js index de75b97..ca39af7 100644 --- a/routes/api.js +++ b/routes/api.js @@ -18,12 +18,34 @@ import { parseQuery } from '../lib/queryParser.js'; import { search as runSearch } from '../lib/searchEngine.js'; import { filterAndLower } from '../lib/titleFilter.js'; -const searchCache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 60 }); +// Rough byte size of a cached value. Strings + numbers + nested objects via +// JSON length — good enough for cap enforcement, fast (no deep walk). +const sizeOf = (v) => { + if (v == null) return 8; + try { + return JSON.stringify(v).length; + } catch { + return 1024; + } +}; + +const searchCache = new LRUCache({ + max: 1000, + maxSize: 100 * 1024 * 1024, // 100 MB + sizeCalculation: sizeOf, + ttl: 1000 * 60 * 60, +}); // Entry cache covers movie/tv/imdb/providers responses. Keeps hot files in RAM // so repeat lookups skip disk + JSON.parse. TTL 30 min (cron may rewrite // underlying detail file via /changes — 30 min keeps staleness bounded). -const entryCache = new LRUCache({ max: 5000, ttl: 1000 * 60 * 30 }); +// Capped at 300 MB to avoid OOM (TMDB tv detail can hit 500 KB each). +const entryCache = new LRUCache({ + max: 2000, + maxSize: 300 * 1024 * 1024, // 300 MB + sizeCalculation: sizeOf, + ttl: 1000 * 60 * 30, +}); async function getDetail(type, id) { const key = `d:${type}:${id}`;