From b44d370b518f382aa80a756b84e5489091c3c5d6 Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 16 May 2026 00:05:14 +0100 Subject: [PATCH] perf: eliminate full Sonarr Series + Radarr Movies library fetches The poller was fetching the entire series and movie libraries on every poll cycle (~9s each). Queue and history records already embed the full series/movie object via includeSeries/includeMovie params. Changes: - Remove 'Sonarr Series' and 'Radarr Movies' timed fetches from poller - Tag queue/history records with _instanceUrl in the poller instead - Build seriesMap/moviesMap from embedded objects in dashboard - Remove poll:sonarr-series and poll:radarr-movies cache keys - Fix missing axios and config imports in dashboard - Net result: ~18s saved per poll cycle, ~2 fewer API calls --- server/routes/dashboard.js | 34 ++++++++++++-------- server/utils/poller.js | 65 ++++++++++++++++++++------------------ 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index f8dd4b9..77fe89b 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -1,9 +1,11 @@ const express = require('express'); const router = express.Router(); +const axios = require('axios'); const { mapTorrentToDownload } = require('../utils/qbittorrent'); const cache = require('../utils/cache'); const { pollAllServices, getLastPollTimings, POLLING_ENABLED } = require('../utils/poller'); +const { getSonarrInstances, getRadarrInstances } = require('../utils/config'); const EMBY_URL = process.env.EMBY_URL; const EMBY_API_KEY = process.env.EMBY_API_KEY; @@ -139,10 +141,8 @@ router.get('/user-downloads', async (req, res) => { const sabQueueData = cache.get('poll:sab-queue') || { slots: [] }; const sabHistoryData = cache.get('poll:sab-history') || { slots: [] }; const sonarrTagsResults = cache.get('poll:sonarr-tags') || []; - const sonarrSeriesData = cache.get('poll:sonarr-series') || []; const sonarrQueueData = cache.get('poll:sonarr-queue') || { records: [] }; const sonarrHistoryData = cache.get('poll:sonarr-history') || { records: [] }; - const radarrMoviesData = cache.get('poll:radarr-movies') || []; const radarrQueueData = cache.get('poll:radarr-queue') || { records: [] }; const radarrHistoryData = cache.get('poll:radarr-history') || { records: [] }; const radarrTagsData = cache.get('poll:radarr-tags') || []; @@ -153,26 +153,32 @@ router.get('/user-downloads', async (req, res) => { const sabnzbdHistory = { data: { history: sabHistoryData } }; const sonarrQueue = { data: sonarrQueueData }; const sonarrHistory = { data: sonarrHistoryData }; - const sonarrSeries = { data: sonarrSeriesData }; const radarrQueue = { data: radarrQueueData }; const radarrHistory = { data: radarrHistoryData }; - const radarrMovies = { data: radarrMoviesData }; const radarrTags = { data: radarrTagsData }; - console.log(`[Dashboard] Cache data - Series: ${sonarrSeries.data.length}, Movies: ${radarrMovies.data.length}, qBit: ${qbittorrentTorrents.length}`); + // Build series/movie maps from embedded objects in queue + history records + // (no need to fetch the full library — queue/history include the full object) + const seriesMap = new Map(); + for (const r of sonarrQueue.data.records) { + if (r.series && r.seriesId) seriesMap.set(r.seriesId, r.series); + } + for (const r of sonarrHistory.data.records) { + if (r.series && r.seriesId && !seriesMap.has(r.seriesId)) seriesMap.set(r.seriesId, r.series); + } + const moviesMap = new Map(); + for (const r of radarrQueue.data.records) { + if (r.movie && r.movieId) moviesMap.set(r.movieId, r.movie); + } + for (const r of radarrHistory.data.records) { + if (r.movie && r.movieId && !moviesMap.has(r.movieId)) moviesMap.set(r.movieId, r.movie); + } - // Create maps for quick lookup - const seriesMap = new Map(sonarrSeries.data.map(s => [s.id, s])); - const moviesMap = new Map(radarrMovies.data.map(m => [m.id, m])); - // Create tag maps (id -> label) const sonarrTagMap = new Map(sonarrTagsResults.flatMap(t => t.data || []).map(t => [t.id, t.label])); const radarrTagMap = new Map(radarrTags.data.map(t => [t.id, t.label])); - console.log(`[Dashboard] Radarr tags:`, JSON.stringify(radarrTags.data)); - console.log(`[Dashboard] Movies map keys:`, Array.from(moviesMap.keys()).slice(0, 10)); - console.log(`[Dashboard] Looking for movieId: 2962`); - console.log(`[Dashboard] Movie 2962:`, JSON.stringify(moviesMap.get(2962))); - console.log(`[Dashboard] Sample movie structure:`, JSON.stringify(radarrMovies.data[0])); + + console.log(`[Dashboard] Cache data - Series: ${seriesMap.size}, Movies: ${moviesMap.size}, qBit: ${qbittorrentTorrents.length}`); // Match SABnzbd downloads to Sonarr/Radarr activity const userDownloads = []; diff --git a/server/utils/poller.js b/server/utils/poller.js index 671d4df..95d3b52 100644 --- a/server/utils/poller.js +++ b/server/utils/poller.js @@ -80,14 +80,6 @@ async function pollAllServices() { return { instance: inst.id, data: { records: [] } }; }) ))), - timed('Sonarr Series', () => Promise.all(sonarrInstances.map(inst => - axios.get(`${inst.url}/api/v3/series`, { - headers: { 'X-Api-Key': inst.apiKey } - }).then(res => ({ instance: inst.id, data: res.data })).catch(err => { - console.error(`[Poller] Sonarr ${inst.id} series error:`, err.message); - return { instance: inst.id, data: [] }; - }) - ))), timed('Radarr Queue', () => Promise.all(radarrInstances.map(inst => axios.get(`${inst.url}/api/v3/queue`, { headers: { 'X-Api-Key': inst.apiKey }, @@ -106,14 +98,6 @@ async function pollAllServices() { return { instance: inst.id, data: { records: [] } }; }) ))), - timed('Radarr Movies', () => Promise.all(radarrInstances.map(inst => - axios.get(`${inst.url}/api/v3/movie`, { - headers: { 'X-Api-Key': inst.apiKey } - }).then(res => ({ instance: inst.id, data: res.data })).catch(err => { - console.error(`[Poller] Radarr ${inst.id} movies error:`, err.message); - return { instance: inst.id, data: [] }; - }) - ))), timed('Radarr Tags', () => Promise.all(radarrInstances.map(inst => axios.get(`${inst.url}/api/v3/tag`, { headers: { 'X-Api-Key': inst.apiKey } @@ -131,9 +115,9 @@ async function pollAllServices() { const [ { result: sabQueues }, { result: sabHistories }, { result: sonarrTagsResults }, { result: sonarrQueues }, - { result: sonarrHistories }, { result: sonarrSeriesResults }, + { result: sonarrHistories }, { result: radarrQueues }, { result: radarrHistories }, - { result: radarrMoviesResults }, { result: radarrTagsResults }, + { result: radarrTagsResults }, { result: qbittorrentTorrents } ] = results; @@ -164,28 +148,49 @@ async function pollAllServices() { // Sonarr cache.set('poll:sonarr-tags', sonarrTagsResults, cacheTTL); + // Tag queue/history records with _instanceUrl so embedded series/movie objects can build links cache.set('poll:sonarr-queue', { - records: sonarrQueues.flatMap(q => q.data.records || []) + records: sonarrQueues.flatMap(q => { + const inst = sonarrInstances.find(i => i.id === q.instance); + const url = inst ? inst.url : null; + return (q.data.records || []).map(r => { + if (r.series) r.series._instanceUrl = url; + return r; + }); + }) }, cacheTTL); cache.set('poll:sonarr-history', { - records: sonarrHistories.flatMap(h => h.data.records || []) + records: sonarrHistories.flatMap(h => { + const inst = sonarrInstances.find(i => i.id === h.instance); + const url = inst ? inst.url : null; + return (h.data.records || []).map(r => { + if (r.series) r.series._instanceUrl = url; + return r; + }); + }) }, cacheTTL); - cache.set('poll:sonarr-series', sonarrSeriesResults.flatMap(s => { - const inst = sonarrInstances.find(i => i.id === s.instance); - return (s.data || []).map(item => ({ ...item, _instanceUrl: inst ? inst.url : null })); - }), cacheTTL); // Radarr cache.set('poll:radarr-queue', { - records: radarrQueues.flatMap(q => q.data.records || []) + records: radarrQueues.flatMap(q => { + const inst = radarrInstances.find(i => i.id === q.instance); + const url = inst ? inst.url : null; + return (q.data.records || []).map(r => { + if (r.movie) r.movie._instanceUrl = url; + return r; + }); + }) }, cacheTTL); cache.set('poll:radarr-history', { - records: radarrHistories.flatMap(h => h.data.records || []) + records: radarrHistories.flatMap(h => { + const inst = radarrInstances.find(i => i.id === h.instance); + const url = inst ? inst.url : null; + return (h.data.records || []).map(r => { + if (r.movie) r.movie._instanceUrl = url; + return r; + }); + }) }, cacheTTL); - cache.set('poll:radarr-movies', radarrMoviesResults.flatMap(m => { - const inst = radarrInstances.find(i => i.id === m.instance); - return (m.data || []).map(item => ({ ...item, _instanceUrl: inst ? inst.url : null })); - }), cacheTTL); cache.set('poll:radarr-tags', radarrTagsResults.flatMap(t => t.data || []), cacheTTL); // qBittorrent