myavana-bot-test-core 2.1.9 โ 2.2.0
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/package.json +1 -1
- package/src/cacheManager.js +290 -141
package/package.json
CHANGED
package/src/cacheManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// cacheManager.js -
|
|
1
|
+
// cacheManager.js - PRODUCTION VERSION WITH MULTI-LAYER FALLBACK
|
|
2
2
|
|
|
3
3
|
const ai = require('./ai');
|
|
4
4
|
const { googleAI } = require('@genkit-ai/google-genai');
|
|
@@ -7,48 +7,75 @@ const { GoogleGenAI } = require("@google/genai");
|
|
|
7
7
|
const config = require('./config');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Cache Manager for Gemini API
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Cache Manager for Gemini API with Multi-Layer Storage
|
|
11
|
+
* Storage Layers (in order of preference):
|
|
12
|
+
* 1. PostgreSQL (persistent, shared across instances)
|
|
13
|
+
* 2. Redis (fast, shared across instances)
|
|
14
|
+
* 3. Memory (fastest, single instance only)
|
|
13
15
|
*/
|
|
14
16
|
const cacheAi = new GoogleGenAI({ apiKey: config.genkitApiKey });
|
|
15
17
|
|
|
16
18
|
class CacheManager {
|
|
17
19
|
constructor() {
|
|
18
20
|
this.CACHE_TTL_HOURS = 24;
|
|
19
|
-
this.CACHE_TTL_SECONDS = this.CACHE_TTL_HOURS * 60 * 60;
|
|
20
|
-
this.CACHE_REFRESH_THRESHOLD_HOURS = 1;
|
|
21
|
+
this.CACHE_TTL_SECONDS = this.CACHE_TTL_HOURS * 60 * 60;
|
|
22
|
+
this.CACHE_REFRESH_THRESHOLD_HOURS = 1;
|
|
23
|
+
|
|
24
|
+
// In-memory fallback (last resort)
|
|
25
|
+
this.memoryCache = new Map();
|
|
26
|
+
|
|
27
|
+
console.log('๐ง Cache Manager initialized with multi-layer storage');
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
|
-
*
|
|
25
|
-
* This is fast and should be called BEFORE expensive prompt construction
|
|
26
|
-
* @param {string} userId - User identifier
|
|
27
|
-
* @returns {Promise<{hasValidCache: boolean, cacheName: string|null, needsRefresh: boolean}>}
|
|
31
|
+
* Check cache validity across all storage layers
|
|
28
32
|
*/
|
|
29
33
|
async checkCacheValidity(userId) {
|
|
30
34
|
console.log('๐ Quick cache check for user:', userId);
|
|
35
|
+
const checkStart = performance.now();
|
|
31
36
|
|
|
32
37
|
try {
|
|
33
|
-
|
|
38
|
+
// Try all layers in order of preference
|
|
39
|
+
let existingCache = null;
|
|
40
|
+
let source = 'none';
|
|
41
|
+
|
|
42
|
+
// Layer 1: PostgreSQL (preferred)
|
|
43
|
+
existingCache = await this.getUserCache(userId);
|
|
44
|
+
if (existingCache) {
|
|
45
|
+
source = 'postgresql';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Layer 2: Redis (if PostgreSQL failed)
|
|
49
|
+
if (!existingCache && redisClient) {
|
|
50
|
+
existingCache = await this.getUserCacheFromRedis(userId);
|
|
51
|
+
if (existingCache) source = 'redis';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Layer 3: Memory (if both failed)
|
|
55
|
+
if (!existingCache) {
|
|
56
|
+
existingCache = this.getUserCacheFromMemory(userId);
|
|
57
|
+
if (existingCache) source = 'memory';
|
|
58
|
+
}
|
|
34
59
|
|
|
35
60
|
if (!existingCache) {
|
|
36
|
-
console.log('๐ญ No cache exists
|
|
61
|
+
console.log('๐ญ No cache exists (checked:', (performance.now() - checkStart).toFixed(0), 'ms)');
|
|
37
62
|
return {
|
|
38
63
|
hasValidCache: false,
|
|
39
64
|
cacheName: null,
|
|
40
65
|
needsRefresh: false,
|
|
41
|
-
reason: 'no_cache'
|
|
66
|
+
reason: 'no_cache',
|
|
67
|
+
source: 'none'
|
|
42
68
|
};
|
|
43
69
|
}
|
|
44
70
|
|
|
45
71
|
if (this.isCacheExpired(existingCache)) {
|
|
46
|
-
console.log('โฐ Cache expired
|
|
72
|
+
console.log('โฐ Cache expired (from:', source, ')');
|
|
47
73
|
return {
|
|
48
74
|
hasValidCache: false,
|
|
49
75
|
cacheName: null,
|
|
50
76
|
needsRefresh: true,
|
|
51
|
-
reason: 'expired'
|
|
77
|
+
reason: 'expired',
|
|
78
|
+
source: source
|
|
52
79
|
};
|
|
53
80
|
}
|
|
54
81
|
|
|
@@ -56,9 +83,11 @@ class CacheManager {
|
|
|
56
83
|
const needsRefresh = remainingHours < this.CACHE_REFRESH_THRESHOLD_HOURS;
|
|
57
84
|
|
|
58
85
|
console.log('โ
Valid cache found:', {
|
|
86
|
+
source: source,
|
|
59
87
|
cacheName: existingCache.cache_name,
|
|
60
88
|
remainingHours: remainingHours,
|
|
61
|
-
needsRefresh: needsRefresh
|
|
89
|
+
needsRefresh: needsRefresh,
|
|
90
|
+
checkTime: (performance.now() - checkStart).toFixed(0) + 'ms'
|
|
62
91
|
});
|
|
63
92
|
|
|
64
93
|
return {
|
|
@@ -66,7 +95,8 @@ class CacheManager {
|
|
|
66
95
|
cacheName: existingCache.cache_name,
|
|
67
96
|
needsRefresh: needsRefresh,
|
|
68
97
|
remainingHours: remainingHours,
|
|
69
|
-
reason: 'valid'
|
|
98
|
+
reason: 'valid',
|
|
99
|
+
source: source
|
|
70
100
|
};
|
|
71
101
|
|
|
72
102
|
} catch (error) {
|
|
@@ -75,17 +105,14 @@ class CacheManager {
|
|
|
75
105
|
hasValidCache: false,
|
|
76
106
|
cacheName: null,
|
|
77
107
|
needsRefresh: false,
|
|
78
|
-
reason: 'error'
|
|
108
|
+
reason: 'error',
|
|
109
|
+
source: 'none'
|
|
79
110
|
};
|
|
80
111
|
}
|
|
81
112
|
}
|
|
82
113
|
|
|
83
114
|
/**
|
|
84
|
-
* Get or create
|
|
85
|
-
* NOW: Can be called conditionally based on checkCacheValidity result
|
|
86
|
-
* @param {string} userId - User identifier
|
|
87
|
-
* @param {string} systemPrompt - The large system instruction to cache
|
|
88
|
-
* @returns {Promise<{cacheName: string, isNewCache: boolean}>}
|
|
115
|
+
* Get or create cache
|
|
89
116
|
*/
|
|
90
117
|
async getOrCreateCache(userId, systemPrompt) {
|
|
91
118
|
console.log('๐ ===== CACHE MANAGER: GET OR CREATE =====');
|
|
@@ -97,18 +124,11 @@ class CacheManager {
|
|
|
97
124
|
});
|
|
98
125
|
|
|
99
126
|
try {
|
|
100
|
-
//
|
|
101
|
-
const existingCache = await this.
|
|
127
|
+
// Quick check across all layers
|
|
128
|
+
const existingCache = await this.getUserCacheFromAnyLayer(userId);
|
|
102
129
|
|
|
103
130
|
if (existingCache && !this.isCacheExpired(existingCache)) {
|
|
104
|
-
console.log('โ
Using existing cache:',
|
|
105
|
-
cacheName: existingCache.cache_name,
|
|
106
|
-
createdAt: existingCache.created_at,
|
|
107
|
-
expiresAt: existingCache.expires_at,
|
|
108
|
-
ageHours: this.getCacheAgeHours(existingCache),
|
|
109
|
-
remainingHours: this.getCacheRemainingHours(existingCache)
|
|
110
|
-
});
|
|
111
|
-
|
|
131
|
+
console.log('โ
Using existing cache:', existingCache.cache_name);
|
|
112
132
|
return {
|
|
113
133
|
cacheName: existingCache.cache_name,
|
|
114
134
|
isNewCache: false
|
|
@@ -116,7 +136,7 @@ class CacheManager {
|
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
// Create new cache
|
|
119
|
-
console.log('๐ Creating new cache
|
|
139
|
+
console.log('๐ Creating new Gemini cache...');
|
|
120
140
|
const cacheStartTime = performance.now();
|
|
121
141
|
const modelName = "gemini-3-flash-preview";
|
|
122
142
|
|
|
@@ -129,17 +149,18 @@ class CacheManager {
|
|
|
129
149
|
});
|
|
130
150
|
|
|
131
151
|
const cacheCreationTime = performance.now() - cacheStartTime;
|
|
132
|
-
console.log('โ
|
|
152
|
+
console.log('โ
Gemini cache created:', {
|
|
133
153
|
cacheName: cache.name,
|
|
134
154
|
creationTime: cacheCreationTime.toFixed(2) + 'ms',
|
|
135
155
|
ttlHours: this.CACHE_TTL_HOURS,
|
|
136
156
|
expiresAt: new Date(Date.now() + this.CACHE_TTL_SECONDS * 1000).toISOString()
|
|
137
157
|
});
|
|
138
158
|
|
|
139
|
-
// Save
|
|
140
|
-
|
|
159
|
+
// Save to all available storage layers
|
|
160
|
+
const saveStart = performance.now();
|
|
161
|
+
await this.saveUserCacheMultiLayer(userId, cache.name);
|
|
162
|
+
console.log('๐พ Multi-layer save completed:', (performance.now() - saveStart).toFixed(0), 'ms');
|
|
141
163
|
|
|
142
|
-
console.log('๐พ Cache info saved to database');
|
|
143
164
|
console.log('๐ ===== CACHE MANAGER: COMPLETE =====');
|
|
144
165
|
|
|
145
166
|
return {
|
|
@@ -154,7 +175,6 @@ class CacheManager {
|
|
|
154
175
|
timestamp: new Date().toISOString()
|
|
155
176
|
});
|
|
156
177
|
|
|
157
|
-
// Return null to indicate cache unavailable - caller should proceed without cache
|
|
158
178
|
return {
|
|
159
179
|
cacheName: null,
|
|
160
180
|
isNewCache: false,
|
|
@@ -164,14 +184,172 @@ class CacheManager {
|
|
|
164
184
|
}
|
|
165
185
|
|
|
166
186
|
/**
|
|
167
|
-
*
|
|
168
|
-
* FIXED: Using correct PostgreSQL syntax (not MySQL)
|
|
169
|
-
* @param {string} userId
|
|
170
|
-
* @returns {Promise<object|null>}
|
|
187
|
+
* Try to get cache from any available layer
|
|
171
188
|
*/
|
|
172
|
-
async
|
|
173
|
-
|
|
189
|
+
async getUserCacheFromAnyLayer(userId) {
|
|
190
|
+
// Try PostgreSQL
|
|
191
|
+
let cache = await this.getUserCache(userId);
|
|
192
|
+
if (cache) return cache;
|
|
193
|
+
|
|
194
|
+
// Try Redis
|
|
195
|
+
if (redisClient) {
|
|
196
|
+
cache = await this.getUserCacheFromRedis(userId);
|
|
197
|
+
if (cache) return cache;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Try Memory
|
|
201
|
+
return this.getUserCacheFromMemory(userId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Save cache to all available layers (don't fail if one fails)
|
|
206
|
+
*/
|
|
207
|
+
async saveUserCacheMultiLayer(userId, cacheName) {
|
|
208
|
+
const expiresAt = new Date(Date.now() + this.CACHE_TTL_SECONDS * 1000);
|
|
209
|
+
const cacheData = {
|
|
210
|
+
cache_name: cacheName,
|
|
211
|
+
created_at: new Date(),
|
|
212
|
+
expires_at: expiresAt
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const results = {
|
|
216
|
+
postgresql: false,
|
|
217
|
+
redis: false,
|
|
218
|
+
memory: false
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Layer 1: PostgreSQL (primary)
|
|
222
|
+
try {
|
|
223
|
+
await this.saveUserCacheToPostgres(userId, cacheName, expiresAt);
|
|
224
|
+
results.postgresql = true;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('โ PostgreSQL save failed:', error.message);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Layer 2: Redis (fast fallback)
|
|
230
|
+
if (redisClient) {
|
|
231
|
+
try {
|
|
232
|
+
await this.saveUserCacheToRedis(userId, cacheData);
|
|
233
|
+
results.redis = true;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('โ Redis save failed:', error.message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Layer 3: Memory (always succeeds)
|
|
240
|
+
try {
|
|
241
|
+
this.saveUserCacheToMemory(userId, cacheData);
|
|
242
|
+
results.memory = true;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('โ Memory save failed:', error.message);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log('๐พ Multi-layer save results:', results);
|
|
248
|
+
|
|
249
|
+
// At least one layer must succeed
|
|
250
|
+
if (!results.postgresql && !results.redis && !results.memory) {
|
|
251
|
+
throw new Error('All cache storage layers failed');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Save to PostgreSQL with automatic fallback to DELETE+INSERT
|
|
257
|
+
*/
|
|
258
|
+
async saveUserCacheToPostgres(userId, cacheName, expiresAt) {
|
|
259
|
+
console.log('๐พ Saving to PostgreSQL...');
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
// First try UPSERT (requires PRIMARY KEY or UNIQUE constraint)
|
|
263
|
+
try {
|
|
264
|
+
const query = `
|
|
265
|
+
INSERT INTO user_gemini_caches (user_id, cache_name, created_at, expires_at)
|
|
266
|
+
VALUES ($1, $2, NOW(), $3)
|
|
267
|
+
ON CONFLICT (user_id)
|
|
268
|
+
DO UPDATE SET
|
|
269
|
+
cache_name = EXCLUDED.cache_name,
|
|
270
|
+
created_at = NOW(),
|
|
271
|
+
expires_at = EXCLUDED.expires_at
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
await pgClient.query(query, [userId, cacheName, expiresAt]);
|
|
275
|
+
console.log('โ
PostgreSQL save successful (UPSERT)');
|
|
276
|
+
return true;
|
|
277
|
+
|
|
278
|
+
} catch (upsertError) {
|
|
279
|
+
// If UPSERT fails (error code 42P10 = no constraint), use DELETE + INSERT
|
|
280
|
+
if (upsertError.code === '42P10') {
|
|
281
|
+
console.log('โ ๏ธ No constraint found, using DELETE+INSERT fallback');
|
|
282
|
+
|
|
283
|
+
// Delete old record
|
|
284
|
+
await pgClient.query(
|
|
285
|
+
`DELETE FROM user_gemini_caches WHERE user_id = $1`,
|
|
286
|
+
[userId]
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Insert new record
|
|
290
|
+
await pgClient.query(
|
|
291
|
+
`INSERT INTO user_gemini_caches (user_id, cache_name, created_at, expires_at)
|
|
292
|
+
VALUES ($1, $2, NOW(), $3)`,
|
|
293
|
+
[userId, cacheName, expiresAt]
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
console.log('โ
PostgreSQL save successful (DELETE+INSERT)');
|
|
297
|
+
return true;
|
|
298
|
+
} else {
|
|
299
|
+
throw upsertError; // Re-throw if it's a different error
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error('โ PostgreSQL save completely failed:', error.message);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Save to Redis
|
|
311
|
+
*/
|
|
312
|
+
async saveUserCacheToRedis(userId, cacheData) {
|
|
313
|
+
console.log('๐พ Saving to Redis...');
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
const key = `gemini_cache:${userId}`;
|
|
317
|
+
await redisClient.setex(
|
|
318
|
+
key,
|
|
319
|
+
this.CACHE_TTL_SECONDS,
|
|
320
|
+
JSON.stringify(cacheData)
|
|
321
|
+
);
|
|
322
|
+
console.log('โ
Redis save successful');
|
|
323
|
+
return true;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error('โ Redis save failed:', error.message);
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Save to Memory (always succeeds)
|
|
332
|
+
*/
|
|
333
|
+
saveUserCacheToMemory(userId, cacheData) {
|
|
334
|
+
console.log('๐พ Saving to memory...');
|
|
335
|
+
this.memoryCache.set(userId, cacheData);
|
|
336
|
+
console.log('โ
Memory save successful');
|
|
337
|
+
|
|
338
|
+
// Auto-cleanup after TTL
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
if (this.memoryCache.has(userId)) {
|
|
341
|
+
this.memoryCache.delete(userId);
|
|
342
|
+
console.log('๐งน Auto-removed expired cache from memory:', userId);
|
|
343
|
+
}
|
|
344
|
+
}, this.CACHE_TTL_SECONDS * 1000);
|
|
174
345
|
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get from PostgreSQL
|
|
351
|
+
*/
|
|
352
|
+
async getUserCache(userId) {
|
|
175
353
|
try {
|
|
176
354
|
const query = `
|
|
177
355
|
SELECT cache_name, created_at, expires_at
|
|
@@ -184,87 +362,57 @@ class CacheManager {
|
|
|
184
362
|
const result = await pgClient.query(query, [userId]);
|
|
185
363
|
|
|
186
364
|
if (result && result.rows && result.rows.length > 0) {
|
|
187
|
-
console.log('โ
Found existing cache record:', {
|
|
188
|
-
cacheName: result.rows[0].cache_name,
|
|
189
|
-
createdAt: result.rows[0].created_at,
|
|
190
|
-
expiresAt: result.rows[0].expires_at
|
|
191
|
-
});
|
|
192
365
|
return result.rows[0];
|
|
193
366
|
}
|
|
194
367
|
|
|
195
|
-
console.log('๐ญ No existing cache found for user');
|
|
196
368
|
return null;
|
|
197
369
|
|
|
198
370
|
} catch (error) {
|
|
199
|
-
console.error('โ
|
|
371
|
+
console.error('โ PostgreSQL fetch failed:', error.message);
|
|
200
372
|
return null;
|
|
201
373
|
}
|
|
202
374
|
}
|
|
203
375
|
|
|
204
376
|
/**
|
|
205
|
-
*
|
|
206
|
-
* FIXED: Using PostgreSQL syntax instead of MySQL
|
|
207
|
-
* @param {string} userId
|
|
208
|
-
* @param {string} cacheName
|
|
377
|
+
* Get from Redis
|
|
209
378
|
*/
|
|
210
|
-
async
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
cacheName: cacheName
|
|
214
|
-
});
|
|
215
|
-
|
|
379
|
+
async getUserCacheFromRedis(userId) {
|
|
380
|
+
if (!redisClient) return null;
|
|
381
|
+
|
|
216
382
|
try {
|
|
217
|
-
const
|
|
383
|
+
const key = `gemini_cache:${userId}`;
|
|
384
|
+
const data = await redisClient.get(key);
|
|
218
385
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
(user_id, cache_name, created_at, expires_at)
|
|
223
|
-
VALUES ($1, $2, NOW(), $3)
|
|
224
|
-
ON CONFLICT (user_id)
|
|
225
|
-
DO UPDATE SET
|
|
226
|
-
cache_name = EXCLUDED.cache_name,
|
|
227
|
-
created_at = NOW(),
|
|
228
|
-
expires_at = EXCLUDED.expires_at
|
|
229
|
-
`;
|
|
230
|
-
|
|
231
|
-
await pgClient.query(query, [userId, cacheName, expiresAt]);
|
|
386
|
+
if (data) {
|
|
387
|
+
return JSON.parse(data);
|
|
388
|
+
}
|
|
232
389
|
|
|
233
|
-
|
|
390
|
+
return null;
|
|
234
391
|
|
|
235
392
|
} catch (error) {
|
|
236
|
-
console.error('โ
|
|
237
|
-
|
|
238
|
-
// Don't throw - cache creation succeeded, DB save is secondary
|
|
393
|
+
console.error('โ Redis fetch failed:', error.message);
|
|
394
|
+
return null;
|
|
239
395
|
}
|
|
240
396
|
}
|
|
241
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Get from Memory
|
|
400
|
+
*/
|
|
401
|
+
getUserCacheFromMemory(userId) {
|
|
402
|
+
return this.memoryCache.get(userId) || null;
|
|
403
|
+
}
|
|
404
|
+
|
|
242
405
|
/**
|
|
243
406
|
* Check if cache is expired
|
|
244
|
-
* @param {object} cacheRecord
|
|
245
|
-
* @returns {boolean}
|
|
246
407
|
*/
|
|
247
408
|
isCacheExpired(cacheRecord) {
|
|
248
409
|
const now = new Date();
|
|
249
410
|
const expiresAt = new Date(cacheRecord.expires_at);
|
|
250
|
-
|
|
251
|
-
const isExpired = now >= expiresAt;
|
|
252
|
-
|
|
253
|
-
if (isExpired) {
|
|
254
|
-
console.log('โฐ Cache expired:', {
|
|
255
|
-
expiresAt: expiresAt.toISOString(),
|
|
256
|
-
now: now.toISOString(),
|
|
257
|
-
expiredBy: ((now - expiresAt) / (1000 * 60)).toFixed(2) + ' minutes'
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return isExpired;
|
|
411
|
+
return now >= expiresAt;
|
|
262
412
|
}
|
|
263
413
|
|
|
264
414
|
/**
|
|
265
415
|
* Get cache age in hours
|
|
266
|
-
* @param {object} cacheRecord
|
|
267
|
-
* @returns {number}
|
|
268
416
|
*/
|
|
269
417
|
getCacheAgeHours(cacheRecord) {
|
|
270
418
|
const now = new Date();
|
|
@@ -275,8 +423,6 @@ class CacheManager {
|
|
|
275
423
|
|
|
276
424
|
/**
|
|
277
425
|
* Get remaining cache lifetime in hours
|
|
278
|
-
* @param {object} cacheRecord
|
|
279
|
-
* @returns {number}
|
|
280
426
|
*/
|
|
281
427
|
getCacheRemainingHours(cacheRecord) {
|
|
282
428
|
const now = new Date();
|
|
@@ -286,34 +432,52 @@ class CacheManager {
|
|
|
286
432
|
}
|
|
287
433
|
|
|
288
434
|
/**
|
|
289
|
-
* Clean up expired caches from
|
|
290
|
-
* FIXED: PostgreSQL syntax
|
|
435
|
+
* Clean up expired caches from all layers
|
|
291
436
|
*/
|
|
292
437
|
async cleanupExpiredCaches() {
|
|
293
|
-
console.log('๐งน Cleaning up expired caches...');
|
|
438
|
+
console.log('๐งน Cleaning up expired caches from all layers...');
|
|
439
|
+
|
|
440
|
+
const results = {
|
|
441
|
+
postgresql: 0,
|
|
442
|
+
redis: 0,
|
|
443
|
+
memory: 0
|
|
444
|
+
};
|
|
294
445
|
|
|
446
|
+
// PostgreSQL cleanup
|
|
295
447
|
try {
|
|
296
|
-
const query = `
|
|
297
|
-
DELETE FROM user_gemini_caches
|
|
298
|
-
WHERE expires_at < NOW()
|
|
299
|
-
`;
|
|
300
|
-
|
|
448
|
+
const query = `DELETE FROM user_gemini_caches WHERE expires_at < NOW()`;
|
|
301
449
|
const result = await pgClient.query(query);
|
|
302
|
-
|
|
303
|
-
console.log('โ
Cleanup complete:', {
|
|
304
|
-
deletedRecords: result.rowCount || 0
|
|
305
|
-
});
|
|
306
|
-
|
|
450
|
+
results.postgresql = result.rowCount || 0;
|
|
307
451
|
} catch (error) {
|
|
308
|
-
console.error('โ
|
|
452
|
+
console.error('โ PostgreSQL cleanup failed:', error.message);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Redis cleanup (would need to scan all keys - expensive, skip for now)
|
|
456
|
+
// Redis auto-expires with TTL, so not critical
|
|
457
|
+
|
|
458
|
+
// Memory cleanup
|
|
459
|
+
for (const [userId, cacheData] of this.memoryCache.entries()) {
|
|
460
|
+
if (this.isCacheExpired(cacheData)) {
|
|
461
|
+
this.memoryCache.delete(userId);
|
|
462
|
+
results.memory++;
|
|
463
|
+
}
|
|
309
464
|
}
|
|
465
|
+
|
|
466
|
+
console.log('โ
Cleanup results:', results);
|
|
467
|
+
return results;
|
|
310
468
|
}
|
|
311
469
|
|
|
312
470
|
/**
|
|
313
|
-
* Get cache statistics
|
|
314
|
-
* FIXED: PostgreSQL syntax with EXTRACT instead of TIMESTAMPDIFF
|
|
471
|
+
* Get cache statistics
|
|
315
472
|
*/
|
|
316
473
|
async getCacheStats() {
|
|
474
|
+
const stats = {
|
|
475
|
+
memory_caches: this.memoryCache.size,
|
|
476
|
+
postgresql: null,
|
|
477
|
+
redis: null
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// PostgreSQL stats
|
|
317
481
|
try {
|
|
318
482
|
const query = `
|
|
319
483
|
SELECT
|
|
@@ -325,37 +489,22 @@ class CacheManager {
|
|
|
325
489
|
`;
|
|
326
490
|
|
|
327
491
|
const result = await pgClient.query(query);
|
|
328
|
-
|
|
329
|
-
|
|
492
|
+
stats.postgresql = result.rows[0];
|
|
330
493
|
} catch (error) {
|
|
331
|
-
console.error('โ
|
|
332
|
-
return null;
|
|
494
|
+
console.error('โ PostgreSQL stats failed:', error.message);
|
|
333
495
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* NEW: Proactively refresh cache in background if it's about to expire
|
|
338
|
-
* This ensures users always have a fresh cache without waiting
|
|
339
|
-
* @param {string} userId
|
|
340
|
-
* @param {function} getSystemPromptFn - Function that returns the system prompt
|
|
341
|
-
*/
|
|
342
|
-
async refreshCacheIfNeeded(userId, getSystemPromptFn) {
|
|
343
|
-
const cacheStatus = await this.checkCacheValidity(userId);
|
|
344
496
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
console.log('โ
Background cache refresh completed');
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error('โ Background cache refresh failed:', error.message);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
497
|
+
// Redis stats (would need dbsize command)
|
|
498
|
+
if (redisClient) {
|
|
499
|
+
try {
|
|
500
|
+
const keys = await redisClient.keys('gemini_cache:*');
|
|
501
|
+
stats.redis = { total_keys: keys.length };
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.error('โ Redis stats failed:', error.message);
|
|
504
|
+
}
|
|
358
505
|
}
|
|
506
|
+
|
|
507
|
+
return stats;
|
|
359
508
|
}
|
|
360
509
|
}
|
|
361
510
|
|