@pioneer-platform/pioneer-cache 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/__tests__/FIX_VERIFICATION.md +290 -0
- package/__tests__/README.md +219 -0
- package/__tests__/TEST_RESULTS.md +309 -0
- package/__tests__/brpop-issue-reproduction.test.ts +356 -0
- package/__tests__/cache-concurrent-operations.test.ts +393 -0
- package/__tests__/redis-connection-pool.test.ts +374 -0
- package/dist/core/base-cache.js +6 -13
- package/jest.config.js +16 -0
- package/package.json +5 -2
- package/src/core/base-cache.ts +7 -14
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Connection Pool Performance Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests to reproduce and validate fixes for:
|
|
5
|
+
* 1. Redis timeout issues with concurrent requests
|
|
6
|
+
* 2. BRPOP blocking operation conflicts
|
|
7
|
+
* 3. Connection pool exhaustion
|
|
8
|
+
* 4. Read/write operation interference
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { redis, redisQueue } = require('@pioneer-platform/default-redis');
|
|
12
|
+
|
|
13
|
+
describe('Redis Connection Pool Performance Tests', () => {
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
// Clear test keys before running
|
|
16
|
+
const keys = await redis.keys('test:*');
|
|
17
|
+
if (keys.length > 0) {
|
|
18
|
+
await redis.del(...keys);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
// Cleanup test keys
|
|
24
|
+
const keys = await redis.keys('test:*');
|
|
25
|
+
if (keys.length > 0) {
|
|
26
|
+
await redis.del(...keys);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('Concurrent Read Operations', () => {
|
|
31
|
+
test('should handle 100 concurrent GET requests without timeout', async () => {
|
|
32
|
+
// Setup: Create 100 test keys
|
|
33
|
+
const promises = [];
|
|
34
|
+
for (let i = 0; i < 100; i++) {
|
|
35
|
+
promises.push(redis.set(`test:concurrent:${i}`, `value_${i}`));
|
|
36
|
+
}
|
|
37
|
+
await Promise.all(promises);
|
|
38
|
+
|
|
39
|
+
// Test: Read all keys concurrently
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
const readPromises = [];
|
|
42
|
+
for (let i = 0; i < 100; i++) {
|
|
43
|
+
readPromises.push(redis.get(`test:concurrent:${i}`));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const results = await Promise.all(readPromises);
|
|
47
|
+
const duration = Date.now() - startTime;
|
|
48
|
+
|
|
49
|
+
// Verify: All reads successful and fast
|
|
50
|
+
expect(results).toHaveLength(100);
|
|
51
|
+
results.forEach((value, i) => {
|
|
52
|
+
expect(value).toBe(`value_${i}`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(`✅ 100 concurrent reads completed in ${duration}ms`);
|
|
56
|
+
expect(duration).toBeLessThan(1000); // Should complete in <1s
|
|
57
|
+
}, 10000);
|
|
58
|
+
|
|
59
|
+
test('should handle 500 concurrent cache reads without blocking', async () => {
|
|
60
|
+
// Setup: Simulate cache miss scenario
|
|
61
|
+
const cacheKeys = [];
|
|
62
|
+
for (let i = 0; i < 50; i++) {
|
|
63
|
+
const key = `test:cache:asset_${i}`;
|
|
64
|
+
cacheKeys.push(key);
|
|
65
|
+
await redis.set(key, JSON.stringify({
|
|
66
|
+
value: `Asset ${i}`,
|
|
67
|
+
timestamp: Date.now()
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Test: Simulate 500 concurrent cache hits (10x more reads than keys)
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
const promises = [];
|
|
74
|
+
for (let i = 0; i < 500; i++) {
|
|
75
|
+
const key = cacheKeys[i % 50]; // Round-robin through keys
|
|
76
|
+
promises.push(redis.get(key));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const results = await Promise.all(promises);
|
|
80
|
+
const duration = Date.now() - startTime;
|
|
81
|
+
|
|
82
|
+
// Verify: No timeouts, all reads successful
|
|
83
|
+
expect(results).toHaveLength(500);
|
|
84
|
+
const timeouts = results.filter(r => r === null).length;
|
|
85
|
+
|
|
86
|
+
console.log(`✅ 500 concurrent cache reads in ${duration}ms (${timeouts} timeouts)`);
|
|
87
|
+
expect(timeouts).toBe(0); // No timeouts expected with pool
|
|
88
|
+
expect(duration).toBeLessThan(2000); // Should complete in <2s
|
|
89
|
+
}, 15000);
|
|
90
|
+
|
|
91
|
+
test('should distribute load across read pool', async () => {
|
|
92
|
+
// This test verifies round-robin distribution is working
|
|
93
|
+
const iterations = 100;
|
|
94
|
+
const startTime = Date.now();
|
|
95
|
+
|
|
96
|
+
// Make 100 sequential reads (should use all 5 pool clients)
|
|
97
|
+
for (let i = 0; i < iterations; i++) {
|
|
98
|
+
await redis.set(`test:pool:${i}`, `value_${i}`);
|
|
99
|
+
await redis.get(`test:pool:${i}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const duration = Date.now() - startTime;
|
|
103
|
+
console.log(`✅ ${iterations} sequential read/write cycles in ${duration}ms`);
|
|
104
|
+
|
|
105
|
+
// With proper pooling, this should be fast
|
|
106
|
+
expect(duration).toBeLessThan(5000);
|
|
107
|
+
}, 10000);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('Blocking Operations Isolation', () => {
|
|
111
|
+
test('should not block cache operations during BRPOP', async () => {
|
|
112
|
+
const queueName = 'test:blocking:queue';
|
|
113
|
+
|
|
114
|
+
// Start a blocking BRPOP (10 second timeout)
|
|
115
|
+
const brpopPromise = redisQueue.brpop(queueName, 10);
|
|
116
|
+
|
|
117
|
+
// While BRPOP is blocking, do cache operations
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
const cacheOps = [];
|
|
120
|
+
for (let i = 0; i < 50; i++) {
|
|
121
|
+
cacheOps.push(
|
|
122
|
+
redis.set(`test:blocking:cache:${i}`, `value_${i}`)
|
|
123
|
+
.then(() => redis.get(`test:blocking:cache:${i}`))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const results = await Promise.all(cacheOps);
|
|
128
|
+
const duration = Date.now() - startTime;
|
|
129
|
+
|
|
130
|
+
// Verify: Cache operations completed quickly despite BRPOP blocking
|
|
131
|
+
expect(results).toHaveLength(50);
|
|
132
|
+
console.log(`✅ 50 cache ops completed in ${duration}ms while BRPOP blocking`);
|
|
133
|
+
expect(duration).toBeLessThan(1000); // Should be fast, not blocked
|
|
134
|
+
|
|
135
|
+
// Cleanup: Cancel BRPOP by pushing to queue
|
|
136
|
+
await redis.lpush(queueName, 'cleanup');
|
|
137
|
+
await brpopPromise;
|
|
138
|
+
await redis.del(queueName);
|
|
139
|
+
}, 15000);
|
|
140
|
+
|
|
141
|
+
test('should handle multiple concurrent BRPOP operations', async () => {
|
|
142
|
+
const queues = ['test:queue1', 'test:queue2', 'test:queue3'];
|
|
143
|
+
|
|
144
|
+
// Start 3 concurrent BRPOP operations
|
|
145
|
+
const brpopPromises = queues.map(q => redisQueue.brpop(q, 5));
|
|
146
|
+
|
|
147
|
+
// After 1 second, push items to queues
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
149
|
+
|
|
150
|
+
const pushPromises = queues.map((q, i) =>
|
|
151
|
+
redis.lpush(q, `item_${i}`)
|
|
152
|
+
);
|
|
153
|
+
await Promise.all(pushPromises);
|
|
154
|
+
|
|
155
|
+
// Wait for all BRPOPs to complete
|
|
156
|
+
const results = await Promise.all(brpopPromises);
|
|
157
|
+
|
|
158
|
+
// Verify: All BRPOP operations completed successfully
|
|
159
|
+
expect(results).toHaveLength(3);
|
|
160
|
+
results.forEach((result, i) => {
|
|
161
|
+
expect(result).toEqual([queues[i], `item_${i}`]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
console.log('✅ Multiple concurrent BRPOP operations handled correctly');
|
|
165
|
+
}, 10000);
|
|
166
|
+
|
|
167
|
+
test('should use separate connection for BRPOP', async () => {
|
|
168
|
+
// This test verifies that redisQueue is a separate connection
|
|
169
|
+
// by checking that we can do operations on both simultaneously
|
|
170
|
+
|
|
171
|
+
const queueName = 'test:separate:queue';
|
|
172
|
+
|
|
173
|
+
// Start BRPOP on queue connection
|
|
174
|
+
const brpopPromise = redisQueue.brpop(queueName, 5);
|
|
175
|
+
|
|
176
|
+
// Immediately do operations on main connection
|
|
177
|
+
const mainOps = Promise.all([
|
|
178
|
+
redis.set('test:separate:key1', 'value1'),
|
|
179
|
+
redis.get('test:separate:key1'),
|
|
180
|
+
redis.set('test:separate:key2', 'value2'),
|
|
181
|
+
redis.get('test:separate:key2')
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
const [mainResults] = await Promise.all([mainOps]);
|
|
185
|
+
|
|
186
|
+
// Push to queue to unblock BRPOP
|
|
187
|
+
await redis.lpush(queueName, 'test');
|
|
188
|
+
const brpopResult = await brpopPromise;
|
|
189
|
+
|
|
190
|
+
// Verify both operations succeeded
|
|
191
|
+
expect(mainResults).toEqual(['OK', 'value1', 'OK', 'value2']);
|
|
192
|
+
expect(brpopResult).toEqual([queueName, 'test']);
|
|
193
|
+
|
|
194
|
+
console.log('✅ Separate connections verified for blocking operations');
|
|
195
|
+
|
|
196
|
+
// Cleanup
|
|
197
|
+
await redis.del(queueName);
|
|
198
|
+
}, 10000);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('High Load Scenarios', () => {
|
|
202
|
+
test('should handle mixed read/write load without timeouts', async () => {
|
|
203
|
+
const operations = 200;
|
|
204
|
+
const startTime = Date.now();
|
|
205
|
+
const promises = [];
|
|
206
|
+
|
|
207
|
+
// Mix of reads and writes
|
|
208
|
+
for (let i = 0; i < operations; i++) {
|
|
209
|
+
if (i % 2 === 0) {
|
|
210
|
+
// Write operation
|
|
211
|
+
promises.push(
|
|
212
|
+
redis.set(`test:mixed:${i}`, JSON.stringify({
|
|
213
|
+
id: i,
|
|
214
|
+
data: `value_${i}`,
|
|
215
|
+
timestamp: Date.now()
|
|
216
|
+
}))
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
// Read operation (may be cache miss)
|
|
220
|
+
promises.push(redis.get(`test:mixed:${i - 1}`));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const results = await Promise.all(promises);
|
|
225
|
+
const duration = Date.now() - startTime;
|
|
226
|
+
|
|
227
|
+
console.log(`✅ ${operations} mixed operations in ${duration}ms`);
|
|
228
|
+
expect(duration).toBeLessThan(3000);
|
|
229
|
+
expect(results).toHaveLength(operations);
|
|
230
|
+
}, 15000);
|
|
231
|
+
|
|
232
|
+
test('should handle cache stampede scenario', async () => {
|
|
233
|
+
// Simulate cache stampede: 100 concurrent requests for same key
|
|
234
|
+
const key = 'test:stampede:popular';
|
|
235
|
+
const concurrentReads = 100;
|
|
236
|
+
|
|
237
|
+
// Setup: Ensure key doesn't exist
|
|
238
|
+
await redis.del(key);
|
|
239
|
+
|
|
240
|
+
// Test: 100 concurrent reads of same missing key
|
|
241
|
+
const startTime = Date.now();
|
|
242
|
+
const promises = [];
|
|
243
|
+
for (let i = 0; i < concurrentReads; i++) {
|
|
244
|
+
promises.push(redis.get(key));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const results = await Promise.all(promises);
|
|
248
|
+
const duration = Date.now() - startTime;
|
|
249
|
+
|
|
250
|
+
// Verify: All completed without timeout
|
|
251
|
+
expect(results).toHaveLength(concurrentReads);
|
|
252
|
+
console.log(`✅ ${concurrentReads} concurrent reads of missing key in ${duration}ms`);
|
|
253
|
+
expect(duration).toBeLessThan(2000);
|
|
254
|
+
}, 10000);
|
|
255
|
+
|
|
256
|
+
test('should handle connection pool under sustained load', async () => {
|
|
257
|
+
// Simulate sustained load for 5 seconds
|
|
258
|
+
const durationMs = 5000;
|
|
259
|
+
const startTime = Date.now();
|
|
260
|
+
let operationCount = 0;
|
|
261
|
+
let timeouts = 0;
|
|
262
|
+
|
|
263
|
+
const runOperations = async () => {
|
|
264
|
+
while (Date.now() - startTime < durationMs) {
|
|
265
|
+
try {
|
|
266
|
+
const key = `test:sustained:${Math.floor(Math.random() * 100)}`;
|
|
267
|
+
|
|
268
|
+
// Random operation
|
|
269
|
+
if (Math.random() > 0.5) {
|
|
270
|
+
await redis.set(key, `value_${operationCount}`);
|
|
271
|
+
} else {
|
|
272
|
+
await redis.get(key);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
operationCount++;
|
|
276
|
+
} catch (error: any) {
|
|
277
|
+
if (error.message?.includes('timeout')) {
|
|
278
|
+
timeouts++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Run 5 concurrent operation streams
|
|
285
|
+
await Promise.all([
|
|
286
|
+
runOperations(),
|
|
287
|
+
runOperations(),
|
|
288
|
+
runOperations(),
|
|
289
|
+
runOperations(),
|
|
290
|
+
runOperations()
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
const opsPerSecond = Math.floor(operationCount / (durationMs / 1000));
|
|
294
|
+
console.log(`✅ Sustained load: ${operationCount} ops in ${durationMs}ms (${opsPerSecond} ops/sec)`);
|
|
295
|
+
console.log(` Timeouts: ${timeouts} (${((timeouts/operationCount)*100).toFixed(2)}%)`);
|
|
296
|
+
|
|
297
|
+
expect(timeouts).toBe(0); // No timeouts expected
|
|
298
|
+
expect(operationCount).toBeGreaterThan(500); // Should handle at least 100 ops/sec
|
|
299
|
+
}, 10000);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Connection Pool Health', () => {
|
|
303
|
+
test('should have connection pool available', () => {
|
|
304
|
+
// Access internal pool structure
|
|
305
|
+
const pool = (redis as any)._readPool;
|
|
306
|
+
expect(pool).toBeDefined();
|
|
307
|
+
expect(Array.isArray(pool)).toBe(true);
|
|
308
|
+
expect(pool.length).toBe(5); // POOL_SIZE = 5
|
|
309
|
+
console.log(`✅ Connection pool size: ${pool.length}`);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('should have separate queue client', () => {
|
|
313
|
+
expect(redisQueue).toBeDefined();
|
|
314
|
+
expect(redisQueue).not.toBe(redis);
|
|
315
|
+
console.log('✅ Separate queue client confirmed');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('should verify all pool clients are connected', async () => {
|
|
319
|
+
const pool = (redis as any)._readPool;
|
|
320
|
+
|
|
321
|
+
// Ping each client in pool
|
|
322
|
+
const pings = await Promise.all(
|
|
323
|
+
pool.map((client: any) => client.ping())
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
pings.forEach((response, i) => {
|
|
327
|
+
expect(response).toBe('PONG');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
console.log(`✅ All ${pool.length} pool clients responding`);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('Error Handling', () => {
|
|
335
|
+
test('should handle connection errors gracefully', async () => {
|
|
336
|
+
// This test verifies error handling doesn't crash the pool
|
|
337
|
+
const promises = [];
|
|
338
|
+
|
|
339
|
+
for (let i = 0; i < 50; i++) {
|
|
340
|
+
promises.push(
|
|
341
|
+
redis.get(`test:error:${i}`).catch((err: any) => null)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const results = await Promise.all(promises);
|
|
346
|
+
expect(results).toHaveLength(50);
|
|
347
|
+
console.log('✅ Error handling verified');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('should recover from temporary connection issues', async () => {
|
|
351
|
+
// Test resilience by doing operations rapidly
|
|
352
|
+
const operations = 100;
|
|
353
|
+
let successes = 0;
|
|
354
|
+
let failures = 0;
|
|
355
|
+
|
|
356
|
+
for (let i = 0; i < operations; i++) {
|
|
357
|
+
try {
|
|
358
|
+
await redis.set(`test:recovery:${i}`, `value_${i}`);
|
|
359
|
+
const value = await redis.get(`test:recovery:${i}`);
|
|
360
|
+
if (value === `value_${i}`) {
|
|
361
|
+
successes++;
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
failures++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const successRate = (successes / operations) * 100;
|
|
369
|
+
console.log(`✅ Recovery test: ${successes}/${operations} succeeded (${successRate.toFixed(1)}%)`);
|
|
370
|
+
|
|
371
|
+
expect(successRate).toBeGreaterThan(95); // At least 95% success rate
|
|
372
|
+
}, 15000);
|
|
373
|
+
});
|
|
374
|
+
});
|
package/dist/core/base-cache.js
CHANGED
|
@@ -150,19 +150,12 @@ class BaseCache {
|
|
|
150
150
|
const tag = this.TAG + 'getCached | ';
|
|
151
151
|
const t0 = Date.now();
|
|
152
152
|
try {
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
const cached = await Promise.race([
|
|
160
|
-
this.redis.get(key),
|
|
161
|
-
new Promise((resolve) => setTimeout(() => {
|
|
162
|
-
log.warn(tag, `⏱️ Redis timeout after ${timeoutMs}ms, returning cache miss`);
|
|
163
|
-
resolve(null);
|
|
164
|
-
}, timeoutMs))
|
|
165
|
-
]);
|
|
153
|
+
// PERFORMANCE FIX: Removed aggressive 1000ms timeout
|
|
154
|
+
// The connection pool is proven reliable (107K ops/sec, 0% timeouts in tests)
|
|
155
|
+
// Redis operations average <1ms, timeout was creating false positive warnings
|
|
156
|
+
// Connection pool already has built-in timeouts (10s) and retry logic (3 retries)
|
|
157
|
+
// See: __tests__/TEST_RESULTS.md for performance benchmarks
|
|
158
|
+
const cached = await this.redis.get(key);
|
|
166
159
|
if (!cached) {
|
|
167
160
|
log.debug(tag, `Cache miss: ${key}`);
|
|
168
161
|
return null;
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/__tests__'],
|
|
5
|
+
testMatch: ['**/*.test.ts'],
|
|
6
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'src/**/*.{ts,tsx}',
|
|
9
|
+
'!src/**/*.d.ts',
|
|
10
|
+
'!src/types/**',
|
|
11
|
+
],
|
|
12
|
+
coverageDirectory: 'coverage',
|
|
13
|
+
testTimeout: 30000, // 30 second timeout for performance tests
|
|
14
|
+
verbose: true,
|
|
15
|
+
maxWorkers: 1, // Run tests serially to avoid Redis connection conflicts
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pioneer-platform/pioneer-cache",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Unified caching system for Pioneer platform with Redis backend",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -21,11 +21,14 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@pioneer-platform/loggerdog": "^8.11.0",
|
|
24
|
-
"@pioneer-platform/redis-queue": "^8.11.
|
|
24
|
+
"@pioneer-platform/redis-queue": "^8.11.6",
|
|
25
25
|
"@pioneer-platform/default-redis": "^8.11.7"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
+
"@types/jest": "^29.5.0",
|
|
28
29
|
"@types/node": "^20.0.0",
|
|
30
|
+
"jest": "^29.5.0",
|
|
31
|
+
"ts-jest": "^29.1.0",
|
|
29
32
|
"typescript": "^5.0.0"
|
|
30
33
|
}
|
|
31
34
|
}
|
package/src/core/base-cache.ts
CHANGED
|
@@ -184,20 +184,13 @@ export abstract class BaseCache<T> {
|
|
|
184
184
|
const t0 = Date.now();
|
|
185
185
|
|
|
186
186
|
try {
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
this.redis.get(key),
|
|
195
|
-
new Promise<null>((resolve) => setTimeout(() => {
|
|
196
|
-
log.warn(tag, `⏱️ Redis timeout after ${timeoutMs}ms, returning cache miss`);
|
|
197
|
-
resolve(null);
|
|
198
|
-
}, timeoutMs))
|
|
199
|
-
]);
|
|
200
|
-
|
|
187
|
+
// PERFORMANCE FIX: Removed aggressive 1000ms timeout
|
|
188
|
+
// The connection pool is proven reliable (107K ops/sec, 0% timeouts in tests)
|
|
189
|
+
// Redis operations average <1ms, timeout was creating false positive warnings
|
|
190
|
+
// Connection pool already has built-in timeouts (10s) and retry logic (3 retries)
|
|
191
|
+
// See: __tests__/TEST_RESULTS.md for performance benchmarks
|
|
192
|
+
const cached = await this.redis.get(key);
|
|
193
|
+
|
|
201
194
|
if (!cached) {
|
|
202
195
|
log.debug(tag, `Cache miss: ${key}`);
|
|
203
196
|
return null;
|