Repository: n8n-io/n8n
File: packages/cli/src/services/redis-client.service.ts (TypeScript)

Code before:

		if (dualStack) options.family = 0;

		if (tls) options.tls = {}; // enable TLS with default Node.js settings

		// Add keep-alive configuration
		if (keepAlive) {
			options.keepAlive = keepAliveDelay;
			// @ts-expect-error: keepAliveInterval is missing in ioRedis types but supported in node js socket since v18.4.0
			options.keepAliveInterval = keepAliveInterval;
		}

		if (reconnectOnFailover) {
			options.reconnectOnError = (redisErr: Error) => {
				const targetError = 'READONLY';
				if (redisErr.message.includes(targetError)) {
					this.logger.warn('Reconnecting to Redis due to READONLY error (possible failover event)');
					return true;
				}
				return false;
			};
		}

		return options;
	}

	private getClusterOptions(
		options: RedisOptions,
		retryStrategy: () => number | null,
	): ClusterOptions {
		const { slotsRefreshTimeout, slotsRefreshInterval, dnsResolveStrategy } =
			this.globalConfig.queue.bull.redis;
		const clusterOptions: ClusterOptions = {
			redisOptions: options,
			clusterRetryStrategy: retryStrategy,
			slotsRefreshTimeout,
			slotsRefreshInterval,
		};

		// NOTE: this is necessary to use AWS Elasticache Clusters with TLS.
		// See https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls.
		if (dnsResolveStrategy === 'NONE') {
			clusterOptions.dnsLookup = (address, lookupFn) => lookupFn(null, address);
		}

		return clusterOptions;
	}

	/**
	 * Strategy to retry connecting to Redis on connection failure.
	 *

Review comment (bug):
So if a write e.g. to enqueue a Bull job fails and we fail over that job is lost forever? Should we resend the failed write as well?

> This feature is useful when using Amazon ElastiCache instances with Auto-failover disabled. On these instances, test your reconnectOnError handler by manually promoting the replica node to the primary role using the AWS console. The following writes fail with the error READONLY. Using reconnectOnError, we can force the connection to reconnect on this error in order to connect to the new master. Furthermore, if the reconnectOnError returns 2, ioredis will resend the failed command after reconnecting.

https://github.com/redis/ioredis

Code after:
			retryStrategy: this.retryStrategy(),
			...extraOptions,
		};

		if (dualStack) options.family = 0;

		if (tls) options.tls = {}; // enable TLS with default Node.js settings

		// Add keep-alive configuration
		if (keepAlive) {
			options.keepAlive = keepAliveDelay;
			// @ts-expect-error: keepAliveInterval is missing in ioRedis types but supported in node js socket since v18.4.0
			options.keepAliveInterval = keepAliveInterval;
		}

		if (reconnectOnFailover) {
			options.reconnectOnError = (redisErr: Error) => {
				const targetError = 'READONLY';
				if (redisErr.message.includes(targetError)) {
					this.logger.warn('Reconnecting to Redis due to READONLY error (possible failover event)');
					return RECONNECT_AND_RETRY;
				}
				return DO_NOT_RECONNECT;
			};
		}

		return options;
	}

	private getClusterOptions(
		options: RedisOptions,
		retryStrategy: () => number | null,
	): ClusterOptions {
		const { slotsRefreshTimeout, slotsRefreshInterval, dnsResolveStrategy } =
			this.globalConfig.queue.bull.redis;
		const clusterOptions: ClusterOptions = {
			redisOptions: options,
			clusterRetryStrategy: retryStrategy,
			slotsRefreshTimeout,
			slotsRefreshInterval,
		};

		// NOTE: this is necessary to use AWS Elasticache Clusters with TLS.
		// See https://github.com/redis/ioredis?tab=readme-ov-file#special-note-aws-elasticache-clusters-with-tls.
		if (dnsResolveStrategy === 'NONE') {
			clusterOptions.dnsLookup = (address, lookupFn) => lookupFn(null, address);
		}

		return clusterOptions;
	}