feat(memory-management): Introduce MEMORY_CONFIG for session and DNS management, including session TTL, cleanup intervals, and proxy dispatcher limits.
This commit is contained in:
parent
372b985ee9
commit
8223c87988
4 changed files with 41 additions and 17 deletions
|
|
@ -487,6 +487,14 @@ export const CACHE_TTL = {
|
||||||
modelAlias: 3600 // 1 hour
|
modelAlias: 3600 // 1 hour
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Memory management config
|
||||||
|
export const MEMORY_CONFIG = {
|
||||||
|
sessionTtlMs: 2 * 60 * 60 * 1000, // 2 hours per session entry
|
||||||
|
sessionCleanupIntervalMs: 30 * 60 * 1000, // cleanup every 30 minutes
|
||||||
|
dnsCacheTtlMs: 5 * 60 * 1000, // 5 minutes DNS TTL
|
||||||
|
proxyDispatchersMaxSize: 20, // max proxy agent instances
|
||||||
|
};
|
||||||
|
|
||||||
// Default max tokens
|
// Default max tokens
|
||||||
export const DEFAULT_MAX_TOKENS = 64000;
|
export const DEFAULT_MAX_TOKENS = 64000;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
import { MEMORY_CONFIG } from "../config/constants.js";
|
||||||
|
|
||||||
const isCloud = typeof caches !== "undefined" && typeof caches === "object";
|
const isCloud = typeof caches !== "undefined" && typeof caches === "object";
|
||||||
|
|
||||||
const originalFetch = globalThis.fetch;
|
const originalFetch = globalThis.fetch;
|
||||||
const proxyDispatchers = new Map();
|
const proxyDispatchers = new Map();
|
||||||
|
|
||||||
// Constants
|
// DNS cache: { [hostname]: { ip, expiry } }
|
||||||
const DNS_CACHE = {};
|
const DNS_CACHE = {};
|
||||||
const MITM_BYPASS_HOSTS = ["cloudcode-pa.googleapis.com", "daily-cloudcode-pa.googleapis.com", "googleapis.com"];
|
const MITM_BYPASS_HOSTS = ["cloudcode-pa.googleapis.com", "daily-cloudcode-pa.googleapis.com", "googleapis.com"];
|
||||||
const MITM_BYPASS_HEADER = "x-request-source";
|
const MITM_BYPASS_HEADER = "x-request-source";
|
||||||
|
|
@ -22,7 +24,8 @@ function normalizeString(value) {
|
||||||
* Resolve real IP using Google DNS (bypass system DNS)
|
* Resolve real IP using Google DNS (bypass system DNS)
|
||||||
*/
|
*/
|
||||||
async function resolveRealIP(hostname) {
|
async function resolveRealIP(hostname) {
|
||||||
if (DNS_CACHE[hostname]) return DNS_CACHE[hostname];
|
const cached = DNS_CACHE[hostname];
|
||||||
|
if (cached && Date.now() < cached.expiry) return cached.ip;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dns = await import("dns");
|
const dns = await import("dns");
|
||||||
|
|
@ -31,7 +34,7 @@ async function resolveRealIP(hostname) {
|
||||||
resolver.setServers(GOOGLE_DNS_SERVERS);
|
resolver.setServers(GOOGLE_DNS_SERVERS);
|
||||||
const resolve4 = promisify(resolver.resolve4.bind(resolver));
|
const resolve4 = promisify(resolver.resolve4.bind(resolver));
|
||||||
const addresses = await resolve4(hostname);
|
const addresses = await resolve4(hostname);
|
||||||
DNS_CACHE[hostname] = addresses[0];
|
DNS_CACHE[hostname] = { ip: addresses[0], expiry: Date.now() + MEMORY_CONFIG.dnsCacheTtlMs };
|
||||||
return addresses[0];
|
return addresses[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`[ProxyFetch] DNS resolve failed for ${hostname}:`, error.message);
|
console.warn(`[ProxyFetch] DNS resolve failed for ${hostname}:`, error.message);
|
||||||
|
|
@ -132,6 +135,10 @@ async function getDispatcher(proxyUrl) {
|
||||||
if (!normalized) return null;
|
if (!normalized) return null;
|
||||||
|
|
||||||
if (!proxyDispatchers.has(normalized)) {
|
if (!proxyDispatchers.has(normalized)) {
|
||||||
|
// Evict oldest entry if max size reached
|
||||||
|
if (proxyDispatchers.size >= MEMORY_CONFIG.proxyDispatchersMaxSize) {
|
||||||
|
proxyDispatchers.delete(proxyDispatchers.keys().next().value);
|
||||||
|
}
|
||||||
const { ProxyAgent } = await import("undici");
|
const { ProxyAgent } = await import("undici");
|
||||||
proxyDispatchers.set(normalized, new ProxyAgent({ uri: normalized }));
|
proxyDispatchers.set(normalized, new ProxyAgent({ uri: normalized }));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,24 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import { MEMORY_CONFIG } from "../config/constants.js";
|
||||||
|
|
||||||
// Runtime storage for session IDs (per connection/account)
|
// Runtime storage: Key = connectionId, Value = { sessionId, lastUsed }
|
||||||
// Key: connectionId (email or identifier), Value: sessionId
|
|
||||||
const runtimeSessionStore = new Map();
|
const runtimeSessionStore = new Map();
|
||||||
|
|
||||||
|
// Periodically evict entries that haven't been used within TTL
|
||||||
|
const cleanupInterval = setInterval(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [key, entry] of runtimeSessionStore) {
|
||||||
|
if (now - entry.lastUsed > MEMORY_CONFIG.sessionTtlMs) {
|
||||||
|
runtimeSessionStore.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, MEMORY_CONFIG.sessionCleanupIntervalMs);
|
||||||
|
|
||||||
|
// Allow Node.js to exit even if interval is still active
|
||||||
|
if (cleanupInterval.unref) cleanupInterval.unref();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a session ID for the given connection.
|
* Get or create a session ID for the given connection.
|
||||||
*
|
*
|
||||||
|
|
@ -30,22 +43,18 @@ const runtimeSessionStore = new Map();
|
||||||
*/
|
*/
|
||||||
export function deriveSessionId(connectionId) {
|
export function deriveSessionId(connectionId) {
|
||||||
if (!connectionId) {
|
if (!connectionId) {
|
||||||
// Fallback for requests without a connection identifier
|
|
||||||
return generateBinaryStyleId();
|
return generateBinaryStyleId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we already have a session ID for this connection in this process run
|
const existing = runtimeSessionStore.get(connectionId);
|
||||||
if (runtimeSessionStore.has(connectionId)) {
|
if (existing) {
|
||||||
return runtimeSessionStore.get(connectionId);
|
existing.lastUsed = Date.now();
|
||||||
|
return existing.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new ID using the binary's exact logic
|
const sessionId = generateBinaryStyleId();
|
||||||
const newSessionId = generateBinaryStyleId();
|
runtimeSessionStore.set(connectionId, { sessionId, lastUsed: Date.now() });
|
||||||
|
return sessionId;
|
||||||
// Store it for future requests from this connection
|
|
||||||
runtimeSessionStore.set(connectionId, newSessionId);
|
|
||||||
|
|
||||||
return newSessionId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ export default function ProfilePage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const observabilityEnabled = settings.observabilityEnabled !== false;
|
const observabilityEnabled = settings.observabilityEnabled === true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue