Skip to content

Commit 3d1cedb

Browse files
authored
📡 refactor: Flush Redis Cache Script (danny-avila#10087)
* 🔧 feat: Enhance Redis Configuration and Connection Handling in Cache Flush Utility - Added support for Redis username, password, and CA certificate. - Improved Redis client creation to handle both cluster and single instance configurations. - Implemented a helper function to read the Redis CA certificate with error handling. - Updated connection logic to include timeout and error handling for Redis connections. * refactor: flush cache if redis URI is defined
1 parent bf2567b commit 3d1cedb

File tree

1 file changed

+86
-13
lines changed

1 file changed

+86
-13
lines changed

config/flush-cache.js

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,38 @@ const fs = require('fs');
1818
// Set up environment
1919
require('dotenv').config({ path: path.join(__dirname, '..', '.env') });
2020

21-
const { USE_REDIS, REDIS_URI, REDIS_KEY_PREFIX } = process.env;
21+
const {
22+
USE_REDIS,
23+
REDIS_URI,
24+
REDIS_USERNAME,
25+
REDIS_PASSWORD,
26+
REDIS_CA,
27+
REDIS_KEY_PREFIX,
28+
USE_REDIS_CLUSTER,
29+
REDIS_USE_ALTERNATIVE_DNS_LOOKUP,
30+
} = process.env;
2231

2332
// Simple utility function
2433
const isEnabled = (value) => value === 'true' || value === true;
2534

35+
// Helper function to read Redis CA certificate
36+
const getRedisCA = () => {
37+
if (!REDIS_CA) {
38+
return null;
39+
}
40+
try {
41+
if (fs.existsSync(REDIS_CA)) {
42+
return fs.readFileSync(REDIS_CA, 'utf8');
43+
} else {
44+
console.warn(`⚠️ Redis CA certificate file not found: ${REDIS_CA}`);
45+
return null;
46+
}
47+
} catch (error) {
48+
console.error(`❌ Failed to read Redis CA certificate file '${REDIS_CA}':`, error.message);
49+
return null;
50+
}
51+
};
52+
2653
async function showHelp() {
2754
console.log(`
2855
LibreChat Cache Flush Utility
@@ -67,21 +94,67 @@ async function flushRedisCache(dryRun = false, verbose = false) {
6794
console.log(` Prefix: ${REDIS_KEY_PREFIX || 'None'}`);
6895
}
6996

70-
// Create Redis client directly
71-
const Redis = require('ioredis');
97+
// Create Redis client using same pattern as main app
98+
const IoRedis = require('ioredis');
7299
let redis;
73100

74-
// Handle cluster vs single Redis
75-
if (process.env.USE_REDIS_CLUSTER === 'true') {
76-
const hosts = REDIS_URI.split(',').map((uri) => {
77-
const [host, port] = uri.split(':');
78-
return { host, port: parseInt(port) || 6379 };
79-
});
80-
redis = new Redis.Cluster(hosts);
101+
// Parse credentials from URI or use environment variables (same as redisClients.ts)
102+
const urls = (REDIS_URI || '').split(',').map((uri) => new URL(uri));
103+
const username = urls[0]?.username || REDIS_USERNAME;
104+
const password = urls[0]?.password || REDIS_PASSWORD;
105+
const ca = getRedisCA();
106+
107+
// Redis options (matching redisClients.ts configuration)
108+
const redisOptions = {
109+
username: username,
110+
password: password,
111+
tls: ca ? { ca } : undefined,
112+
connectTimeout: 10000,
113+
maxRetriesPerRequest: 3,
114+
enableOfflineQueue: true,
115+
lazyConnect: false,
116+
};
117+
118+
// Handle cluster vs single Redis (same logic as redisClients.ts)
119+
const useCluster = urls.length > 1 || isEnabled(USE_REDIS_CLUSTER);
120+
121+
if (useCluster) {
122+
const clusterOptions = {
123+
redisOptions,
124+
enableOfflineQueue: true,
125+
};
126+
127+
// Add DNS lookup for AWS ElastiCache if needed (same as redisClients.ts)
128+
if (isEnabled(REDIS_USE_ALTERNATIVE_DNS_LOOKUP)) {
129+
clusterOptions.dnsLookup = (address, callback) => callback(null, address);
130+
}
131+
132+
redis = new IoRedis.Cluster(
133+
urls.map((url) => ({ host: url.hostname, port: parseInt(url.port, 10) || 6379 })),
134+
clusterOptions,
135+
);
81136
} else {
82-
redis = new Redis(REDIS_URI);
137+
// @ts-ignore - ioredis default export is constructable despite linter warning
138+
redis = new IoRedis(REDIS_URI, redisOptions);
83139
}
84140

141+
// Wait for connection
142+
await new Promise((resolve, reject) => {
143+
const timeout = setTimeout(() => {
144+
reject(new Error('Connection timeout'));
145+
}, 10000);
146+
147+
redis.once('ready', () => {
148+
clearTimeout(timeout);
149+
resolve(undefined);
150+
});
151+
152+
redis.once('error', (err) => {
153+
clearTimeout(timeout);
154+
reject(err);
155+
});
156+
});
157+
85158
if (dryRun) {
86159
console.log('🔍 [DRY RUN] Would flush Redis cache');
87160
try {
@@ -105,7 +178,7 @@ async function flushRedisCache(dryRun = false, verbose = false) {
105178
try {
106179
const keys = await redis.keys('*');
107180
keyCount = keys.length;
108-
} catch (error) {
181+
} catch (_error) {
109182
// Continue with flush even if we can't count keys
110183
}
111184

@@ -209,7 +282,7 @@ async function main() {
209282
}
210283

211284
let success = true;
212-
const isRedisEnabled = isEnabled(USE_REDIS) && REDIS_URI;
285+
const isRedisEnabled = isEnabled(USE_REDIS) || (REDIS_URI != null && REDIS_URI !== '');
213286

214287
// Flush the appropriate cache type
215288
if (isRedisEnabled) {

0 commit comments

Comments
 (0)