From 107b4b83c1350401b96d3fbdf3db0b691eceef6c Mon Sep 17 00:00:00 2001 From: mikiher Date: Wed, 22 Nov 2023 18:40:42 +0200 Subject: [PATCH] Add cache middleware to most /libraries get requests --- server/managers/ApiCacheManager | 59 ++++++++++++++++++--------------- server/routers/ApiRouter.js | 20 +++++------ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/server/managers/ApiCacheManager b/server/managers/ApiCacheManager index 9d80fdb2..882b9b61 100644 --- a/server/managers/ApiCacheManager +++ b/server/managers/ApiCacheManager @@ -1,42 +1,47 @@ const { LRUCache } = require('lru-cache') const Logger = require('../Logger') -const { measure } = require('../utils/timing') +const Database = require('../Database') class ApiCacheManager { - constructor() { - this.options = { - max: 1000, - maxSize: 10 * 1000 * 1000, - sizeCalculation: item => item.length, - } + constructor(options = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: item => item.length }) { + this.options = options } - init() { + init(database = Database) { this.cache = new LRUCache(this.options) + let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy'] + hooks.forEach(hook => database.sequelize.addHook(hook, (model) => this.clear(model, hook))) + } + + clear(model, hook) { + Logger.debug(`[ApiCacheManager] ${model.constructor.name}.${hook}: Clearing cache`) + this.cache.clear() } get middleware() { return (req, res, next) => { - measure('ApiCacheManager.middleware', () => { - const key = req.originalUrl || req.url - Logger.debug(`[ApiCacheManager] Cache key: ${key}`) - Logger.debug(`[ApiCacheManager] Cache: ${this.cache} count: ${this.cache.size} size: ${this.cache.calculatedSize}`) - const cached = this.cache.get(key) - if (cached) { - Logger.debug(`[ApiCacheManager] Cache hit: ${key}`) - res.send(cached) - return + const key = { user: req.user.username, url: req.url } + const stringifiedKey = JSON.stringify(key) + Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`) + Logger.debug(`[ApiCacheManager] Cache key: ${stringifiedKey}`) + const cached = this.cache.get(stringifiedKey) + if (cached) { + Logger.debug(`[ApiCacheManager] Cache hit: ${stringifiedKey}`) + res.send(cached) + return + } + res.sendResponse = res.send + res.send = (body) => { + Logger.debug(`[ApiCacheManager] Cache miss: ${stringifiedKey}`) + if (key.url.search(/^\/libraries\/.*?\/personalized/) !== -1) { + Logger.debug(`[ApiCacheManager] Caching personalized with 30 minues TTL`) + this.cache.set(stringifiedKey, body, { ttl: 30 * 60 * 1000 }) + } else { + this.cache.set(stringifiedKey, body) } - res.sendResponse = res.send - res.send = (body) => { - Logger.debug(`[ApiCacheManager] Cache miss: ${key}`) - measure('ApiCacheManager.middleware: res.send', () => { - this.cache.set(key, body) - res.sendResponse(body) - }) - } - next() - }) + res.sendResponse(body) + } + next() } } } diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 43c32628..e499b2cf 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -68,20 +68,20 @@ class ApiRouter { this.router.get('/libraries/:id/items', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getLibraryItems.bind(this)) this.router.delete('/libraries/:id/issues', LibraryController.middleware.bind(this), LibraryController.removeLibraryItemsWithIssues.bind(this)) - this.router.get('/libraries/:id/episode-downloads', LibraryController.middleware.bind(this), LibraryController.getEpisodeDownloadQueue.bind(this)) - this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), LibraryController.getAllSeriesForLibrary.bind(this)) + this.router.get('/libraries/:id/episode-downloads', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getEpisodeDownloadQueue.bind(this)) + this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getAllSeriesForLibrary.bind(this)) this.router.get('/libraries/:id/series/:seriesId', LibraryController.middleware.bind(this), LibraryController.getSeriesForLibrary.bind(this)) - this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), LibraryController.getCollectionsForLibrary.bind(this)) - this.router.get('/libraries/:id/playlists', LibraryController.middleware.bind(this), LibraryController.getUserPlaylistsForLibrary.bind(this)) - this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), LibraryController.getUserPersonalizedShelves.bind(this)) - this.router.get('/libraries/:id/filterdata', LibraryController.middleware.bind(this), LibraryController.getLibraryFilterData.bind(this)) - this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this)) + this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getCollectionsForLibrary.bind(this)) + this.router.get('/libraries/:id/playlists', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getUserPlaylistsForLibrary.bind(this)) + this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getUserPersonalizedShelves.bind(this)) + this.router.get('/libraries/:id/filterdata', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getLibraryFilterData.bind(this)) + this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.search.bind(this)) this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this)) - this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), LibraryController.getAuthors.bind(this)) - this.router.get('/libraries/:id/narrators', LibraryController.middleware.bind(this), LibraryController.getNarrators.bind(this)) + this.router.get('/libraries/:id/authors', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getAuthors.bind(this)) + this.router.get('/libraries/:id/narrators', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.getNarrators.bind(this)) this.router.patch('/libraries/:id/narrators/:narratorId', LibraryController.middleware.bind(this), LibraryController.updateNarrator.bind(this)) this.router.delete('/libraries/:id/narrators/:narratorId', LibraryController.middleware.bind(this), LibraryController.removeNarrator.bind(this)) - this.router.get('/libraries/:id/matchall', LibraryController.middleware.bind(this), LibraryController.matchAll.bind(this)) + this.router.get('/libraries/:id/matchall', LibraryController.middleware.bind(this), cacheMiddleware, LibraryController.matchAll.bind(this)) this.router.post('/libraries/:id/scan', LibraryController.middleware.bind(this), LibraryController.scan.bind(this)) this.router.get('/libraries/:id/recent-episodes', LibraryController.middleware.bind(this), LibraryController.getRecentEpisodes.bind(this)) this.router.get('/libraries/:id/opml', LibraryController.middleware.bind(this), LibraryController.getOPMLFile.bind(this))