From ef365c8433bf31bcf158798af1b3fdab818033f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Levy?= Date: Thu, 8 Jan 2026 18:00:24 +0000 Subject: [PATCH] feat(cache): add Redis single-key operations support for ElastiCache Serverless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add REDIS_SINGLE_KEY_OPS configuration option to force single-key Redis operations even in non-cluster mode. This is required for AWS ElastiCache Serverless which internally shards data but doesn't support the Redis Cluster protocol, causing CROSSSLOT errors with batch operations. - Add REDIS_SINGLE_KEY_OPS env var (default: false) in cacheConfig.ts - Update batchDeleteKeys() in redisUtils.ts to use single-key ops when enabled - Add 'single-key-ops' mode logging for observability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 2 ++ packages/api/src/cache/cacheConfig.ts | 11 +++++++++++ packages/api/src/cache/redisUtils.ts | 15 +++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d173d26b60a9..564845f412bf 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,5 @@ coordination/orchestration/* claude-flow # Removed Windows wrapper files per user request hive-mind-prompt-*.txt +infrastructure/ +.playwright-* diff --git a/packages/api/src/cache/cacheConfig.ts b/packages/api/src/cache/cacheConfig.ts index 32ea2cddd106..60994942174d 100644 --- a/packages/api/src/cache/cacheConfig.ts +++ b/packages/api/src/cache/cacheConfig.ts @@ -90,6 +90,17 @@ const cacheConfig = { REDIS_USE_ALTERNATIVE_DNS_LOOKUP: isEnabled(process.env.REDIS_USE_ALTERNATIVE_DNS_LOOKUP), /** Enable redis cluster without the need of multiple URIs */ USE_REDIS_CLUSTER: isEnabled(process.env.USE_REDIS_CLUSTER ?? 'false'), + /** + * Force single-key operations for Redis commands even in non-cluster mode. + * Required for AWS ElastiCache Serverless which internally shards data but + * doesn't support the Redis Cluster protocol. This avoids CROSSSLOT errors + * when using batch operations on a sharded backend. + * + * Note: When USE_REDIS_CLUSTER=true, single-key operations are always used. + * This option only affects behavior when USE_REDIS_CLUSTER=false. + * @default false + */ + REDIS_SINGLE_KEY_OPS: isEnabled(process.env.REDIS_SINGLE_KEY_OPS ?? 'false'), CI: isEnabled(process.env.CI), DEBUG_MEMORY_CACHE: isEnabled(process.env.DEBUG_MEMORY_CACHE), diff --git a/packages/api/src/cache/redisUtils.ts b/packages/api/src/cache/redisUtils.ts index 334fe1e82a10..77b105a5a228 100644 --- a/packages/api/src/cache/redisUtils.ts +++ b/packages/api/src/cache/redisUtils.ts @@ -31,17 +31,24 @@ export async function batchDeleteKeys( } const size = chunkSize ?? cacheConfig.REDIS_DELETE_CHUNK_SIZE; - const mode = cacheConfig.USE_REDIS_CLUSTER ? 'cluster' : 'single-node'; + // Use single-key operations if cluster mode OR if explicitly enabled for sharded backends like ElastiCache Serverless + const useSingleKeyOps = cacheConfig.USE_REDIS_CLUSTER || cacheConfig.REDIS_SINGLE_KEY_OPS; + const mode = cacheConfig.USE_REDIS_CLUSTER + ? 'cluster' + : cacheConfig.REDIS_SINGLE_KEY_OPS + ? 'single-key-ops' + : 'single-node'; const deletePromises = []; - if (cacheConfig.USE_REDIS_CLUSTER) { - // Cluster mode: Delete each key individually in parallel chunks to avoid CROSSSLOT errors + if (useSingleKeyOps) { + // Single-key mode: Delete each key individually in parallel chunks to avoid CROSSSLOT errors + // Required for Redis Cluster or sharded backends like AWS ElastiCache Serverless for (let i = 0; i < keys.length; i += size) { const chunk = keys.slice(i, i + size); deletePromises.push(Promise.all(chunk.map((key) => client.del(key)))); } } else { - // Single-node mode: Batch delete chunks using DEL with array + // Batch mode: Delete chunks using DEL with array (only safe for true single-node Redis) for (let i = 0; i < keys.length; i += size) { const chunk = keys.slice(i, i + size); deletePromises.push(client.del(chunk));