mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: enhance sync cache with size limits and entry management
This commit is contained in:
@@ -107,6 +107,12 @@
|
|||||||
// In-memory /api/sync response cache TTL (milliseconds).
|
// In-memory /api/sync response cache TTL (milliseconds).
|
||||||
// /api/sync 内存缓存有效期(毫秒)。
|
// /api/sync 内存缓存有效期(毫秒)。
|
||||||
syncResponseTtlMs: 30 * 1000,
|
syncResponseTtlMs: 30 * 1000,
|
||||||
|
// Max size of a single cached /api/sync body in bytes.
|
||||||
|
// 单个 /api/sync 缓存响应允许的最大字节数。
|
||||||
|
syncResponseMaxBodyBytes: 512 * 1024,
|
||||||
|
// Max total in-memory bytes used by /api/sync cache per isolate.
|
||||||
|
// 每个 isolate 中 /api/sync 缓存允许占用的最大总字节数。
|
||||||
|
syncResponseMaxTotalBytes: 2 * 1024 * 1024,
|
||||||
// Max in-memory /api/sync cache entries per isolate.
|
// Max in-memory /api/sync cache entries per isolate.
|
||||||
// 每个 isolate 的 /api/sync 最大缓存条目数。
|
// 每个 isolate 的 /api/sync 最大缓存条目数。
|
||||||
syncResponseMaxEntries: 64,
|
syncResponseMaxEntries: 64,
|
||||||
|
|||||||
+58
-7
@@ -11,11 +11,16 @@ import {
|
|||||||
} from '../utils/user-decryption';
|
} from '../utils/user-decryption';
|
||||||
|
|
||||||
interface SyncCacheEntry {
|
interface SyncCacheEntry {
|
||||||
|
userId: string;
|
||||||
|
revisionDate: string;
|
||||||
body: string;
|
body: string;
|
||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
|
bytes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncResponseCache = new Map<string, SyncCacheEntry>();
|
const syncResponseCache = new Map<string, SyncCacheEntry>();
|
||||||
|
let syncResponseCacheTotalBytes = 0;
|
||||||
|
const textEncoder = new TextEncoder();
|
||||||
|
|
||||||
function buildSyncCacheKey(userId: string, revisionDate: string, excludeDomains: boolean): string {
|
function buildSyncCacheKey(userId: string, revisionDate: string, excludeDomains: boolean): string {
|
||||||
return `${userId}:${revisionDate}:${excludeDomains ? '1' : '0'}`;
|
return `${userId}:${revisionDate}:${excludeDomains ? '1' : '0'}`;
|
||||||
@@ -25,21 +30,67 @@ function readSyncCache(key: string): string | null {
|
|||||||
const hit = syncResponseCache.get(key);
|
const hit = syncResponseCache.get(key);
|
||||||
if (!hit) return null;
|
if (!hit) return null;
|
||||||
if (hit.expiresAt <= Date.now()) {
|
if (hit.expiresAt <= Date.now()) {
|
||||||
syncResponseCache.delete(key);
|
deleteSyncCacheEntry(key, hit);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return hit.body;
|
return hit.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeSyncCache(key: string, body: string): void {
|
function deleteSyncCacheEntry(key: string, entry?: SyncCacheEntry): void {
|
||||||
if (syncResponseCache.size >= LIMITS.cache.syncResponseMaxEntries) {
|
const existing = entry ?? syncResponseCache.get(key);
|
||||||
const oldestKey = syncResponseCache.keys().next().value as string | undefined;
|
if (!existing) return;
|
||||||
if (oldestKey) syncResponseCache.delete(oldestKey);
|
syncResponseCache.delete(key);
|
||||||
|
syncResponseCacheTotalBytes = Math.max(0, syncResponseCacheTotalBytes - existing.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneExpiredSyncCache(nowMs: number = Date.now()): void {
|
||||||
|
for (const [key, entry] of syncResponseCache.entries()) {
|
||||||
|
if (entry.expiresAt <= nowMs) {
|
||||||
|
deleteSyncCacheEntry(key, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneStaleUserSyncCache(userId: string, revisionDate: string): void {
|
||||||
|
for (const [key, entry] of syncResponseCache.entries()) {
|
||||||
|
if (entry.userId === userId && entry.revisionDate !== revisionDate) {
|
||||||
|
deleteSyncCacheEntry(key, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSyncCache(userId: string, revisionDate: string, key: string, body: string): void {
|
||||||
|
const nowMs = Date.now();
|
||||||
|
pruneExpiredSyncCache(nowMs);
|
||||||
|
pruneStaleUserSyncCache(userId, revisionDate);
|
||||||
|
|
||||||
|
const bodyBytes = textEncoder.encode(body).byteLength;
|
||||||
|
if (bodyBytes > LIMITS.cache.syncResponseMaxBodyBytes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = syncResponseCache.get(key);
|
||||||
|
if (existing) {
|
||||||
|
deleteSyncCacheEntry(key, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (
|
||||||
|
syncResponseCache.size >= LIMITS.cache.syncResponseMaxEntries ||
|
||||||
|
syncResponseCacheTotalBytes + bodyBytes > LIMITS.cache.syncResponseMaxTotalBytes
|
||||||
|
) {
|
||||||
|
const oldestKey = syncResponseCache.keys().next().value as string | undefined;
|
||||||
|
if (!oldestKey) break;
|
||||||
|
deleteSyncCacheEntry(oldestKey);
|
||||||
|
}
|
||||||
|
|
||||||
syncResponseCache.set(key, {
|
syncResponseCache.set(key, {
|
||||||
|
userId,
|
||||||
|
revisionDate,
|
||||||
body,
|
body,
|
||||||
expiresAt: Date.now() + LIMITS.cache.syncResponseTtlMs,
|
expiresAt: nowMs + LIMITS.cache.syncResponseTtlMs,
|
||||||
|
bytes: bodyBytes,
|
||||||
});
|
});
|
||||||
|
syncResponseCacheTotalBytes += bodyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/sync
|
// GET /api/sync
|
||||||
@@ -137,7 +188,7 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
|
|||||||
};
|
};
|
||||||
|
|
||||||
const body = JSON.stringify(syncResponse);
|
const body = JSON.stringify(syncResponse);
|
||||||
writeSyncCache(cacheKey, body);
|
writeSyncCache(userId, revisionDate, cacheKey, body);
|
||||||
|
|
||||||
return new Response(body, {
|
return new Response(body, {
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
Reference in New Issue
Block a user