From 8cdea537e9345475ffe009e7484910440883b5c0 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 30 Jun 2024 10:55:13 +0100 Subject: [PATCH 1/2] cache URL previews on the server we already tell browsers to cache the preview for 7 days, but each browser will ask the server, and the server will talk to the network, hammering the poor site that got mentioned on fedi let's instead cache the preview on the server! --- .../src/server/web/UrlPreviewService.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 8f8f08a305..23340b2930 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -17,20 +17,32 @@ import { bindThis } from '@/decorators.js'; import { ApiError } from '@/server/api/error.js'; import { MiMeta } from '@/models/Meta.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; +import * as Redis from 'ioredis'; +import { RedisKVCache } from '@/misc/cache.js'; @Injectable() export class UrlPreviewService { private logger: Logger; + private previewCache: RedisKVCache; constructor( @Inject(DI.config) private config: Config, + @Inject(DI.redis) + private redisClient: Redis.Redis, + private metaService: MetaService, private httpRequestService: HttpRequestService, private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('url-preview'); + this.previewCache = new RedisKVCache(this.redisClient, 'summaly', { + lifetime: 1000 * 86400, + memoryCacheLifetime: 1000 * 10 * 60, + toRedisConverter: (value) => JSON.stringify(value), + fromRedisConverter: (value) => JSON.parse(value), + }); } @bindThis @@ -75,9 +87,19 @@ export class UrlPreviewService { }; } + const key = `${url}@${lang}`; + const cached = await this.previewCache.get(key); + if (cached !== undefined) { + this.logger.info(`Returning cache preview of ${key}`); + // Cache 7days + reply.header('Cache-Control', 'max-age=604800, immutable'); + + return cached; + } + this.logger.info(meta.urlPreviewSummaryProxyUrl - ? `(Proxy) Getting preview of ${url}@${lang} ...` - : `Getting preview of ${url}@${lang} ...`); + ? `(Proxy) Getting preview of ${key} ...` + : `Getting preview of ${key} ...`); try { const summary = meta.urlPreviewSummaryProxyUrl @@ -97,6 +119,8 @@ export class UrlPreviewService { summary.icon = this.wrap(summary.icon); summary.thumbnail = this.wrap(summary.thumbnail); + this.previewCache.set(key, summary); + // Cache 7days reply.header('Cache-Control', 'max-age=604800, immutable'); From 320db585e30c7310f94813b00cd23b5e1204aab9 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 30 Jun 2024 11:08:21 +0100 Subject: [PATCH 2/2] pass all the options to the cache constructor --- packages/backend/src/server/web/UrlPreviewService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 23340b2930..96038d9c1e 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -40,6 +40,7 @@ export class UrlPreviewService { this.previewCache = new RedisKVCache(this.redisClient, 'summaly', { lifetime: 1000 * 86400, memoryCacheLifetime: 1000 * 10 * 60, + fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); }, toRedisConverter: (value) => JSON.stringify(value), fromRedisConverter: (value) => JSON.parse(value), });