-
Notifications
You must be signed in to change notification settings - Fork 23
feat: improve redis cluster handling #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: improve redis cluster handling #119
Conversation
ce9c42b to
c118c46
Compare
c118c46 to
f2ee096
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the Redis Cluster TLS configuration logic in getRedisClient to improve flexibility and maintainability. The changes introduce a more structured approach to merging TLS configurations from multiple sources (environment variables, supplied options, and cluster-level options).
- Extracts TLS environment variable handling into a dedicated function
getClusterTlsFromEnv() - Implements a generic
mergeTlsConfigs()function to merge multiple TLS configuration sources with proper precedence - Refactors cluster Redis options handling to support nested
redisOptionswith proper username, password, and TLS configuration merging
Comments suppressed due to low confidence (1)
lib/queue-factory.ts:174
- The cache key computation using
JSON.stringify(redisOpts)may not includeclusterNodeswhich affects the actual Redis client configuration. When the sameredisOptsis used with differentclusterNodes, it will incorrectly return the same cached client. The cache key should incorporateclusterNodesto ensure unique clients are created for different cluster configurations.
// Compute checksum for redisOpts
const checksumJson = JSON.stringify(redisOpts);
const checksum = require("crypto")
.createHash("md5")
.update(checksumJson)
.digest("hex");
const key = `${type}-${checksum}`;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
lib/queue-factory.ts
Outdated
| } | ||
| return { | ||
| cert: Buffer.from( | ||
| process.env.REDIS_CLUSTER_TLS ?? "", |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nullish coalescing operator is unnecessary here since the condition on line 344 already ensures process.env.REDIS_CLUSTER_TLS is truthy. This can be simplified to just process.env.REDIS_CLUSTER_TLS.
| process.env.REDIS_CLUSTER_TLS ?? "", | |
| process.env.REDIS_CLUSTER_TLS, |
lib/queue-factory.ts
Outdated
| const baseRedisOptions: NestedRedisOptions = { | ||
| ...suppliedRedisOptions, | ||
| }; | ||
| delete baseRedisOptions.username; | ||
| delete baseRedisOptions.password; | ||
| delete baseRedisOptions.tls; |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using delete on properties can impact performance in V8 optimization. Consider using object destructuring to exclude these properties instead: const { username, password, tls, ...baseRedisOptions } = suppliedRedisOptions;
| const baseRedisOptions: NestedRedisOptions = { | |
| ...suppliedRedisOptions, | |
| }; | |
| delete baseRedisOptions.username; | |
| delete baseRedisOptions.password; | |
| delete baseRedisOptions.tls; | |
| const { username, password, tls, ...baseRedisOptions } = suppliedRedisOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const mergedTls = Object.assign( | ||
| {}, | ||
| tlsFromEnv ?? {}, | ||
| (userRedisOptions.tls as TlsConnectionOptions | undefined) ?? {}, | ||
| (redisOpts.tls as TlsConnectionOptions | undefined) ?? {} | ||
| ) as TlsConnectionOptions; |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using Object.assign with an empty object is less idiomatic than using object spread syntax. Consider replacing this with: const mergedTls = { ...tlsFromEnv, ...userRedisOptions.tls, ...redisOpts.tls } as TlsConnectionOptions; Note that you'll need to handle undefined values explicitly or use ?? {} on each source.
| const mergedTls = Object.assign( | |
| {}, | |
| tlsFromEnv ?? {}, | |
| (userRedisOptions.tls as TlsConnectionOptions | undefined) ?? {}, | |
| (redisOpts.tls as TlsConnectionOptions | undefined) ?? {} | |
| ) as TlsConnectionOptions; | |
| const mergedTls = { | |
| ...(tlsFromEnv ?? {}), | |
| ...(userRedisOptions.tls as TlsConnectionOptions | undefined ?? {}), | |
| ...(redisOpts.tls as TlsConnectionOptions | undefined ?? {}) | |
| } as TlsConnectionOptions; |
| const firstClusterNode = clusterNodes[0]; | ||
| const nodeCredentials: Partial<RedisOptions> = firstClusterNode | ||
| ? redisOptsFromUrl(firstClusterNode) | ||
| : {}; |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditional check if (firstClusterNode) on line 172 is redundant since clusterNodes[0] is already checked for truthiness on line 170 (clusterNodes && clusterNodes.length). An empty array would have falsy length, so firstClusterNode will always be defined when this code is reached.
| const firstClusterNode = clusterNodes[0]; | |
| const nodeCredentials: Partial<RedisOptions> = firstClusterNode | |
| ? redisOptsFromUrl(firstClusterNode) | |
| : {}; | |
| const nodeCredentials: Partial<RedisOptions> = redisOptsFromUrl(clusterNodes[0]); |
| ((redisOpts as any).redisOptions as Record<string, any>) ?? {}; | ||
| const redisOptions: Record<string, any> = { | ||
| ...userRedisOptions, | ||
| }; |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using as any casting and Record<string, any> weakens type safety. Consider defining a proper type for the nested redisOptions property or using TypeScript's utility types to extract the correct type from ioredis ClusterOptions.
| const resolvedUsername = | ||
| redisOpts.username ?? | ||
| userRedisOptions.username ?? | ||
| nodeCredentials.username; | ||
| if (resolvedUsername !== undefined) { | ||
| redisOptions.username = resolvedUsername; | ||
| } else { | ||
| delete redisOptions.username; | ||
| } | ||
|
|
||
| const resolvedPassword = | ||
| redisOpts.password ?? | ||
| userRedisOptions.password ?? | ||
| nodeCredentials.password; | ||
| if (resolvedPassword !== undefined) { | ||
| redisOptions.password = resolvedPassword; | ||
| } else { | ||
| delete redisOptions.password; | ||
| } | ||
|
|
||
| const tlsFromEnv = process.env.REDIS_CLUSTER_TLS | ||
| ? ({ | ||
| cert: Buffer.from( | ||
| process.env.REDIS_CLUSTER_TLS, | ||
| "base64" | ||
| ).toString("ascii"), | ||
| } as TlsConnectionOptions) | ||
| : undefined; | ||
| const mergedTls = Object.assign( | ||
| {}, | ||
| tlsFromEnv ?? {}, | ||
| (userRedisOptions.tls as TlsConnectionOptions | undefined) ?? {}, | ||
| (redisOpts.tls as TlsConnectionOptions | undefined) ?? {} | ||
| ) as TlsConnectionOptions; | ||
| if (Object.keys(mergedTls).length) { | ||
| redisOptions.tls = mergedTls; | ||
| } else { | ||
| delete redisOptions.tls; | ||
| } |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The credential merging logic (lines 181-199) and TLS merging logic (lines 201-219) are repetitive. Consider extracting a generic helper function like mergeRedisOption(optionKey, ...sources) to reduce code duplication and improve maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
| cert: Buffer.from( | ||
| process.env.REDIS_CLUSTER_TLS, | ||
| "base64" | ||
| ).toString("ascii"), |
Copilot
AI
Nov 10, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TLS certificate is decoded from base64 to ASCII. However, PEM-formatted certificates are already base64-encoded text. This double conversion may corrupt the certificate. Consider using .toString('utf8') instead of .toString('ascii'), or if the environment variable already contains a PEM certificate, no base64 decoding may be needed.
| ).toString("ascii"), | |
| ).toString("utf8"), |
Summary
Testing