@@ -13,6 +13,7 @@ import type { DbOrTx, NormalizedWorkflowData } from '@sim/workflow-persistence/t
1313import type { BlockState , Loop , Parallel , WorkflowState } from '@sim/workflow-types/workflow'
1414import type { InferSelectModel } from 'drizzle-orm'
1515import { and , desc , eq , inArray , lt , sql } from 'drizzle-orm'
16+ import { LRUCache } from 'lru-cache'
1617import type { Edge } from 'reactflow'
1718import { remapConditionBlockIds , remapConditionEdgeHandle } from '@/lib/workflows/condition-ids'
1819import {
@@ -100,35 +101,31 @@ export async function blockExistsInDeployment(
100101}
101102
102103/**
103- * Maximum number of deployed-state entries retained in the process-local cache.
104104 * Each entry is keyed by an immutable `deploymentVersionId` and holds a
105- * fully-migrated {@link DeployedWorkflowData} snapshot (tens of KB to ~1MB),
106- * so the bound keeps worst-case memory within a sane envelope.
105+ * fully-migrated {@link DeployedWorkflowData} snapshot (tens of KB to ~1MB);
106+ * the bound keeps worst-case memory within a sane envelope.
107107 */
108108const DEPLOYED_STATE_CACHE_MAX_ENTRIES = 500
109109const DEPLOYED_STATE_CACHE_TTL_MS = 5 * 60 * 1000
110110
111- interface DeployedStateCacheEntry {
112- data : DeployedWorkflowData
113- expiresAt : number
114- }
115-
116111/**
117112 * Process-local cache of fully-loaded, post-migration deployed workflow state,
118113 * keyed by the immutable `deploymentVersionId`.
119114 *
120115 * The id is unique per deploy — a redeploy mints a new id and a rollback
121116 * reactivates an existing id — so the active-version lookup naturally selects a
122117 * different (or already-cached) key whenever the active deployment changes,
123- * making the cache self-invalidating across redeploy/rollback. Insertion order
124- * is used for oldest-first (LRU-style) eviction once the bound is reached.
118+ * making the cache self-invalidating across redeploy/rollback.
125119 *
126- * A short TTL bounds the one piece of the cached state that is not strictly
127- * immutable: `applyBlockMigrations` resolves legacy credential references via a
128- * live lookup, so the TTL lets a credential change propagate across ECS tasks
129- * without waiting for redeploy or eviction .
120+ * The TTL is absolute (not reset on read) on purpose: it bounds the one piece
121+ * of the cached state that is not strictly immutable — `applyBlockMigrations`
122+ * resolves legacy credential references via a live lookup — so a credential
123+ * change propagates across ECS tasks even for a continuously-running workflow .
130124 */
131- const deployedStateCache = new Map < string , DeployedStateCacheEntry > ( )
125+ const deployedStateCache = new LRUCache < string , DeployedWorkflowData > ( {
126+ max : DEPLOYED_STATE_CACHE_MAX_ENTRIES ,
127+ ttl : DEPLOYED_STATE_CACHE_TTL_MS ,
128+ } )
132129
133130/**
134131 * Drop cached deployed state. Pass a `deploymentVersionId` to evict a single
@@ -171,10 +168,7 @@ export async function loadDeployedWorkflowState(
171168
172169 const cached = deployedStateCache . get ( active . id )
173170 if ( cached ) {
174- if ( cached . expiresAt > Date . now ( ) ) {
175- return structuredClone ( cached . data )
176- }
177- deployedStateCache . delete ( active . id )
171+ return structuredClone ( cached )
178172 }
179173
180174 const state = active . state as WorkflowState & { variables ?: Record < string , unknown > }
@@ -204,16 +198,7 @@ export async function loadDeployedWorkflowState(
204198 deploymentVersionId : active . id ,
205199 }
206200
207- if ( deployedStateCache . size >= DEPLOYED_STATE_CACHE_MAX_ENTRIES ) {
208- const oldestKey = deployedStateCache . keys ( ) . next ( ) . value
209- if ( oldestKey !== undefined ) {
210- deployedStateCache . delete ( oldestKey )
211- }
212- }
213- deployedStateCache . set ( active . id , {
214- data : deployedState ,
215- expiresAt : Date . now ( ) + DEPLOYED_STATE_CACHE_TTL_MS ,
216- } )
201+ deployedStateCache . set ( active . id , deployedState )
217202
218203 return structuredClone ( deployedState )
219204 } catch ( error ) {
0 commit comments