Skip to content

Commit dc846b7

Browse files
committed
improvement(memory): replace manual TTL Maps with lru-cache for toolSchemaCache and effectiveEnvCache
Replaces the homegrown Map + setInterval sweep pattern with LRUCache from the lru-cache npm package, which is the standard Node.js solution for bounded in-process caching with TTL. Changes per cache: - Removes manual ToolSchemaCacheEntry / EffectiveEnvCacheEntry types - Removes setInterval sweep timers (and the .unref() boilerplate) - Removes the two-phase promise->value entry update inside the IIFE - Stores Promise<T> directly — in-flight and resolved states share one type - max: 200 (toolSchemaCache) / max: 500 (effectiveEnvCache) as hard ceilings - TTL behaviour and concurrent-request deduplication are preserved exactly - cache-registry .size reporting works unchanged via lru-cache's .size prop
1 parent 6766bd2 commit dc846b7

4 files changed

Lines changed: 24 additions & 64 deletions

File tree

apps/sim/lib/copilot/chat/payload.ts

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { toError } from '@sim/utils/errors'
3+
import { LRUCache } from 'lru-cache'
34
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
45
import { isPaid } from '@/lib/billing/plan-helpers'
56
import { getToolEntry } from '@/lib/copilot/tool-executor/router'
@@ -14,20 +15,10 @@ import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils'
1415
const logger = createLogger('CopilotChatPayload')
1516
const TOOL_SCHEMA_CACHE_TTL_MS = 30_000
1617

17-
type ToolSchemaCacheEntry = {
18-
expiresAt: number
19-
value?: ToolSchema[]
20-
promise?: Promise<ToolSchema[]>
21-
}
22-
23-
const toolSchemaCache = new Map<string, ToolSchemaCacheEntry>()
24-
25-
setInterval(() => {
26-
const now = Date.now()
27-
for (const [key, entry] of toolSchemaCache) {
28-
if (entry.expiresAt <= now) toolSchemaCache.delete(key)
29-
}
30-
}, TOOL_SCHEMA_CACHE_TTL_MS).unref()
18+
const toolSchemaCache = new LRUCache<string, Promise<ToolSchema[]>>({
19+
max: 200,
20+
ttl: TOOL_SCHEMA_CACHE_TTL_MS,
21+
})
3122

3223
registerCache('toolSchemaCache', () => toolSchemaCache.size)
3324

@@ -84,13 +75,10 @@ export async function buildIntegrationToolSchemas(
8475
workspaceId?: string
8576
): Promise<ToolSchema[]> {
8677
const cacheKey = `${userId}:${workspaceId ?? ''}:${options.schemaSurface ?? 'copilot'}`
87-
const now = Date.now()
78+
8879
const cached = toolSchemaCache.get(cacheKey)
89-
if (cached?.value && cached.expiresAt > now) {
90-
return cached.value.map((tool) => ({ ...tool, input_schema: { ...tool.input_schema } }))
91-
}
92-
if (cached?.promise) {
93-
const tools = await cached.promise
80+
if (cached) {
81+
const tools = await cached
9482
return tools.map((tool) => ({ ...tool, input_schema: { ...tool.input_schema } }))
9583
}
9684

@@ -197,18 +185,10 @@ export async function buildIntegrationToolSchemas(
197185
)
198186
}
199187

200-
toolSchemaCache.set(cacheKey, {
201-
value: integrationTools,
202-
expiresAt: Date.now() + TOOL_SCHEMA_CACHE_TTL_MS,
203-
})
204-
205188
return integrationTools
206189
})()
207190

208-
toolSchemaCache.set(cacheKey, {
209-
expiresAt: now + TOOL_SCHEMA_CACHE_TTL_MS,
210-
promise,
211-
})
191+
toolSchemaCache.set(cacheKey, promise)
212192

213193
const integrationTools = await promise
214194
return integrationTools.map((tool) => ({ ...tool, input_schema: { ...tool.input_schema } }))

apps/sim/lib/environment/utils.ts

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
44
import { getErrorMessage } from '@sim/utils/errors'
55
import { generateId } from '@sim/utils/id'
66
import { eq, inArray } from 'drizzle-orm'
7+
import { LRUCache } from 'lru-cache'
78
import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption'
89
import {
910
createWorkspaceEnvCredentials,
@@ -16,20 +17,10 @@ import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
1617
const logger = createLogger('EnvironmentUtils')
1718
const EFFECTIVE_ENV_CACHE_TTL_MS = 15_000
1819

19-
type EffectiveEnvCacheEntry = {
20-
expiresAt: number
21-
value?: Record<string, string>
22-
promise?: Promise<Record<string, string>>
23-
}
24-
25-
const effectiveEnvCache = new Map<string, EffectiveEnvCacheEntry>()
26-
27-
setInterval(() => {
28-
const now = Date.now()
29-
for (const [key, entry] of effectiveEnvCache) {
30-
if (!entry.promise && entry.expiresAt <= now) effectiveEnvCache.delete(key)
31-
}
32-
}, EFFECTIVE_ENV_CACHE_TTL_MS).unref()
20+
const effectiveEnvCache = new LRUCache<string, Promise<Record<string, string>>>({
21+
max: 500,
22+
ttl: EFFECTIVE_ENV_CACHE_TTL_MS,
23+
})
3324

3425
registerCache('effectiveEnvCache', () => effectiveEnvCache.size)
3526

@@ -335,37 +326,24 @@ export async function getEffectiveDecryptedEnv(
335326
workspaceId?: string
336327
): Promise<Record<string, string>> {
337328
const cacheKey = getEffectiveEnvCacheKey(userId, workspaceId)
338-
const now = Date.now()
339-
const cached = effectiveEnvCache.get(cacheKey)
340329

341-
if (cached?.value && cached.expiresAt > now) {
342-
return { ...cached.value }
343-
}
344-
345-
if (cached?.promise) {
346-
const value = await cached.promise
330+
const cached = effectiveEnvCache.get(cacheKey)
331+
if (cached) {
332+
const value = await cached
347333
return { ...value }
348334
}
349335

350336
const promise = getPersonalAndWorkspaceEnv(userId, workspaceId)
351-
.then(({ personalDecrypted, workspaceDecrypted }) => {
352-
const value = { ...personalDecrypted, ...workspaceDecrypted }
353-
effectiveEnvCache.set(cacheKey, {
354-
value,
355-
expiresAt: Date.now() + EFFECTIVE_ENV_CACHE_TTL_MS,
356-
})
357-
return value
358-
})
337+
.then(({ personalDecrypted, workspaceDecrypted }) => ({
338+
...personalDecrypted,
339+
...workspaceDecrypted,
340+
}))
359341
.catch((error) => {
360342
effectiveEnvCache.delete(cacheKey)
361343
throw error
362344
})
363345

364-
effectiveEnvCache.set(cacheKey, {
365-
expiresAt: now + EFFECTIVE_ENV_CACHE_TTL_MS,
366-
promise,
367-
})
368-
346+
effectiveEnvCache.set(cacheKey, promise)
369347
const value = await promise
370348
return { ...value }
371349
}

apps/sim/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
"json5": "2.2.3",
148148
"jszip": "3.10.1",
149149
"jwt-decode": "^4.0.0",
150+
"lru-cache": "11.3.6",
150151
"lucide-react": "^0.479.0",
151152
"mammoth": "^1.9.0",
152153
"mermaid": "11.15.0",

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)