Skip to content

Commit

Permalink
feat: add support for clustered Redis caches (#31185)
Browse files Browse the repository at this point in the history
  • Loading branch information
neoeinstein authored Sep 5, 2024
1 parent a9fa518 commit faa0902
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 6 deletions.
4 changes: 4 additions & 0 deletions docs/usage/self-hosted-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,10 @@ For TLS/SSL-enabled connections, use rediss prefix

Example URL structure: `rediss://[[username]:[password]]@localhost:6379/0`.

Renovate also supports connecting to Redis clusters as well. In order to connect to a cluster, provide the connection string using the `redis+cluster` or `rediss+cluster` schema as appropriate.

Example URL structure: `redis+cluster://[[username]:[password]]@redis.cluster.local:6379/0`

## reportPath

`reportPath` describes the location where the report is written to.
Expand Down
29 changes: 29 additions & 0 deletions lib/util/cache/package/redis.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { normalizeRedisUrl } from './redis';

describe('util/cache/package/redis', () => {
describe('normalizeRedisUrl', () => {
it('leaves standard Redis URL alone', () => {
const url = 'redis://user:password@localhost:6379';
expect(normalizeRedisUrl(url)).toBe(url);
});

it('leaves secure Redis URL alone', () => {
const url = 'rediss://user:password@localhost:6379';
expect(normalizeRedisUrl(url)).toBe(url);
});

it('rewrites standard Redis Cluster URL', () => {
const url = 'redis+cluster://user:password@localhost:6379';
expect(normalizeRedisUrl(url)).toBe(
'redis://user:password@localhost:6379',
);
});

it('rewrites secure Redis Cluster URL', () => {
const url = 'rediss+cluster://user:password@localhost:6379';
expect(normalizeRedisUrl(url)).toBe(
'rediss://user:password@localhost:6379',
);
});
});
});
32 changes: 26 additions & 6 deletions lib/util/cache/package/redis.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
/* istanbul ignore file */
import { DateTime } from 'luxon';
import { createClient } from 'redis';
import { createClient, createCluster } from 'redis';
import { logger } from '../../../logger';
import { compressToBase64, decompressFromBase64 } from '../../compress';
import { regEx } from '../../regex';
import type { PackageCacheNamespace } from './types';

let client: ReturnType<typeof createClient> | undefined;
let client:
| ReturnType<typeof createClient>
| ReturnType<typeof createCluster>
| undefined;
let rprefix: string | undefined;

function getKey(namespace: PackageCacheNamespace, key: string): string {
return `${rprefix}${namespace}-${key}`;
}

export function normalizeRedisUrl(url: string): string {
return url.replace(regEx(/^(rediss?)\+cluster:\/\//), '$1://');
}

export async function end(): Promise<void> {
try {
// https://github.com/redis/node-redis#disconnecting
Expand Down Expand Up @@ -94,16 +102,28 @@ export async function init(
}
rprefix = prefix ?? '';
logger.debug('Redis cache init');
client = createClient({
url,

const rewrittenUrl = normalizeRedisUrl(url);
// If any replacement was made, it means the regex matched and we are in clustered mode
const clusteredMode = rewrittenUrl.length !== url.length;

const config = {
url: rewrittenUrl,
socket: {
reconnectStrategy: (retries) => {
reconnectStrategy: (retries: number) => {
// Reconnect after this time
return Math.min(retries * 100, 3000);
},
},
pingInterval: 30000, // 30s
});
};
if (clusteredMode) {
client = createCluster({
rootNodes: [config],
});
} else {
client = createClient(config);
}
await client.connect();
logger.debug('Redis cache connected');
}

0 comments on commit faa0902

Please sign in to comment.