From df6ea76c1f6df4b76dce0e794518882449278247 Mon Sep 17 00:00:00 2001 From: stack72 Date: Fri, 24 Oct 2025 16:47:16 +0100 Subject: [PATCH] Make redis connection more robust --- packages/web/.env.example | 1 + packages/web/src/index.ts | 77 +++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/packages/web/.env.example b/packages/web/.env.example index 55d3bfd..1145c15 100644 --- a/packages/web/.env.example +++ b/packages/web/.env.example @@ -21,4 +21,5 @@ AWS_REGION=us-east-1 # Use IAM authentication for ElastiCache # Set to 'true' to enable IAM auth (required for AWS ElastiCache with IAM) # Set to 'false' or leave unset for standard auth (local development) +# NOTE: IAM auth automatically enables TLS/SSL encryption (required by AWS) USE_IAM_AUTH=false diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index fbb1886..36d6161 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -37,13 +37,32 @@ async function initializeRedisClient(): Promise { const username = 'tonys-chips-web'; // Must match ElastiCache User console.log('Initializing Valkey client with IAM authentication...'); + console.log(`Connecting to: ${host}:${port}`); + console.log('TLS encryption: ENABLED (required for IAM auth)'); // Generate initial IAM auth token const token = await generateIAMAuthToken(host, port, username, awsRegion); - // Create Redis client with IAM credentials + // Create Redis client with IAM credentials and TLS + // TLS is REQUIRED for IAM authentication with AWS ElastiCache client = createClient({ - socket: { host, port }, + socket: { + host, + port, + connectTimeout: 10000, // 10 second connection timeout + // Enable TLS/SSL - required for IAM authentication + tls: true, + // Optionally reject unauthorized certificates in production + // rejectUnauthorized: process.env.NODE_ENV === 'production', + reconnectStrategy: (retries: number) => { + if (retries > 3) { + console.error('Max Redis reconnection attempts reached'); + return new Error('Max reconnection attempts reached'); + } + console.log(`Reconnecting to Redis, attempt ${retries + 1}`); + return Math.min(retries * 100, 3000); // Exponential backoff, max 3s + }, + }, username, password: token, }); @@ -55,28 +74,72 @@ async function initializeRedisClient(): Promise { try { console.log('Refreshing IAM auth token...'); const newToken = await generateIAMAuthToken(host, port, username, awsRegion); + + // Use AUTH command to refresh credentials + // This also extends the 12-hour connection limit await client.auth({ username, password: newToken }); console.log('IAM auth token refreshed successfully'); } catch (error) { console.error('Failed to refresh IAM auth token:', error); + // Connection might be dead after 12 hours, try to reconnect + if (error instanceof Error && error.message.includes('closed')) { + console.log('Connection appears closed, will attempt reconnection...'); + } } }, 10 * 60 * 1000); // 10 minutes } else { // Use standard connection (for local development) console.log('Initializing Valkey client with standard connection...'); - client = createClient({ url: redisUrl }); + console.log(`Connecting to: ${redisUrl}`); + + client = createClient({ + url: redisUrl, + socket: { + connectTimeout: 10000, // 10 second connection timeout + reconnectStrategy: (retries: number) => { + if (retries > 3) { + console.error('Max Redis reconnection attempts reached'); + return new Error('Max reconnection attempts reached'); + } + console.log(`Reconnecting to Redis, attempt ${retries + 1}`); + return Math.min(retries * 100, 3000); // Exponential backoff, max 3s + }, + // Enable TCP keep-alive to detect dead connections + keepAlive: true, + }, + }); } - client.on('error', (err) => { + // Event handlers for better debugging + client.on('error', (err: Error) => { console.error('Valkey Client Error:', err); }); client.on('connect', () => { - console.log('Valkey Client Connected'); + console.log('Valkey Client: TCP connection established'); + }); + + client.on('ready', () => { + console.log('Valkey Client: Ready to accept commands'); + }); + + client.on('reconnecting', () => { + console.log('Valkey Client: Attempting to reconnect...'); + }); + + client.on('end', () => { + console.log('Valkey Client: Connection closed'); }); - // Connect to Valkey - await client.connect(); + // Connect to Valkey with timeout + console.log('Attempting Redis connection...'); + try { + await client.connect(); + console.log('Valkey Client: Connected successfully'); + } catch (error) { + console.error('Failed to connect to Redis:', error); + throw error; + } return client; }