@@ -41,9 +41,11 @@ class ClusterCache {
4141 this . keySizes = new Map ( ) // Track size of each cached value in bytes
4242 this . totalBytes = 0 // Track total cache size in bytes
4343 this . localCache = new Map ( )
44+ this . clearGeneration = 0 // Track clear operations to coordinate across workers
4445
4546 // Background stats sync every 5 seconds
4647 this . statsInterval = setInterval ( ( ) => {
48+ this . _checkClearSignal ( ) . catch ( ( ) => { } )
4749 this . _syncStats ( ) . catch ( ( ) => { } )
4850 } , 5000 )
4951 }
@@ -216,43 +218,89 @@ class ClusterCache {
216218 /**
217219 * Clear all cache entries and reset stats
218220 */
221+ /**
222+ * Clear all cache entries and reset stats across all workers
223+ */
219224 async clear ( ) {
220225 try {
221226 clearInterval ( this . statsInterval )
222227
228+ // Increment clear generation to signal all workers
229+ this . clearGeneration ++
230+
231+ // Broadcast clear signal to all workers via cluster cache
232+ await this . clusterCache . set ( '_clear_signal' , {
233+ generation : this . clearGeneration ,
234+ timestamp : Date . now ( )
235+ } , 60000 ) // 1 minute TTL
236+
237+ // Flush all cache data
223238 await this . clusterCache . flush ( )
239+
240+ // Reset local state
224241 this . allKeys . clear ( )
225- this . keyAccessTimes . clear ( ) // Clear access time tracking
226- this . keySizes . clear ( ) // Clear size tracking
242+ this . keyAccessTimes . clear ( )
243+ this . keySizes . clear ( )
227244 this . totalBytes = 0
228245 this . localCache . clear ( )
229246
230247 this . stats = {
231248 hits : 0 ,
232249 misses : 0 ,
233- evictions : 1 ,
250+ evictions : 0 ,
234251 sets : 0 ,
235252 invalidations : 0
236253 }
237254
238- await new Promise ( resolve => setTimeout ( resolve , 100 ) )
239-
255+ // Restart stats sync interval
240256 this . statsInterval = setInterval ( ( ) => {
257+ this . _checkClearSignal ( ) . catch ( ( ) => { } )
241258 this . _syncStats ( ) . catch ( ( ) => { } )
242259 } , 5000 )
260+
261+ // Immediately sync our fresh stats
262+ await this . _syncStats ( )
263+
264+ // Wait for all workers to see the clear signal and reset
265+ // Workers check every 5 seconds, so wait 6 seconds to be safe
266+ await new Promise ( resolve => setTimeout ( resolve , 6000 ) )
267+
268+ // Delete all old worker stats keys
269+ const keysMap = await this . clusterCache . keys ( )
270+ const deletePromises = [ ]
271+ for ( const instanceKeys of Object . values ( keysMap ) ) {
272+ if ( Array . isArray ( instanceKeys ) ) {
273+ for ( const key of instanceKeys ) {
274+ if ( key . startsWith ( '_stats_worker_' ) ) {
275+ deletePromises . push ( this . clusterCache . delete ( key ) )
276+ }
277+ }
278+ }
279+ }
280+ await Promise . all ( deletePromises )
281+
282+ // Final sync after cleanup
283+ await this . _syncStats ( )
243284 } catch ( err ) {
244285 console . error ( 'Cache clear error:' , err )
245286 this . localCache . clear ( )
246287 this . allKeys . clear ( )
247- this . keyAccessTimes . clear ( ) // Clear access time tracking
248- this . keySizes . clear ( ) // Clear size tracking
288+ this . keyAccessTimes . clear ( )
289+ this . keySizes . clear ( )
249290 this . totalBytes = 0
250- this . stats . evictions ++
291+ this . stats = {
292+ hits : 0 ,
293+ misses : 0 ,
294+ evictions : 0 ,
295+ sets : 0 ,
296+ invalidations : 0
297+ }
251298
252299 if ( ! this . statsInterval . _destroyed ) {
253300 clearInterval ( this . statsInterval )
254301 }
255302 this . statsInterval = setInterval ( ( ) => {
303+ this . _checkClearSignal ( ) . catch ( ( ) => { } )
256304 this . _syncStats ( ) . catch ( ( ) => { } )
257305 } , 5000 )
258306 }
@@ -427,6 +475,81 @@ class ClusterCache {
427475 }
428476 }
429477
478+ /**
479+ * Get detailed list of all cache entries
480+ * @returns {Promise<Array> } Array of cache entry details
481+ */
482+ async getDetails ( ) {
483+ try {
484+ const keysMap = await this . clusterCache . keys ( )
485+ const allKeys = new Set ( )
486+
487+ for ( const instanceKeys of Object . values ( keysMap ) ) {
488+ if ( Array . isArray ( instanceKeys ) ) {
489+ instanceKeys . forEach ( key => {
490+ if ( ! key . startsWith ( '_stats_worker_' ) && ! key . startsWith ( '_clear_signal' ) ) {
491+ allKeys . add ( key )
492+ }
493+ } )
494+ }
495+ }
496+
497+ const details = [ ]
498+ let position = 0
499+ for ( const key of allKeys ) {
500+ const value = await this . clusterCache . get ( key , undefined )
501+ const size = this . _calculateSize ( value )
502+
503+ details . push ( {
504+ position,
505+ key,
506+ bytes : size
507+ } )
508+ position ++
509+ }
510+
511+ return details
512+ } catch ( err ) {
513+ console . error ( 'Cache getDetails error:' , err )
514+ return [ ]
515+ }
516+ }
517+
518+ /**
519+ * Check for clear signal from other workers
520+ * @private
521+ */
522+ async _checkClearSignal ( ) {
523+ try {
524+ const signal = await this . clusterCache . get ( '_clear_signal' , undefined )
525+ if ( signal && signal . generation > this . clearGeneration ) {
526+ // Another worker initiated a clear - reset our local state
527+ this . clearGeneration = signal . generation
528+
529+ this . allKeys . clear ( )
530+ this . keyAccessTimes . clear ( )
531+ this . keySizes . clear ( )
532+ this . totalBytes = 0
533+ this . localCache . clear ( )
534+
535+ this . stats = {
536+ hits : 0 ,
537+ misses : 0 ,
538+ evictions : 0 ,
539+ sets : 0 ,
540+ invalidations : 0
541+ }
542+
543+ // Delete our worker stats key immediately
544+ const workerId = process . env . pm_id || process . pid
545+ const statsKey = `_stats_worker_${ workerId } `
546+ await this . clusterCache . delete ( statsKey )
547+ }
548+ } catch ( err ) {
549+ // Silently fail
550+ }
551+ }
552+
430553 /**
431554 * Sync current worker stats to cluster cache (called by background interval)
432555 * @private
0 commit comments