switch to lru in memory cache and dont cache failed sessions

This commit is contained in:
miloschwartz
2026-09-10 16:48:44 -04:00
parent 80d08d4f8e
commit c7926223dc
6 changed files with 98 additions and 31 deletions
+1 -1
View File
@@ -96,6 +96,7 @@
"jmespath": "0.16.0", "jmespath": "0.16.0",
"js-yaml": "5.4.1", "js-yaml": "5.4.1",
"jsonwebtoken": "9.0.3", "jsonwebtoken": "9.0.3",
"lru-cache": "11.5.2",
"lucide-react": "1.38.0", "lucide-react": "1.38.0",
"maxmind": "5.0.7", "maxmind": "5.0.7",
"moment": "2.30.1", "moment": "2.30.1",
@@ -103,7 +104,6 @@
"next-intl": "4.14.1", "next-intl": "4.14.1",
"next-themes": "0.4.6", "next-themes": "0.4.6",
"nextjs-toploader": "3.9.17", "nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "9.1.0", "nodemailer": "9.1.0",
"oslo": "1.2.1", "oslo": "1.2.1",
"pg": "8.23.0", "pg": "8.23.0",
+2 -8
View File
@@ -1,13 +1,7 @@
import NodeCache from "node-cache";
import logger from "@server/logger"; import logger from "@server/logger";
import { createLocalCache } from "@server/lib/createLocalCache";
// Create local cache with maxKeys limit to prevent memory leaks export const localCache = createLocalCache();
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
export const localCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
// Log cache statistics periodically for monitoring // Log cache statistics periodically for monitoring
// setInterval(() => { // setInterval(() => {
+79
View File
@@ -0,0 +1,79 @@
import { LRUCache } from "lru-cache";
const DEFAULT_MAX_KEYS = 10000;
const DEFAULT_TTL_MS = 3600 * 1000;
export type LocalCache = {
get<T>(key: string): T | undefined;
set(key: string, value: unknown, ttlSeconds?: number): boolean;
del(key: string | string[]): number;
has(key: string): boolean;
keys(): string[];
flushAll(): void;
getStats(): { keys: number };
getTtl(key: string): number | undefined;
};
export function createLocalCache(max = DEFAULT_MAX_KEYS): LocalCache {
const lru = new LRUCache<string, {}>({
max,
ttl: DEFAULT_TTL_MS,
updateAgeOnGet: false
});
return {
get<T>(key: string): T | undefined {
return lru.get(key) as T | undefined;
},
set(key: string, value: unknown, ttlSeconds?: number): boolean {
const stored = value as {};
if (ttlSeconds === undefined) {
lru.set(key, stored);
} else if (ttlSeconds === 0) {
lru.set(key, stored, { ttl: 0 });
} else {
lru.set(key, stored, { ttl: ttlSeconds * 1000 });
}
return true;
},
del(key: string | string[]): number {
const keys = Array.isArray(key) ? key : [key];
let deleted = 0;
for (const k of keys) {
if (lru.delete(k)) {
deleted++;
}
}
return deleted;
},
has(key: string): boolean {
return lru.has(key);
},
keys(): string[] {
return [...lru.keys()];
},
flushAll(): void {
lru.clear();
},
getStats(): { keys: number } {
return { keys: lru.size };
},
getTtl(key: string): number | undefined {
if (!lru.has(key)) {
return undefined;
}
const remaining = lru.getRemainingTTL(key);
if (!Number.isFinite(remaining)) {
return 0;
}
return Date.now() + remaining;
}
};
}
+4 -14
View File
@@ -11,17 +11,11 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import NodeCache from "node-cache";
import logger from "@server/logger"; import logger from "@server/logger";
import { createLocalCache } from "@server/lib/createLocalCache";
import { redisManager, regionalRedisManager } from "@server/private/lib/redis"; import { redisManager, regionalRedisManager } from "@server/private/lib/redis";
// Create local cache with maxKeys limit to prevent memory leaks export const localCache = createLocalCache();
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
export const localCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
// Log cache statistics periodically for monitoring // Log cache statistics periodically for monitoring
// setInterval(() => { // setInterval(() => {
@@ -301,15 +295,11 @@ export default cache;
/** /**
* Regional adaptive cache backed by the in-cluster Redis instance. * Regional adaptive cache backed by the in-cluster Redis instance.
* Falls back to a local NodeCache when the regional Redis is unavailable. * Falls back to a local LRU cache when the regional Redis is unavailable.
* Use this for data that is regional in nature (e.g. status history) so * Use this for data that is regional in nature (e.g. status history) so
* reads are served from the same cluster the user is hitting. * reads are served from the same cluster the user is hitting.
*/ */
const regionalLocalCache = new NodeCache({ const regionalLocalCache = createLocalCache();
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
class RegionalAdaptiveCache { class RegionalAdaptiveCache {
private useRedis(): boolean { private useRedis(): boolean {
+6 -6
View File
@@ -14,7 +14,7 @@
import { db, HostMeta, sites, users } from "@server/db"; import { db, HostMeta, sites, users } from "@server/db";
import { hostMeta, licenseKey } from "@server/db"; import { hostMeta, licenseKey } from "@server/db";
import logger from "@server/logger"; import logger from "@server/logger";
import NodeCache from "node-cache"; import { createLocalCache } from "@server/lib/createLocalCache";
import { validateJWT } from "./licenseJwt"; import { validateJWT } from "./licenseJwt";
import { count, eq } from "drizzle-orm"; import { count, eq } from "drizzle-orm";
import moment from "moment"; import moment from "moment";
@@ -65,8 +65,8 @@ export class License {
private validationServerUrl = `${this.serverBaseUrl}/api/v1/license/enterprise/validate`; private validationServerUrl = `${this.serverBaseUrl}/api/v1/license/enterprise/validate`;
private activationServerUrl = `${this.serverBaseUrl}/api/v1/license/enterprise/activate`; private activationServerUrl = `${this.serverBaseUrl}/api/v1/license/enterprise/activate`;
private statusCache = new NodeCache(); private statusCache = createLocalCache();
private licenseKeyCache = new NodeCache(); private licenseKeyCache = createLocalCache();
private statusKey = "status"; private statusKey = "status";
private serverSecret!: string; private serverSecret!: string;
@@ -179,7 +179,7 @@ LQIDAQAB
status.isHostLicensed = false; status.isHostLicensed = false;
// Invalidate all and set new cache (empty) // Invalidate all and set new cache (empty)
this.licenseKeyCache.flushAll(); this.licenseKeyCache.flushAll();
this.statusCache.set(this.statusKey, status); this.statusCache.set(this.statusKey, status, 0);
return status; return status;
} }
@@ -389,7 +389,7 @@ LQIDAQAB
// Invalidate old cache and set new cache // Invalidate old cache and set new cache
this.licenseKeyCache.flushAll(); this.licenseKeyCache.flushAll();
for (const [key, value] of newCache.entries()) { for (const [key, value] of newCache.entries()) {
this.licenseKeyCache.set<LicenseKeyCache>(key, value); this.licenseKeyCache.set(key, value, 0);
} }
} catch (error) { } catch (error) {
logger.error("Error checking license status:"); logger.error("Error checking license status:");
@@ -398,7 +398,7 @@ LQIDAQAB
this.checkInProgress = false; this.checkInProgress = false;
} }
this.statusCache.set(this.statusKey, status); this.statusCache.set(this.statusKey, status, 0);
return status; return status;
} }
+6 -2
View File
@@ -691,7 +691,9 @@ export async function verifyResourceSession(
); );
resourceSession = result?.resourceSession; resourceSession = result?.resourceSession;
localCache.set(sessionCacheKey, resourceSession, 5); if (resourceSession) {
localCache.set(sessionCacheKey, resourceSession, 5);
}
} }
if (resourceSession?.isRequestToken) { if (resourceSession?.isRequestToken) {
@@ -1121,7 +1123,9 @@ async function allowAccessToken(
resource.resourceId resource.resourceId
); );
resourceSession = result?.resourceSession; resourceSession = result?.resourceSession;
localCache.set(sessionCacheKey, resourceSession, 5); if (resourceSession) {
localCache.set(sessionCacheKey, resourceSession, 5);
}
} }
if ( if (