+15
server/routes.ts
+15
server/routes.ts
···
4394
4394
}
4395
4395
});
4396
4396
4397
+
app.post('/api/cache/clear-hydration', async (_req, res) => {
4398
+
try {
4399
+
const { optimizedHydrator } = await import('./services/hydration/optimized-hydrator');
4400
+
await optimizedHydrator.clearCache();
4401
+
4402
+
res.json({
4403
+
success: true,
4404
+
message: 'Hydration cache cleared successfully'
4405
+
});
4406
+
} catch (err) {
4407
+
console.error('[CACHE] Error clearing hydration cache:', err);
4408
+
res.status(500).json({ error: 'InternalServerError', message: 'Failed to clear hydration cache' });
4409
+
}
4410
+
});
4411
+
4397
4412
// Minimal dead-letter inspection endpoint (admin only in production)
4398
4413
app.get('/api/redis/dead-letters', async (_req, res) => {
4399
4414
try {
+46
-1
server/services/hydration/cache.ts
+46
-1
server/services/hydration/cache.ts
···
1
1
import { CacheService } from '../cache';
2
2
3
3
export class HydrationCache {
4
-
private readonly TTL = 1800; // 30 minutes (increased for small userbase)
4
+
private readonly TTL = 300; // 5 minutes (reduced to avoid stale profile data)
5
5
private cache: CacheService;
6
6
7
7
constructor() {
···
169
169
*/
170
170
labelsKey(uri: string): string {
171
171
return `labels:${uri}`;
172
+
}
173
+
174
+
/**
175
+
* Clear all hydration cache (useful after profile updates or new installs)
176
+
*/
177
+
async clearAll(): Promise<void> {
178
+
const cacheService = this.cache as any;
179
+
if (!cacheService.redis || !cacheService.isInitialized) {
180
+
console.warn('[HYDRATION_CACHE] Redis not available, cannot clear cache');
181
+
return;
182
+
}
183
+
184
+
try {
185
+
// Use SCAN to find all hydration keys and delete them
186
+
const pattern = 'hydration:*';
187
+
const keys: string[] = [];
188
+
let cursor = '0';
189
+
190
+
do {
191
+
const result = await cacheService.redis.scan(
192
+
cursor,
193
+
'MATCH',
194
+
pattern,
195
+
'COUNT',
196
+
100
197
+
);
198
+
cursor = result[0];
199
+
keys.push(...result[1]);
200
+
} while (cursor !== '0');
201
+
202
+
if (keys.length > 0) {
203
+
console.log(`[HYDRATION_CACHE] Clearing ${keys.length} cached items`);
204
+
// Delete in batches of 100
205
+
for (let i = 0; i < keys.length; i += 100) {
206
+
const batch = keys.slice(i, i + 100);
207
+
await cacheService.redis.del(...batch);
208
+
}
209
+
console.log('[HYDRATION_CACHE] Cache cleared successfully');
210
+
} else {
211
+
console.log('[HYDRATION_CACHE] No cached items to clear');
212
+
}
213
+
} catch (error) {
214
+
console.error('[HYDRATION_CACHE] Error clearing cache:', error);
215
+
throw error;
216
+
}
172
217
}
173
218
}
+5
-9
server/services/hydration/optimized-hydrator.ts
+5
-9
server/services/hydration/optimized-hydrator.ts
···
724
724
this.embedResolver.clearCache();
725
725
this.requestCache.clear();
726
726
727
-
if (this.redis) {
728
-
try {
729
-
const keys = await this.redis.keys('hydration:*');
730
-
if (keys.length > 0) {
731
-
await this.redis.del(...keys);
732
-
}
733
-
} catch (error) {
734
-
console.error('[OPTIMIZED_HYDRATION] Cache clear error:', error);
735
-
}
727
+
// Use HydrationCache's clearAll method which uses SCAN instead of KEYS
728
+
try {
729
+
await this.cache.clearAll();
730
+
} catch (error) {
731
+
console.error('[OPTIMIZED_HYDRATION] Cache clear error:', error);
736
732
}
737
733
}
738
734