perf: cache slow-changing data (series, movies, tags) with 60s TTL
- Add MemoryCache utility with get/set/invalidate/clear - Cache Sonarr series, Sonarr tags, Radarr movies, Radarr tags - 60-second TTL - first request fetches, subsequent requests served from cache - Queue, history, and torrent data remain uncached (changes frequently) - On cache hit, these 4 heavy API calls resolve instantly
This commit is contained in:
@@ -8,6 +8,9 @@ const {
|
||||
getSonarrInstances,
|
||||
getRadarrInstances
|
||||
} = require('../utils/config');
|
||||
const cache = require('../utils/cache');
|
||||
|
||||
const CACHE_TTL = 60 * 1000; // 60 seconds for slow-changing data (series, movies, tags)
|
||||
|
||||
const EMBY_URL = process.env.EMBY_URL;
|
||||
const EMBY_API_KEY = process.env.EMBY_API_KEY;
|
||||
@@ -94,6 +97,19 @@ function getRadarrLink(movie) {
|
||||
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
|
||||
}
|
||||
|
||||
// Cached fetch helper - returns cached data if available, otherwise fetches and caches
|
||||
async function cachedFetch(cacheKey, fetchFn, ttl = CACHE_TTL) {
|
||||
const cached = cache.get(cacheKey);
|
||||
if (cached) {
|
||||
console.log(`[Dashboard] Cache HIT: ${cacheKey}`);
|
||||
return cached;
|
||||
}
|
||||
console.log(`[Dashboard] Cache MISS: ${cacheKey}`);
|
||||
const data = await fetchFn();
|
||||
cache.set(cacheKey, data, ttl);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Get user downloads for authenticated user
|
||||
router.get('/user-downloads', async (req, res) => {
|
||||
try {
|
||||
@@ -139,14 +155,16 @@ router.get('/user-downloads', async (req, res) => {
|
||||
})
|
||||
);
|
||||
|
||||
// Fetch from all Sonarr instances
|
||||
const sonarrTagsPromises = sonarrInstances.map(inst =>
|
||||
axios.get(`${inst.url}/api/v3/tag`, {
|
||||
headers: { 'X-Api-Key': inst.apiKey }
|
||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||
console.error(`[Dashboard] Sonarr ${inst.id} tags error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
// Fetch from all Sonarr instances (tags cached)
|
||||
const sonarrTagsPromise = cachedFetch('sonarr-tags', () =>
|
||||
Promise.all(sonarrInstances.map(inst =>
|
||||
axios.get(`${inst.url}/api/v3/tag`, {
|
||||
headers: { 'X-Api-Key': inst.apiKey }
|
||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||
console.error(`[Dashboard] Sonarr ${inst.id} tags error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
const sonarrQueuePromises = sonarrInstances.map(inst =>
|
||||
@@ -169,13 +187,15 @@ router.get('/user-downloads', async (req, res) => {
|
||||
})
|
||||
);
|
||||
|
||||
const sonarrSeriesPromises = 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(`[Dashboard] Sonarr ${inst.id} series error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
const sonarrSeriesPromise = cachedFetch('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(`[Dashboard] Sonarr ${inst.id} series error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
// Fetch from all Radarr instances
|
||||
@@ -199,25 +219,29 @@ router.get('/user-downloads', async (req, res) => {
|
||||
})
|
||||
);
|
||||
|
||||
const radarrMoviesPromises = 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(`[Dashboard] Radarr ${inst.id} movies error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
const radarrMoviesPromise = cachedFetch('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(`[Dashboard] Radarr ${inst.id} movies error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
const radarrTagsPromises = radarrInstances.map(inst =>
|
||||
axios.get(`${inst.url}/api/v3/tag`, {
|
||||
headers: { 'X-Api-Key': inst.apiKey }
|
||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||
console.error(`[Dashboard] Radarr ${inst.id} tags error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
const radarrTagsPromise = cachedFetch('radarr-tags', () =>
|
||||
Promise.all(radarrInstances.map(inst =>
|
||||
axios.get(`${inst.url}/api/v3/tag`, {
|
||||
headers: { 'X-Api-Key': inst.apiKey }
|
||||
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
|
||||
console.error(`[Dashboard] Radarr ${inst.id} tags error:`, err.message);
|
||||
return { instance: inst.id, data: [] };
|
||||
})
|
||||
))
|
||||
);
|
||||
|
||||
// Execute all requests
|
||||
// Execute all requests (cached items resolve instantly on cache hit)
|
||||
const [
|
||||
sabQueues, sabHistories, sonarrTagsResults, sonarrQueues, sonarrHistories, sonarrSeriesResults,
|
||||
radarrQueues, radarrHistories, radarrMoviesResults, radarrTagsResults,
|
||||
@@ -225,14 +249,14 @@ router.get('/user-downloads', async (req, res) => {
|
||||
] = await Promise.all([
|
||||
Promise.all(sabQueuePromises),
|
||||
Promise.all(sabHistoryPromises),
|
||||
Promise.all(sonarrTagsPromises),
|
||||
sonarrTagsPromise,
|
||||
Promise.all(sonarrQueuePromises),
|
||||
Promise.all(sonarrHistoryPromises),
|
||||
Promise.all(sonarrSeriesPromises),
|
||||
sonarrSeriesPromise,
|
||||
Promise.all(radarrQueuePromises),
|
||||
Promise.all(radarrHistoryPromises),
|
||||
Promise.all(radarrMoviesPromises),
|
||||
Promise.all(radarrTagsPromises),
|
||||
radarrMoviesPromise,
|
||||
radarrTagsPromise,
|
||||
getTorrents().catch(err => {
|
||||
console.error(`[Dashboard] qBittorrent error:`, err.message);
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user