Skip to content

Commit

Permalink
feat(redis): sentinel support (#8260)
Browse files Browse the repository at this point in the history
* feat(redis): sentinel support

* Fix tests

* Sentinel support

* Update changeset
  • Loading branch information
ardatan authored Feb 11, 2025
1 parent acf4b5d commit fc44a1e
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 8 deletions.
12 changes: 12 additions & 0 deletions .changeset/eleven-carpets-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@graphql-mesh/cache-redis': patch
---

Support REDIS_FAMILY to set family of the IP address (IPv4 or IPv6).

This enhancement allows you to explicitly specify the IP address family when connecting to Redis instances:
- 4: Force IPv4
- 6: Force IPv6
- 0: Automatic (default)

This is particularly useful in network environments where specific IP protocols are required or when troubleshooting connection issues.
6 changes: 6 additions & 0 deletions .changeset/ten-houses-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-mesh/types': patch
'@graphql-mesh/cache-redis': patch
---

Support Redis Sentinels - [See more](https://github.com/redis/ioredis?tab=readme-ov-file#sentinel)
21 changes: 20 additions & 1 deletion packages/cache/redis/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@ export default class RedisCache<V = string> implements KeyValueCache<V>, Disposa

constructor(options: YamlConfig.Cache['redis'] & { pubsub?: MeshPubSub; logger: Logger }) {
const lazyConnect = options.lazyConnect !== false;
if (options.url) {
if ('sentinels' in options) {
this.client = new Redis({
name: options.name,
sentinelPassword:
options.sentinelPassword && interpolateStrWithEnv(options.sentinelPassword),
sentinels: options.sentinels.map(s => ({
host: s.host && interpolateStrWithEnv(s.host),
port: s.port && parseInt(interpolateStrWithEnv(s.port)),
family: s.family && parseInt(interpolateStrWithEnv(s.family)),
})),
role: options.role,
enableTLSForSentinelMode: options.enableTLSForSentinelMode,
enableAutoPipelining: true,
enableOfflineQueue: true,
lazyConnect,
});
} else if (options.url) {
const redisUrl = new URL(interpolateStrWithEnv(options.url));

if (!['redis:', 'rediss:'].includes(redisUrl.protocol)) {
Expand Down Expand Up @@ -50,6 +66,8 @@ export default class RedisCache<V = string> implements KeyValueCache<V>, Disposa
const parsedPassword =
interpolateStrWithEnv(options.password?.toString()) || process.env.REDIS_PASSWORD;
const parsedDb = interpolateStrWithEnv(options.db?.toString()) || process.env.REDIS_DB;
const parsedFamily =
interpolateStrWithEnv(options.family?.toString()) || process.env.REDIS_FAMILY;
const numPort = parseInt(parsedPort);
const numDb = parseInt(parsedDb);
if (parsedHost) {
Expand All @@ -60,6 +78,7 @@ export default class RedisCache<V = string> implements KeyValueCache<V>, Disposa
username: parsedUsername,
password: parsedPassword,
db: isNaN(numDb) ? undefined : numDb,
family: parsedFamily === '6' ? 6 : undefined,
...(lazyConnect ? { lazyConnect: true } : {}),
enableAutoPipelining: true,
enableOfflineQueue: true,
Expand Down
45 changes: 44 additions & 1 deletion packages/cache/redis/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,56 @@ type Cache {
redis: RedisConfig
}

type RedisConfig @md {
union RedisConfig = RedisConfigSentinel | RedisConfigSingle

type RedisConfigSentinel @md {
"""
identifies a group of Redis instances composed of a master and one or more slaves
"""
name: String!
"""
(optional) password for Sentinel instances.
"""
sentinelPassword: String
"""
A list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one.
"""
sentinels: [RedisSentinelConfig!]!
"""
(optional) with a value of slave will return a random slave from the Sentinel group.
"""
role: RedisSentinelRole
"""
(optional) set to true if connecting to sentinel instances that are encrypted
"""
enableTLSForSentinelMode: Boolean
"""
Flag to indicate lazyConnect value for Redis client.
@default: true
"""
lazyConnect: Boolean
}

enum RedisSentinelRole {
master
slave
}

type RedisSentinelConfig @md {
host: String!
port: String!
family: String
}

type RedisConfigSingle @md {
host: String
port: String
username: String
password: String
db: Int
url: String
family: String
"""
Flag to indicate lazyConnect value for Redis client.
Expand Down
70 changes: 67 additions & 3 deletions packages/legacy/types/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@
"$ref": "#/definitions/LocalforageConfig"
},
"redis": {
"$ref": "#/definitions/RedisConfig"
"description": "Any of: RedisConfigSentinel, RedisConfigSingle",
"anyOf": [
{
"$ref": "#/definitions/RedisConfigSentinel"
},
{
"$ref": "#/definitions/RedisConfigSingle"
}
]
}
}
},
Expand Down Expand Up @@ -73,10 +81,63 @@
}
}
},
"RedisConfig": {
"RedisConfigSentinel": {
"additionalProperties": false,
"type": "object",
"title": "RedisConfigSentinel",
"properties": {
"name": {
"type": "string",
"description": "identifies a group of Redis instances composed of a master and one or more slaves"
},
"sentinelPassword": {
"type": "string",
"description": "(optional) password for Sentinel instances."
},
"sentinels": {
"type": "array",
"items": {
"$ref": "#/definitions/RedisSentinelConfig"
},
"additionalItems": false,
"description": "A list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one."
},
"role": {
"type": "string",
"enum": ["master", "slave"],
"description": "(optional) with a value of slave will return a random slave from the Sentinel group. (Allowed values: master, slave)"
},
"enableTLSForSentinelMode": {
"type": "boolean",
"description": "(optional) set to true if connecting to sentinel instances that are encrypted"
},
"lazyConnect": {
"type": "boolean",
"description": "Flag to indicate lazyConnect value for Redis client.\n\n@default: true"
}
},
"required": ["name", "sentinels"]
},
"RedisSentinelConfig": {
"additionalProperties": false,
"type": "object",
"title": "RedisSentinelConfig",
"properties": {
"host": {
"type": "string"
},
"port": {
"type": "string"
},
"family": {
"type": "string"
}
}
},
"RedisConfigSingle": {
"additionalProperties": false,
"type": "object",
"title": "RedisConfig",
"title": "RedisConfigSingle",
"properties": {
"host": {
"type": "string"
Expand All @@ -96,6 +157,9 @@
"url": {
"type": "string"
},
"family": {
"type": "string"
},
"lazyConnect": {
"type": "boolean",
"description": "Flag to indicate lazyConnect value for Redis client.\n\n@default: true"
Expand Down
41 changes: 39 additions & 2 deletions packages/legacy/types/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,10 @@ export interface Cache {
cfwKv?: CFWorkersKVCacheConfig;
file?: FileCacheConfig;
localforage?: LocalforageConfig;
redis?: RedisConfig;
/**
* Any of: RedisConfigSentinel, RedisConfigSingle
*/
redis?: RedisConfigSentinel | RedisConfigSingle;
[k: string]: any;
}
export interface CFWorkersKVCacheConfig {
Expand All @@ -1826,13 +1829,47 @@ export interface LocalforageConfig {
storeName?: string;
description?: string;
}
export interface RedisConfig {
export interface RedisConfigSentinel {
/**
* identifies a group of Redis instances composed of a master and one or more slaves
*/
name: string;
/**
* (optional) password for Sentinel instances.
*/
sentinelPassword?: string;
/**
* A list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one.
*/
sentinels: RedisSentinelConfig[];
/**
* (optional) with a value of slave will return a random slave from the Sentinel group. (Allowed values: master, slave)
*/
role?: 'master' | 'slave';
/**
* (optional) set to true if connecting to sentinel instances that are encrypted
*/
enableTLSForSentinelMode?: boolean;
/**
* Flag to indicate lazyConnect value for Redis client.
*
* @default: true
*/
lazyConnect?: boolean;
}
export interface RedisSentinelConfig {
host?: string;
port?: string;
family?: string;
}
export interface RedisConfigSingle {
host?: string;
port?: string;
username?: string;
password?: string;
db?: number;
url?: string;
family?: string;
/**
* Flag to indicate lazyConnect value for Redis client.
*
Expand Down
10 changes: 9 additions & 1 deletion website/src/generated-markdown/RedisConfig.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@
* `url` (type: `String`)
* `lazyConnect` (type: `Boolean`) - Flag to indicate lazyConnect value for Redis client.

@default: true
@default: true
* `name` (type: `String`) - identifies a group of Redis instances composed of a master and one or more slaves
* `sentinelPassword` (type: `String`) - (optional) password for Sentinel instances.
* `sentinels` (type: `Array of Object`) - A list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one.:
* `host` (type: `String`)
* `port` (type: `String`)
* `role` (type: `String`) - (optional) with a value of slave will return a random slave from the Sentinel group.
* `preferredSlavbe` (type: `String`) - (optional) can be used to prefer a particular slave or set of slaves based on priority. It accepts a function or array.
* `enableTLSForSentinelMode` (type: `Boolean`) - (optional) set to true if connecting to sentinel instances that are encrypted
12 changes: 12 additions & 0 deletions website/src/generated-markdown/RedisConfigSentinel.generated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

* `name` (type: `String`, required) - identifies a group of Redis instances composed of a master and one or more slaves
* `sentinelPassword` (type: `String`) - (optional) password for Sentinel instances.
* `sentinels` (type: `Array of Object`, required) - A list of sentinels to connect to. The list does not need to enumerate all your sentinel instances, but a few so that if one is down the client will try the next one.:
* `host` (type: `String`)
* `port` (type: `String`)
* `family` (type: `String`)
* `role` (type: `String (master | slave)`) - (optional) with a value of slave will return a random slave from the Sentinel group.
* `enableTLSForSentinelMode` (type: `Boolean`) - (optional) set to true if connecting to sentinel instances that are encrypted
* `lazyConnect` (type: `Boolean`) - Flag to indicate lazyConnect value for Redis client.

@default: true
11 changes: 11 additions & 0 deletions website/src/generated-markdown/RedisConfigSingle.generated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

* `host` (type: `String`)
* `port` (type: `String`)
* `username` (type: `String`)
* `password` (type: `String`)
* `db` (type: `Int`)
* `url` (type: `String`)
* `family` (type: `String`)
* `lazyConnect` (type: `Boolean`) - Flag to indicate lazyConnect value for Redis client.

@default: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

* `host` (type: `String`)
* `port` (type: `String`)
* `family` (type: `String`)

0 comments on commit fc44a1e

Please sign in to comment.