mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-11 13:31:27 +02:00
switch to lru in memory cache and dont cache failed sessions
This commit is contained in:
+1
-1
@@ -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
@@ -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(() => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user